eyeswitch
v1.0.12
Published
Head-tracking monitor focus switcher — look at a screen, it gets focus
Maintainers
Readme
eyeswitch
Look at a screen. It gets focus.
eyeswitch uses your webcam and TensorFlow.js to track where your head is pointing, then automatically moves macOS focus to whichever monitor you're looking at — no keyboard shortcut, no clicking, no magic.
Requirements
- macOS (uses CoreGraphics + Accessibility APIs)
- Node.js ≥ 18
- Xcode Command Line Tools —
xcode-select --install - A webcam
Installation
npm install -g eyeswitchThe native helper binary compiles automatically on install. If it fails:
npm run build:helperGrant two permissions (one-time):
| Permission | Where | |---|---| | Camera | System Settings → Privacy & Security → Camera | | Accessibility | System Settings → Privacy & Security → Accessibility → enable your terminal app |
Quick start
eyeswitch doctor # check everything is set up
eyeswitch calibrate # look at each monitor when prompted
eyeswitch # start trackingPress p to pause/resume. Ctrl+C to stop.
Commands
eyeswitch — start tracking
Options:
--sensitivity <level> Preset: low | medium | high
--no-click Warp cursor only, no synthetic click
--dry-run Log gaze without switching focus
--verbose Print yaw/pitch values on every frame
--camera <index> Camera index (default: 0)
--calibration-file <path> Custom path to calibration JSON
--calibrate Force recalibration on startupeyeswitch calibrate
Look at each screen and press Enter — eyeswitch samples your gaze for ~8 seconds per monitor and saves the result.
eyeswitch calibrate # calibrate all monitors
eyeswitch calibrate --monitor 2 # recalibrate only monitor 2 (1-based)eyeswitch doctor
Checks the native helper, camera, accessibility permission, calibration data, and TF.js model:
✓ Native helper binary
✓ Accessibility permission
✓ Camera access
✓ Calibration data 2 monitors calibrated
✓ TF.js modeleyeswitch config get [key]
Print the current config (or a single value).
eyeswitch config get
eyeswitch config get smoothingFactoreyeswitch config set <key> <value>
Persist a config value to ~/.config/eyeswitch/config.json.
eyeswitch config set smoothingFactor 0.4
eyeswitch config set switchCooldownMs 300eyeswitch status
Show whether eyeswitch is calibrated and which monitor is currently focused.
eyeswitch reset
Delete saved calibration data.
eyeswitch calibration export
Export calibration data to stdout or a file.
eyeswitch calibration export > ~/cal-backup.json
eyeswitch calibration export -o ~/cal-backup.jsoneyeswitch calibration import <file>
Import calibration from a JSON file.
eyeswitch calibration import ~/cal-backup.jsonConfiguration
All values live in ~/.config/eyeswitch/config.json. Edit with eyeswitch config set or directly.
| Key | Default | Description |
|---|---|---|
| smoothingFactor | 0.3 | EMA smoothing — 0 = raw, closer to 1 = very smooth |
| switchCooldownMs | 500 | Minimum ms between focus switches |
| hysteresisFactor | 0.25 | Bias toward staying on the current monitor (0–1) |
| minFaceConfidence | 0.4 | Minimum detection confidence to process a frame |
| cameraIndex | 0 | Which webcam to use |
| targetFps | 30 | Frame capture rate |
| verticalSwitching | false | Enable pitch-based switching for top/bottom monitor layouts |
Sensitivity presets
| Preset | smoothingFactor | hysteresisFactor | switchCooldownMs |
|---|---|---|---|
| low | 0.5 | 0.40 | 800 ms |
| medium | 0.3 | 0.25 | 500 ms (default) |
| high | 0.1 | 0.10 | 200 ms |
eyeswitch --sensitivity high # snappier switching
eyeswitch --sensitivity low # more stable, fewer accidental switchesTroubleshooting
| Problem | Fix |
|---|---|
| Focus doesn't switch | Check Accessibility permission — eyeswitch doctor |
| Camera not found | Try --camera 1 if you have multiple webcams |
| Switching feels jittery | Use --sensitivity low or eyeswitch config set switchCooldownMs 800 |
| Slow to detect face | Lower minFaceConfidence — eyeswitch config set minFaceConfidence 0.3 |
| npm run build:helper fails | Run xcode-select --install first |
| "Native helper not available" | Run npm run build:helper manually |
| Wrong monitor gets focus | Recalibrate — eyeswitch calibrate |
How it works
- Your webcam captures frames via
node-webcam - TensorFlow.js + MediaPipe FaceMesh extracts 468 3D facial landmarks per frame
- Yaw and pitch are computed from jaw-outline and nose-tip landmarks (works with glasses)
- An EMA filter smooths the pose to cut down on jitter
- A calibration map translates gaze angles to the nearest monitor using Euclidean distance with hysteresis
- The native ObjC helper (
eyeswitch-helper) uses CoreGraphics to warp the cursor and the Accessibility API to fire a synthetic click
Platform support
| Platform | Status | |---|---| | macOS | Full support | | Linux | Not supported (native helper is macOS-only) | | Windows | Not supported |
Development
git clone https://github.com/Abhijitam01/eyeswitch.git
cd eyeswitch
npm install
npm run build
npm testTo iterate on the native ObjC helper:
npm run build:helperProject structure
src/
index.ts CLI entry point (Commander.js)
cli.ts Output helpers (chalk, ora)
config.ts Config schema, load/save, sensitivity presets
types.ts Shared TypeScript interfaces
camera/
frame-capture.ts Webcam → FrameBuffer
face/
face-detector.ts TF.js MediaPipe FaceMesh wrapper
pose-estimator.ts Landmarks → yaw/pitch with EMA smoothing
calibration/
calibration-manager.ts Sample collection, persistence, gaze→monitor mapping
sample-aggregator.ts Immutable median aggregator for calibration samples
monitor/
focus-switcher.ts Calls native helper to switch focus
monitor-detector.ts Queries monitor layout via native helper
monitor-mapper.ts Maps gaze to MonitorLayout using calibration data
native/
native-bridge.ts Cross-platform TypeScript wrapper for the native helper
helper/
eyeswitch-helper.m macOS native helper (CoreGraphics, Accessibility)
eyeswitch-helper-win.c Windows native helper (Win32 user32/gdi32)