A lightweight macOS menu bar app that turns the Ulanzi Dial D100H into a fully customisable shortcut launcher.
The Ulanzi Dial D100H presents itself as a standard HID keyboard — every button and the scroll wheel emit hardcoded key combos (Cmd+V, Cmd+C, media keys, volume, etc.). Dial Controller intercepts those events before they reach any other app, suppresses them, and fires whatever shortcut you assigned instead.
- 7 buttons + scroll wheel (clockwise / counter-clockwise), all mappable
- Any shortcut — including multi-modifier combos and global hotkeys registered by other apps
- Assignments survive restarts (stored in UserDefaults)
- No Dock icon, lives entirely in the menu bar
- macOS 13 Ventura or later (tested on macOS 26 Tahoe)
- Ulanzi Dial D100H (VID
0xFFF1, PID0x0082) - Accessibility access must be granted in System Settings → Privacy & Security → Accessibility
- Download
DialController.app.zipfrom the latest release - Unzip and move
DialController.appto/Applications - Launch the app — macOS will ask for Accessibility permission; grant it and relaunch
- The dial icon appears in the menu bar
Click the menu bar icon to open the configuration popover.
| Action | How |
|---|---|
| Map a button | Click Learn, press the dial button you want to map, then record the shortcut |
| Map scroll wheel | Click Learn, turn the dial (CW or CCW), then record the shortcut |
| Remove a mapping | Click the × next to any entry |
The app intercepts the dial's built-in events system-wide, so mappings work in any app.
Requires Xcode Command Line Tools.
git clone https://github.com/noestreich/DialController.git
cd DialController
make run # build, bundle (ad-hoc signed), and launchFor a Developer-ID-signed and notarised build:
# One-time: store your Apple notary credentials
xcrun notarytool store-credentials DialControllerNotary \
--apple-id "you@example.com" \
--team-id "TEAMID" \
--password "xxxx-xxxx-xxxx-xxxx"
make release DEV_ID="Developer ID Application: Your Name (TEAMID)"- HIDManager — opens the device via
IOHIDManager, seizes it exclusively, and collects button/dial events. Because the Ulanzi fires the entire press+release sequence in ~300 µs (modifier after key, unlike normal keyboards), emission is delayed 25 ms so all IOKit callbacks of a burst arrive before the shortcut is dispatched. - EventSuppressor — an always-active
CGEventTapat session level. When HIDManager detects a dial event it callssuppressNext(), which opens a 100 ms suppression window. Any keyDown/keyUp/flagsChanged that arrives during that window is dropped before reaching other apps. - ShortcutRecorder — records shortcuts via a
CGEventTap(notNSEvent) so it sees events even when another app holds a global hotkey viaRegisterEventHotKey. UsesCGEventSource(stateID: .privateState)when translating key codes so held modifiers don't bleed into the displayed character. - KeySender — synthesises events with a private
CGEventSourceand marks them with a custom userData tag so EventSuppressor lets them pass through.
MIT
