You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

212 lines
6.6 KiB

# 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 <http://www.gnu.org/licenses/>.
"""
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)