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:
@@ -211,6 +211,7 @@ class Layout:
|
||||
# covers several different pads.
|
||||
KNOWN_LAYOUTS = {
|
||||
("1189", "8840"): Layout(12, 2, 3, 4, "12 keys, 2 knobs"),
|
||||
("8808", "660a"): Layout(9, 0, 3, 3, "ITPROTECH MK-09, 9 keys"),
|
||||
}
|
||||
DEFAULT_LAYOUT = KNOWN_LAYOUTS[(VID, PID)]
|
||||
|
||||
@@ -569,7 +570,8 @@ def _matching_rules():
|
||||
s = line.strip()
|
||||
if s.startswith("#"):
|
||||
continue
|
||||
if re.search(rf'idVendor\}}=="{VID}"', s, re.I) and "uaccess" in s:
|
||||
if "uaccess" in s and any(
|
||||
re.search(rf'idVendor\}}=="{v}"', s, re.I) for v in vendors()):
|
||||
hits.append(f)
|
||||
break
|
||||
return hits
|
||||
@@ -698,7 +700,9 @@ def diagnose(keyd=True):
|
||||
"macropad-probe.py and check the interface list."],
|
||||
warnings=warnings)
|
||||
return Diagnosis("unplugged", "No pad connected.",
|
||||
detail=[f"Looking for {DEVICE_ID} on USB."],
|
||||
detail=["Looking for " + ", ".join(
|
||||
f"{v}:{'/'.join(p) or '*'}" for v, p, _ in backends().known_ids())
|
||||
+ " on USB."],
|
||||
warnings=warnings)
|
||||
|
||||
d = found[0]
|
||||
@@ -862,10 +866,13 @@ class ReadError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _exchange(path, request, want=None):
|
||||
def _exchange(path, request, want=None, repeat=1):
|
||||
"""
|
||||
Send one request and collect replies until the pad goes quiet.
|
||||
|
||||
repeat sends the same request that many times, WRITE_GAP apart, for
|
||||
firmware that acts on every other report (the ITPROTECH pads).
|
||||
|
||||
Reading is the one place we write to the pad without changing it: both
|
||||
commands here are queries, byte for byte what the vendor software sends.
|
||||
"""
|
||||
@@ -884,7 +891,10 @@ def _exchange(path, request, want=None):
|
||||
except OSError:
|
||||
break
|
||||
try:
|
||||
os.write(fd, request)
|
||||
for i in range(repeat):
|
||||
if i:
|
||||
time.sleep(WRITE_GAP)
|
||||
os.write(fd, request)
|
||||
except OSError as e:
|
||||
raise ReadError(_explain(e)) from e
|
||||
|
||||
|
||||
Reference in New Issue
Block a user