A lightweight macOS menu bar app that displays a pair of googly eyes that follow your mouse cursor, blink when you click, and provide quick utilities.
Inspired by Googly Eyes by Sindre Sorhus.
Vibe coded with DeepSeek V4.1 Flash / OpenCode
- Eyes follow mouse — Each eye independently tracks the mouse cursor; when the cursor is between the eyes, they go cross-eyed 👀
- Blink on click — Global left-click → left eye blinks, global right-click → right eye blinks; clicking the menu bar icon also blinks. Hold to keep closed, release to open
- Left-click → Context menu — Left-click the menu bar icon for:
- Current Finder path display (truncated with tooltip) & copy
- Open terminal
- Refresh position
- Prevent Sleep (checkmark when enabled)
- Launch at Login (checkmark when enabled)
- Settings...
- About (with version)
- Restart — relaunch the app to reclaim memory
- Quit
- Right-click left eye → Open Terminal — Right-click the left eye area to launch your preferred terminal at the Finder's current window path. Built-in presets (Terminal, iTerm, Alacritty, Kitty, WezTerm, Ghostty) are launched correctly; a custom command with
{path}/{app}placeholders can be set for full control and runs in a login shell - Right-click right eye → Prevent Sleep — Right-click the right eye area to toggle macOS sleep prevention (highlight turns red when active); auto-deactivates on screen lock or system sleep; persistence mode configurable in Settings (always maintain / don't maintain on sleep-lock / never maintain)
- Configurable — Adjust eye size, pupil size, and eye gap via Settings
- Launch at Login — Toggle from the menu or Settings (requires .app bundle)
- Minimal resources — ~0% CPU when idle, only 20–40MB memory
- macOS 15.0 (Sequoia) or later
- Apple Silicon Mac (M-series, arm64) — Intel Macs are not supported
- Xcode 16+ (for .app bundle build)
- Swift 6.0+ / SPM (for bare binary build)
git clone https://github.com/WHYBBE/SwiftEyes.git
cd SwiftEyes
swift build -c release
.build/release/SwiftEyes
⚠️ SPM produces a bare binary without an app bundle. Launch-at-login and Finder path access via Apple Events will not work in this mode.
cd SwiftEyes
open SwiftEyes.xcodeprojBuild & run from Xcode (⌘R). Or from the command line:
xcodebuild -project SwiftEyes.xcodeproj -scheme SwiftEyes -configuration Release -derivedDataPath .build/xcode build
open .build/xcode/Build/Products/Release/SwiftEyes.appTo install:
cp -r .build/xcode/Build/Products/Release/SwiftEyes.app /Applications/| Action | Effect |
|---|---|
| Move mouse | Pupils follow cursor direction |
| Left-click menu bar icon | Context menu (and a blink) |
| Right-click left eye area | Open terminal at Finder path |
| Right-click right eye area | Toggle sleep prevention |
| Left-click anywhere | Left eye blinks (hold to keep closed) |
| Right-click anywhere | Right eye blinks (hold to keep closed) |
| Context menu | |
| ↳ Path / Copy Path | Show (truncated, tooltip for full) & copy Finder's front window path |
| ↳ Open Terminal | Open terminal at Finder path |
| ↳ Refresh Position | Recalculate eye centers |
| ↳ Prevent Sleep | Toggle sleep prevention (checkmark when enabled; auto-deactivates on lock/sleep) |
| ↳ Launch at Login | Toggle launch at login (checkmark when enabled) |
| ↳ Settings... | Open settings window |
| ↳ About | Show about window with repo link (menu shows version) |
| ↳ Restart | Relaunch the app, releasing all memory |
| ↳ Quit | Quit app |
| State | Left Eye | Right Eye |
|---|---|---|
| Default | Black pupil, white highlight | Black pupil, white highlight |
| Sleep prevention OFF | — | Black pupil + white highlight |
| Sleep prevention ON | — | Black pupil + red highlight |
| Blinking | Closed line | Closed line |
The left eye is a one-shot action (open terminal), so it has no persistent active state. The right eye's highlight turns red while sleep prevention is active.
| Left blink | Right blink | Prevent Sleep |
|---|---|---|
![]() |
![]() |
![]() |
| Menu | Settings |
|---|---|
![]() |
![]() |
| Setting | Default | Range |
|---|---|---|
| Eye size | 11 | 6–11 (capped to the menu bar height) |
| Pupil size | 5 | 2–10 |
| Eye gap | 6 | 0–20 |
| Terminal app | Terminal / iTerm / Alacritty / Kitty / WezTerm / Ghostty / Custom | Preset or any .app |
| Custom command | (empty) | Shell command using {path} / {app} (Custom mode) |
| Language | System | System / 中文 / English |
| Launch at login | Off | On/Off |
| Sleep prevention persistence | Don't maintain on sleep/lock, maintain on restart | Always maintain / Don't maintain on sleep/lock / Never maintain |
Each slider has a reset button (↺) to restore its default value. The custom command runs in a login shell, so tools installed via Homebrew are on
PATH.
Sources/SwiftEyes/
├── SwiftEyesApp.swift # AppKit @main entry + AppDelegate (no SwiftUI)
├── Info.plist # Bundle metadata (LSUIElement, etc.)
├── SwiftEyes.entitlements # Apple Events entitlement
├── Assets.xcassets/ # App icon & asset catalog
├── StatusBar/
│ └── StatusBarController.swift # NSStatusItem + GooglyEyesNSView + context menu
├── Views/
│ ├── GooglyEyesView.swift # NSView-based eye drawing (Core Graphics)
│ ├── SettingsView.swift # AppKit settings form (Auto Layout)
│ ├── SettingsWindowController.swift # Settings window management
│ └── AboutWindowController.swift # About window with repo link
└── Services/
├── EyesConfig.swift # Eye parameters + sleep persist mode
├── EyesState.swift # Blink & active state (event monitors)
├── MouseTracker.swift # Global mouse tracking (throttled)
├── TerminalLauncher.swift # Finder path (osascript) + terminal launch
├── SleepPreventer.swift # IOKit IOPMAssertion (system + display sleep)
├── WindowActivationManager.swift # Reference-counted NSApp activation policy
├── L10n.swift # Localization (Chinese / English)
└── Log.swift # os.Logger
- Pure AppKit — A plain
NSApplicationDelegateentry point; the status item, windows and the settings form are all AppKit (NSStackView+ Auto Layout). No SwiftUI dependency. - NSView + Core Graphics — Direct drawing via
NSView.draw()withneedsDisplay, no diffing or hosting layer overhead - Throttled mouse tracking — Global
NSEventmonitor at ~12fps with offset deduplication;onOffsetChangedtriggers a redraw only when the pupil position actually changes - No Combine in the hot path —
MouseTrackerandEyesStateuse plain properties + callbacks instead of@Published/ObservableObject - Blink handling — Global monitors cover other apps; the menu bar icon click is handled explicitly (global monitors ignore our own events). Blink/active changes repaint immediately so the status item updates even while a menu is tracking
- Eye size cap — The eye radius is clamped to half the status bar height so it never clips
- Coalesced layout updates —
scheduleUpdateEyeCenters()batches same-runloop coordinate recalculations when dragging settings sliders - IOKit dual assertion —
PreventUserIdleSystemSleep+PreventUserIdleDisplaySleepheld while active; auto-released on screen lock or system sleep; desired state persisted with a configurable persistence mode; always-maintain mode re-arms assertions on wake/unlock - Async Finder path — The front Finder window path is fetched off the main thread via
osascriptand cached (2s TTL); the first Automation prompt never blocks the UI - Terminal launch — Known terminals are launched via their bundle binaries (so a new window opens even when the app is already running); a user command template with
{path}/{app}can override this and runs in a login shell - Dirty-rect partial redraw — Mouse moves invalidate the union of the previous and current pupil rects, not the whole view; full redraw is reserved for blink/config changes
hypotfor distance — Useshypot(dx, dy)instead ofsqrt(dx*dx + dy*dy)- Static CGColor constants — Eye colors pre-converted to
CGColoronce, avoiding per-frame bridging WindowActivationManager— Reference-countedpushRegular/popRegularso opening both Settings and About won't prematurely flip back to.accessory; keeps the app out of the Dock while letting windows come to the front- L10n dictionary — In-memory translation table keyed by language code, no .strings files;
languagepersisted via UserDefaults and defaulting to System







