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:
@@ -0,0 +1,83 @@
|
||||
"""Predator key listener.
|
||||
|
||||
The PH16-71 Predator key produces no input event: its keyboard usage (0xC9) is outside the
|
||||
range the keyboard declares, so the kernel drops it. The key is also reported on the
|
||||
vendor report 4 of USB interface 2: `04 81 ff` on press, `04 00 00` on release.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pwd
|
||||
import select
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
from .keyboard import HOTKEY_INTERFACE, find_hidraw
|
||||
|
||||
PRESS_PREFIX = b"\x04\x81"
|
||||
|
||||
|
||||
def active_session_user():
|
||||
"""(uid, name) of the active graphical session, or None."""
|
||||
out = subprocess.run(["loginctl", "list-sessions", "--no-legend"],
|
||||
capture_output=True, text=True).stdout
|
||||
for line in out.splitlines():
|
||||
sid = line.split()[0] if line.split() else None
|
||||
if not sid:
|
||||
continue
|
||||
props = dict(l.split("=", 1) for l in subprocess.run(
|
||||
["loginctl", "show-session", sid, "-p", "Active", "-p", "Type", "-p", "User", "-p", "Name"],
|
||||
capture_output=True, text=True).stdout.splitlines() if "=" in l)
|
||||
if props.get("Active") == "yes" and props.get("Type") in ("wayland", "x11"):
|
||||
return int(props["User"]), props["Name"]
|
||||
return None
|
||||
|
||||
|
||||
def launch_in_session(args):
|
||||
sess = active_session_user()
|
||||
if not sess:
|
||||
return False
|
||||
uid, name = sess
|
||||
# runs in the user's systemd manager, which carries WAYLAND_DISPLAY/DISPLAY on Plasma
|
||||
r = subprocess.run(["systemd-run", "--quiet", "--user", "--machine=%s@.host" % name, "--collect",
|
||||
"--unit=predator-control-launch-%d" % int(time.time() * 1000)] + args,
|
||||
capture_output=True, text=True)
|
||||
return r.returncode == 0
|
||||
|
||||
|
||||
class HotkeyListener(threading.Thread):
|
||||
def __init__(self, on_press, log):
|
||||
super().__init__(daemon=True)
|
||||
self.on_press = on_press
|
||||
self.log = log
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
path = find_hidraw(HOTKEY_INTERFACE)
|
||||
if not path:
|
||||
time.sleep(5)
|
||||
continue
|
||||
try:
|
||||
fd = os.open(path, os.O_RDONLY)
|
||||
except OSError as e:
|
||||
self.log("hotkey: cannot open %s: %s" % (path, e))
|
||||
time.sleep(5)
|
||||
continue
|
||||
last = 0.0
|
||||
try:
|
||||
while True:
|
||||
select.select([fd], [], [])
|
||||
data = os.read(fd, 64)
|
||||
if not data:
|
||||
break
|
||||
if data.startswith(PRESS_PREFIX) and time.monotonic() - last > 0.4:
|
||||
last = time.monotonic()
|
||||
try:
|
||||
self.on_press()
|
||||
except Exception as e:
|
||||
self.log("hotkey: %s" % e)
|
||||
except OSError:
|
||||
pass # device vanished (suspend/replug) – reopen
|
||||
finally:
|
||||
os.close(fd)
|
||||
time.sleep(1)
|
||||
Reference in New Issue
Block a user