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.

345 lines
11 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 keyboard 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 pynput._util.darwin import (
get_unicode_to_keycode_map,
keycode_context,
ListenerMixin)
from pynput._util.darwin_vks import SYMBOLS
from . import _base
# From hidsystem/ev_keymap.h
NX_KEYTYPE_PLAY = 16
NX_KEYTYPE_MUTE = 7
NX_KEYTYPE_SOUND_DOWN = 1
NX_KEYTYPE_SOUND_UP = 0
NX_KEYTYPE_NEXT = 17
NX_KEYTYPE_PREVIOUS = 18
# pylint: disable=C0103; We want to use the names from the C API
# This is undocumented, but still widely known
kSystemDefinedEventMediaKeysSubtype = 8
# We extract this here since the name is very long
otherEventWithType = getattr(
Quartz.NSEvent,
'otherEventWithType_'
'location_'
'modifierFlags_'
'timestamp_'
'windowNumber_'
'context_'
'subtype_'
'data1_'
'data2_')
# pylint: enable=C0103
class KeyCode(_base.KeyCode):
_PLATFORM_EXTENSIONS = (
# Whether this is a media key
'_is_media',
)
# Be explicit about fields
_is_media = None
@classmethod
def _from_media(cls, vk, **kwargs):
"""Creates a media key from a key code.
:param int vk: The key code.
:return: a key code
"""
return cls.from_vk(vk, _is_media=True, **kwargs)
def _event(self, modifiers, mapping, is_pressed):
"""This key as a *Quartz* event.
:param set modifiers: The currently active modifiers.
:param mapping: The current keyboard mapping.
:param bool is_press: Whether to generate a press event.
:return: a *Quartz* event
"""
vk = self.vk or mapping.get(self.char)
if self._is_media:
result = otherEventWithType(
Quartz.NSSystemDefined,
(0, 0),
0xa00 if is_pressed else 0xb00,
0,
0,
0,
8,
(self.vk << 16) | ((0xa if is_pressed else 0xb) << 8),
-1).CGEvent()
else:
result = Quartz.CGEventCreateKeyboardEvent(
None, 0 if vk is None else vk, is_pressed)
Quartz.CGEventSetFlags(
result,
0
| (Quartz.kCGEventFlagMaskAlternate
if Key.alt in modifiers else 0)
| (Quartz.kCGEventFlagMaskCommand
if Key.cmd in modifiers else 0)
| (Quartz.kCGEventFlagMaskControl
if Key.ctrl in modifiers else 0)
| (Quartz.kCGEventFlagMaskShift
if Key.shift in modifiers else 0))
if vk is None and self.char is not None:
Quartz.CGEventKeyboardSetUnicodeString(
result, len(self.char), self.char)
return result
# pylint: disable=W0212
class Key(enum.Enum):
# Default keys
alt = KeyCode.from_vk(0x3A)
alt_l = KeyCode.from_vk(0x3A)
alt_r = KeyCode.from_vk(0x3D)
alt_gr = KeyCode.from_vk(0x3D)
backspace = KeyCode.from_vk(0x33)
caps_lock = KeyCode.from_vk(0x39)
cmd = KeyCode.from_vk(0x37)
cmd_l = KeyCode.from_vk(0x37)
cmd_r = KeyCode.from_vk(0x36)
ctrl = KeyCode.from_vk(0x3B)
ctrl_l = KeyCode.from_vk(0x3B)
ctrl_r = KeyCode.from_vk(0x3E)
delete = KeyCode.from_vk(0x75)
down = KeyCode.from_vk(0x7D)
end = KeyCode.from_vk(0x77)
enter = KeyCode.from_vk(0x24)
esc = KeyCode.from_vk(0x35)
f1 = KeyCode.from_vk(0x7A)
f2 = KeyCode.from_vk(0x78)
f3 = KeyCode.from_vk(0x63)
f4 = KeyCode.from_vk(0x76)
f5 = KeyCode.from_vk(0x60)
f6 = KeyCode.from_vk(0x61)
f7 = KeyCode.from_vk(0x62)
f8 = KeyCode.from_vk(0x64)
f9 = KeyCode.from_vk(0x65)
f10 = KeyCode.from_vk(0x6D)
f11 = KeyCode.from_vk(0x67)
f12 = KeyCode.from_vk(0x6F)
f13 = KeyCode.from_vk(0x69)
f14 = KeyCode.from_vk(0x6B)
f15 = KeyCode.from_vk(0x71)
f16 = KeyCode.from_vk(0x6A)
f17 = KeyCode.from_vk(0x40)
f18 = KeyCode.from_vk(0x4F)
f19 = KeyCode.from_vk(0x50)
f20 = KeyCode.from_vk(0x5A)
home = KeyCode.from_vk(0x73)
left = KeyCode.from_vk(0x7B)
page_down = KeyCode.from_vk(0x79)
page_up = KeyCode.from_vk(0x74)
right = KeyCode.from_vk(0x7C)
shift = KeyCode.from_vk(0x38)
shift_l = KeyCode.from_vk(0x38)
shift_r = KeyCode.from_vk(0x3C)
space = KeyCode.from_vk(0x31, char=' ')
tab = KeyCode.from_vk(0x30)
up = KeyCode.from_vk(0x7E)
media_play_pause = KeyCode._from_media(NX_KEYTYPE_PLAY)
media_volume_mute = KeyCode._from_media(NX_KEYTYPE_MUTE)
media_volume_down = KeyCode._from_media(NX_KEYTYPE_SOUND_DOWN)
media_volume_up = KeyCode._from_media(NX_KEYTYPE_SOUND_UP)
media_previous = KeyCode._from_media(NX_KEYTYPE_PREVIOUS)
media_next = KeyCode._from_media(NX_KEYTYPE_NEXT)
# pylint: enable=W0212
class Controller(_base.Controller):
_KeyCode = KeyCode
_Key = Key
def __init__(self):
super(Controller, self).__init__()
self._mapping = get_unicode_to_keycode_map()
def _handle(self, key, is_press):
with self.modifiers as modifiers:
Quartz.CGEventPost(
Quartz.kCGHIDEventTap,
(key if key not in (k for k in Key) else key.value)._event(
modifiers, self._mapping, is_press))
class Listener(ListenerMixin, _base.Listener):
#: The events that we listen to
_EVENTS = (
Quartz.CGEventMaskBit(Quartz.kCGEventKeyDown) |
Quartz.CGEventMaskBit(Quartz.kCGEventKeyUp) |
Quartz.CGEventMaskBit(Quartz.kCGEventFlagsChanged) |
Quartz.CGEventMaskBit(Quartz.NSSystemDefined)
)
# pylint: disable=W0212
#: A mapping from keysym to special key
_SPECIAL_KEYS = {
(key.value.vk, key.value._is_media): key
for key in Key}
# pylint: enable=W0212
#: The event flags set for the various modifier keys
_MODIFIER_FLAGS = {
Key.alt: Quartz.kCGEventFlagMaskAlternate,
Key.alt_l: Quartz.kCGEventFlagMaskAlternate,
Key.alt_r: Quartz.kCGEventFlagMaskAlternate,
Key.cmd: Quartz.kCGEventFlagMaskCommand,
Key.cmd_l: Quartz.kCGEventFlagMaskCommand,
Key.cmd_r: Quartz.kCGEventFlagMaskCommand,
Key.ctrl: Quartz.kCGEventFlagMaskControl,
Key.ctrl_l: Quartz.kCGEventFlagMaskControl,
Key.ctrl_r: Quartz.kCGEventFlagMaskControl,
Key.shift: Quartz.kCGEventFlagMaskShift,
Key.shift_l: Quartz.kCGEventFlagMaskShift,
Key.shift_r: Quartz.kCGEventFlagMaskShift}
def __init__(self, *args, **kwargs):
super(Listener, self).__init__(*args, **kwargs)
self._flags = 0
self._context = None
self._intercept = self._options.get(
'intercept',
None)
def _run(self):
with keycode_context() as context:
self._context = context
try:
super(Listener, self)._run()
finally:
self._context = None
def _handle(self, _proxy, event_type, event, _refcon):
# Convert the event to a KeyCode; this may fail, and in that case we
# pass None
try:
key = self._event_to_key(event)
except IndexError:
key = None
try:
if event_type == Quartz.kCGEventKeyDown:
# This is a normal key press
self.on_press(key)
elif event_type == Quartz.kCGEventKeyUp:
# This is a normal key release
self.on_release(key)
elif key == Key.caps_lock:
# We only get an event when caps lock is toggled, so we fake
# press and release
self.on_press(key)
self.on_release(key)
elif event_type == Quartz.NSSystemDefined:
sys_event = Quartz.NSEvent.eventWithCGEvent_(event)
if sys_event.subtype() == kSystemDefinedEventMediaKeysSubtype:
# The key in the special key dict; True since it is a media
# key
key = ((sys_event.data1() & 0xffff0000) >> 16, True)
if key in self._SPECIAL_KEYS:
flags = sys_event.data1() & 0x0000ffff
is_press = ((flags & 0xff00) >> 8) == 0x0a
if is_press:
self.on_press(self._SPECIAL_KEYS[key])
else:
self.on_release(self._SPECIAL_KEYS[key])
else:
# This is a modifier event---excluding caps lock---for which we
# must check the current modifier state to determine whether
# the key was pressed or released
flags = Quartz.CGEventGetFlags(event)
is_press = flags & self._MODIFIER_FLAGS.get(key, 0)
if is_press:
self.on_press(key)
else:
self.on_release(key)
finally:
# Store the current flag mask to be able to detect modifier state
# changes
self._flags = Quartz.CGEventGetFlags(event)
def _event_to_key(self, event):
"""Converts a *Quartz* event to a :class:`KeyCode`.
:param event: The event to convert.
:return: a :class:`pynput.keyboard.KeyCode`
:raises IndexError: if the key code is invalid
"""
vk = Quartz.CGEventGetIntegerValueField(
event, Quartz.kCGKeyboardEventKeycode)
event_type = Quartz.CGEventGetType(event)
is_media = True if event_type == Quartz.NSSystemDefined else None
# First try special keys...
key = (vk, is_media)
if key in self._SPECIAL_KEYS:
return self._SPECIAL_KEYS[key]
# ...then try characters...
length, chars = Quartz.CGEventKeyboardGetUnicodeString(
event, 100, None, None)
try:
printable = chars.isprintable()
except AttributeError:
printable = chars.isalnum()
if not printable and vk in SYMBOLS \
and Quartz.CGEventGetFlags(event) \
& Quartz.kCGEventFlagMaskControl:
return KeyCode.from_char(SYMBOLS[vk], vk=vk)
elif length > 0:
return KeyCode.from_char(chars, vk=vk)
# ...and fall back on a virtual key code
return KeyCode.from_vk(vk)