The app can:
- Show connected displays from the menu bar.
- Change HiDPI, resolution, and refresh rate.
- Change display mode, dithering, and ICC profile.
- Save, rename, delete, and switch display profiles.
- Keep the selected profile applied after wake, display changes, external changes, and periodic checks.
Risky resolution, display mode, and profile changes use a short Keep/Restore confirmation so you can return to the previous state.
The project also includes omd, a companion CLI for terminal use, scripts, and automation. The app and CLI use the same display control core.
This software only sets display modes reported by the device. Even so, a display may report a mode that it does not actually support, which can cause unexpected behavior or other adverse effects. Use it at your own risk.
Some display properties are backed by public CoreGraphics, ColorSync, and IOKit APIs. Display mode control uses Quartz/CADisplay behavior that is not part of a stable public Apple API surface, so it may vary across macOS releases and display hardware.
Use omd when you want the same display control from a terminal, script, or .command file.
The CLI can:
- List connected displays and stable selectors.
- Read the current display state.
- List available resolution modes.
- List available display modes.
- List installed ColorSync ICC profiles.
- Set resolution modes by exact mode ID or by user-facing options.
- Set display modes by exact mode ID or by user-facing options.
- Set dithering on or off.
- Set a display ICC profile.
- Emit JSON for scripting and automation.
- macOS 14 or newer
- Swift 6 toolchain
Build the app bundle:
Packaging/package-app.shThe app is written to:
dist/OhMyDisplay.appOpen the app:
open dist/OhMyDisplay.appBuild the CLI:
swift build -c release --product omdThe executable is written to:
.build/release/omdExamples below assume omd is on your PATH. If not, replace omd with .build/release/omd or .build/debug/omd.
For development builds:
swift build --product omd
.build/debug/omd versionList displays:
omd display listRead the main display state:
omd display getList available resolution modes for the main display:
omd display resolutionsList available display modes for the main display:
omd display modesList installed ICC profiles:
omd icc listAll read commands support JSON output:
omd display get --json
omd display resolutions --json
omd display modes --json
omd icc list --json--display defaults to main for display-scoped commands:
omd display get
omd display set --dithering offFor scripts that target a specific display, copy a stable selector from:
omd display listThen pass it explicitly:
omd display get --display 'uuid:...'
omd display set --display 'uuid:...' --dithering off--display all is not supported for mutation.
Exact mode IDs are copied from the list commands.
Set an exact resolution mode:
omd display resolutions
omd display set --resolution-mode '<resolutionMode>' --yesSet an exact display mode:
omd display modes
omd display set --display-mode '<displayMode>' --yesYou can also ask omd to resolve a mode from user-facing flags.
Set a resolution by logical size, HiDPI state, and refresh rate:
omd display set --resolution 1920x1080 --hidpi on --refresh 120 --yesSet display-mode properties for the current timing:
omd display set --encoding rgb --bpc 10 --range full --hdr sdr --yesCombine a resolution change with semantic display-mode properties:
omd display set \
--resolution 1920x1080 \
--hidpi on \
--refresh 120 \
--encoding rgb \
--bpc 10 \
--range full \
--hdr sdr \
--yesDirect settings:
omd display set --dithering on
omd display set --dithering off
omd display set --icc ~/Library/ColorSync/Profiles/Display.iccWhen a resolution or display mode may change, non-interactive use requires --yes. Interactive terminals prompt before applying the change.
Operations run in this order:
resolution -> displayMode -> dithering -> icc
If a later display-mode operation fails after a resolution change, omd attempts to restore the original resolution and display mode.
omd display list [--json]
omd display get [--display <display>] [--json]
omd display resolutions [--display <display>] [--json]
omd display modes [--display <display>] [--json]
omd display set [--display <display>] [set options] [--json] [--yes]
omd icc list [--json]
omd version
display set options:
--resolution-mode <resolutionMode>
--resolution <width>x<height>
--hidpi on|off
--refresh <hz>
--display-mode <displayMode>
--encoding rgb|ycbcr
--bpc <bits-per-component>
--range full|limited
--chroma 444|422|420
--hdr sdr|hdr10|dolby-vision|dolby-vision-low-latency
--vrr on|off
--dithering on|off
--icc <path>
Rules:
--resolution-modecannot be combined with--resolution,--hidpi, or--refresh.--display-modecannot be combined with semantic display-mode flags such as--bpc,--hdr, or--vrr.--display-modecannot be combined with a resolution change. Use semantic display-mode flags if the desired display mode should be resolved after the resolution changes.- Omit
--encodingand--chromafor Dolby Vision modes; they reportnonein JSON and-in human tables. - Omit
--chromafor RGB modes; use it for YCbCr 444, 422, or 420 modes. - Semantic display-mode selection defaults to
--vrr off; pass--vrr onto select a VRR mode.
0 success
2 blocked before mutation
3 partial failure after a mutation was attempted
64 usage error
70 unexpected error
For automation, prefer --json; set commands include per-operation status and whether a mutation was attempted.
The package also exposes OMDCore as a thin Swift library.
import OMDCore
let displays = try listDisplays()
let display = displays.first { $0.isMain }!
let state = try readDisplayState(display.selector)
let resolutions = try listResolutionModes(display.selector)
let displayModes = try listDisplayModes(display.selector)
let result = try setDithering(display.selector, enabled: false)Public functions:
listDisplays()
readDisplayState(_:)
listResolutionModes(_:)
setResolutionMode(_:modeID:)
listDisplayModes(_:)
setDisplayMode(_:modeID:)
setDithering(_:enabled:)
setICCProfile(_:profileURL:)The core library intentionally stays thin. Higher-level features such as saved presets should live in a caller layer.
swift testMIT. See LICENSE.