omma (/'o.ma/) — from Greek ὄμμα: eye.
Eye-strain break reminder for the macOS menu bar, written in Rust.
omma counts down to your next break from the menu bar. Just before it, a bead drops from the icon with a warning, then the break covers every display with a countdown. It can hold breaks back while you're in a meeting or a fullscreen app, and the schedule pauses while you're away from the keyboard.
omma needs macOS 26 or later. The warning and the break screen use Liquid Glass,
and on an older system omma exits with omma: requires macOS 26 or later.
With Homebrew:
brew tap museslabs/omma
brew install ommaWith cargo (Rust 1.89 or later):
cargo install ommaFrom source:
git clone https://github.com/museslabs/omma
cd omma
cargo build --releaseOr download the Apple silicon build from the releases page and unpack it. It isn't notarized. If macOS won't run it, clear its quarantine flag:
tar -xzf omma-v0.1.0-aarch64-apple-darwin.tar.gz
cd omma-v0.1.0-aarch64-apple-darwin
xattr -d com.apple.quarantine omma # only if macOS blocks itomma is a single binary with no Dock icon, not an app bundle. Put it on your
PATH to run it as omma.
With Homebrew:
brew services start ommaOtherwise, save this as ~/Library/LaunchAgents/com.museslabs.omma.plist, with
the full path to your omma binary and to a log file. launchd doesn't expand
~.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.museslabs.omma</string>
<key>ProgramArguments</key>
<array>
<string>/Users/you/.cargo/bin/omma</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>Crashed</key>
<true/>
</dict>
<key>ProcessType</key>
<string>Interactive</string>
<key>StandardErrorPath</key>
<string>/Users/you/Library/Logs/omma.log</string>
</dict>
</plist>Load it, which also starts omma now:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.museslabs.omma.plistTo stop starting it at login:
launchctl bootout gui/$(id -u)/com.museslabs.omma
rm ~/Library/LaunchAgents/com.museslabs.omma.plistlaunchd restarts omma only after a crash. A second copy that finds omma already running exits, and stays stopped.
Started by launchd, omma behaves differently in two ways:
-
It doesn't see your shell's environment. If you set
XDG_CONFIG_HOME, add it to the plist, or omma reads~/.config/omma/config.toml:<key>EnvironmentVariables</key> <dict> <key>XDG_CONFIG_HOME</key> <string>/Users/you/.config</string> </dict>
-
It no longer borrows your terminal's Accessibility permission. Allow omma itself for the fullscreen and window-title rules; see Accessibility.
macOS shows a "Background Items Added" notice the first time.
The fullscreen and window-title rules of do not disturb need Accessibility permission. omma asks for it when it starts with one of them on, or when a save turns one on. Grant it in System Settings → Privacy & Security → Accessibility (Device Control and Data Access on macOS 27), and the rules work from the next break, with no restart. Started from a terminal, omma uses the terminal's permission instead of its own.
Released binaries aren't signed with a Developer ID, and macOS treats each upgrade as a different program. After upgrading, allow omma again in the same list. Until then the menu shows a ⚠ item for it.
Run omma and it shows up in the menu bar. By default a 20-second break comes
every 15 minutes and a one-minute break every 75, and the icon blinks in the
minute before each one.
Before each break a bead drops from the menu-bar icon on a thread, with a small
panel beside it that counts down and has Skip and Postpone buttons. In strict
mode Skip becomes Dismiss, which only closes the warning. The warning never
takes keyboard focus from the app you're typing in, and pre_break_warning = 0
turns it off.
The break screen covers every display with the break's name, a countdown in a bead whose colour drains as the break runs, an optional background image, and Skip and Postpone buttons. The countdown waits for the screen to fade in, which adds about 0.6 s to a break, or 0.25 s with Reduce Motion on. omma never becomes the active app, and your app gets the keyboard back when the break ends.
A sound plays when the warning shows up and when a break runs to its end.
[sounds] in the config picks them.
The menu shows the time to the next break, with items to take a break now (the next one, a short one or a long one), Pause (for 30 minutes, an hour, two hours or the rest of the day), Enabled, Open config…, Help, the version and Quit. It also lists anything that needs fixing; see When something's wrong.
omma start the menu-bar app
omma --take-break (-t) take the next break now
omma --short-break (-s) take a short break now
omma --long-break (-l) take a long break now
omma --enable (-e) enable the running instance
omma --disable (-d) disable the running instance
omma --pause <DURATION> pause for 90s, 30m, 2h30m and so on, up to 24h
omma --quit (-q) quit the running instance
omma --status print the running instance's status
omma --reload reread the config now
omma --print-default-config print the default config
Flags can't be combined. Each command waits for the running instance to act on it, then prints what it did or why it refused. The exit code is:
- 0 when the command worked;
- 1 when omma isn't running, or, when starting it, is already running;
- 2 when the running instance refused the command (a break while omma is
disabled, say, or
--reloadwith a broken config), or the flags are invalid; - 3 when anything else went wrong.
The running instance holds a lock file and listens on a socket, both in
~/Library/Application Support/omma/.
The menu lists problems under the status line, each marked ⚠. A long one is cut short, and hovering it shows all of it:
- an error in the config file; clicking it opens the file;
- a break image that couldn't be shown; clicking it opens the config file;
- a warning shortcut the system wouldn't register;
- a fullscreen or window-title rule without Accessibility permission; clicking it opens System Settings at the Accessibility list.
omma --status lists them too, and omma writes each one to stderr.
The config file is $XDG_CONFIG_HOME/omma/config.toml, or
~/.config/omma/config.toml when XDG_CONFIG_HOME isn't set. omma creates it
with the defaults on first run, and "Open config…" creates it again if it's
gone. omma --print-default-config prints the default file, which documents
every key with its range.
omma rereads the file a second or two after every save, during a break too. A warning or break already on screen keeps the buttons and shortcuts it has until it closes, and one the new config forbids does nothing. Parsing is strict:
- A key omma doesn't know is an error. So is a top-level key placed under a
[table]header, and the error says where it belongs. - Times are whole seconds within a range: intervals and
postpone_duration1 s to 24 h, break durations 1 s to 1 h,pre_break_warning0 to 10 min, andidle_seconds10 s to 24 h. With both kinds of break,short_break_intervalmust be less thanlong_break_interval. - A break
imagemust be an absolute path or start with~/, and the file must exist when the config loads.
On an error the menu shows it, with its line when it has one, and omma keeps the last config that loaded, or the defaults at startup. Fix the file and save, and the error goes.
Each break in the [[short_breaks]] and [[long_breaks]] lists has a name,
and can set its own duration and image:
[[long_breaks]]
name = "Take a walk"
duration = 300
image = "~/Pictures/trail.jpg"[keybindings]
skip = "escape" # on the break screen
postpone = "space"
warning_skip = "" # on the warning, from any app
warning_postpone = ""skip and postpone work on the break screen once its buttons have appeared,
a little over a second in. A key you were typing as the break began doesn't
skip it.
warning_skip and warning_postpone are off by default. They're system-wide
shortcuts that work from any app while the warning shows, and the warning never
takes focus. Each needs cmd or ctrl, for example "cmd+shift+option+space".
A shortcut is one key after any of the modifiers cmd, shift, alt (or
option) and ctrl, joined by +: a single character, or escape, space,
enter, tab or delete (esc, return and backspace work too). Shift
goes only with a letter, escape, space, enter or delete. "" turns a
shortcut off. With allow_postpone on, skip and postpone must differ. A
shortcut that breaks these rules is a config error. Postpone shortcuts need
allow_postpone, and with strict_break on no shortcut works.
[smart_pause]
enabled = true
idle_seconds = 60After idle_seconds without keyboard or mouse input the schedule pauses. Your
rest counts from your last input, threshold included. Once you're back the next
break is a full interval away, and time away at least as long as a long break
counts as one. Sleep counts as time away too, and waking the Mac never brings on
a break at once.
[do_not_disturb]
action = "skip" # or "postpone"
# need no permission
apps_running = ["zoom"]
apps_in_front = [] # only while the app is frontmost
# need Accessibility permission
fullscreen = false # while the frontmost window is fullscreen
window_titles = ["Zoom Meeting", '\bPlain\b']omma checks the rules when a break comes due. When one holds, action = "skip"
drops the break and "postpone" tries again after postpone_duration, even with
allow_postpone off or strict_break on. The menu then says why, for example
"Skipped the last break: Zoom is running", while omma waits for the next break.
apps_runningandapps_in_frontmatch part of an app's name or bundle id, ignoring case."zoom"covers Zoom being open at all.window_titlesholds a break while a window on screen has a matching title. Minimized windows and hidden apps don't count. Each entry is a regex that ignores case. A plain word matches anywhere in a title:"Plain"also matches "explain". A TOML literal string with word boundaries,'\bPlain\b', matches the word alone. A browser window shows only its active tab's title.- Each request omma sends an app gives up after 0.25 s. An app that's hung, or slower than that, counts as having no matching window.
The fullscreen and window-title rules need Accessibility permission; see Accessibility.
omma's default schedule and most of its break prompts come from Safe Eyes. The eye prompts follow the 20-20-20 rule instead of eye rotations, which don't help, according to the American Academy of Ophthalmology.
omma talks to macOS through objc2. Its
unsafe code is that interop, and each block says why it's sound.
GPL-3.0-or-later. See LICENSE.