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.
221 lines
6.7 KiB
221 lines
6.7 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 *Windows*.
|
|
"""
|
|
|
|
# pylint: disable=C0111
|
|
# The documentation is extracted from the base classes
|
|
|
|
# pylint: disable=R0903
|
|
# We implement stubs
|
|
|
|
import ctypes
|
|
import enum
|
|
|
|
from ctypes import (
|
|
windll,
|
|
wintypes)
|
|
|
|
from pynput._util import NotifierMixin
|
|
from pynput._util.win32 import (
|
|
INPUT,
|
|
INPUT_union,
|
|
ListenerMixin,
|
|
MOUSEINPUT,
|
|
SendInput,
|
|
SystemHook)
|
|
from . import _base
|
|
|
|
#: A constant used as a factor when constructing mouse scroll data.
|
|
WHEEL_DELTA = 120
|
|
|
|
|
|
class Button(enum.Enum):
|
|
"""The various buttons.
|
|
"""
|
|
unknown = None
|
|
left = (MOUSEINPUT.LEFTUP, MOUSEINPUT.LEFTDOWN, 0)
|
|
middle = (MOUSEINPUT.MIDDLEUP, MOUSEINPUT.MIDDLEDOWN, 0)
|
|
right = (MOUSEINPUT.RIGHTUP, MOUSEINPUT.RIGHTDOWN, 0)
|
|
x1 = (MOUSEINPUT.XUP, MOUSEINPUT.XDOWN, MOUSEINPUT.XBUTTON1)
|
|
x2 = (MOUSEINPUT.XUP, MOUSEINPUT.XDOWN, MOUSEINPUT.XBUTTON2)
|
|
|
|
|
|
class Controller(NotifierMixin, _base.Controller):
|
|
__GetCursorPos = windll.user32.GetCursorPos
|
|
__SetCursorPos = windll.user32.SetCursorPos
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(Controller, self).__init__(*args, **kwargs)
|
|
|
|
def _position_get(self):
|
|
point = wintypes.POINT()
|
|
if self.__GetCursorPos(ctypes.byref(point)):
|
|
return (point.x, point.y)
|
|
else:
|
|
return None
|
|
|
|
def _position_set(self, pos):
|
|
pos = int(pos[0]), int(pos[1])
|
|
self.__SetCursorPos(*pos)
|
|
self._emit('on_move', *pos)
|
|
|
|
def _scroll(self, dx, dy):
|
|
if dy:
|
|
SendInput(
|
|
1,
|
|
ctypes.byref(INPUT(
|
|
type=INPUT.MOUSE,
|
|
value=INPUT_union(
|
|
mi=MOUSEINPUT(
|
|
dwFlags=MOUSEINPUT.WHEEL,
|
|
mouseData=int(dy * WHEEL_DELTA))))),
|
|
ctypes.sizeof(INPUT))
|
|
|
|
if dx:
|
|
SendInput(
|
|
1,
|
|
ctypes.byref(INPUT(
|
|
type=INPUT.MOUSE,
|
|
value=INPUT_union(
|
|
mi=MOUSEINPUT(
|
|
dwFlags=MOUSEINPUT.HWHEEL,
|
|
mouseData=int(dx * WHEEL_DELTA))))),
|
|
ctypes.sizeof(INPUT))
|
|
|
|
if dx or dy:
|
|
px, py = self._position_get()
|
|
self._emit('on_scroll', px, py, dx, dy)
|
|
|
|
def _press(self, button):
|
|
SendInput(
|
|
1,
|
|
ctypes.byref(INPUT(
|
|
type=INPUT.MOUSE,
|
|
value=INPUT_union(
|
|
mi=MOUSEINPUT(
|
|
dwFlags=button.value[1],
|
|
mouseData=button.value[2])))),
|
|
ctypes.sizeof(INPUT))
|
|
|
|
def _release(self, button):
|
|
SendInput(
|
|
1,
|
|
ctypes.byref(INPUT(
|
|
type=INPUT.MOUSE,
|
|
value=INPUT_union(
|
|
mi=MOUSEINPUT(
|
|
dwFlags=button.value[0],
|
|
mouseData=button.value[2])))),
|
|
ctypes.sizeof(INPUT))
|
|
|
|
|
|
@Controller._receiver
|
|
class Listener(ListenerMixin, _base.Listener):
|
|
#: The Windows hook ID for low level mouse events, ``WH_MOUSE_LL``
|
|
_EVENTS = 14
|
|
|
|
WM_LBUTTONDOWN = 0x0201
|
|
WM_LBUTTONUP = 0x0202
|
|
WM_MBUTTONDOWN = 0x0207
|
|
WM_MBUTTONUP = 0x0208
|
|
WM_MOUSEMOVE = 0x0200
|
|
WM_MOUSEWHEEL = 0x020A
|
|
WM_MOUSEHWHEEL = 0x020E
|
|
WM_RBUTTONDOWN = 0x0204
|
|
WM_RBUTTONUP = 0x0205
|
|
WM_XBUTTONDOWN = 0x20B
|
|
WM_XBUTTONUP = 0x20C
|
|
|
|
MK_XBUTTON1 = 0x0020
|
|
MK_XBUTTON2 = 0x0040
|
|
|
|
XBUTTON1 = 1
|
|
XBUTTON2 = 2
|
|
|
|
#: A mapping from messages to button events
|
|
CLICK_BUTTONS = {
|
|
WM_LBUTTONDOWN: (Button.left, True),
|
|
WM_LBUTTONUP: (Button.left, False),
|
|
WM_MBUTTONDOWN: (Button.middle, True),
|
|
WM_MBUTTONUP: (Button.middle, False),
|
|
WM_RBUTTONDOWN: (Button.right, True),
|
|
WM_RBUTTONUP: (Button.right, False)}
|
|
|
|
#: A mapping from message to X button events.
|
|
X_BUTTONS = {
|
|
WM_XBUTTONDOWN: {
|
|
XBUTTON1: (Button.x1, True),
|
|
XBUTTON2: (Button.x2, True)},
|
|
WM_XBUTTONUP: {
|
|
XBUTTON1: (Button.x1, False),
|
|
XBUTTON2: (Button.x2, False)}}
|
|
|
|
#: A mapping from messages to scroll vectors
|
|
SCROLL_BUTTONS = {
|
|
WM_MOUSEWHEEL: (0, 1),
|
|
WM_MOUSEHWHEEL: (1, 0)}
|
|
|
|
_HANDLED_EXCEPTIONS = (
|
|
SystemHook.SuppressException,)
|
|
|
|
class _MSLLHOOKSTRUCT(ctypes.Structure):
|
|
"""Contains information about a mouse event passed to a ``WH_MOUSE_LL``
|
|
hook procedure, ``MouseProc``.
|
|
"""
|
|
_fields_ = [
|
|
('pt', wintypes.POINT),
|
|
('mouseData', wintypes.DWORD),
|
|
('flags', wintypes.DWORD),
|
|
('time', wintypes.DWORD),
|
|
('dwExtraInfo', ctypes.c_void_p)]
|
|
|
|
#: A pointer to a :class:`_MSLLHOOKSTRUCT`
|
|
_LPMSLLHOOKSTRUCT = ctypes.POINTER(_MSLLHOOKSTRUCT)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(Listener, self).__init__(*args, **kwargs)
|
|
self._event_filter = self._options.get(
|
|
'event_filter',
|
|
lambda msg, data: True)
|
|
|
|
def _handle(self, code, msg, lpdata):
|
|
if code != SystemHook.HC_ACTION:
|
|
return
|
|
|
|
data = ctypes.cast(lpdata, self._LPMSLLHOOKSTRUCT).contents
|
|
|
|
# Suppress further propagation of the event if it is filtered
|
|
if self._event_filter(msg, data) is False:
|
|
return
|
|
|
|
if msg == self.WM_MOUSEMOVE:
|
|
self.on_move(data.pt.x, data.pt.y)
|
|
|
|
elif msg in self.CLICK_BUTTONS:
|
|
button, pressed = self.CLICK_BUTTONS[msg]
|
|
self.on_click(data.pt.x, data.pt.y, button, pressed)
|
|
|
|
elif msg in self.X_BUTTONS:
|
|
button, pressed = self.X_BUTTONS[msg][data.mouseData >> 16]
|
|
self.on_click(data.pt.x, data.pt.y, button, pressed)
|
|
|
|
elif msg in self.SCROLL_BUTTONS:
|
|
mx, my = self.SCROLL_BUTTONS[msg]
|
|
dd = wintypes.SHORT(data.mouseData >> 16).value // WHEEL_DELTA
|
|
self.on_scroll(data.pt.x, data.pt.y, dd * mx, dd * my)
|
|
|