A Windows tray app that adjusts external monitors over DDC/CI to reduce eye strain. It sets brightness and colour warmth from the sun's position at your location, your bedtime, and the light in your room. When a game or video goes full-screen on one monitor, it dims the others.
It changes the monitors' own settings, not the graphics card's colour ramp. That keeps the full colour range and works alongside any GPU. Developed against an Acer KA242Y and a Samsung S24B350H; any monitor with DDC/CI enabled in its menu should work.
Requires Windows 10 version 2004 or later, 64-bit.
| Input | Effect |
|---|---|
| Sun elevation | Blends from the day profile to evening across civil twilight (sun from +6° to −6°). |
| Bedtime | Blends from evening to night over 45 minutes after night starts, and back after night ends. |
| Room lights | While modelled lights are on (from the on time until the off time, which may be after midnight), brightness stays at or above a floor for the light level, and warmth follows the light's colour (see Warmth and the room below). With Brightness curve on, the floor starts at a third of its level, peaks at the middle of the window and fades back. |
| Camera light meter (opt-in) | Every 10 minutes, the webcam measures the room light at a fixed gain, and changes too small to notice are ignored. Brightness follows the line through your bright-room and dim-room calibrations, and warmth follows the light's colour, moving at most 300K per reading. This replaces the room-light model while readings are fresh (30 minutes). |
| Full-screen | After 3 s settled, the other monitors drop to a share of their scheduled brightness (default 20%). |
| Per monitor | A brightness offset evens out panels; any monitor can be left alone. |
| Display changes | Within a second of Windows reporting a monitor being connected, removed, swapped, woken or rearranged (a wake Windows doesn't report is noticed within 15 s), the app checks what each monitor supports over DDC/CI and adjusts it. A monitor that doesn't answer yet is retried after about 2, 5, 10 and 20 s, then after 1, 2, 4, 8 and 16 minutes, then every 30 minutes, until the displays change again. While the display is off nothing is sent to the monitors, and when it comes back on they are checked as if newly connected. The settings window shows each monitor as Brightness and colour, Brightness only, or not answering DDC/CI. |
Defaults: day 60% / 6500K, evening 35% / 4500K, night 15% / 3400K, night 22:30–07:00, neutral 4000K room lights on from 16:00 to 23:00.
Once the sun is down, the screens' warmth is the average of the schedule's and the room light's, so the screens follow a warm lamp down and a cool ceiling light up. The room's share grows from none in daylight to half when the sun is 6° below the horizon, so it follows the seasons without set hours. When the two differ by more than 1000K, the room's share fades out, and from 1500K the schedule's warmth is used. Whatever the blend, the screens are never more than 500K warmer than the room's colour, so they don't look orange in a cool-lit room.
The app only writes a value when it moves by 2 or more. Some monitors store every DDC/CI write in their own memory, which wears with use.
Before it changes a monitor for the first time, the app saves that monitor's settings. Restore original monitor settings in the tray menu puts them back and pauses the app.
Switch on Camera light meter in settings, then calibrate two levels:
- Bright room: with the room lit as usual, drag Comfortable now until the screens feel right (they preview as you drag), then press Save for bright room. Keep the light steady for about 20 seconds. The first time, this also measures how the camera responds to exposure and gain.
- Dim room: turn the lights down, set Comfortable now again, and press Save for dim room.
With more than one camera connected, a Camera list appears; otherwise the app uses the first camera with manual exposure, skipping virtual cameras. Calibration belongs to the camera it was made with: readings never come from another camera, and choosing a different one means calibrating again. A camera without manual gain works on exposure alone, with less range in a dark room.
Brightness then follows the straight line through your two choices, measured in stops of light, and continues past them, within 5–100%. With only one level saved, it changes 10 points per stop around that level. Either level can be redone at any time.
The camera light comes on for about 3 seconds per reading. No image is saved or sent; only the
light level and colour are logged. Readings are skipped while the app is paused, after 15
minutes without keyboard or mouse input, and while another app (such as a video call) is using
the camera. MonitorComfort.exe --measure-light takes one reading and writes it to the log; --probe-light logs frame-by-frame picture brightness across gain settings. Both need the tray app closed.
In a dark room the camera mostly sees light from the screens, so their brightness feeds back
into the reading. The adjustment settles rather than running away, but it may settle a little
off the calibrated level; calibrating with the room lit as usual keeps this small.
See docs/adr/0002-webcam-as-light-meter.md for how it works, and docs/adr/0005 for how it
handles cameras other than the C920 it was developed with.
Requires the exact .NET SDK pinned in global.json. The lock files record the SDK's own packages,
so after changing the SDK run dotnet restore MonitorComfort.slnx --force-evaluate and commit the
updated packages.lock.json files.
dotnet build MonitorComfort.slnx -c Release
dotnet run --project tests/MonitorComfort.Tests -c Releasedotnet run --project src/MonitorComfort.App -c ReleaseOn first start the settings window opens with Stockholm as the location; change it there if needed, then close the window. The app keeps running in the tray. (Use my location needs the Windows location service, which some managed PCs disable.)
Download MonitorComfort-<version>-win-x64.exe from the
latest release, put it anywhere
permanent and run it. Start with Windows in settings registers that copy to start at sign-in.
The executable is unsigned, so Windows SmartScreen may warn on first launch (More info →
Run anyway). Before that, check it is the one the release workflow built. Compare its checksum
with the .sha256 file next to it in the release, or verify its build provenance with the GitHub CLI:
(Get-FileHash .\MonitorComfort-0.5.0-win-x64.exe -Algorithm SHA256).Hash
gh attestation verify .\MonitorComfort-0.5.0-win-x64.exe --repo gravenuance/MonitorComfortTo build the executable yourself instead:
dotnet publish src/MonitorComfort.App -c Release -o publish| What | Where |
|---|---|
| Settings | %APPDATA%\MonitorComfort\settings.json |
| Monitors' original settings | %APPDATA%\MonitorComfort\original-monitor-settings.json |
| Last settings page | %APPDATA%\MonitorComfort\ui-state.json |
| Log (10 MB × 5) | %LOCALAPPDATA%\MonitorComfort\logs\monitorcomfort.log |
Set MONITORCOMFORT_LOG_LEVEL=Debug to log every value written.
MIT; see LICENSE.