123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- import os
- import datetime
- import threading
- import Quartz
- from ._mouse_event import ButtonEvent, WheelEvent, MoveEvent, LEFT, RIGHT, MIDDLE, X, X2, UP, DOWN
- _button_mapping = {
- LEFT: (Quartz.kCGMouseButtonLeft, Quartz.kCGEventLeftMouseDown, Quartz.kCGEventLeftMouseUp, Quartz.kCGEventLeftMouseDragged),
- RIGHT: (Quartz.kCGMouseButtonRight, Quartz.kCGEventRightMouseDown, Quartz.kCGEventRightMouseUp, Quartz.kCGEventRightMouseDragged),
- MIDDLE: (Quartz.kCGMouseButtonCenter, Quartz.kCGEventOtherMouseDown, Quartz.kCGEventOtherMouseUp, Quartz.kCGEventOtherMouseDragged)
- }
- _button_state = {
- LEFT: False,
- RIGHT: False,
- MIDDLE: False
- }
- _last_click = {
- "time": None,
- "button": None,
- "position": None,
- "click_count": 0
- }
- class MouseEventListener(object):
- def __init__(self, callback, blocking=False):
- self.blocking = blocking
- self.callback = callback
- self.listening = True
- def run(self):
- """ Creates a listener and loops while waiting for an event. Intended to run as
- a background thread. """
- self.tap = Quartz.CGEventTapCreate(
- Quartz.kCGSessionEventTap,
- Quartz.kCGHeadInsertEventTap,
- Quartz.kCGEventTapOptionDefault,
- Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDown) |
- Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseUp) |
- Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDown) |
- Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseUp) |
- Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDown) |
- Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseUp) |
- Quartz.CGEventMaskBit(Quartz.kCGEventMouseMoved) |
- Quartz.CGEventMaskBit(Quartz.kCGEventScrollWheel),
- self.handler,
- None)
- loopsource = Quartz.CFMachPortCreateRunLoopSource(None, self.tap, 0)
- loop = Quartz.CFRunLoopGetCurrent()
- Quartz.CFRunLoopAddSource(loop, loopsource, Quartz.kCFRunLoopDefaultMode)
- Quartz.CGEventTapEnable(self.tap, True)
- while self.listening:
- Quartz.CFRunLoopRunInMode(Quartz.kCFRunLoopDefaultMode, 5, False)
- def handler(self, proxy, e_type, event, refcon):
- # TODO Separate event types by button/wheel/move
- scan_code = Quartz.CGEventGetIntegerValueField(event, Quartz.kCGKeyboardEventKeycode)
- key_name = name_from_scancode(scan_code)
- flags = Quartz.CGEventGetFlags(event)
- event_type = ""
- is_keypad = (flags & Quartz.kCGEventFlagMaskNumericPad)
- if e_type == Quartz.kCGEventKeyDown:
- event_type = "down"
- elif e_type == Quartz.kCGEventKeyUp:
- event_type = "up"
- if self.blocking:
- return None
- self.callback(KeyboardEvent(event_type, scan_code, name=key_name, is_keypad=is_keypad))
- return event
- # Exports
- def init():
- """ Initializes mouse state """
- pass
- def listen(queue):
- """ Appends events to the queue (ButtonEvent, WheelEvent, and MoveEvent). """
- if not os.geteuid() == 0:
- raise OSError("Error 13 - Must be run as administrator")
- listener = MouseEventListener(lambda e: queue.put(e) or is_allowed(e.name, e.event_type == KEY_UP))
- t = threading.Thread(target=listener.run, args=())
- t.daemon = True
- t.start()
- def press(button=LEFT):
- """ Sends a down event for the specified button, using the provided constants """
- location = get_position()
- button_code, button_down, _, _ = _button_mapping[button]
- e = Quartz.CGEventCreateMouseEvent(
- None,
- button_down,
- location,
- button_code)
- # Check if this is a double-click (same location within the last 300ms)
- if _last_click["time"] is not None and datetime.datetime.now() - _last_click["time"] < datetime.timedelta(seconds=0.3) and _last_click["button"] == button and _last_click["position"] == location:
- # Repeated Click
- _last_click["click_count"] = min(3, _last_click["click_count"]+1)
- else:
- # Not a double-click - Reset last click
- _last_click["click_count"] = 1
- Quartz.CGEventSetIntegerValueField(
- e,
- Quartz.kCGMouseEventClickState,
- _last_click["click_count"])
- Quartz.CGEventPost(Quartz.kCGHIDEventTap, e)
- _button_state[button] = True
- _last_click["time"] = datetime.datetime.now()
- _last_click["button"] = button
- _last_click["position"] = location
- def release(button=LEFT):
- """ Sends an up event for the specified button, using the provided constants """
- location = get_position()
- button_code, _, button_up, _ = _button_mapping[button]
- e = Quartz.CGEventCreateMouseEvent(
- None,
- button_up,
- location,
- button_code)
- if _last_click["time"] is not None and _last_click["time"] > datetime.datetime.now() - datetime.timedelta(microseconds=300000) and _last_click["button"] == button and _last_click["position"] == location:
- # Repeated Click
- Quartz.CGEventSetIntegerValueField(
- e,
- Quartz.kCGMouseEventClickState,
- _last_click["click_count"])
- Quartz.CGEventPost(Quartz.kCGHIDEventTap, e)
- _button_state[button] = False
- def wheel(delta=1):
- """ Sends a wheel event for the provided number of clicks. May be negative to reverse
- direction. """
- location = get_position()
- e = Quartz.CGEventCreateMouseEvent(
- None,
- Quartz.kCGEventScrollWheel,
- location,
- Quartz.kCGMouseButtonLeft)
- e2 = Quartz.CGEventCreateScrollWheelEvent(
- None,
- Quartz.kCGScrollEventUnitLine,
- 1,
- delta)
- Quartz.CGEventPost(Quartz.kCGHIDEventTap, e)
- Quartz.CGEventPost(Quartz.kCGHIDEventTap, e2)
- def move_to(x, y):
- """ Sets the mouse's location to the specified coordinates. """
- for b in _button_state:
- if _button_state[b]:
- e = Quartz.CGEventCreateMouseEvent(
- None,
- _button_mapping[b][3], # Drag Event
- (x, y),
- _button_mapping[b][0])
- break
- else:
- e = Quartz.CGEventCreateMouseEvent(
- None,
- Quartz.kCGEventMouseMoved,
- (x, y),
- Quartz.kCGMouseButtonLeft)
- Quartz.CGEventPost(Quartz.kCGHIDEventTap, e)
- def get_position():
- """ Returns the mouse's location as a tuple of (x, y). """
- e = Quartz.CGEventCreate(None)
- point = Quartz.CGEventGetLocation(e)
- return (point.x, point.y)
|