From 862887c16334be783cdf526457cfbbacc71203da Mon Sep 17 00:00:00 2001 From: Sudheer Date: Mon, 8 Jun 2026 04:43:17 +0200 Subject: [PATCH] fix: Fix spurious Noop MouseGesture firing after KeyIsDown + wheel rule Releasing a button used as a KeyIsDown modifier sends a single-element MOUSE_GESTURE notification (Noop) that can incorrectly match MouseGesture rules bound to the same button. Track the state across notifications with three module-level variables: - keys_used_while_held: buttons where a KeyIsDown rule fired during hold - suppress_noop_for_buttons: buttons whose next Noop should be swallowed - _suppress_current_noop: per-notification flag checked by MouseGesture.evaluate KeyIsDown.evaluate records the button when a non-button notification co-fires (e.g. scroll, gesture). On key-up, process_notification moves it to suppress_noop_for_buttons. evaluate_rules sets _suppress_current_noop when the incoming Noop matches. MouseGesture.evaluate returns False. Re-pressing the button before release clears any pending suppression. --- lib/logitech_receiver/diversion.py | 31 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/logitech_receiver/diversion.py b/lib/logitech_receiver/diversion.py index 6b1a3ad6..0f959eaa 100644 --- a/lib/logitech_receiver/diversion.py +++ b/lib/logitech_receiver/diversion.py @@ -139,10 +139,12 @@ g_keys_down = 0 m_keys_down = 0 mr_key_down = False thumb_wheel_displacement = 0 -# When a button fires a KeyIsDown action while held, the Noop gesture generated on release is spurious. -# These three track that state so MouseGesture.evaluate can swallow the unwanted Noop. -keys_used_while_held = set() # control IDs of buttons that had a KeyIsDown action fire while held -suppress_noop_for_buttons = set() # control IDs whose next single-element MOUSE_GESTURE should be suppressed +# Spurious Noop suppression on KeyIsDown button release: +# When a button used as a KeyIsDown modifier is released, the firmware sends a single-element +# MOUSE_GESTURE (Noop) that can incorrectly match MouseGesture rules. +# keys_used_while_held / suppress_noop_for_buttons / _suppress_current_noop track and swallow it. +keys_used_while_held = set() # button IDs that had a KeyIsDown action fire while held +suppress_noop_for_buttons = set() # button IDs whose next single-element MOUSE_GESTURE should be suppressed _suppress_current_noop = False # reset each notification in evaluate_rules, checked in MouseGesture.evaluate _dbus_interface = None @@ -884,16 +886,17 @@ class KeyIsDown(Condition): if logger.isEnabledFor(logging.DEBUG): logger.debug("evaluate condition: %s", self) result = key_is_down(self.key) - # Mark the button as "used while held" so its release Noop gesture can be suppressed. - # Skip key-down notifications themselves (those features trigger on address 0x00 and would - # record every press rather than only presses that co-occur with another action). - if result and feature not in ( - SupportedFeature.REPROG_CONTROLS_V4, - SupportedFeature.GKEY, - SupportedFeature.MKEYS, - SupportedFeature.MR, - ): - keys_used_while_held.add(int(self.key)) + if result: + # Mark the button as "used while held" so its release Noop gesture can be suppressed. + # Skip button-down notifications (REPROG_CONTROLS_V4 etc.) — only record when a non-button + # action (e.g. scroll, gesture) co-fires with the held button. + if feature not in ( + SupportedFeature.REPROG_CONTROLS_V4, + SupportedFeature.GKEY, + SupportedFeature.MKEYS, + SupportedFeature.MR, + ): + keys_used_while_held.add(int(self.key)) return result def data(self):