Turn an old Galaxy Watch 4 Classic into a hardware knob and status display for your Mac.
detent (n.) — the little click a rotary knob makes as it settles into each position. Every turn of the watch bezel is one. The logo is the app's own volume gauge: a row of detents, a knob, and a pointer on the last one that clicked.
The watch sits on its charger next to the keyboard. Its rotating bezel becomes a clicky, haptic volume knob; its round screen shows what matters right now — a clock, what's playing, the call you're in — and gives you one-tap controls for all of it. No root, no cloud, no browser extension: just Bluetooth LE between a Wear OS app and a native macOS menu-bar app.
⬇ Download · Quick install · Protocol · How it was built
| 🎚️ Volume knob | Each bezel click = one volume step with a haptic tick, the native macOS volume HUD, and ~27 ms watch→Mac latency. Optional fine steps (64 instead of 16). |
| 🔀 App switcher | Press Back → a ring of your 8 most recent apps with their real icons. Turn to pick, pause or tap to switch. A lone Back press flips to the previous app. |
| 🎵 Now playing | Spotify (via macOS Now Playing) and YouTube in Chrome: album art, progress ring, play/pause/next/previous. Tap the ring for seek mode — the bezel scrubs ±5 s per click. |
| 📞 Meeting deck | Google Meet and Zoom web: mic and camera with their real state, raise hand, hold-to-leave, call timer. Appears automatically when a call starts. |
| 🕐 Idle screen | Editor-styled clock (JetBrains Mono, One Dark), syntax-coloured date, next calendar event as dailyStandup() // in 4m with tap-to-join, and an optional animated sticker. |
| ⚡ Quick actions | Swipe up anywhere: audio output, lock, screen off, push-to-talk, plus any shortcut you drop into a Shortcuts folder named Detent. |
| 🎧 Audio output | Switch between AirPods, speakers, displays, USB and AirPlay devices from the wrist. |
| 🎙️ Push-to-talk | Holds your dictation app's push-to-talk key (Right ⌥, e.g. Muesli) from Talk to Stop, so you can dictate hands-free. |
The watch always shows the most relevant screen, in this order: app switcher › push-to-talk › output picker › quick actions › meeting › now playing › idle clock.
┌──────────── Galaxy Watch 4 Classic (Wear OS 6) ────────────┐
│ Compose UI · rotary input + haptics · ambient mode │
│ BezelService (foreground) — GATT server + advertiser │
└─────────────────────────────┬──────────────────────────────┘
│ Bluetooth LE, custom service
│ input (notify) · state (JSON) · blob (icons, art)
┌─────────────────────────────┴──────────────────────────────┐
│ Detent.app — SwiftUI menu-bar app (macOS 14+) │
│ CoreBluetooth · media keys · CoreAudio · EventKit │
│ MediaRemote (Spotify) · AppleScript→Chrome JS (YouTube, │
│ Meet, Zoom web) · NSWorkspace · Shortcuts │
└────────────────────────────────────────────────────────────┘
- The watch is the peripheral: it advertises a custom GATT service; the Mac connects, subscribes, and reconnects automatically (sleep/wake, Bluetooth toggles, silent links via a 20 s watchdog).
- The watch owns interaction (selection, seek previews, optimistic toggles) so everything feels
instant; the Mac owns truth and pushes small JSON state messages plus chunked binary blobs
(app icons, album art, the sticker). Full spec:
docs/protocol.md. - Everything is native and local. Chrome is driven through its AppleScript
execute javascriptbridge, so there's no extension to install.
- A Galaxy Watch 4 / 4 Classic (tested: SM-R895F on Wear OS 6). Any Wear OS 3+ watch with a rotating bezel or crown should work; the rotating bezel is what makes it great.
- A Mac on macOS 14+ (tested on macOS 26) with Bluetooth.
- Xcode command-line tools, Android SDK + JDK 17, CMake (
brew install cmake). - Google Chrome if you want YouTube / Meet / Zoom-web support.
Grab the latest files from Releases:
- Mac — unzip
Detent-…-macOS.zip, move Detent.app to Applications. It isn't notarized yet, so the first launch needs right-click → Open (orxattr -dr com.apple.quarantine /Applications/Detent.app). - Watch — enable ADB debugging + Wireless debugging on the watch (see below), then
adb install -r -g Detent-…-watch.apk. - Grant the permissions in the table below, flip Chrome's Allow JavaScript from Apple Events, open Detent on the watch and put it on its charger.
cd mac
./build.sh --run # builds, signs with your Apple Development identity, installs to ~/Applicationsbuild.sh also fetches and builds a pinned copy of
mediaremote-adapter (needed for Now Playing on
macOS 15.4+) and bundles it into the app.
Grant these when macOS asks (or later from the menu-bar icon):
| Permission | Why |
|---|---|
| Accessibility | Volume/media keys, app switching, push-to-talk |
| Bluetooth | Talking to the watch |
| Automation → Google Chrome | YouTube, Meet and Zoom web |
| Calendar | The next-event line on the idle screen |
In Chrome, enable View → Developer → Allow JavaScript from Apple Events (in every Chrome profile you use for calls or YouTube).
- On the watch: Settings → About watch → Software → tap Software version 5×, then enable ADB debugging and Wireless debugging.
- Pair and connect (the pairing port and the connect port are different):
adb pair <watch-ip>:<pair-port> <code> adb connect <watch-ip>:<debug-port>
- Build and install:
cd watch ./gradlew assembleDebug adb install -r -g app/build/outputs/apk/debug/app-debug.apk - Open Detent on the watch, put it on its charger, and it will find the Mac on its own.
On the charger the screen stays on (with burn-in protection: a slow pixel drift and dimming after a quiet minute). Off the charger, Wear OS returns to the watch face after ~60 s unless you change Display → Show last app on the watch.
| Gesture | Does |
|---|---|
| Turn the bezel | Volume (shows out = "airpods"; vol = 42; on the idle screen) |
| Back button | App switcher · press again to cancel |
| Swipe up / tap the clock | Quick actions · swipe down or Back to close |
| Tap the progress ring (now playing) | Seek mode — the bezel scrubs |
| Hold Leave (meeting) | Leave the call (release early to cancel) |
| Tap the next-event line | Open the call link |
- Your own buttons: create a folder named Detent in the Shortcuts app and put shortcuts in it (a Focus toggle, a HomeKit scene, a script…). They show up as tiles within a minute.
- Sticker: menu-bar icon → Choose Idle Sticker… and pick any GIF, PNG, JPEG, WebP or HEIC. It's shrunk to 128 px (animations keep their timing) and stored on the watch.
- Fine volume steps: menu-bar icon → Fine volume steps.
detent/
├── mac/ SwiftPM menu-bar app (build.sh → ~/Applications/Detent.app)
├── watch/ Wear OS app (Kotlin, Compose for Wear OS)
├── docs/ protocol.md, screenshots, logo/ (generate.py + build-icons.sh → every icon)
├── spikes/ M0 experiments: BLE latency, now playing, meetings, mic-in-use
├── prototype/ the very first version: adb + getevent → media keys
└── PLAN.md scope, milestones, decisions
- Design preview: pin the idle screen while media is playing (debug builds only):
adb shell am start -n dev.aniket.detent/.MainActivity --es preview idle(… preview offto clear). - Raw bezel events:
adb shell getevent -l /dev/input/event2(detent_bezel,REL_WHEEL ±1). - Watch logs:
adb logcat -s BleLink BlobStore.
| Symptom | Fix |
|---|---|
adb pair → protocol fault (couldn't read status message) / No route to host |
macOS Local Network privacy is blocking your terminal: System Settings → Privacy & Security → Local Network → enable it, then fully restart the terminal. |
| Bezel works but nothing happens on the Mac | Grant Accessibility to Detent (remove and re-add it if it's listed but not working). |
| YouTube / Meet / Zoom not detected | Enable Allow JavaScript from Apple Events in that Chrome profile, and allow Automation for Chrome. The menu shows the exact error. |
| Watch says "waiting for mac…" | Bluetooth on the watch must be on; the Mac app reconnects within a few seconds. |
| Wrong output shown as current | It corrects itself within 10 s (CoreAudio sometimes misses Bluetooth hand-offs). |
- v2 — agentic workflow: Claude Code status ring (working / waiting / done), approve or deny permission prompts from the wrist, push-to-talk to an agent, a desk buzz when long tasks finish.
- Parked ideas: "you're talking while muted" alert, instant replay of the last minute of a meeting,
presenter mode (bezel = slides, gyro = laser pointer). See
PLAN.md.
MIT © 2026 Aniket Singh. Built on a lot of late-night pairing with Claude Code.
- JetBrains Mono — SIL Open Font License 1.1
(
watch/third_party/JetBrainsMono-OFL.txt) - Lucide icons — ISC (
watch/third_party/Lucide-LICENSE.txt) - mediaremote-adapter — BSD 3-Clause (fetched at build time)
- Colours from the One Dark theme.





