feat(input): Ulanzi Dial mapper — Linux evdev backend + visual configurator#3238
Merged
ten9876 merged 1 commit intoMay 28, 2026
Merged
Conversation
…urator First-platform delivery of #3232 (cross-platform BT-HID encoder support). Linux side complete; Windows/macOS land in follow-up PRs. Backend (EvdevEncoderManager, Linux-only): - Scans /dev/input/event* for "Ulanzi Dial Keyboard" sub-device - Opens with EVIOCGRAB to claim it exclusively so the dial's media-key emissions (KEY_PREVIOUSSONG/NEXTSONG/PLAYPAUSE/MUTE/Ctrl+chords) don't leak to the focused window - Event-driven via QSocketNotifier; no polling - Chord assembly state machine decodes Ctrl+key + compound chord events - Hot-plug via QFileSystemWatcher on /dev/input/ UI (UlanziDialMapperDialog, fixed 640x640): - Product image of the dial in the centre, action-combo "pills" positioned around it at each physical control - Each pill is a QComboBox listing every registered ShortcutManager action + every discrete-type MIDI param (shortcut:<id> / midi:<id> tagged so MainWindow dispatcher can route correctly) - Signature-to-pill mapping is firmware-property (hardcoded in kPillSpecs); the function assigned to each pill is user-configurable via the combo and persists to AppSettings under UlanziDial/action/<pillId> MainWindow integration: - Instantiates the manager on the existing ExtControllers thread - Rotary tuneSteps coalesces into the active slice tune step (same pattern as HidEncoderManager from #3171) - buttonEvent dispatcher parses the prefix and invokes the matching ShortcutManager handler or MIDI param setter; falls back to the per-pill defaultAction so first-launch bindings work without the user ever opening the mapper Files: - src/core/EvdevEncoderManager.{h,cpp} - new, Linux-only - src/gui/UlanziDialMapperDialog.{h,cpp} - new, Linux-only - resources/images/ulanzi-dial.png - product image - src/gui/MainWindow.{h,cpp} - wiring + menu entry - CMakeLists.txt - gate new sources on UNIX AND NOT APPLE Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ten9876
added a commit
that referenced
this pull request
May 28, 2026
…per (#3239) Completes the platform matrix promised in #3232. Linux (evdev) landed in #3238; this PR adds the Windows (hidapi) and macOS (IOKit) sides. The mapper dialog drops its Linux guard and now builds everywhere. Architecture ------------ All three backends expose the same Qt signal contract: tuneSteps(int) - rotary delta buttonEvent(sig, action) - canonical Linux-keycode-name strings connectionChanged(bool, name) A new typedef `UlanziDialBackend` (src/core/UlanziDialBackend.h) resolves to the right concrete class per platform. Mapper dialog and MainWindow dispatcher use only the alias and have no platform switches of their own. Windows (UlanziDialWindowsManager) ---------------------------------- - hidapi (already a project dep) enumerates HID devices, matches by product_string substring "Ulanzi Dial" - Opens every matching interface (the dial enumerates as multiple HID interfaces on Windows: Keyboard + Consumer Control, possibly Mouse) and polls each via QTimer at 5ms intervals - HID report parsing: standard 8-byte keyboard report (modifier mask + up-to-6-keys array, diffed across frames for transitions) and Consumer Control reports (2-byte usage code) - HID usage codes mapped to canonical Linux keycode integers so the signature strings (`KEY_PLAYPAUSE`, `Ctrl+V`, ...) match Linux exactly and `kPillSpecs` is unchanged - Chord assembly state machine identical to the Linux backend - Hot-plug via 3s rescan timer - LIMITATION: no exclusive-claim equivalent yet. The dial's keystrokes still reach the focused window because hidapi doesn't suppress OS input routing. RawInput + RIDEV_NOLEGACY is the proper fix; tracked for a follow-up macOS (UlanziDialMacOSManager) ------------------------------ - IOKit HID Manager with device-matching dictionary ({ kIOHIDProductKey: "Ulanzi Dial" }) - `IOHIDManagerOpen` with `kIOHIDOptionsTypeSeizeDevice` -- the documented macOS exclusive-claim mechanism. Equivalent to Linux EVIOCGRAB: when seized, the dial's events stop reaching the OS keyboard stack, so its media keys don't leak to the focused window - Event-driven via `IOHIDManagerRegisterInputValueCallback`; no polling - Same usage-page-to-Linux-keycode mapping and chord assembly as Windows backend - Scheduled on `CFRunLoopGetMain()` (IOKit requires a CFRunLoop, and only the main thread has one integrated with Qt's event loop on macOS). MainWindow conditionally skips `moveToThread` on macOS to match this constraint - Hot-plug native via IOHIDManager device-matching/removal callbacks Build wiring (CMakeLists.txt) ----------------------------- - UlanziDialBackend.h + UlanziDialMapperDialog.{cpp,h}: unconditional - EvdevEncoderManager.{cpp,h}: UNIX AND NOT APPLE (Linux) - UlanziDialWindowsManager.{cpp,h}: WIN32 - UlanziDialMacOSManager.{cpp,h}: APPLE - macOS links -framework IOKit and -framework CoreFoundation Tested ------ - Linux: compiles + runs as before (Linux behaviour unchanged; same EvdevEncoderManager underneath the alias) - Windows + macOS: compile validation gated by CI (check-windows from #3210 and check-macos from #3223). Live testing on hardware happens in the user's environment Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 tasks
6 tasks
jensenpat
added a commit
that referenced
this pull request
Jun 2, 2026
## Summary Adds **`plugins/ulanzi-aethersdr/`** as the third Stream-Deck-style sibling alongside `elgato-aethersdr` and `streamcontroller-aethersdr`. Drives AetherSDR over TCI WebSocket from Ulanzi-Studio-compatible macro keypads / dials. - **22 actions**, as both keypad buttons and rotary (Encoder) knobs: - **Encoder (dial):** VFO Tune, plus rotary **AF Gain / RF Power / Mic Gain / Filter Width / Squelch Level** (rotate to adjust, press+rotate = coarse). VFO dial press toggles MOX; level-knob press is a deliberate no-op so a knob can't key TX. - **TX:** MOX Toggle, TUNE / ATU, RIT Toggle. - **Modes:** Mode Cycle, plus direct USB / LSB / CW / DIGU. - **Bands:** Band Up, Band Down. - **Gain (keypad):** AF / RF / Mic ±5 per press. - TCI command vocabulary kept faithful to `elgato-aethersdr` so both behave identically against the same radio. - Iconography generated programmatically from a single config (`scripts/Generate-Icons.ps1`, PowerShell + `System.Drawing`, no npm/Node deps) → 196×196 PNGs matching Ulanzi Studio's marketplace icon convention. Currently Windows-only; a cross-platform port (ImageMagick / SkiaSharp) is future work. - 5 coordinated shack-companion launcher tiles (AetherSDR / TCI Monitor / ShackLog / IQ Capture / aether-pad) for pairing with Studio's built-in `System → Open` action. - Bundled D100H default profile (`profiles/aethersdr-d100h-default.ulanziDeckProfile`) — one-click Studio import. - **License: GPL-3.0-or-later**, matching the parent project. The vendored Ulanzi SDK in `libs/common-node/` and `libs/common-html/` keeps its own license. The change is fully self-contained under `plugins/ulanzi-aethersdr/` — no C++, CMake, or CI changes. ## Status - **D100H / KEHWIN Dial_Lite** (6 keys + 1 dial, BLE HOGP): smoke-tested end-to-end; MOX, AF, RF and **mic gain all confirmed live** against the radio. (I run this unit on CAD duty now, but it remains a fully supported radio surface for anyone who wants it.) - **D200 / Zkswe "ulanzi"** (USB): used for the gain-seeding and coexistence verification below. - **D200H** (14 keys): plugin code complete, profile layout in progress. - **D200X** (14 keys + 3 knobs): unit arriving shortly; profile follows once on the bench. ### Gain seeding (fixes first-press jump) On TCI connect the plugin issues GET `volume;` / `drive;` / `mic_level;` so the AF / RF / mic local mirrors start from the radio's real values. Without this the first ±5 press jumped from the hard-coded defaults (50 / 100 / 50). AE's init burst does not emit `volume:`, so this pairs with the server-side init-burst fix in **#3245** — the GET-on-connect makes the plugin robust regardless of which AE build it talks to. Verified live 2026-05-28: all three step smoothly with no jump. ### Mic gain `mic_level:` is an AE extension (not in the published TCI spec) but it **is honoured** — verified live 2026-05-28 to actually drive TX mic gain. ## Coexistence with the native UlanziDial backend AetherSDR `main` now ships a native UlanziDial HID backend (**#3238** Linux evdev, **#3239** Windows hidapi + macOS IOKit). This plugin and that backend do **not** collide — verified live 2026-05-28: - The native Windows matcher keeps only HID devices whose hidapi `product_string` contains the case-sensitive substring `"Ulanzi Dial"` (`src/core/UlanziDialWindowsManager.cpp`, `kProductMatch`). - `hid.enumerate()` on this machine reports: D100H → product `Dial_Lite` (manuf `KEHWIN`, VID `0xFFF1` / PID `0x0082`, BLE HOGP); D200 → product `ulanzi` (manuf `Zkswe`, VID `0x2207` / PID `0x0019`, USB). **Neither contains `"Ulanzi Dial"`**, so the native backend claims neither device — both stay free for this plugin via Ulanzi Studio. - Tested against a clean build of this branch (rebased on `main`, `-DHAVE_HIDAPI`, native backend compiled in) with both Ulanzi units plugged in: no double-claim, no conflict. - Trap for reviewers: Windows Device Manager may display the D100H's PnP *FriendlyName* as "Ulanzi Dial", but that is a **different field** from the hidapi `product_string` the matcher reads (which is `Dial_Lite`). ## Repository relationship Independent public-facing releases / Ulanzi Marketplace path lives at **[nigelfenton/aethersdr-ulanzi-plugin](https://github.com/nigelfenton/aethersdr-ulanzi-plugin)**. Both repos stay alive — the standalone is canonical for end-user downloads; this in-tree copy is bundled context for AetherSDR developers. The README inside the plugin documents the relationship explicitly and is the authoritative description of the plugin. ## Test plan - [x] Plugin source loads in Ulanzi Studio without errors (D100H, Windows) - [x] TCI WebSocket connects to AetherSDR on `ws://127.0.0.1:40001` - [x] MOX action flips radio state (`trx:0,<bool>;`) on D100H - [x] AF / RF / mic gain seed from the radio's real values on connect — no first-press jump (2026-05-28) - [x] `mic_level:` verified to actually drive TX mic gain on AE (2026-05-28) - [x] Coexists with the native UlanziDial backend — neither Ulanzi unit matches `kProductMatch` (2026-05-28) - [x] D100H default profile imports cleanly into a fresh Studio - [x] Icon generator (`scripts/Generate-Icons.ps1`) regenerates the PNGs idempotently - [ ] D200H full-keypad layout validated (in progress) - [ ] D200X layout validated (waiting on hardware) - [ ] macOS Studio compatibility re-confirmed against this in-repo path ## Out of scope / future work - Per-app sub-profiles (TCI Monitor / ShackLog / IQ Capture / aether-pad) using Studio's built-in `System.Hotkey` — will land here once each app's hotkey table is known. - Cross-platform icon generator (port `Generate-Icons.ps1` to ImageMagick / SkiaSharp so macOS/Linux contributors can regenerate without `pwsh`). - Marketplace submission (independent of this PR). - A properly-stateful slice-cycle action (the earlier local-index version was removed in v0.1.7; a future one should be driven by AE's slice-list event rather than a local counter). - Broadening the native backend's device match so it also recognises OEM-rebranded Ulanzi units (`Dial_Lite` / `ulanzi`) — raised separately on #3239. --- <sub>_PR description refreshed to match v0.1.7 (rotary knobs, slice-cycle removed, `Generate-Icons.ps1`, GPL-3.0-or-later). The in-plugin README remains the authoritative source of truth._</sub> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: jensenpat <patjensen@gmail.com>
7 tasks
G6PWY-Chris
pushed a commit
to G6PWY-Chris/AetherSDR
that referenced
this pull request
Jun 22, 2026
…urator (aethersdr#3238) First-platform delivery of aethersdr#3232 (cross-platform BT-HID encoder support). Linux side complete; Windows/macOS land in follow-up PRs. Backend (EvdevEncoderManager, Linux-only): - Scans /dev/input/event* for "Ulanzi Dial Keyboard" sub-device - Opens with EVIOCGRAB to claim it exclusively so the dial's media-key emissions (KEY_PREVIOUSSONG/NEXTSONG/PLAYPAUSE/MUTE/Ctrl+chords) don't leak to the focused window - Event-driven via QSocketNotifier; no polling - Chord assembly state machine decodes Ctrl+key + compound chord events - Hot-plug via QFileSystemWatcher on /dev/input/ UI (UlanziDialMapperDialog, fixed 640x640): - Product image of the dial in the centre, action-combo "pills" positioned around it at each physical control - Each pill is a QComboBox listing every registered ShortcutManager action + every discrete-type MIDI param (shortcut:<id> / midi:<id> tagged so MainWindow dispatcher can route correctly) - Signature-to-pill mapping is firmware-property (hardcoded in kPillSpecs); the function assigned to each pill is user-configurable via the combo and persists to AppSettings under UlanziDial/action/<pillId> MainWindow integration: - Instantiates the manager on the existing ExtControllers thread - Rotary tuneSteps coalesces into the active slice tune step (same pattern as HidEncoderManager from aethersdr#3171) - buttonEvent dispatcher parses the prefix and invokes the matching ShortcutManager handler or MIDI param setter; falls back to the per-pill defaultAction so first-launch bindings work without the user ever opening the mapper Files: - src/core/EvdevEncoderManager.{h,cpp} - new, Linux-only - src/gui/UlanziDialMapperDialog.{h,cpp} - new, Linux-only - resources/images/ulanzi-dial.png - product image - src/gui/MainWindow.{h,cpp} - wiring + menu entry - CMakeLists.txt - gate new sources on UNIX AND NOT APPLE Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris
pushed a commit
to G6PWY-Chris/AetherSDR
that referenced
this pull request
Jun 22, 2026
…per (aethersdr#3239) Completes the platform matrix promised in aethersdr#3232. Linux (evdev) landed in aethersdr#3238; this PR adds the Windows (hidapi) and macOS (IOKit) sides. The mapper dialog drops its Linux guard and now builds everywhere. Architecture ------------ All three backends expose the same Qt signal contract: tuneSteps(int) - rotary delta buttonEvent(sig, action) - canonical Linux-keycode-name strings connectionChanged(bool, name) A new typedef `UlanziDialBackend` (src/core/UlanziDialBackend.h) resolves to the right concrete class per platform. Mapper dialog and MainWindow dispatcher use only the alias and have no platform switches of their own. Windows (UlanziDialWindowsManager) ---------------------------------- - hidapi (already a project dep) enumerates HID devices, matches by product_string substring "Ulanzi Dial" - Opens every matching interface (the dial enumerates as multiple HID interfaces on Windows: Keyboard + Consumer Control, possibly Mouse) and polls each via QTimer at 5ms intervals - HID report parsing: standard 8-byte keyboard report (modifier mask + up-to-6-keys array, diffed across frames for transitions) and Consumer Control reports (2-byte usage code) - HID usage codes mapped to canonical Linux keycode integers so the signature strings (`KEY_PLAYPAUSE`, `Ctrl+V`, ...) match Linux exactly and `kPillSpecs` is unchanged - Chord assembly state machine identical to the Linux backend - Hot-plug via 3s rescan timer - LIMITATION: no exclusive-claim equivalent yet. The dial's keystrokes still reach the focused window because hidapi doesn't suppress OS input routing. RawInput + RIDEV_NOLEGACY is the proper fix; tracked for a follow-up macOS (UlanziDialMacOSManager) ------------------------------ - IOKit HID Manager with device-matching dictionary ({ kIOHIDProductKey: "Ulanzi Dial" }) - `IOHIDManagerOpen` with `kIOHIDOptionsTypeSeizeDevice` -- the documented macOS exclusive-claim mechanism. Equivalent to Linux EVIOCGRAB: when seized, the dial's events stop reaching the OS keyboard stack, so its media keys don't leak to the focused window - Event-driven via `IOHIDManagerRegisterInputValueCallback`; no polling - Same usage-page-to-Linux-keycode mapping and chord assembly as Windows backend - Scheduled on `CFRunLoopGetMain()` (IOKit requires a CFRunLoop, and only the main thread has one integrated with Qt's event loop on macOS). MainWindow conditionally skips `moveToThread` on macOS to match this constraint - Hot-plug native via IOHIDManager device-matching/removal callbacks Build wiring (CMakeLists.txt) ----------------------------- - UlanziDialBackend.h + UlanziDialMapperDialog.{cpp,h}: unconditional - EvdevEncoderManager.{cpp,h}: UNIX AND NOT APPLE (Linux) - UlanziDialWindowsManager.{cpp,h}: WIN32 - UlanziDialMacOSManager.{cpp,h}: APPLE - macOS links -framework IOKit and -framework CoreFoundation Tested ------ - Linux: compiles + runs as before (Linux behaviour unchanged; same EvdevEncoderManager underneath the alias) - Windows + macOS: compile validation gated by CI (check-windows from aethersdr#3210 and check-macos from aethersdr#3223). Live testing on hardware happens in the user's environment Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris
pushed a commit
to G6PWY-Chris/AetherSDR
that referenced
this pull request
Jun 22, 2026
…r#3227) ## Summary Adds **`plugins/ulanzi-aethersdr/`** as the third Stream-Deck-style sibling alongside `elgato-aethersdr` and `streamcontroller-aethersdr`. Drives AetherSDR over TCI WebSocket from Ulanzi-Studio-compatible macro keypads / dials. - **22 actions**, as both keypad buttons and rotary (Encoder) knobs: - **Encoder (dial):** VFO Tune, plus rotary **AF Gain / RF Power / Mic Gain / Filter Width / Squelch Level** (rotate to adjust, press+rotate = coarse). VFO dial press toggles MOX; level-knob press is a deliberate no-op so a knob can't key TX. - **TX:** MOX Toggle, TUNE / ATU, RIT Toggle. - **Modes:** Mode Cycle, plus direct USB / LSB / CW / DIGU. - **Bands:** Band Up, Band Down. - **Gain (keypad):** AF / RF / Mic ±5 per press. - TCI command vocabulary kept faithful to `elgato-aethersdr` so both behave identically against the same radio. - Iconography generated programmatically from a single config (`scripts/Generate-Icons.ps1`, PowerShell + `System.Drawing`, no npm/Node deps) → 196×196 PNGs matching Ulanzi Studio's marketplace icon convention. Currently Windows-only; a cross-platform port (ImageMagick / SkiaSharp) is future work. - 5 coordinated shack-companion launcher tiles (AetherSDR / TCI Monitor / ShackLog / IQ Capture / aether-pad) for pairing with Studio's built-in `System → Open` action. - Bundled D100H default profile (`profiles/aethersdr-d100h-default.ulanziDeckProfile`) — one-click Studio import. - **License: GPL-3.0-or-later**, matching the parent project. The vendored Ulanzi SDK in `libs/common-node/` and `libs/common-html/` keeps its own license. The change is fully self-contained under `plugins/ulanzi-aethersdr/` — no C++, CMake, or CI changes. ## Status - **D100H / KEHWIN Dial_Lite** (6 keys + 1 dial, BLE HOGP): smoke-tested end-to-end; MOX, AF, RF and **mic gain all confirmed live** against the radio. (I run this unit on CAD duty now, but it remains a fully supported radio surface for anyone who wants it.) - **D200 / Zkswe "ulanzi"** (USB): used for the gain-seeding and coexistence verification below. - **D200H** (14 keys): plugin code complete, profile layout in progress. - **D200X** (14 keys + 3 knobs): unit arriving shortly; profile follows once on the bench. ### Gain seeding (fixes first-press jump) On TCI connect the plugin issues GET `volume;` / `drive;` / `mic_level;` so the AF / RF / mic local mirrors start from the radio's real values. Without this the first ±5 press jumped from the hard-coded defaults (50 / 100 / 50). AE's init burst does not emit `volume:`, so this pairs with the server-side init-burst fix in **aethersdr#3245** — the GET-on-connect makes the plugin robust regardless of which AE build it talks to. Verified live 2026-05-28: all three step smoothly with no jump. ### Mic gain `mic_level:` is an AE extension (not in the published TCI spec) but it **is honoured** — verified live 2026-05-28 to actually drive TX mic gain. ## Coexistence with the native UlanziDial backend AetherSDR `main` now ships a native UlanziDial HID backend (**aethersdr#3238** Linux evdev, **aethersdr#3239** Windows hidapi + macOS IOKit). This plugin and that backend do **not** collide — verified live 2026-05-28: - The native Windows matcher keeps only HID devices whose hidapi `product_string` contains the case-sensitive substring `"Ulanzi Dial"` (`src/core/UlanziDialWindowsManager.cpp`, `kProductMatch`). - `hid.enumerate()` on this machine reports: D100H → product `Dial_Lite` (manuf `KEHWIN`, VID `0xFFF1` / PID `0x0082`, BLE HOGP); D200 → product `ulanzi` (manuf `Zkswe`, VID `0x2207` / PID `0x0019`, USB). **Neither contains `"Ulanzi Dial"`**, so the native backend claims neither device — both stay free for this plugin via Ulanzi Studio. - Tested against a clean build of this branch (rebased on `main`, `-DHAVE_HIDAPI`, native backend compiled in) with both Ulanzi units plugged in: no double-claim, no conflict. - Trap for reviewers: Windows Device Manager may display the D100H's PnP *FriendlyName* as "Ulanzi Dial", but that is a **different field** from the hidapi `product_string` the matcher reads (which is `Dial_Lite`). ## Repository relationship Independent public-facing releases / Ulanzi Marketplace path lives at **[nigelfenton/aethersdr-ulanzi-plugin](https://github.com/nigelfenton/aethersdr-ulanzi-plugin)**. Both repos stay alive — the standalone is canonical for end-user downloads; this in-tree copy is bundled context for AetherSDR developers. The README inside the plugin documents the relationship explicitly and is the authoritative description of the plugin. ## Test plan - [x] Plugin source loads in Ulanzi Studio without errors (D100H, Windows) - [x] TCI WebSocket connects to AetherSDR on `ws://127.0.0.1:40001` - [x] MOX action flips radio state (`trx:0,<bool>;`) on D100H - [x] AF / RF / mic gain seed from the radio's real values on connect — no first-press jump (2026-05-28) - [x] `mic_level:` verified to actually drive TX mic gain on AE (2026-05-28) - [x] Coexists with the native UlanziDial backend — neither Ulanzi unit matches `kProductMatch` (2026-05-28) - [x] D100H default profile imports cleanly into a fresh Studio - [x] Icon generator (`scripts/Generate-Icons.ps1`) regenerates the PNGs idempotently - [ ] D200H full-keypad layout validated (in progress) - [ ] D200X layout validated (waiting on hardware) - [ ] macOS Studio compatibility re-confirmed against this in-repo path ## Out of scope / future work - Per-app sub-profiles (TCI Monitor / ShackLog / IQ Capture / aether-pad) using Studio's built-in `System.Hotkey` — will land here once each app's hotkey table is known. - Cross-platform icon generator (port `Generate-Icons.ps1` to ImageMagick / SkiaSharp so macOS/Linux contributors can regenerate without `pwsh`). - Marketplace submission (independent of this PR). - A properly-stateful slice-cycle action (the earlier local-index version was removed in v0.1.7; a future one should be driven by AE's slice-list event rather than a local counter). - Broadening the native backend's device match so it also recognises OEM-rebranded Ulanzi units (`Dial_Lite` / `ulanzi`) — raised separately on aethersdr#3239. --- <sub>_PR description refreshed to match v0.1.7 (rotary knobs, slice-cycle removed, `Generate-Icons.ps1`, GPL-3.0-or-later). The in-plugin README remains the authoritative source of truth._</sub> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: jensenpat <patjensen@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First-platform delivery of #3232 (cross-platform BT-HID encoder support).
Linux side complete; Windows/macOS land in follow-up PRs.
Backend (EvdevEncoderManager, Linux-only):
emissions (KEY_PREVIOUSSONG/NEXTSONG/PLAYPAUSE/MUTE/Ctrl+chords) don't
leak to the focused window
UI (UlanziDialMapperDialog, fixed 640x640):
positioned around it at each physical control
action + every discrete-type MIDI param (shortcut: / midi:
tagged so MainWindow dispatcher can route correctly)
kPillSpecs); the function assigned to each pill is user-configurable
via the combo and persists to AppSettings under
UlanziDial/action/
MainWindow integration:
(same pattern as HidEncoderManager from feat(hid): recognize aether-pad RC-28 emulator (Arduino VID/PID alias) #3171)
ShortcutManager handler or MIDI param setter; falls back to the
per-pill defaultAction so first-launch bindings work without the
user ever opening the mapper
Files:
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com