Add the ITPROTECH MK series (8808) as a third pad family
Confirmed on the MK-09 (8808:660a, 9 keys): the vendor's .NET configurator was decompiled for the wire format and every frame was then written to a real pad and read back. Keys, media keys and multikey sequences are supported; lighting from the command line. The firmware acts on every other output report, so each report is sent twice (as the vendor software does) and every write is verified by reading the key back. The app now keeps one state file per pad and starts with a known layout for a known USB id, and the editor asks the backend whether a binding fits before offering to write it. The actions listener and the udev rule cover every supported vendor. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -243,6 +243,7 @@ class Inspector(QWidget):
|
||||
self.control = None
|
||||
self._loading = False
|
||||
self.editing_layout = False
|
||||
self.backend = None # the family of the pad plugged in, if any
|
||||
|
||||
v = QVBoxLayout(self)
|
||||
v.setContentsMargins(24, 24, 24, 16)
|
||||
@@ -596,13 +597,20 @@ class Inspector(QWidget):
|
||||
self.keys_hint.setText(f"<span style='color:{BAD}'>{e}</span>")
|
||||
self.changed.emit(self.control, None)
|
||||
return
|
||||
if b.is_sequence and not core.SEQUENCES_VERIFIED:
|
||||
sequences_ok = (self.backend.sequences if self.backend is not None
|
||||
else core.SEQUENCES_VERIFIED)
|
||||
if b.is_sequence and not sequences_ok:
|
||||
self.keys_hint.setText(
|
||||
f"<span style='color:{BAD}'>Sequences aren't verified on this "
|
||||
"pad yet, so they're switched off for now. One shortcut per "
|
||||
"control.</span>")
|
||||
self.changed.emit(self.control, None)
|
||||
return
|
||||
why = self.backend.check(b) if self.backend is not None else None
|
||||
if why:
|
||||
self.keys_hint.setText(f"<span style='color:{BAD}'>{why}</span>")
|
||||
self.changed.emit(self.control, None)
|
||||
return
|
||||
self.keys_hint.setText(f"Sends {b.label()}")
|
||||
self.changed.emit(self.control, b)
|
||||
|
||||
@@ -1170,6 +1178,7 @@ class Window(QMainWindow):
|
||||
f"{d.device.vid}:{d.device.pid}" in self.adopted:
|
||||
self.diag = core.Diagnosis("ready", f"Ready on {d.device.path}.",
|
||||
device=d.device, warnings=d.warnings)
|
||||
switched = self._follow_device()
|
||||
colour = {"ready": GOOD, "unplugged": INK_DIM.name()}.get(self.diag.status, BAD)
|
||||
self.dot.setStyleSheet(f"background: {colour}; border-radius: 4px;")
|
||||
if self.diag.device:
|
||||
@@ -1186,11 +1195,40 @@ class Window(QMainWindow):
|
||||
self.inspector.show_problem(self.diag, self.note or self.shape_note,
|
||||
offer_detect=bool(self.shape_note))
|
||||
self.inspector.show_background(bool(act.listener_pid()))
|
||||
if self.diag.status != old:
|
||||
self._refresh(editor=False)
|
||||
if self.diag.ready and old in ("unplugged", "no-interface", None):
|
||||
if self.diag.status != old or switched:
|
||||
self._refresh(editor=switched)
|
||||
if self.diag.ready and (switched or
|
||||
old in ("unplugged", "no-interface", None)):
|
||||
self._read(quiet=True)
|
||||
|
||||
def _follow_device(self):
|
||||
"""
|
||||
Keep the shadow state and the drawing on the pad that is plugged in.
|
||||
|
||||
Each family keeps its own state file. The first time a pad with a
|
||||
known USB id turns up it is drawn with the layout we know for it,
|
||||
so a 9 key pad is never shown as 12 keys and 2 knobs. Returns True
|
||||
when the state was switched to another pad.
|
||||
"""
|
||||
dev = self.diag.device if self.diag else None
|
||||
if not dev:
|
||||
self.inspector.backend = None
|
||||
return False
|
||||
self.inspector.backend = core.backend_for(dev)
|
||||
ident = f"{dev.vid}:{dev.pid}"
|
||||
if ident == self.state.device_id:
|
||||
return False
|
||||
state = core.State(device_id=ident)
|
||||
if not state.path.exists():
|
||||
known = core.KNOWN_LAYOUTS.get((dev.vid, dev.pid))
|
||||
if known:
|
||||
state.layout = known
|
||||
self.state = state
|
||||
self.desired.clear()
|
||||
self.pending_actions.clear()
|
||||
self.pad.set_layout(self.state.layout) # drops a stale selection
|
||||
return True
|
||||
|
||||
# ------------------------------------------------------------- write
|
||||
|
||||
def _write(self, controls):
|
||||
@@ -1339,7 +1377,7 @@ class Window(QMainWindow):
|
||||
return
|
||||
|
||||
layout = found.layout
|
||||
self.state = core.State(layout=layout)
|
||||
self.state = core.State(layout=layout, device_id=f"{dev.vid}:{dev.pid}")
|
||||
self.state.layout = layout
|
||||
for control, binding in found.bindings.items():
|
||||
self.state.record(control, binding)
|
||||
|
||||
Reference in New Issue
Block a user