A lightweight macOS menu-bar utility that reminds you to rest your eyes using the 20‑20‑20 rule: every 20 minutes, take a short break and look away from the screen.
Table of Contents
- Features
- Requirements
- Quick start
- Build from source
- Usage
- Configuration (UserDefaults keys)
- Developer notes / important files
- Packaging & distribution
- Troubleshooting
- Contributing
- License & acknowledgments
Features
- Stable, flicker-free full-screen blur overlay (per-screen)
- Shows above all apps (including full‑screen) using
.screenSaverwindow level - Keyboard & mouse input blocking during breaks (Accessibility permission required)
- Emergency-escape chord: Ctrl + Option + Command + Shift + E
- Multi-monitor support (one overlay per NSScreen)
- Configurable interval and break duration
Requirements
- macOS 13.0+ (Info.plist minimum)
- Accessibility permission (for the CGEventTap input blocker)
- Screen Recording permission may be required on some macOS versions for reliable screenshots
Quick start (user)
- Download EyeRest.app from Releases (or build from source below).
- Copy to /Applications
- Open the app and grant Accessibility (and Screen Recording if prompted).
- The app runs from the menu bar — use the menu to pause or change interval.
Build from source
Prerequisites
- Xcode (with command-line tools) installed (install with
xcode-select --installif needed)
Build & install (recommended)
A convenience script is included to clean previous builds, build the Release configuration, and copy the built app into /Applications.
cd /path/to/eye-rest
chmod +x ./scripts/build-and-install.sh
./scripts/build-and-install.shThe script may prompt for your password because it uses sudo to copy to /Applications and to perform codesigning/xattr operations.
Manual command-line build (no script)
cd /path/to/eye-rest
xcodebuild -project EyeRest.xcodeproj -scheme EyeRest -configuration Release clean build
BUILD_APP=$(find ~/Library/Developer/Xcode/DerivedData -path "*/Build/Products/Release/EyeRest.app" -print -quit)
cp -R "$BUILD_APP" /Applications/
codesign --force --deep --sign - /Applications/EyeRest.app
xattr -cr /Applications/EyeRest.appBuild in Xcode
Open EyeRest.xcodeproj in Xcode and build/run (⌘R). After installation, grant Accessibility permission in System Settings → Privacy & Security → Accessibility.
Usage
- Menu bar popover shows next break, lets you trigger a break now, pause the schedule, and change interval/duration.
- During a break a full-screen static blurred snapshot is shown with a countdown ring.
- Emergency escape: press Ctrl+Option+Cmd+Shift+E to end a break early.
Configuration (UserDefaults)
The app stores settings in ~/Library/Preferences/<bundle-id>.plist (example bundle id: com.yourname.EyeRest). Use defaults to read/write values.
Important keys (as used in the code):
breakIntervalMinutes(Int) — interval between breaks in minutes (default: 20)breakDurationSeconds(Int) — break length in seconds (default: 30)countdownDuration(Int) — deprecated but still referenced by the UI countdown ringpaused(Bool) — pause/resume break scheduling
Examples:
# Set 30-minute interval
defaults write com.yourname.EyeRest breakIntervalMinutes -int 30
# Set 15-second break duration
defaults write com.yourname.EyeRest breakDurationSeconds -int 15
# Pause breaks
defaults write com.yourname.EyeRest paused -bool true
# Quick test: 1-minute interval + 5-second break
defaults write com.yourname.EyeRest breakIntervalMinutes -int 1
defaults write com.yourname.EyeRest breakDurationSeconds -int 5Developer notes / important files
- Language: Swift (SwiftUI + AppKit hybrid)
- Key code locations:
- App entry & lifecycle:
EyeRest/App/EyeRestApp.swift,EyeRest/App/AppDelegate.swift - Scheduler:
EyeRest/Scheduling/BreakScheduler.swift - Overlay windows and blur:
EyeRest/Overlay/OverlayWindowController.swift,EyeRest/Overlay/OverlayView.swift - Input blocking (CGEventTap):
EyeRest/Input/InputBlocker.swift - Menu bar UI:
EyeRest/MenuBar/MenuBarView.swift - Preferences wrapper:
EyeRest/Preferences/UserPreferences.swift
- App entry & lifecycle:
- Blur implementation:
- Captures a screenshot per screen using
CGDisplayCreateImage()and applies a Core ImageCIGaussianBlur(radius ≈ 30) to produce a static, flicker-free overlay (seeBLUR_IMPLEMENTATION.md).
- Captures a screenshot per screen using
- Overlay window:
- Uses
NSWindow.Level.screenSaverand collection behavior.canJoinAllSpacesto appear above everything.
- Uses
- Input blocking:
- Uses
CGEvent.tapCreateat.cgAnnotatedSessionEventTapto intercept keyboard/mouse events while a break is active.
- Uses
Packaging & distribution
- For personal use: copy the built
.appto/Applicationsand runxattr -cr /Applications/EyeRest.appif Gatekeeper blocks it. - For public distribution: sign, enable hardened runtime, build a signed DMG, and notarize via Apple's notarization flow. See
PACKAGING.mdfor detailed instructions and sample GitHub Actions workflow.
Troubleshooting
- Accessibility not granted: System Settings → Privacy & Security → Accessibility → Enable EyeRest
- Screen Recording prompt: EyeRest triggers a check on first launch using ScreenCaptureKit to show the Screen Recording permission dialog. If denied, enable System Settings → Privacy & Security → Screen Recording.
- Gatekeeper:
xattr -cr /Applications/EyeRest.appor use System Settings → Privacy & Security → Open Anyway. - If the blur flickers, check
BLUR_IMPLEMENTATION.mdandTROUBLESHOOTING.mdfor mitigation strategies (screenshot-based blur is the recommended approach).
Contributing
- Bug reports and pull requests welcome.
- Open an issue describing the problem and include macOS version and exact steps to reproduce.
- Follow code style in the repository; prefer small, focused PRs.
License & acknowledgments
- License: MIT
- Built with Swift, SwiftUI, AppKit, Core Image. Thanks to the open-source community for examples and guidance.
If you want a short checklist or a quick demo script, see PACKAGING.md and BLUR_IMPLEMENTATION.md for more details.