Skip to content

Multi-device support: DeviceProfile abstraction, hotplug, contribution pipeline - #1

Merged
modfox merged 7 commits into
mainfrom
multi-device-support
Jul 13, 2026
Merged

Multi-device support: DeviceProfile abstraction, hotplug, contribution pipeline#1
modfox merged 7 commits into
mainfrom
multi-device-support

Conversation

@modfox

@modfox modfox commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Restructures Nib so supporting another tablet is data, not new decode code, and adds the tooling to bring up a device from a capture bundle without the maintainer owning the hardware. Answers "how do we support more tablets when I only have the DTH2420?"

Four self-contained commits:

  1. Extract DeviceProfile abstraction + attach-time enumeration — all model-specific identity, report layouts, mode-switch and touch-enable move into a DeviceProfile; PenReport/TouchReport parse from a layout; the profile is resolved from connected hardware at startup. Pure refactor, tests stay green.
  2. DeviceManager: hotplug-driven profile re-resolution — per-vendor watcher transports re-detect the connected profile on attach/detach and rebuild the pen/touch pipeline when it changes. One running instance drives whichever supported tablet is plugged in, comes up cleanly when a tablet is attached after launch, and switches models without a restart.
  3. Fix pen mapping to wrong screen after replug — on replug the HID interface re-attaches before the tablet's display re-registers, so the attach-time mapping fell back to the main display. A CGDisplayReconfiguration callback rebuilds the mappings once the display layout settles (also covers resolution/arrangement changes).
  4. Device-contribution pipelinenib capture (raw multi-interface dump), a data-driven DeviceFixtures/ProfileConformanceTests harness (a new profile with fixtures is validated against real hardware output, no per-device test code), and docs/ADDING-A-DEVICE.md covering the capture-bundle format, profile template, and on-hardware sign-off.

Testing

  • swift test — 17 tests green (14 existing + 3 conformance).
  • Built the signed Nib.app, reloaded the LaunchAgent, and verified live on the DTH2420: profile detection, mode-switch, touch-enable, hotplug re-resolution (unplug → no supported tablet connected, replug → now driving …), and the replug wrong-screen fix.
  • nib capture smoke-tested: enumerates and opens the pen / ExpressKey Remote / touch interfaces.

Notes / follow-ups

  • The descriptor can't provide the pen mode-switch / touch-enable sequences — that recovery still needs someone with the device (documented in the guide).
  • Deferred by design: an offline descriptor→DeviceProfile generator, to build once a real second device or capture bundle exists.

🤖 Generated with Claude Code

modfox and others added 7 commits July 13, 2026 08:40
Move all per-device identity, report layouts, mode-switch and touch-enable
sequences into a data-driven DeviceProfile (NibCore/DeviceProfile.swift), so
adding a tablet model is data rather than edits to the decode path.

- PenReport/TouchReport parse from a PenLayout/TouchLayout (default .dth2420).
- main.swift threads a resolved `profile` through display match, mode-switch,
  touch-enable, transports and report guards.
- C shim gains nib_hid_enumerate() and reports each device's real VID/PID on
  attach/detach; Swift adds HIDTransport.enumerate + DeviceProfile.detectConnected.
- profile is now chosen from connected hardware at startup, falling back to the
  DTH2420 reference when nothing recognized is attached.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the pen + touch transport lifecycle out of main.swift into a DeviceManager
that watches every known vendor for attach/detach and, on each event, re-detects
the connected profile. When it changes, it tears down and rebuilds the pen/touch
pipeline for the new device — so one running instance can drive whichever supported
tablet is plugged in, come up cleanly when a tablet is attached after launch, and
switch models without a restart.

- HIDTransport gains an onDetach callback and applyFeatures/applyOutputs helpers.
- main.swift's `run` builds a DeviceManager; rebuildMappings recomputes pen + touch
  maps (using each profile's own logical maxes) on profile change; one-shot modes
  resolve once via detectProfile().
- StatusBar takes a calibrationBounds closure so recalibration uses the active
  tablet's display.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On replug the pen/touch HID interface re-attaches before the tablet's DisplayPort
output re-registers with the window server, so the attach-time mapping fails to
find the panel and falls back to the main display — the pen then tracks on the
wrong screen.

Register a CGDisplayReconfiguration callback that rebuilds the pen + touch mappings
for the active profile once the display layout settles, so the mapping self-corrects
when the panel appears. Also covers resolution and monitor-arrangement changes while
the tablet stays connected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make it possible to add a tablet from a capture bundle without the maintainer
owning the hardware.

- `nib capture`: opens every connected interface of known vendors, sends the
  best-guess Wacom enable sequences, and dumps raw reports tagged by PID — one
  command a contributor runs while performing labeled gestures.
- DeviceFixtures + ProfileConformanceTests: a data-driven registry where each
  device contributes its profile plus real captured gestures; the suite asserts
  the profile decodes them. Adding a device with fixtures adds its coverage with
  no per-device test code. Seeded with the DTH2420 captures (17 tests total).
- docs/ADDING-A-DEVICE.md: the end-to-end workflow (capture bundle format, profile
  template, fixtures, on-hardware sign-off) and an honest note on what still needs
  the physical device (mode-switch / touch-enable recovery).
- ROADMAP/README updated: device-config abstraction marked done; pointers added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Runs `swift build` (all products, incl. the AppKit app target) and `swift test`
(NibCore unit tests + ProfileConformanceTests) on every push to main and every PR,
so contributed device profiles get validated against their fixtures automatically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- HIDTransport: balance the start() passRetained with a release in stop() (stored
  as selfRef). Transports are now ephemeral (a fresh pen/touch pair per reconcile),
  so the previously-harmless unbalanced retain leaked one wrapper per hotplug swap.
- DeviceManager.bringUp: weakly capture penT/touchT in their own onAttach closures
  to break the transport→closure→transport retain cycle (leaked a pen+touch wrapper
  on every device swap).
- enableTouch: label the log by index + report ID instead of the positional
  "multitouch"/"configure", which only held for the DTH2420's exact sequence.
- Document the intentional last-known-profile fallback when no tablet is connected.

Verified: swift test (17) green; rebuilt/reloaded the signed app, still drives the
DTH2420 (detection, mode-switch, touch-enable, mapping).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reframe as a driver for Wacom pen displays with the DTH2420 as the verified
reference device; add a Supported devices section; note runtime profile-based
detection + hotplug in How it works. Repo description updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@modfox
modfox merged commit 097d161 into main Jul 13, 2026
1 check passed
@modfox
modfox deleted the multi-device-support branch July 13, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant