From 4ada0f500e3e3fa0ef5f02eb956379325411b8d8 Mon Sep 17 00:00:00 2001 From: honjow Date: Sun, 14 Sep 2025 15:59:11 +0800 Subject: [PATCH 1/4] fix: handle device ungrab failure in close method --- src/hhd/controller/physical/evdev.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hhd/controller/physical/evdev.py b/src/hhd/controller/physical/evdev.py index 1b455350..31a00e50 100644 --- a/src/hhd/controller/physical/evdev.py +++ b/src/hhd/controller/physical/evdev.py @@ -350,6 +350,12 @@ def open(self) -> Sequence[int]: def close(self, exit: bool) -> bool: if self.dev: + try: + if self.grab: + self.dev.ungrab() + except Exception as e: + logger.warning(f"Failed to ungrab device {self.dev.path}: {e}") + if self.hidden and exit: unhide_gamepad(self.dev.path, self.hidden) self.dev.close() From 98b0debdf889aaf791b0a1f4828aebef59408029 Mon Sep 17 00:00:00 2001 From: honjow Date: Sun, 14 Sep 2025 15:59:11 +0800 Subject: [PATCH 2/4] refactor: implement dynamic grabbing for desktop detectors --- src/hhd/device/claw/base.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/hhd/device/claw/base.py b/src/hhd/device/claw/base.py index eb9906cc..2ace1927 100644 --- a/src/hhd/device/claw/base.py +++ b/src/hhd/device/claw/base.py @@ -455,21 +455,25 @@ def controller_loop( btn_map=dconf.get("btn_mapping", MSI_CLAW_MAPPINGS), ) - # Mute these so after suspend we do not get stray keypresses + # Desktop detectors with dynamic grabbing d_kbd_2 = DesktopDetectorEvdev( vid=[MSI_CLAW_VID], pid=[MSI_CLAW_XINPUT_PID, MSI_CLAW_DINPUT_PID], required=False, - grab=True, + grab=False, # Start without grabbing, grab dynamically when needed capabilities={EC("EV_KEY"): [EC("KEY_ESC")]}, ) d_mouse = DesktopDetectorEvdev( vid=[MSI_CLAW_VID], pid=[MSI_CLAW_XINPUT_PID, MSI_CLAW_DINPUT_PID], required=False, - grab=True, + grab=False, # Start without grabbing, grab dynamically when needed capabilities={EC("EV_KEY"): [EC("BTN_MOUSE")]}, ) + + # Track grabbing state for desktop detectors + d_kbd_2_grabbed = False + d_mouse_grabbed = False kargs = {} @@ -559,6 +563,22 @@ def prepare(m): if id(d) in to_run: evs.extend(d.produce(r)) + # Dynamic grabbing for desktop detectors + # Only grab when we need to detect desktop events, release when not needed + if not d_kbd_2_grabbed and d_kbd_2.dev: + try: + d_kbd_2.dev.grab() + d_kbd_2_grabbed = True + except Exception: + pass # Ignore grab failures, continue without grabbing + + if not d_mouse_grabbed and d_mouse.dev: + try: + d_mouse.dev.grab() + d_mouse_grabbed = True + except Exception: + pass # Ignore grab failures, continue without grabbing + # Detect if we are in desktop mode through events desktop_mode = d_mouse.desktop or d_kbd_2.desktop d_mouse.desktop = False From 65baabd0cfab5186c838aac9a63a0d8c00ff1ddc Mon Sep 17 00:00:00 2001 From: honjow Date: Sun, 14 Sep 2025 15:59:11 +0800 Subject: [PATCH 3/4] fix: (powerbutton) add physical device identifiers for Claw A8 --- src/hhd/plugins/powerbutton/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hhd/plugins/powerbutton/const.py b/src/hhd/plugins/powerbutton/const.py index 2b72f2a8..b5231eaf 100644 --- a/src/hhd/plugins/powerbutton/const.py +++ b/src/hhd/plugins/powerbutton/const.py @@ -84,6 +84,7 @@ class PowerButtonConfig(NamedTuple): "MSI Claw A8", "Claw A8 BZ2EM", type="only_press", + phys=["gpio-keys", "LNXPWRBN", "PNP0C0C"], ), ] From bbf1d4fa19c70bf0796f492753ab1ecd684b0414 Mon Sep 17 00:00:00 2001 From: honjow Date: Sun, 14 Sep 2025 15:59:11 +0800 Subject: [PATCH 4/4] fix: restore device permissions when HHD exits Fix unhide_all() to trigger udev reload even when _hidden list is empty. Resolves devices remaining with 000 permissions after HHD stops. --- src/hhd/controller/lib/hide.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/hhd/controller/lib/hide.py b/src/hhd/controller/lib/hide.py index ac27b231..e47d372f 100644 --- a/src/hhd/controller/lib/hide.py +++ b/src/hhd/controller/lib/hide.py @@ -199,10 +199,18 @@ def unhide_all(): except Exception: pass - if not removed: + # Always trigger udev reload if we removed rules OR have hidden devices + if removed or _hidden: + # We have to reload affected devices if we removed rules + for parent in _hidden: + reload_children(parent) + + # If no specific devices to reload but we removed rules, reload all input devices + if removed and not _hidden: + subprocess.run(["udevadm", "trigger", "--subsystem-match=input"], capture_output=True) + subprocess.run(["udevadm", "settle"], capture_output=True) + + _hidden.clear() return True - # We have to reload affected devices if we removed rules - for parent in _hidden: - reload_children(parent) - _hidden.clear() \ No newline at end of file + return True \ No newline at end of file