diff --git a/core/key_simulator.py b/core/key_simulator.py index 766369f..b582dce 100644 --- a/core/key_simulator.py +++ b/core/key_simulator.py @@ -555,6 +555,7 @@ def execute_action(action_id): # ================================================================== elif sys.platform == "darwin": + _INJECTED_EVENT_MARKER = 0x4D4F5554 import ctypes try: @@ -628,6 +629,13 @@ def inject_scroll(flags, delta): else: event = Quartz.CGEventCreateScrollWheelEvent(None, 0, 2, 0, delta) if event: + try: + # Mark synthetic scroll events so the CGEventTap can ignore them + Quartz.CGEventSetIntegerValueField( + event, Quartz.kCGEventSourceUserData, _INJECTED_EVENT_MARKER + ) + except Exception: + pass Quartz.CGEventPost(Quartz.kCGHIDEventTap, event) # Mouse button simulation @@ -668,6 +676,13 @@ def _inject_mac_mouse(action_id, is_down): loc = Quartz.CGEventGetLocation(Quartz.CGEventCreate(None)) ev = Quartz.CGEventCreateMouseEvent(None, evt_type, loc, entry["button"]) if ev: + try: + # Mark synthetic mouse events so the CGEventTap can ignore them + Quartz.CGEventSetIntegerValueField( + ev, Quartz.kCGEventSourceUserData, _INJECTED_EVENT_MARKER + ) + except Exception: + pass Quartz.CGEventPost(Quartz.kCGHIDEventTap, ev) def inject_mouse_down(action_id): diff --git a/core/mouse_hook.py b/core/mouse_hook.py index 3acf0d8..825747c 100644 --- a/core/mouse_hook.py +++ b/core/mouse_hook.py @@ -990,6 +990,7 @@ def stop(self): _BTN_BACK = 3 _BTN_FORWARD = 4 _SCROLL_INVERT_MARKER = 0x4D4F5553 + _INJECTED_EVENT_MARKER = 0x4D4F5554 _kCGEventTapDisabledByTimeout = 0xFFFFFFFE _kCGEventTapDisabledByUserInput = 0xFFFFFFFF @@ -1379,6 +1380,16 @@ def _event_tap_callback(self, proxy, event_type, cg_event, refcon): self._first_event_logged = True print("[MouseHook] CGEventTap: first event received", flush=True) + # Ignore events we injected ourselves. The key_simulator marks + # synthetic events by setting kCGEventSourceUserData to _INJECTED_EVENT_MARKER. + try: + if Quartz.CGEventGetIntegerValueField( + cg_event, Quartz.kCGEventSourceUserData + ) == _INJECTED_EVENT_MARKER: + return cg_event + except Exception: + # If the field isn't available or the call fails, continue. + pass mouse_event = None should_block = False