Skip to content
Anikets08Public

About

Turn an old Galaxy Watch into a hardware knob and status display for your Mac — bezel volume, app switcher, now playing, meeting controls. No root, just Bluetooth LE.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

25 Commits

Folders and files

Repository files navigation

Detent logo

Detent

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.

Idle screen: editor-styled clock with a segmented volume gauge Turning the bezel: out = "airpods"; vol = 100; Quick actions grid

App switcher ring Meeting deck Audio output picker

⬇ Download · Quick install · Protocol · How it was built


What it does

🎚️ 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.

How it works

┌──────────── 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 javascript bridge, so there's no extension to install.

Requirements

  • 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.

Quick install

Grab the latest files from Releases:

  1. Mac — unzip Detent-…-macOS.zip, move Detent.app to Applications. It isn't notarized yet, so the first launch needs right-click → Open (or xattr -dr com.apple.quarantine /Applications/Detent.app).
  2. Watch — enable ADB debugging + Wireless debugging on the watch (see below), then adb install -r -g Detent-…-watch.apk.
  3. 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.

Build from source

1. Mac app

cd mac
./build.sh --run      # builds, signs with your Apple Development identity, installs to ~/Applications

build.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).

2. Watch app

  1. On the watch: Settings → About watch → Software → tap Software version 5×, then enable ADB debugging and Wireless debugging.
  2. 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>
  3. Build and install:
    cd watch
    ./gradlew assembleDebug
    adb install -r -g app/build/outputs/apk/debug/app-debug.apk
  4. 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.

Using it

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

Make it yours

  • 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.

Development

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 off to clear).
  • Raw bezel events: adb shell getevent -l /dev/input/event2 (detent_bezel, REL_WHEEL ±1).
  • Watch logs: adb logcat -s BleLink BlobStore.

Troubleshooting

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).

Roadmap

  • 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.

Licence

MIT © 2026 Aniket Singh. Built on a lot of late-night pairing with Claude Code.

Credits & licences

  • 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.

About

Turn an old Galaxy Watch into a hardware knob and status display for your Mac — bezel volume, app switcher, now playing, meeting controls. No root, just Bluetooth LE.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages