Personal prototype built in July 2026. It works on my machine and I use it for fun — it is not a polished or maintained product.
Native macOS camera hand-control prototype for Roblox-style games. It uses Apple Vision hand pose tracking and synthesizes real keyboard key down/up events (CGEvents), which macOS only allows after the app is granted the Accessibility permission.
This project is inspired by minwoo19930301/mac-hand-control, but it targets game movement instead of Spaces/fullscreen shortcuts.
- Fold index finger: holds
A. - Fold middle finger: holds
W. - Fold ring finger: holds
D. - Fold pinky finger: holds
S. - Fold two or three fingers: holds the matching movement keys together (folding all four becomes the fist gesture instead).
- Thumb-index pinch: taps
Spacefor jump. - Thumb-middle pinch: taps
Ifor Roblox-style zoom in. - Thumb-ring pinch: taps
Ofor Roblox-style zoom out. - Fist: taps
E(and releases any held movement keys). - All fingers spread out: releases movement keys and stops.
Everything lives in one Swift file (Sources/MacFingerGameControl/main.swift, ~1,450 lines) with no third-party dependencies — just Apple frameworks: AVFoundation, Vision, CoreGraphics, ApplicationServices, CoreImage, and AppKit.
- Capture — an
AVCaptureSession(.mediumpreset) reads the frontbuiltInWideAngleCamera(falling back to the system default camera if that isn't available) through anAVCaptureVideoDataOutputwithalwaysDiscardsLateVideoFrames, delivering frames on a serial dispatch queue. Frames are throttled to at most ~30 fps. - Hand pose — each frame goes through a
VNImageRequestHandlerrunning aVNDetectHumanHandPoseRequestwithmaximumHandCount = 2. Joints come fromrecognizedPoints(.all)and are kept only when confidence is at least 0.30. Only the first detected hand drives the controls. - Gesture classification — a small state machine (
GameGestureController) turns joint positions into intent:- Finger bend: for each finger, extension = distance(wrist, tip) − distance(wrist, PIP), normalized by a palm scale (wrist-to-index-MCP distance). A finger counts as extended above 0.14× palm scale (0.12× for the pinky) and folded below 0.10× (0.08× for the pinky); in between it is neutral, which acts as a dead band so a half-bent finger doesn't flicker between key press and release.
- Pinch: the thumb tip's distance to the index/middle/ring tips is normalized by palm width (index-MCP to little-MCP); the closest tip within 0.46× palm width counts as a pinch. Pinch taps are edge-triggered with a 0.45 s cooldown, and a pinched finger's movement key is suppressed so pinching for
Spacedoesn't also holdA. - Fist / stop: all four fingers folded taps
E(edge-triggered, 0.55 s cooldown); all four extended is a stop pose that releases every held key.
- Keyboard output — a
KeyboardDrivercreates keyboardCGEvents from aCGEventSource(stateID: .hidSystemState)and posts them to.cghidEventTap. Held movement keys are updated as a set diff (key-down for newly folded fingers, key-up for released ones), and taps are key-down + 35 ms + key-up. Nothing is posted unlessAXIsProcessTrusted()returns true; the app prompts viaAXIsProcessTrustedWithOptionsand links to the Accessibility pane. - Debug UI — an AppKit window (floating level, joins all Spaces) shows the mirrored camera feed (rendered to
CGImageviaCIContext), a hand-skeleton overlay drawn withNSBezierPathjoint chains, green badges on fingertips classified as folded (which normally means their movement key is held — except a pinch-suppressed finger), pinch/fist/STOP readouts, and a status panel. The same Pause / Reset / Release actions are also available from anNSStatusItemmenu-bar item, so the window can be closed while playing.
Requires macOS 14+ and the Xcode Command Line Tools (swiftc).
./build.shThe app bundle is created at:
build/Mac Finger Game Control.app- Open the app from
build/Mac Finger Game Control.app. - Approve Camera permission.
- Click
Enable AXand allow the app in System Settings > Privacy & Security > Accessibility. - Put Roblox or another game in front.
- Fold individual fingers as button controls.
The debug window can stay floating while you play, or you can close it and keep the menu bar item running.
./package.shThis creates:
dist/Mac-Finger-Game-Control.dmg- Only the first detected hand sends keys; a second hand is drawn in the overlay but ignored for control.
- The key mapping (
W/A/S/D,Space,E,I,O) and all gesture thresholds are hardcoded — there is no remapping UI and no per-user calibration. - Pinch and fist taps are rate-limited by fixed cooldowns (0.45 s and 0.55 s), so rapid repeated taps aren't possible.
- Keyboard only: folded fingers map to on/off key holds. There is no mouse/camera-look control and no analog input.
- Tracking needs the wrist, the index MCP (the palm-scale reference), and all four finger tip/PIP joints visible above the confidence threshold; if any drop out, the app falls back to "waiting" and releases all keys.
- Frames are processed at most ~30 fps, so there is noticeable latency between a gesture and the key event.
- macOS 14+ only.
build.shcompiles for the host architecture only (not a universal binary), and games that read input below the HID event tap may ignore syntheticCGEvents.
- If a gesture gets confused, use
Resetin the app or menu bar. - If a key seems stuck, use
Releaseor spread all fingers out. - Some games may require you to click back into the game after opening the debug window.
- This is ad-hoc signed for local use, so macOS may show an extra security prompt the first time you run it.
- Everything runs on-device; the app has no networking code and never records or uploads camera frames.