diff --git a/README.md b/README.md index 6b188de..4de6b64 100644 --- a/README.md +++ b/README.md @@ -448,7 +448,7 @@ What the hardware can and can't do: | Key sequences | Yes, up to 10 keys with **one** set of modifiers for the whole sequence, no delays | | Mouse actions | The pad can, the app doesn't offer them yet | | Layers | None | -| Lighting | 15 modes and a custom colour, from the command line: `python3 -m macropad_gui.backends.itprotech rgb "press: blue"` | +| Lighting | 15 modes and a custom colour, from the command line: `macropad lighting rgb "press: blue"`, `macropad lighting colour ff8800`, `macropad lighting show` | Other sizes under vendor `8808` are found and offered **Ask the pad what it is**, which reads only. If the pad answers like an MK-09 does, the app adopts it. If you have one, a report from that button is all it takes to add it to the confirmed list. diff --git a/macropad_gui/__main__.py b/macropad_gui/__main__.py index 490fd95..baa4b2c 100644 --- a/macropad_gui/__main__.py +++ b/macropad_gui/__main__.py @@ -7,6 +7,9 @@ The rest are for poking at the core from a terminal: python3 -m macropad_gui state python3 -m macropad_gui write key1 "ctrl+c" python3 -m macropad_gui write dial1-right --media volumeup + python3 -m macropad_gui lighting show ITPROTECH pads only + python3 -m macropad_gui lighting rgb "press: blue" + python3 -m macropad_gui lighting colour ff8800 """ import argparse @@ -100,6 +103,46 @@ def cmd_write(a): return 0 +def cmd_lighting(a): + """Lighting mode and custom colour on pads whose backend has them.""" + d = core.diagnose(keyd=False) + backend = core.backend_for(d.device) if d.device else None + if not d.device or not hasattr(backend, "set_lighting"): + print(f"{R}No pad with lighting control found.{O} " + + (d.headline if not d.device else f"{backend.name} has no lighting here.")) + return 1 + from .backends import itprotech_proto as wire + modes = ", ".join(f"{i} {m}" for i, m in enumerate(wire.RGB_MODES)) + if a.what == "show": + mode, colour = backend.lighting(d.device) + name = wire.RGB_MODES[mode] if mode is not None and mode < len(wire.RGB_MODES) else "?" + hexcol = "#%02x%02x%02x" % colour if colour else "?" + print(f"lighting {mode} ({name}), custom colour {hexcol}") + return 0 + if not d.ready: + return cmd_doctor(None) + if a.what == "rgb": + want = (a.value or "").lower() + if want.isdigit() and int(want) < len(wire.RGB_MODES): + mode = int(want) + elif want in wire.RGB_MODES: + mode = wire.RGB_MODES.index(want) + else: + print(f"Pick a mode: {modes}") + return 1 + backend.set_lighting(d.device, mode) + print(f"{G}Lighting set to {mode} ({wire.RGB_MODES[mode]}).{O}") + return 0 + hexcol = (a.value or "").lstrip("#") + if len(hexcol) != 6 or any(c not in "0123456789abcdefABCDEF" for c in hexcol): + print("Give the colour as RRGGBB, e.g. ff8800") + return 1 + rgb = tuple(int(hexcol[i:i + 2], 16) for i in (0, 2, 4)) + backend.set_lighting(d.device, wire.RGB_CUSTOM, rgb) + print(f"{G}Custom colour #{hexcol.lower()} set (mode {wire.RGB_CUSTOM}).{O}") + return 0 + + def main(): ap = argparse.ArgumentParser(prog="python3 -m macropad_gui") sub = ap.add_subparsers(dest="cmd") @@ -114,6 +157,9 @@ def main(): w.add_argument("keys", nargs="?", help="e.g. 'ctrl+c' or 'a,b,c'") w.add_argument("--media", help="e.g. volumeup") w.add_argument("--delay", type=int, default=0) + li = sub.add_parser("lighting", help="lighting mode and colour (ITPROTECH pads)") + li.add_argument("what", choices=("show", "rgb", "colour", "color")) + li.add_argument("value", nargs="?", help="a mode number or name, or RRGGBB") a = ap.parse_args() if a.cmd is None: try: @@ -124,7 +170,8 @@ def main(): return 1 return run() return {"doctor": cmd_doctor, "state": cmd_state, "write": cmd_write, - "detect": cmd_detect, "actions": cmd_actions}[a.cmd](a) + "detect": cmd_detect, "actions": cmd_actions, + "lighting": cmd_lighting}[a.cmd](a) if __name__ == "__main__": diff --git a/macropad_gui/app.py b/macropad_gui/app.py index 5b2ec0f..386dc7b 100644 --- a/macropad_gui/app.py +++ b/macropad_gui/app.py @@ -1227,6 +1227,11 @@ class Window(QMainWindow): self.desired.clear() self.pending_actions.clear() self.pad.set_layout(self.state.layout) # drops a stale selection + # The upright view suits a pad that stands on its edge, which the + # knob pads do. A knobless pad lies flat, so draw it that way until + # the owner says otherwise. + if not self.state.layout.knobs and self.settings.value("orientation") is None: + self.pad.set_orientation("flat") return True # ------------------------------------------------------------- write diff --git a/macropad_gui/backends/itprotech.py b/macropad_gui/backends/itprotech.py index 8de0766..bc264a3 100644 --- a/macropad_gui/backends/itprotech.py +++ b/macropad_gui/backends/itprotech.py @@ -14,12 +14,11 @@ Three things about this firmware shape the code: read again and has to come back byte for byte as sent. A write that doesn't is reported as such, and the control is marked unknown. -Run this module for the two things the GUI has no place for yet, lighting -and colour: +Lighting and colour have no place in the GUI yet; they are a subcommand: - python3 -m macropad_gui.backends.itprotech show - python3 -m macropad_gui.backends.itprotech rgb "press: blue" - python3 -m macropad_gui.backends.itprotech colour ff8800 + macropad lighting show + macropad lighting rgb "press: blue" + macropad lighting colour ff8800 """ import errno @@ -195,60 +194,3 @@ class Itprotech(Backend): model=f"ITPROTECH {model}") -# -------------------------------------------------------------------- cli - -def _main(argv): - import sys - from .. import core - - def usage(): - print(__doc__.split("Run this module")[1].strip("\n")) - return 2 - - if not argv or argv[0] not in ("show", "rgb", "colour", "color"): - return usage() - d = core.diagnose(keyd=False) - if not d.device or d.device.vid != wire.VID: - print("No ITPROTECH pad found." if not d.device else d.headline) - return 1 - backend = Itprotech() - dev = d.device - if argv[0] == "show": - dump = backend._dump(dev) - for index, report in sorted(dump["keys"].items()): - got = wire.decode(report) - label = got[1].label() if got and got[1] else "(not decodable)" - print(f" key{index:<3} {report[:8].hex(' ')} {label}") - mode = dump["rgb"] - name = wire.RGB_MODES[mode] if mode is not None and mode < len(wire.RGB_MODES) else "?" - print(f" lighting: {mode} ({name}), colour " - f"{'#%02x%02x%02x' % dump['colour'] if dump['colour'] else '?'}") - return 0 - if not d.ready: - print(d.headline) - return 1 - if argv[0] == "rgb": - if len(argv) < 2: - return usage() - want = argv[1].lower() - mode = int(want) if want.isdigit() else ( - wire.RGB_MODES.index(want) if want in wire.RGB_MODES else None) - if mode is None: - print("Modes: " + ", ".join(f"{i} {m}" for i, m in enumerate(wire.RGB_MODES))) - return 1 - backend.set_lighting(dev, mode) - print(f"Lighting set to {mode} ({wire.RGB_MODES[mode]}).") - return 0 - hexcol = argv[1].lstrip("#") if len(argv) > 1 else "" - if len(hexcol) != 6: - print("Give the colour as RRGGBB, e.g. ff8800") - return 1 - rgb = tuple(int(hexcol[i:i + 2], 16) for i in (0, 2, 4)) - backend.set_lighting(dev, wire.RGB_CUSTOM, rgb) - print(f"Custom colour #{hexcol} set, lighting mode {wire.RGB_CUSTOM}.") - return 0 - - -if __name__ == "__main__": - import sys - sys.exit(_main(sys.argv[1:]))