Open the app with the Predator key, bump to 1.3.0

The Predator key sends keyboard usage 0xC9, which lies outside the
logical range the keyboard declares, so the kernel never emits an input
event for it. It is also reported on vendor report 4 of USB interface 2
(04 81 ff on press). predatord now listens there and opens the app in
the active graphical session via the user's systemd manager.

The action is configurable on the System page: open the app, switch to
the next performance mode, or do nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 17:51:21 +02:00
co-authored by Claude Opus 5.5
parent cc4fdb9cd0
commit 5b7534560f
11 changed files with 142 additions and 8 deletions
+21
View File
@@ -25,6 +25,7 @@ import traceback
from . import CONFIG_DIR, CONFIG_PATH, SOCKET_PATH, STATE_PATH, __version__
from . import hardware as hw
from . import updater
from .hotkey import HotkeyListener, launch_in_session
from .keyboard import EFFECTS, NUM_CELLS, Keyboard
DEFAULT_CONFIG = {
@@ -49,6 +50,7 @@ DEFAULT_CONFIG = {
"battery": {"limit": None}, # None = leave the firmware state alone
"keyboard_presets": {},
"updates": {"auto_check": True, "interval_hours": 6},
"predator_key": "open", # open | cycle_profile | none
}
@@ -277,6 +279,18 @@ class Daemon:
self._last_try = time.time()
self.check_update_async()
def predator_key(self):
action = self.config.get("predator_key", "open")
if action == "open":
if not launch_in_session(["predator-control"]):
log("Predator key: no active graphical session to open the app in")
elif action == "cycle_profile":
with self.lock:
ch = hw.profile_choices()
cur = hw.get_profile()
nxt = ch[(ch.index(cur) + 1) % len(ch)] if cur in ch else ch[0]
self._try("profile", hw.set_profile, nxt)
def loop(self):
while True:
try:
@@ -328,6 +342,12 @@ class Daemon:
merge(self.config["updates"], {k: a[k] for k in ("auto_check", "interval_hours") if k in a})
self.save_config()
return {}
if cmd == "set_predator_key":
if a["action"] not in ("open", "cycle_profile", "none"):
raise ValueError("unknown action")
self.config["predator_key"] = a["action"]
self.save_config()
return {}
if cmd == "restore":
self.restore()
return {}
@@ -511,6 +531,7 @@ def main():
signal.signal(signal.SIGTERM, lambda *_: (server.server_close(), sys.exit(0)))
signal.signal(signal.SIGUSR1, lambda *_: threading.Thread(target=lambda: (d.lock.acquire(), d.restore(), d.lock.release())).start())
threading.Thread(target=d.loop, daemon=True).start()
HotkeyListener(d.predator_key, log).start()
server.serve_forever()