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.
- macOS (uses CoreGraphics + Accessibility APIs)
- Node.js ≥ 18
- Xcode Command Line Tools —
xcode-select --install - A webcam
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 |
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.
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 startup
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)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 model
Print the current config (or a single value).
eyeswitch config get
eyeswitch config get smoothingFactorPersist a config value to ~/.config/eyeswitch/config.json.
eyeswitch config set smoothingFactor 0.4
eyeswitch config set switchCooldownMs 300Show whether eyeswitch is calibrated and which monitor is currently focused.
Delete saved calibration data.
Export calibration data to stdout or a file.
eyeswitch calibration export > ~/cal-backup.json
eyeswitch calibration export -o ~/cal-backup.jsonImport calibration from a JSON file.
eyeswitch calibration import ~/cal-backup.jsonAll 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 |
| 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 switches| 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 |
- 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 | Status |
|---|---|
| macOS | Full support |
| Linux | Not supported (native helper is macOS-only) |
| Windows | Not supported |
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:helpersrc/
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)
Built by Abhijitam01