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
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)
|
|
|