# coding=utf-8 # pynput # Copyright (C) 2015-2022 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . """ The mouse implementation for *macOS*. """ # pylint: disable=C0111 # The documentation is extracted from the base classes # pylint: disable=R0903 # We implement stubs import enum import Quartz from AppKit import NSEvent from pynput._util.darwin import ( ListenerMixin) from . import _base def _button_value(base_name, mouse_button): """Generates the value tuple for a :class:`Button` value. :param str base_name: The base name for the button. This should be a string like ``'kCGEventLeftMouse'``. :param int mouse_button: The mouse button ID. :return: a value tuple """ return ( tuple( getattr(Quartz, '%sMouse%s' % (base_name, name)) for name in ('Down', 'Up', 'Dragged')), mouse_button) class Button(enum.Enum): """The various buttons. """ unknown = None left = _button_value('kCGEventLeft', 0) middle = _button_value('kCGEventOther', 2) right = _button_value('kCGEventRight', 1) class Controller(_base.Controller): #: The scroll speed _SCROLL_SPEED = 10 def __init__(self, *args, **kwargs): super(Controller, self).__init__(*args, **kwargs) self._click = None self._drag_button = None def _position_get(self): pos = NSEvent.mouseLocation() return pos.x, Quartz.CGDisplayPixelsHigh(0) - pos.y def _position_set(self, pos): try: (_, _, mouse_type), mouse_button = self._drag_button.value except AttributeError: mouse_type = Quartz.kCGEventMouseMoved mouse_button = 0 Quartz.CGEventPost( Quartz.kCGHIDEventTap, Quartz.CGEventCreateMouseEvent( None, mouse_type, pos, mouse_button)) def _scroll(self, dx, dy): dx = int(dx) dy = int(dy) Quartz.CGEventPost( Quartz.kCGHIDEventTap, Quartz.CGEventCreateScrollWheelEvent( None, Quartz.kCGScrollEventUnitPixel, 2, dy * self._SCROLL_SPEED, dx * self._SCROLL_SPEED)) def _press(self, button): (press, _, _), mouse_button = button.value event = Quartz.CGEventCreateMouseEvent( None, press, self.position, mouse_button) # If we are performing a click, we need to set this state flag if self._click is not None: self._click += 1 Quartz.CGEventSetIntegerValueField( event, Quartz.kCGMouseEventClickState, self._click) Quartz.CGEventPost(Quartz.kCGHIDEventTap, event) # Store the button to enable dragging self._drag_button = button def _release(self, button): (_, release, _), mouse_button = button.value event = Quartz.CGEventCreateMouseEvent( None, release, self.position, mouse_button) # If we are performing a click, we need to set this state flag if self._click is not None: Quartz.CGEventSetIntegerValueField( event, Quartz.kCGMouseEventClickState, self._click) Quartz.CGEventPost(Quartz.kCGHIDEventTap, event) if button == self._drag_button: self._drag_button = None def __enter__(self): self._click = 0 return self def __exit__(self, exc_type, value, traceback): self._click = None class Listener(ListenerMixin, _base.Listener): #: The events that we listen to _EVENTS = ( Quartz.CGEventMaskBit(Quartz.kCGEventMouseMoved) | Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDown) | Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseUp) | Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDragged) | Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDown) | Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseUp) | Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDragged) | Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDown) | Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseUp) | Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDragged) | Quartz.CGEventMaskBit(Quartz.kCGEventScrollWheel)) def __init__(self, *args, **kwargs): super(Listener, self).__init__(*args, **kwargs) self._intercept = self._options.get( 'intercept', None) def _handle(self, _proxy, event_type, event, _refcon): """The callback registered with *macOS* for mouse events. This method will call the callbacks registered on initialisation. """ try: (px, py) = Quartz.CGEventGetLocation(event) except AttributeError: # This happens during teardown of the virtual machine return # Quickly detect the most common event type if event_type == Quartz.kCGEventMouseMoved: self.on_move(px, py) elif event_type == Quartz.kCGEventScrollWheel: dx = Quartz.CGEventGetIntegerValueField( event, Quartz.kCGScrollWheelEventDeltaAxis2) dy = Quartz.CGEventGetIntegerValueField( event, Quartz.kCGScrollWheelEventDeltaAxis1) self.on_scroll(px, py, dx, dy) else: for button in Button: try: (press, release, drag), _ = button.value except TypeError: # Button.unknown cannot be enumerated continue # Press and release generate click events, and drag # generates move events if event_type in (press, release): self.on_click(px, py, button, event_type == press) elif event_type == drag: self.on_move(px, py)