Merged in feature/PYPE-14-ftrack-stop-timer (pull request #26)

Feature/PYPE-14 ftrack stop timer

Approved-by: Milan Kolar <milan@orbi.tools>
This commit is contained in:
Jakub Trllo 2018-12-09 17:09:58 +00:00 committed by Milan Kolar
commit d0ade3d8f0
28 changed files with 7761 additions and 51 deletions

View file

@ -4,10 +4,12 @@ import argparse
import subprocess
import threading
import time
import ftrack_api
from app import style
from app.vendor.Qt import QtCore, QtGui, QtWidgets
from pype.ftrack import credentials, login_dialog as login_dialog
from pype.vendor.pynput import mouse, keyboard
from FtrackServer import FtrackServer
from pype import api as pype
@ -17,22 +19,28 @@ from pype import api as pype
pype.load_data_from_templates()
log = pype.Logger.getLogger(__name__, "ftrack")
# Validation if alredy logged into Ftrack
class FtrackRunner:
def __init__(self, main_parent=None, parent=None):
self.parent = parent
self.loginWidget = login_dialog.Login_Dialog_ui(self)
self.actionThread = None
self.actionServer = FtrackServer('action')
self.widget_login = login_dialog.Login_Dialog_ui(self)
self.widget_timer = StopTimer(self)
self.action_server = FtrackServer('action')
self.thread_action_server = None
self.thread_timer = None
self.thread_timer_coundown = None
self.boolLogged = False
self.boolActionServer = False
# self.signal_start_timer.connect(self.timerStart)
def showLoginWidget(self):
self.loginWidget.show()
self.bool_logged = False
self.bool_action_server = False
self.bool_timer_event = False
def show_login_widget(self):
self.widget_login.show()
def validate(self):
validation = False
@ -44,9 +52,9 @@ class FtrackRunner:
cred['apiKey']
)
if validation is False:
self.showLoginWidget()
self.show_login_widget()
else:
self.showLoginWidget()
self.show_login_widget()
except Exception as e:
log.error("We are unable to connect to Ftrack: {0}".format(e))
@ -57,57 +65,56 @@ class FtrackRunner:
self.loginChange()
else:
log.warning("Please sign in to Ftrack")
self.boolLogged = False
self.setMenuVisibility()
self.bool_logged = False
self.set_menu_visibility()
return validation
# Necessary - login_dialog works with this method after logging in
def loginChange(self):
self.boolLogged = True
self.setMenuVisibility()
self.runActionServer()
self.bool_logged = True
self.set_menu_visibility()
self.start_action_server()
def logout(self):
credentials._clear_credentials()
self.stopActionServer()
self.stop_action_server()
log.info("Logged out of Ftrack")
self.boolLogged = False
self.setMenuVisibility()
self.bool_logged = False
self.set_menu_visibility()
# Actions part
def runActionServer(self):
if self.actionThread is None:
self.actionThread = threading.Thread(target=self.setActionServer)
self.actionThread.daemon = True
self.actionThread.start()
def start_action_server(self):
if self.thread_action_server is None:
self.thread_action_server = threading.Thread(target=self.set_action_server)
self.thread_action_server.daemon = True
self.thread_action_server.start()
log.info("Ftrack action server launched")
self.boolActionServer = True
self.setMenuVisibility()
self.bool_action_server = True
self.set_menu_visibility()
def setActionServer(self):
self.actionServer.run_server()
def set_action_server(self):
self.action_server.run_server()
def resetActionServer(self):
self.stopActionServer()
self.runActionServer()
def reset_action_server(self):
self.stop_action_server()
self.start_action_server()
def stopActionServer(self):
def stop_action_server(self):
try:
self.actionServer.stop_session()
if self.actionThread is not None:
self.actionThread.join()
self.actionThread = None
self.action_server.stop_session()
if self.thread_action_server is not None:
self.thread_action_server.join()
self.thread_action_server = None
log.info("Ftrack action server stopped")
self.boolActionServer = False
self.setMenuVisibility()
self.bool_action_server = False
self.set_menu_visibility()
except Exception as e:
log.error("During Killing action server: {0}".format(e))
# Definition of Tray menu
def trayMenu(self, parent):
# Menu for Tray App
@ -118,11 +125,11 @@ class FtrackRunner:
# Actions - server
self.smActionS = self.menu.addMenu("Action server")
self.aRunActionS = QtWidgets.QAction("Run action server", self.smActionS)
self.aRunActionS.triggered.connect(self.runActionServer)
self.aRunActionS.triggered.connect(self.start_action_server)
self.aResetActionS = QtWidgets.QAction("Reset action server", self.smActionS)
self.aResetActionS.triggered.connect(self.resetActionServer)
self.aResetActionS.triggered.connect(self.reset_action_server)
self.aStopActionS = QtWidgets.QAction("Stop action server", self.smActionS)
self.aStopActionS.triggered.connect(self.stopActionServer)
self.aStopActionS.triggered.connect(self.stop_action_server)
self.smActionS.addAction(self.aRunActionS)
self.smActionS.addAction(self.aResetActionS)
@ -137,21 +144,363 @@ class FtrackRunner:
self.menu.addAction(self.aLogin)
self.menu.addAction(self.aLogout)
self.boolLogged = False
self.setMenuVisibility()
self.bool_logged = False
self.set_menu_visibility()
return self.menu
# Definition of visibility of each menu actions
def setMenuVisibility(self):
def set_menu_visibility(self):
self.smActionS.menuAction().setVisible(self.boolLogged)
self.aLogin.setVisible(not self.boolLogged)
self.aLogout.setVisible(self.boolLogged)
self.smActionS.menuAction().setVisible(self.bool_logged)
self.aLogin.setVisible(not self.bool_logged)
self.aLogout.setVisible(self.bool_logged)
if self.boolLogged is False:
if self.bool_logged is False:
if self.bool_timer_event is True:
self.stop_timer_thread()
return
self.aRunActionS.setVisible(not self.boolActionServer)
self.aResetActionS.setVisible(self.boolActionServer)
self.aStopActionS.setVisible(self.boolActionServer)
self.aRunActionS.setVisible(not self.bool_action_server)
self.aResetActionS.setVisible(self.bool_action_server)
self.aStopActionS.setVisible(self.bool_action_server)
if self.bool_timer_event is False:
self.start_timer_thread()
def start_timer_thread(self):
if self.thread_timer is None:
self.thread_timer = FtrackEventsThread(self)
self.bool_timer_event = True
self.thread_timer.signal_timer_started.connect(self.timer_started)
self.thread_timer.signal_timer_stopped.connect(self.timer_stopped)
self.thread_timer.start()
def stop_timer_thread(self):
try:
if self.thread_timer is not None:
self.thread_timer.terminate()
self.thread_timer.wait()
self.thread_timer = None
except Exception as e:
log.error("During Killing Timer event server: {0}".format(e))
def start_countdown_thread(self):
if self.thread_timer_coundown is None:
self.thread_timer_coundown = CountdownThread(self)
self.thread_timer_coundown.signal_show_question.connect(self.show_widget_timer)
self.thread_timer_coundown.signal_send_time.connect(self.change_count_widget)
self.thread_timer_coundown.signal_stop_timer.connect(self.timer_stop)
self.thread_timer_coundown.start()
def stop_countdown_thread(self):
if self.thread_timer_coundown is not None:
self.thread_timer_coundown.runs=False
self.thread_timer_coundown.terminate()
self.thread_timer_coundown.wait()
self.thread_timer_coundown = None
def show_widget_timer(self):
self.widget_timer.show()
self.widget_timer.setWindowState(QtCore.Qt.WindowMinimized)
self.widget_timer.setWindowState(QtCore.Qt.WindowActive)
# self.widget_timer.activateWindow()
def change_count_widget(self, time):
self.widget_timer.lbl_rest_time.setText(str(time))
def timer_started(self):
self.start_countdown_thread()
def timer_stopped(self):
self.stop_countdown_thread()
def timer_stop(self):
if self.thread_timer is not None:
self.widget_timer.main_context = False
self.widget_timer.refresh_context()
self.thread_timer.signal_stop_timer.emit()
if self.thread_timer_coundown is not None:
self.stop_countdown_thread()
def timer_continue(self):
if self.thread_timer_coundown is not None:
self.thread_timer_coundown.signal_continue_timer.emit()
class FtrackEventsThread(QtCore.QThread):
# Senders
signal_timer_started = QtCore.Signal()
signal_timer_stopped = QtCore.Signal()
# Listeners
signal_stop_timer = QtCore.Signal()
def __init__(self, parent):
super(FtrackEventsThread, self).__init__()
cred = credentials._get_credentials()
self.username = cred['username']
self.signal_stop_timer.connect(self.ftrack_stop_timer)
def run(self):
self.timer_session = ftrack_api.Session(auto_connect_event_hub=True)
self.timer_session.event_hub.subscribe(
'topic=ftrack.update and source.user.username={}'.format(self.username),
self.event_handler)
self.timer_session.event_hub.wait()
def event_handler(self, event):
try:
if event['data']['entities'][0]['objectTypeId'] != 'timer':
return
except:
return
new = event['data']['entities'][0]['changes']['start']['new']
old = event['data']['entities'][0]['changes']['start']['old']
self.userId = event['source']['user']['id']
if old is None and new is None:
return
elif old is None:
self.signal_timer_started.emit()
elif new is None:
self.signal_timer_stopped.emit()
def ftrack_stop_timer(self):
try:
user = self.timer_session.query('User where id is ' + self.userId).one()
user.stop_timer()
self.timer_session.commit()
except Exception as e:
log.debug("Timer stop had issues: {}".format(e))
class CountdownThread(QtCore.QThread):
# Senders
signal_show_question = QtCore.Signal()
signal_send_time = QtCore.Signal(object)
signal_stop_timer = QtCore.Signal()
signal_stop_countdown = QtCore.Signal()
# Listeners
signal_reset_timer = QtCore.Signal()
signal_continue_timer = QtCore.Signal()
def __init__(self, parent):
super(CountdownThread, self).__init__()
self.runs = True
self.over_line = False
self.count_length = 60*5 # 5 minutes
self.border_line = 31
self.reset_count()
self.signal_reset_timer.connect(self.reset_count)
self.signal_continue_timer.connect(self.continue_timer)
def continue_timer(self):
self.over_line = False
self.reset_count()
def reset_count(self):
if self.over_line is True:
self.actual = self.border_line
else:
self.actual = self.count_length
def stop(self):
self.runs = False
def run(self):
thread_mouse = MouseThread(self)
thread_mouse.start()
thread_keyboard = KeyboardThread(self)
thread_keyboard.start()
while self.runs:
if self.actual == self.border_line:
self.signal_show_question.emit()
self.over_line = True
if self.actual <= self.border_line:
self.signal_send_time.emit(self.actual)
time.sleep(1)
self.actual -= 1
if self.actual == 0:
self.runs = False
self.signal_stop_timer.emit()
thread_mouse.signal_stop.emit()
thread_mouse.terminate()
thread_mouse.wait()
thread_keyboard.signal_stop.emit()
thread_keyboard.terminate()
thread_keyboard.wait()
class MouseThread(QtCore.QThread):
signal_stop = QtCore.Signal()
def __init__(self, parent):
super(MouseThread, self).__init__()
self.parent = parent
self.signal_stop.connect(self.stop)
self.m_listener = None
def stop(self):
if self.m_listener is not None:
self.m_listener.stop()
def on_move(self, posx, posy):
self.parent.signal_reset_timer.emit()
def run(self):
self.m_listener = mouse.Listener(on_move=self.on_move)
self.m_listener.start()
class KeyboardThread(QtCore.QThread):
signal_stop = QtCore.Signal()
def __init__(self, parent):
super(KeyboardThread, self).__init__()
self.parent = parent
self.signal_stop.connect(self.stop)
self.k_listener = None
def stop(self):
if self.k_listener is not None:
self.k_listener.stop()
def on_press(self, key):
self.parent.signal_reset_timer.emit()
def run(self):
self.k_listener = keyboard.Listener(on_press=self.on_press)
self.k_listener.start()
class StopTimer(QtWidgets.QWidget):
SIZE_W = 300
SIZE_H = 160
def __init__(self, parent=None):
super(StopTimer, self).__init__()
self.main_context = True
self.parent = parent
self.setWindowIcon(self.parent.parent.icon)
self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint)
self._translate = QtCore.QCoreApplication.translate
self.font = QtGui.QFont()
self.font.setFamily("DejaVu Sans Condensed")
self.font.setPointSize(9)
self.font.setBold(True)
self.font.setWeight(50)
self.font.setKerning(True)
self.resize(self.SIZE_W, self.SIZE_H)
self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H))
self.setMaximumSize(QtCore.QSize(self.SIZE_W+100, self.SIZE_H+100))
self.setStyleSheet(style.load_stylesheet())
self.setLayout(self._main())
self.refresh_context()
self.setWindowTitle('Pype - Stop Ftrack timer')
def _main(self):
self.main = QtWidgets.QVBoxLayout()
self.main.setObjectName("main")
self.form = QtWidgets.QFormLayout()
self.form.setContentsMargins(10, 15, 10, 5)
self.form.setObjectName("form")
msg_info = "You didn't work for a long time."
msg_question = "Would you like to stop Ftrack timer?"
msg_stopped = "Your Ftrack timer was stopped!"
self.lbl_info = QtWidgets.QLabel(msg_info)
self.lbl_info.setFont(self.font)
self.lbl_info.setTextFormat(QtCore.Qt.RichText)
self.lbl_info.setObjectName("lbl_info")
self.lbl_info.setWordWrap(True);
self.lbl_question = QtWidgets.QLabel(msg_question)
self.lbl_question.setFont(self.font)
self.lbl_question.setTextFormat(QtCore.Qt.RichText)
self.lbl_question.setObjectName("lbl_question")
self.lbl_question.setWordWrap(True);
self.lbl_stopped = QtWidgets.QLabel(msg_stopped)
self.lbl_stopped.setFont(self.font)
self.lbl_stopped.setTextFormat(QtCore.Qt.RichText)
self.lbl_stopped.setObjectName("lbl_stopped")
self.lbl_stopped.setWordWrap(True);
self.lbl_rest_time = QtWidgets.QLabel("")
self.lbl_rest_time.setFont(self.font)
self.lbl_rest_time.setTextFormat(QtCore.Qt.RichText)
self.lbl_rest_time.setObjectName("lbl_rest_time")
self.lbl_rest_time.setWordWrap(True);
self.lbl_rest_time.setAlignment(QtCore.Qt.AlignCenter)
self.form.addRow(self.lbl_info)
self.form.addRow(self.lbl_question)
self.form.addRow(self.lbl_stopped)
self.form.addRow(self.lbl_rest_time)
self.group_btn = QtWidgets.QHBoxLayout()
self.group_btn.addStretch(1)
self.group_btn.setObjectName("group_btn")
self.btn_stop = QtWidgets.QPushButton("Stop timer")
self.btn_stop.setToolTip('Stop\'s Ftrack timer')
self.btn_stop.clicked.connect(self.stop_timer)
self.btn_continue = QtWidgets.QPushButton("Continue")
self.btn_continue.setToolTip('Timer will continue')
self.btn_continue.clicked.connect(self.continue_timer)
self.btn_ok = QtWidgets.QPushButton("OK")
self.btn_ok.setToolTip('Close window')
self.btn_ok.clicked.connect(self.close_widget)
self.group_btn.addWidget(self.btn_continue)
self.group_btn.addWidget(self.btn_stop)
self.group_btn.addWidget(self.btn_ok)
self.main.addLayout(self.form)
self.main.addLayout(self.group_btn)
return self.main
def refresh_context(self):
self.lbl_question.setVisible(self.main_context)
self.lbl_rest_time.setVisible(self.main_context)
self.lbl_stopped.setVisible(not self.main_context)
self.btn_continue.setVisible(self.main_context)
self.btn_stop.setVisible(self.main_context)
self.btn_ok.setVisible(not self.main_context)
def stop_timer(self):
self.parent.timer_stop()
self.close_widget()
def continue_timer(self):
self.parent.timer_continue()
self.close_widget()
def closeEvent(self, event):
event.ignore()
if self.main_context is True:
self.continue_timer()
else:
self.close_widget()
def close_widget(self):
self.main_context = True
self.refresh_context()
self.hide()

View file

@ -0,0 +1,451 @@
pynput
======
This library allows you to control and monitor input devices.
Currently, mouse and keyboard input and monitoring are supported.
See `here <https://pynput.readthedocs.io/en/latest/>`_ for the full
documentation.
Controlling the mouse
---------------------
Use ``pynput.mouse.Controller`` like this::
from pynput.mouse import Button, Controller
mouse = Controller()
# Read pointer position
print('The current pointer position is {0}'.format(
mouse.position))
# Set pointer position
mouse.position = (10, 20)
print('Now we have moved it to {0}'.format(
mouse.position))
# Move pointer relative to current position
mouse.move(5, -5)
# Press and release
mouse.press(Button.left)
mouse.release(Button.left)
# Double click; this is different from pressing and releasing
# twice on Mac OSX
mouse.click(Button.left, 2)
# Scroll two steps down
mouse.scroll(0, 2)
Monitoring the mouse
--------------------
Use ``pynput.mouse.Listener`` like this::
from pynput import mouse
def on_move(x, y):
print('Pointer moved to {0}'.format(
(x, y)))
def on_click(x, y, button, pressed):
print('{0} at {1}'.format(
'Pressed' if pressed else 'Released',
(x, y)))
if not pressed:
# Stop listener
return False
def on_scroll(x, y, dx, dy):
print('Scrolled {0} at {1}'.format(
'down' if dy < 0 else 'up',
(x, y)))
# Collect events until released
with mouse.Listener(
on_move=on_move,
on_click=on_click,
on_scroll=on_scroll) as listener:
listener.join()
A mouse listener is a ``threading.Thread``, and all callbacks will be invoked
from the thread.
Call ``pynput.mouse.Listener.stop`` from anywhere, raise ``StopException`` or
return ``False`` from a callback to stop the listener.
The mouse listener thread
~~~~~~~~~~~~~~~~~~~~~~~~~
The listener callbacks are invoked directly from an operating thread on some
platforms, notably *Windows*.
This means that long running procedures and blocking operations should not be
invoked from the callback, as this risks freezing input for all processes.
A possible workaround is to just dispatch incoming messages to a queue, and let
a separate thread handle them.
Handling mouse listener errors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a callback handler raises an exception, the listener will be stopped. Since
callbacks run in a dedicated thread, the exceptions will not automatically be
reraised.
To be notified about callback errors, call ``Thread.join`` on the listener
instance::
from pynput import mouse
class MyException(Exception): pass
def on_click(x, y, button, pressed):
if button == mouse.Button.left:
raise MyException(button)
# Collect events until released
with mouse.Listener(
on_click=on_click) as listener:
try:
listener.join()
except MyException as e:
print('{0} was clicked'.format(e.args[0]))
Controlling the keyboard
------------------------
Use ``pynput.keyboard.Controller`` like this::
from pynput.keyboard import Key, Controller
keyboard = Controller()
# Press and release space
keyboard.press(Key.space)
keyboard.release(Key.space)
# Type a lower case A; this will work even if no key on the
# physical keyboard is labelled 'A'
keyboard.press('a')
keyboard.release('a')
# Type two upper case As
keyboard.press('A')
keyboard.release('A')
with keyboard.pressed(Key.shift):
keyboard.press('a')
keyboard.release('a')
# Type 'Hello World' using the shortcut type method
keyboard.type('Hello World')
Monitoring the keyboard
-----------------------
Use ``pynput.keyboard.Listener`` like this::
from pynput import keyboard
def on_press(key):
try:
print('alphanumeric key {0} pressed'.format(
key.char))
except AttributeError:
print('special key {0} pressed'.format(
key))
def on_release(key):
print('{0} released'.format(
key))
if key == keyboard.Key.esc:
# Stop listener
return False
# Collect events until released
with keyboard.Listener(
on_press=on_press,
on_release=on_release) as listener:
listener.join()
A keyboard listener is a ``threading.Thread``, and all callbacks will be
invoked from the thread.
Call ``pynput.keyboard.Listener.stop`` from anywhere, raise ``StopException``
or return ``False`` from a callback to stop the listener.
The ``key`` parameter passed to callbacks is a ``pynput.keyboard.Key``, for
special keys, a ``pynput.keyboard.KeyCode`` for normal alphanumeric keys, or
just ``None`` for unknown keys.
The keyboard listener thread
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The listener callbacks are invoked directly from an operating thread on some
platforms, notably *Windows*.
This means that long running procedures and blocking operations should not be
invoked from the callback, as this risks freezing input for all processes.
A possible workaround is to just dispatch incoming messages to a queue, and let
a separate thread handle them.
Handling keyboard listener errors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a callback handler raises an exception, the listener will be stopped. Since
callbacks run in a dedicated thread, the exceptions will not automatically be
reraised.
To be notified about callback errors, call ``Thread.join`` on the listener
instance::
from pynput import keyboard
class MyException(Exception): pass
def on_press(key):
if key == keyboard.Key.esc:
raise MyException(key)
# Collect events until released
with keyboard.Listener(
on_press=on_press) as listener:
try:
listener.join()
except MyException as e:
print('{0} was pressed'.format(e.args[0]))
Release Notes
=============
v1.4 - Injected event flags end event suppression
-------------------------------------------------
* Added possibility to fully suppress events when listening.
* Added support for typing some control characters.
* Added support for mouse drag events on *OSX*. Thanks to *jungledrum*!
* Include the key code in keyboard listener events.
* Correctly handle the numeric key pad on *Xorg* with *num lock* active.
Thanks to *TheoRet*!
* Corrected handling of current thread keyboard layout on *Windows*. Thanks to
*Schmettaling*!
* Corrected stopping of listeners on *Xorg*.
* Corrected import of ``Xlib.keysymdef.xkb`` on *Xorg*. Thanks to *Glandos*!
v1.3.10 - Do not crash under *Xephyr*
-------------------------------------
* Do not crash when ``Xlib.display.Display.get_input_focus`` returns an
integer, as it may when running under *Xephyr*. Thanks to *Eli Skeggs*!
v1.3.9 - Correctly handle the letter *A* on *OSX*
-------------------------------------------------
* Corrected check for virtual key code when generating keyboard events on
*OSX*. This fixes an issue where pressing *A* with *shift* explicitly pressed
would still type a miniscule letter.
v1.3.8 - Do not crash on some keyboard layouts on *OSX*
-------------------------------------------------------
* Fall back on a different method to retrieve the keyboard layout on *OSX*.
This helps for some keyboard layouts, such as *Chinese*. Thanks to
*haoflynet*!
v1.3.7 - *Xorg* corrections
---------------------------
* Include mouse buttons up to *30* for *Xorg*.
v1.3.6 - *win32* corrections
----------------------------
* Corrected double delivery of fake keyboard events on *Windows*.
* Corrected handling of synthetic unicode keys on *Windows*.
v1.3.5 - Corrected dependencies again
-------------------------------------
* Reverted changes in *1.3.3*.
* Corrected platform specifier for *Python 2* on *Linux*.
v1.3.4 - *Xorg* corrections
---------------------------
* Corrected bounds check for values on *Xorg*.
v1.3.3 - Make dependencies non-optional
---------------------------------------
* Made platform depdendencies non-optional.
v1.3.2 - Fix for button click on Mac
------------------------------------
* Corrected regression from previous release where button clicks would
crash the *Mac* mouse listener.
v1.3.1 - Fixes for unknown buttons on Linux
-------------------------------------------
* Fall back on `Button.unknown` for unknown mouse buttons in *Xorg* mouse
listener.
v1.3 - Platform specific features
---------------------------------
* Added ability to stop event propagation on *Windows*. This will prevent
events from reaching other applications.
* Added ability to ignore events on *Windows*. This is a workaround for systems
where the keyboard monitor interferes with normal keyboard events.
* Added ability to modify events on *OSX*. This allows intercepting and
altering input events before they reach other applications.
* Corrected crash on *OSX* when some types of third party input sources are
installed.
v1.2 - Improved error handling
------------------------------
* Allow catching exceptions thrown from listener callbacks. This changes the
API, as joining a listener now potentially raises unhandled exceptions,
and unhandled exceptions will stop listeners.
* Added support for the numeric keypad on *Linux*.
* Improved documentation.
* Thanks to *jollysean* and *gilleswijnker* for their input!
v1.1.7 - Handle middle button on Windows
----------------------------------------
* Listen for and dispatch middle button mouse clicks on *Windows*.
v1.1.6 - Corrected context manager for pressing keys
----------------------------------------------------
* Corrected bug in ``pynput.keyboard.Controller.pressed`` which caused it to
never release the key. Many thanks to Toby Southwell!
v1.1.5 - Corrected modifier key combinations on Linux
-----------------------------------------------------
* Corrected handling of modifier keys to allow them to be composable on
*Linux*.
v1.1.4 - Small bugfixes
-----------------------
* Corrected error generation when ``GetKeyboardState`` fails.
* Make sure to apply shift state to borrowed keys on *X*.
* Use *pylint*.
v1.1.3 - Changed Xlib backend library
-------------------------------------
* Changed *Xlib* library.
v1.1.2 - Added missing type for Python 2
----------------------------------------
* Added missing ``LPDWORD`` for *Python 2* on *Windows*.
v1.1.1 - Fixes for listeners and controllers on Windows
-------------------------------------------------------
* Corrected keyboard listener on *Windows*. Modifier keys and other keys
changing the state of the keyboard are now handled correctly.
* Corrected mouse click and release on *Windows*.
* Corrected code samples.
v1.1 - Simplified usage on Linux
--------------------------------
* Propagate import errors raised on Linux to help troubleshoot missing
``Xlib`` module.
* Declare ``python3-xlib`` as dependency on *Linux* for *Python 3*.
v1.0.6 - Universal wheel
------------------------
* Make sure to build a universal wheel for all python versions.
v1.0.5 - Fixes for dragging on OSX
----------------------------------
* Corrected dragging on *OSX*.
* Added scroll speed constant for *OSX* to correct slow scroll speed.
v1.0.4 - Fixes for clicking and scrolling on Windows
----------------------------------------------------
* Corrected name of mouse input field when sending click and scroll events.
v1.0.3 - Fixes for Python 3 on Windows
--------------------------------------
* Corrected use of ``ctypes`` on Windows.
v1.0.2 - Fixes for thread identifiers
-------------------------------------
* Use thread identifiers to identify threads, not Thread instances.
v1.0.1 - Fixes for Python 3
---------------------------
* Corrected bugs which prevented the library from being used on *Python 3*.
v1.0 - Stable Release
---------------------
* Changed license to *LGPL*.
* Corrected minor bugs and inconsistencies.
* Corrected and extended documentation.
v0.6 - Keyboard Monitor
-----------------------
* Added support for monitoring the keyboard.
* Corrected wheel packaging.
* Corrected deadlock when stopping a listener in some cases on *X*.
* Corrected key code constants on *Mac OSX*.
* Do not intercept events on *Mac OSX*.
v0.5.1 - Do not die on dead keys
--------------------------------
* Corrected handling of dead keys.
* Corrected documentation.
v0.5 - Keyboard Modifiers
-------------------------
* Added support for modifiers.
v0.4 - Keyboard Controller
--------------------------
* Added keyboard controller.
v0.3 - Cleanup
------------------------------------------------------------
* Moved ``pynput.mouse.Controller.Button`` to top-level.
v0.2 - Initial Release
----------------------
* Support for controlling the mouse on *Linux*, *Mac OSX* and *Windows*.
* Support for monitoring the mouse on *Linux*, *Mac OSX* and *Windows*.

View file

@ -0,0 +1 @@
pip

View file

@ -0,0 +1,478 @@
Metadata-Version: 2.0
Name: pynput
Version: 1.4
Summary: Monitor and control user input devices
Home-page: https://github.com/moses-palmer/pynput
Author: Moses Palmér
Author-email: moses.palmer@gmail.com
License: LGPLv3
Description-Content-Type: UNKNOWN
Keywords: control mouse,mouse input,control keyboard,keyboard input
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.4
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Monitoring
Requires-Dist: six
Requires-Dist: python-xlib (>=0.17); "linux" in sys_platform
Requires-Dist: enum34; python_version == "2.7"
Requires-Dist: pyobjc-framework-Quartz (>=3.0); sys_platform == "darwin"
pynput
======
This library allows you to control and monitor input devices.
Currently, mouse and keyboard input and monitoring are supported.
See `here <https://pynput.readthedocs.io/en/latest/>`_ for the full
documentation.
Controlling the mouse
---------------------
Use ``pynput.mouse.Controller`` like this::
from pynput.mouse import Button, Controller
mouse = Controller()
# Read pointer position
print('The current pointer position is {0}'.format(
mouse.position))
# Set pointer position
mouse.position = (10, 20)
print('Now we have moved it to {0}'.format(
mouse.position))
# Move pointer relative to current position
mouse.move(5, -5)
# Press and release
mouse.press(Button.left)
mouse.release(Button.left)
# Double click; this is different from pressing and releasing
# twice on Mac OSX
mouse.click(Button.left, 2)
# Scroll two steps down
mouse.scroll(0, 2)
Monitoring the mouse
--------------------
Use ``pynput.mouse.Listener`` like this::
from pynput import mouse
def on_move(x, y):
print('Pointer moved to {0}'.format(
(x, y)))
def on_click(x, y, button, pressed):
print('{0} at {1}'.format(
'Pressed' if pressed else 'Released',
(x, y)))
if not pressed:
# Stop listener
return False
def on_scroll(x, y, dx, dy):
print('Scrolled {0} at {1}'.format(
'down' if dy < 0 else 'up',
(x, y)))
# Collect events until released
with mouse.Listener(
on_move=on_move,
on_click=on_click,
on_scroll=on_scroll) as listener:
listener.join()
A mouse listener is a ``threading.Thread``, and all callbacks will be invoked
from the thread.
Call ``pynput.mouse.Listener.stop`` from anywhere, raise ``StopException`` or
return ``False`` from a callback to stop the listener.
The mouse listener thread
~~~~~~~~~~~~~~~~~~~~~~~~~
The listener callbacks are invoked directly from an operating thread on some
platforms, notably *Windows*.
This means that long running procedures and blocking operations should not be
invoked from the callback, as this risks freezing input for all processes.
A possible workaround is to just dispatch incoming messages to a queue, and let
a separate thread handle them.
Handling mouse listener errors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a callback handler raises an exception, the listener will be stopped. Since
callbacks run in a dedicated thread, the exceptions will not automatically be
reraised.
To be notified about callback errors, call ``Thread.join`` on the listener
instance::
from pynput import mouse
class MyException(Exception): pass
def on_click(x, y, button, pressed):
if button == mouse.Button.left:
raise MyException(button)
# Collect events until released
with mouse.Listener(
on_click=on_click) as listener:
try:
listener.join()
except MyException as e:
print('{0} was clicked'.format(e.args[0]))
Controlling the keyboard
------------------------
Use ``pynput.keyboard.Controller`` like this::
from pynput.keyboard import Key, Controller
keyboard = Controller()
# Press and release space
keyboard.press(Key.space)
keyboard.release(Key.space)
# Type a lower case A; this will work even if no key on the
# physical keyboard is labelled 'A'
keyboard.press('a')
keyboard.release('a')
# Type two upper case As
keyboard.press('A')
keyboard.release('A')
with keyboard.pressed(Key.shift):
keyboard.press('a')
keyboard.release('a')
# Type 'Hello World' using the shortcut type method
keyboard.type('Hello World')
Monitoring the keyboard
-----------------------
Use ``pynput.keyboard.Listener`` like this::
from pynput import keyboard
def on_press(key):
try:
print('alphanumeric key {0} pressed'.format(
key.char))
except AttributeError:
print('special key {0} pressed'.format(
key))
def on_release(key):
print('{0} released'.format(
key))
if key == keyboard.Key.esc:
# Stop listener
return False
# Collect events until released
with keyboard.Listener(
on_press=on_press,
on_release=on_release) as listener:
listener.join()
A keyboard listener is a ``threading.Thread``, and all callbacks will be
invoked from the thread.
Call ``pynput.keyboard.Listener.stop`` from anywhere, raise ``StopException``
or return ``False`` from a callback to stop the listener.
The ``key`` parameter passed to callbacks is a ``pynput.keyboard.Key``, for
special keys, a ``pynput.keyboard.KeyCode`` for normal alphanumeric keys, or
just ``None`` for unknown keys.
The keyboard listener thread
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The listener callbacks are invoked directly from an operating thread on some
platforms, notably *Windows*.
This means that long running procedures and blocking operations should not be
invoked from the callback, as this risks freezing input for all processes.
A possible workaround is to just dispatch incoming messages to a queue, and let
a separate thread handle them.
Handling keyboard listener errors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a callback handler raises an exception, the listener will be stopped. Since
callbacks run in a dedicated thread, the exceptions will not automatically be
reraised.
To be notified about callback errors, call ``Thread.join`` on the listener
instance::
from pynput import keyboard
class MyException(Exception): pass
def on_press(key):
if key == keyboard.Key.esc:
raise MyException(key)
# Collect events until released
with keyboard.Listener(
on_press=on_press) as listener:
try:
listener.join()
except MyException as e:
print('{0} was pressed'.format(e.args[0]))
Release Notes
=============
v1.4 - Injected event flags end event suppression
-------------------------------------------------
* Added possibility to fully suppress events when listening.
* Added support for typing some control characters.
* Added support for mouse drag events on *OSX*. Thanks to *jungledrum*!
* Include the key code in keyboard listener events.
* Correctly handle the numeric key pad on *Xorg* with *num lock* active.
Thanks to *TheoRet*!
* Corrected handling of current thread keyboard layout on *Windows*. Thanks to
*Schmettaling*!
* Corrected stopping of listeners on *Xorg*.
* Corrected import of ``Xlib.keysymdef.xkb`` on *Xorg*. Thanks to *Glandos*!
v1.3.10 - Do not crash under *Xephyr*
-------------------------------------
* Do not crash when ``Xlib.display.Display.get_input_focus`` returns an
integer, as it may when running under *Xephyr*. Thanks to *Eli Skeggs*!
v1.3.9 - Correctly handle the letter *A* on *OSX*
-------------------------------------------------
* Corrected check for virtual key code when generating keyboard events on
*OSX*. This fixes an issue where pressing *A* with *shift* explicitly pressed
would still type a miniscule letter.
v1.3.8 - Do not crash on some keyboard layouts on *OSX*
-------------------------------------------------------
* Fall back on a different method to retrieve the keyboard layout on *OSX*.
This helps for some keyboard layouts, such as *Chinese*. Thanks to
*haoflynet*!
v1.3.7 - *Xorg* corrections
---------------------------
* Include mouse buttons up to *30* for *Xorg*.
v1.3.6 - *win32* corrections
----------------------------
* Corrected double delivery of fake keyboard events on *Windows*.
* Corrected handling of synthetic unicode keys on *Windows*.
v1.3.5 - Corrected dependencies again
-------------------------------------
* Reverted changes in *1.3.3*.
* Corrected platform specifier for *Python 2* on *Linux*.
v1.3.4 - *Xorg* corrections
---------------------------
* Corrected bounds check for values on *Xorg*.
v1.3.3 - Make dependencies non-optional
---------------------------------------
* Made platform depdendencies non-optional.
v1.3.2 - Fix for button click on Mac
------------------------------------
* Corrected regression from previous release where button clicks would
crash the *Mac* mouse listener.
v1.3.1 - Fixes for unknown buttons on Linux
-------------------------------------------
* Fall back on `Button.unknown` for unknown mouse buttons in *Xorg* mouse
listener.
v1.3 - Platform specific features
---------------------------------
* Added ability to stop event propagation on *Windows*. This will prevent
events from reaching other applications.
* Added ability to ignore events on *Windows*. This is a workaround for systems
where the keyboard monitor interferes with normal keyboard events.
* Added ability to modify events on *OSX*. This allows intercepting and
altering input events before they reach other applications.
* Corrected crash on *OSX* when some types of third party input sources are
installed.
v1.2 - Improved error handling
------------------------------
* Allow catching exceptions thrown from listener callbacks. This changes the
API, as joining a listener now potentially raises unhandled exceptions,
and unhandled exceptions will stop listeners.
* Added support for the numeric keypad on *Linux*.
* Improved documentation.
* Thanks to *jollysean* and *gilleswijnker* for their input!
v1.1.7 - Handle middle button on Windows
----------------------------------------
* Listen for and dispatch middle button mouse clicks on *Windows*.
v1.1.6 - Corrected context manager for pressing keys
----------------------------------------------------
* Corrected bug in ``pynput.keyboard.Controller.pressed`` which caused it to
never release the key. Many thanks to Toby Southwell!
v1.1.5 - Corrected modifier key combinations on Linux
-----------------------------------------------------
* Corrected handling of modifier keys to allow them to be composable on
*Linux*.
v1.1.4 - Small bugfixes
-----------------------
* Corrected error generation when ``GetKeyboardState`` fails.
* Make sure to apply shift state to borrowed keys on *X*.
* Use *pylint*.
v1.1.3 - Changed Xlib backend library
-------------------------------------
* Changed *Xlib* library.
v1.1.2 - Added missing type for Python 2
----------------------------------------
* Added missing ``LPDWORD`` for *Python 2* on *Windows*.
v1.1.1 - Fixes for listeners and controllers on Windows
-------------------------------------------------------
* Corrected keyboard listener on *Windows*. Modifier keys and other keys
changing the state of the keyboard are now handled correctly.
* Corrected mouse click and release on *Windows*.
* Corrected code samples.
v1.1 - Simplified usage on Linux
--------------------------------
* Propagate import errors raised on Linux to help troubleshoot missing
``Xlib`` module.
* Declare ``python3-xlib`` as dependency on *Linux* for *Python 3*.
v1.0.6 - Universal wheel
------------------------
* Make sure to build a universal wheel for all python versions.
v1.0.5 - Fixes for dragging on OSX
----------------------------------
* Corrected dragging on *OSX*.
* Added scroll speed constant for *OSX* to correct slow scroll speed.
v1.0.4 - Fixes for clicking and scrolling on Windows
----------------------------------------------------
* Corrected name of mouse input field when sending click and scroll events.
v1.0.3 - Fixes for Python 3 on Windows
--------------------------------------
* Corrected use of ``ctypes`` on Windows.
v1.0.2 - Fixes for thread identifiers
-------------------------------------
* Use thread identifiers to identify threads, not Thread instances.
v1.0.1 - Fixes for Python 3
---------------------------
* Corrected bugs which prevented the library from being used on *Python 3*.
v1.0 - Stable Release
---------------------
* Changed license to *LGPL*.
* Corrected minor bugs and inconsistencies.
* Corrected and extended documentation.
v0.6 - Keyboard Monitor
-----------------------
* Added support for monitoring the keyboard.
* Corrected wheel packaging.
* Corrected deadlock when stopping a listener in some cases on *X*.
* Corrected key code constants on *Mac OSX*.
* Do not intercept events on *Mac OSX*.
v0.5.1 - Do not die on dead keys
--------------------------------
* Corrected handling of dead keys.
* Corrected documentation.
v0.5 - Keyboard Modifiers
-------------------------
* Added support for modifiers.
v0.4 - Keyboard Controller
--------------------------
* Added keyboard controller.
v0.3 - Cleanup
------------------------------------------------------------
* Moved ``pynput.mouse.Controller.Button`` to top-level.
v0.2 - Initial Release
----------------------
* Support for controlling the mouse on *Linux*, *Mac OSX* and *Windows*.
* Support for monitoring the mouse on *Linux*, *Mac OSX* and *Windows*.

45
pype/vendor/pynput-1.4.dist-info/RECORD vendored Normal file
View file

@ -0,0 +1,45 @@
pynput/__init__.py,sha256=EJgBRe_1_qHzZ1gSkLmCLKC2aByszo4TpCkV0bNBb8o,846
pynput/_info.py,sha256=Qzy8h5tmZvp5hXiUGyJF2wFi2PnsfncKrIznGRUnqAw,772
pynput/_util/__init__.py,sha256=PajyeFBKHIBWAsEITpH-50IeCkdT0RbDwDSpT7fnkQc,8947
pynput/_util/darwin.py,sha256=YMiCP5Jdo3U8FxgJtwCOzodd0XF2buFqEj5PrQsffz8,8438
pynput/_util/win32.py,sha256=W4nH2A9VoWEeHVzV-W-tKfVKYX7Y8LTD2Ljn3ZbUGvk,19385
pynput/_util/win32_vks.py,sha256=YIp3xaZOVYqyNw2dwV-laKyYBw4_Kj-qsyVqbVW1m4k,2894
pynput/_util/xorg.py,sha256=NSoP0mL0v87VjFXybZpp6b_LtevlKuLFfhyvQVP5370,14703
pynput/_util/xorg_keysyms.py,sha256=xWqfQQva2R78FrQUQWzfUGsJcNeMe7U6y4KFyLu0XqI,69338
pynput/keyboard/__init__.py,sha256=r2pwWTmqVrskvGnAnXDQw3_PH07EKq6dWkb46wn6s00,1922
pynput/keyboard/_base.py,sha256=XWKIv_mIs1zoGjZmaPJo2bsjYlyZt3UUXY11hakY4Xg,20417
pynput/keyboard/_darwin.py,sha256=vqu6aiEcLuXBjKhtsjsZIy9INrGRnTkynpmAN4X3HlQ,8093
pynput/keyboard/_win32.py,sha256=_OFN5HyqDDvuSyZ5Nb2GUWXpqkw-oZK1G9GJ5XUgDns,8733
pynput/keyboard/_xorg.py,sha256=wc9BX-hYXCb0HIfLvMgugv6-PGap66072BGQJyu2knU,20379
pynput/mouse/__init__.py,sha256=1GoOHfyHRd3O9AnscLS7wKVP-RcAinuKDiqUi-T82a8,1822
pynput/mouse/_base.py,sha256=2ZIK_mBbUIA1JUsPs2ZIFarrpExDAMwIlaq5Jr-6ouM,8240
pynput/mouse/_darwin.py,sha256=5hMK4UhldxfFS9uTx5mn_jud-tgxcFbgJVADzuKGRfY,6929
pynput/mouse/_win32.py,sha256=m3vXMo2cUteF4AQFInIQ82_-NDd-LwYW719oQq4IOjU,5802
pynput/mouse/_xorg.py,sha256=ml5HgCTZAmAWHzd93qvOMtvwf1E_-bY4iIgkF6Qp3kU,5069
pynput-1.4.dist-info/DESCRIPTION.rst,sha256=lwkCa212Gyw15jTNx0qejZaZo6Ijp7bqYaDrxxMtgkc,13177
pynput-1.4.dist-info/METADATA,sha256=fvh0F8de_SWIE05HJoPJIeA5FQXVgedt6D0a5ydEfrY,14321
pynput-1.4.dist-info/RECORD,,
pynput-1.4.dist-info/WHEEL,sha256=kdsN-5OJAZIiHN-iO4Rhl82KyS0bDWf4uBwMbkNafr8,110
pynput-1.4.dist-info/metadata.json,sha256=FKkgyQGstvv12SPB1PsI2ugZ61QkCX5YTzZdEXI7C3o,1390
pynput-1.4.dist-info/pbr.json,sha256=CAnWerrCQ6A-ekJTVVKkD9J-ia4q-xoZzQWKOlPcseQ,47
pynput-1.4.dist-info/top_level.txt,sha256=DpJjYf-VkYaa_COk_yUczD0pHqsLndB9SjmwcQGkXJQ,7
pynput-1.4.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
pynput-1.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
pynput/keyboard/_darwin.pyc,,
pynput/keyboard/_xorg.pyc,,
pynput/_util/xorg_keysyms.pyc,,
pynput/keyboard/_win32.pyc,,
pynput/_util/xorg.pyc,,
pynput/mouse/_xorg.pyc,,
pynput/_info.pyc,,
pynput/mouse/__init__.pyc,,
pynput/_util/__init__.pyc,,
pynput/keyboard/__init__.pyc,,
pynput/mouse/_darwin.pyc,,
pynput/_util/darwin.pyc,,
pynput/mouse/_win32.pyc,,
pynput/mouse/_base.pyc,,
pynput/_util/win32.pyc,,
pynput/_util/win32_vks.pyc,,
pynput/__init__.pyc,,
pynput/keyboard/_base.pyc,,

View file

@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.30.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View file

@ -0,0 +1 @@
{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows :: Windows NT/2000", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Monitoring"], "description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "moses.palmer@gmail.com", "name": "Moses Palm\u00e9r", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/moses-palmer/pynput"}}}, "extras": [], "generator": "bdist_wheel (0.30.0)", "keywords": ["control", "mouse", "mouse", "input", "control", "keyboard", "keyboard", "input"], "license": "LGPLv3", "metadata_version": "2.0", "name": "pynput", "run_requires": [{"requires": ["six"]}, {"environment": "\"linux\" in sys_platform", "requires": ["python-xlib (>=0.17)"]}, {"environment": "python_version == \"2.7\"", "requires": ["enum34"]}, {"environment": "sys_platform == \"darwin\"", "requires": ["pyobjc-framework-Quartz (>=3.0)"]}], "summary": "Monitor and control user input devices", "version": "1.4"}

View file

@ -0,0 +1 @@
{"is_release": false, "git_version": "2d6ab69"}

View file

@ -0,0 +1 @@
pynput

View file

@ -0,0 +1 @@

24
pype/vendor/pynput/__init__.py vendored Normal file
View file

@ -0,0 +1,24 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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 main *pynput* module.
This module imports ``keyboard`` and ``mouse``.
"""
from . import keyboard
from . import mouse

19
pype/vendor/pynput/_info.py vendored Normal file
View file

@ -0,0 +1,19 @@
# coding=utf-8
# pystray
# Copyright (C) 2015-2017 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/>.
__author__ = u'Moses Palmér'
__version__ = (1, 4)

290
pype/vendor/pynput/_util/__init__.py vendored Normal file
View file

@ -0,0 +1,290 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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/>.
"""
General utility functions and classes.
"""
# pylint: disable=R0903
# We implement minimal mixins
# pylint: disable=W0212
# We implement an internal API
import contextlib
import functools
import sys
import threading
import six
from six.moves import queue
class AbstractListener(threading.Thread):
"""A class implementing the basic behaviour for event listeners.
Instances of this class can be used as context managers. This is equivalent
to the following code::
listener.start()
listener.wait()
try:
with_statements()
finally:
listener.stop()
:param bool suppress: Whether to suppress events. Setting this to ``True``
will prevent the input events from being passed to the rest of the
system.
:param kwargs: A mapping from callback attribute to callback handler. All
handlers will be wrapped in a function reading the return value of the
callback, and if it ``is False``, raising :class:`StopException`.
Any callback that is falsy will be ignored.
"""
class StopException(Exception):
"""If an event listener callback raises this exception, the current
listener is stopped.
"""
pass
#: Exceptions that are handled outside of the emitter and should thus not be
#: passed through the queue
_HANDLED_EXCEPTIONS = tuple()
def __init__(self, suppress=False, **kwargs):
super(AbstractListener, self).__init__()
def wrapper(f):
def inner(*args):
if f(*args) is False:
raise self.StopException()
return inner
self._suppress = suppress
self._running = False
self._thread = threading.current_thread()
self._condition = threading.Condition()
self._ready = False
# Allow multiple calls to stop
self._queue = queue.Queue(10)
self.daemon = True
for name, callback in kwargs.items():
setattr(self, name, wrapper(callback or (lambda *a: None)))
@property
def suppress(self):
"""Whether to suppress events.
"""
return self._suppress
@property
def running(self):
"""Whether the listener is currently running.
"""
return self._running
def stop(self):
"""Stops listening for events.
When this method returns, no more events will be delivered.
"""
if self._running:
self._running = False
self._queue.put(None)
self._stop_platform()
def __enter__(self):
self.start()
self.wait()
return self
def __exit__(self, exc_type, value, traceback):
self.stop()
def wait(self):
"""Waits for this listener to become ready.
"""
self._condition.acquire()
while not self._ready:
self._condition.wait()
self._condition.release()
def run(self):
"""The thread runner method.
"""
self._running = True
self._thread = threading.current_thread()
self._run()
# Make sure that the queue contains something
self._queue.put(None)
@classmethod
def _emitter(cls, f):
"""A decorator to mark a method as the one emitting the callbacks.
This decorator will wrap the method and catch exception. If a
:class:`StopException` is caught, the listener will be stopped
gracefully. If any other exception is caught, it will be propagated to
the thread calling :meth:`join` and reraised there.
"""
@functools.wraps(f)
def inner(self, *args, **kwargs):
# pylint: disable=W0702; we want to catch all exception
try:
return f(self, *args, **kwargs)
except Exception as e:
if not isinstance(e, self._HANDLED_EXCEPTIONS):
self._queue.put(
None if isinstance(e, cls.StopException)
else sys.exc_info())
self.stop()
raise
# pylint: enable=W0702
return inner
def _mark_ready(self):
"""Marks this listener as ready to receive events.
This method must be called from :meth:`_run`. :meth:`wait` will block
until this method is called.
"""
self._condition.acquire()
self._ready = True
self._condition.notify()
self._condition.release()
def _run(self):
"""The implementation of the :meth:`run` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _stop_platform(self):
"""The implementation of the :meth:`stop` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def join(self, *args):
super(AbstractListener, self).join(*args)
# Reraise any exceptions
try:
exc_type, exc_value, exc_traceback = self._queue.get()
except TypeError:
return
six.reraise(exc_type, exc_value, exc_traceback)
class NotifierMixin(object):
"""A mixin for notifiers of fake events.
This mixin can be used for controllers on platforms where sending fake
events does not cause a listener to receive a notification.
"""
def _emit(self, action, *args):
"""Sends a notification to all registered listeners.
This method will ensure that listeners that raise
:class:`StopException` are stopped.
:param str action: The name of the notification.
:param args: The arguments to pass.
"""
stopped = []
for listener in self._listeners():
try:
getattr(listener, action)(*args)
except listener.StopException:
stopped.append(listener)
for listener in stopped:
listener.stop()
@classmethod
def _receiver(cls, listener_class):
"""A decorator to make a class able to receive fake events from a
controller.
This decorator will add the method ``_receive`` to the decorated class.
This method is a context manager which ensures that all calls to
:meth:`_emit` will invoke the named method in the listener instance
while the block is active.
"""
@contextlib.contextmanager
def receive(self):
"""Executes a code block with this listener instance registered as
a receiver of fake input events.
"""
self._controller_class._add_listener(self)
try:
yield
finally:
self._controller_class._remove_listener(self)
listener_class._receive = receive
listener_class._controller_class = cls
# Make sure this class has the necessary attributes
if not hasattr(cls, '_listener_cache'):
cls._listener_cache = set()
cls._listener_lock = threading.Lock()
return listener_class
@classmethod
def _listeners(cls):
"""Iterates over the set of running listeners.
This method will quit without acquiring the lock if the set is empty,
so there is potential for race conditions. This is an optimisation,
since :class:`Controller` will need to call this method for every
control event.
"""
if not cls._listener_cache:
return
with cls._listener_lock:
for listener in cls._listener_cache:
yield listener
@classmethod
def _add_listener(cls, listener):
"""Adds a listener to the set of running listeners.
:param listener: The listener for fake events.
"""
with cls._listener_lock:
cls._listener_cache.add(listener)
@classmethod
def _remove_listener(cls, listener):
"""Removes this listener from the set of running listeners.
:param listener: The listener for fake events.
"""
with cls._listener_lock:
cls._listener_cache.remove(listener)

273
pype/vendor/pynput/_util/darwin.py vendored Normal file
View file

@ -0,0 +1,273 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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/>.
"""
Utility functions and classes for the *Darwin* backend.
"""
# pylint: disable=C0103
# pylint: disable=R0903
# This module contains wrapper classes
import contextlib
import ctypes
import ctypes.util
import six
import objc
import CoreFoundation
import Quartz
from . import AbstractListener
#: The objc module as a library handle
OBJC = ctypes.PyDLL(objc._objc.__file__)
OBJC.PyObjCObject_New.restype = ctypes.py_object
OBJC.PyObjCObject_New.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
def _wrap_value(value):
"""Converts a pointer to a *Python objc* value.
:param value: The pointer to convert.
:return: a wrapped value
"""
return OBJC.PyObjCObject_New(value, 0, 1)
@contextlib.contextmanager
def _wrapped(value):
"""A context manager that converts a raw pointer to a *Python objc* value.
When the block is exited, the value is released.
:param value: The raw value to wrap.
"""
wrapped_value = _wrap_value(value)
try:
yield value
finally:
CoreFoundation.CFRelease(wrapped_value)
class CarbonExtra(object):
"""A class exposing some missing functionality from *Carbon* as class
attributes.
"""
_Carbon = ctypes.cdll.LoadLibrary(ctypes.util.find_library('Carbon'))
_Carbon.TISCopyCurrentKeyboardInputSource.argtypes = []
_Carbon.TISCopyCurrentKeyboardInputSource.restype = ctypes.c_void_p
_Carbon.TISCopyCurrentASCIICapableKeyboardLayoutInputSource.argtypes = []
_Carbon.TISCopyCurrentASCIICapableKeyboardLayoutInputSource.restype = \
ctypes.c_void_p
_Carbon.TISGetInputSourceProperty.argtypes = [
ctypes.c_void_p, ctypes.c_void_p]
_Carbon.TISGetInputSourceProperty.restype = ctypes.c_void_p
_Carbon.LMGetKbdType.argtypes = []
_Carbon.LMGetKbdType.restype = ctypes.c_uint32
_Carbon.UCKeyTranslate.argtypes = [
ctypes.c_void_p,
ctypes.c_uint16,
ctypes.c_uint16,
ctypes.c_uint32,
ctypes.c_uint32,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint32),
ctypes.c_uint8,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_uint16 * 4]
_Carbon.UCKeyTranslate.restype = ctypes.c_uint32
TISCopyCurrentKeyboardInputSource = \
_Carbon.TISCopyCurrentKeyboardInputSource
TISCopyCurrentASCIICapableKeyboardLayoutInputSource = \
_Carbon.TISCopyCurrentASCIICapableKeyboardLayoutInputSource
kTISPropertyUnicodeKeyLayoutData = ctypes.c_void_p.in_dll(
_Carbon, 'kTISPropertyUnicodeKeyLayoutData')
TISGetInputSourceProperty = \
_Carbon.TISGetInputSourceProperty
LMGetKbdType = \
_Carbon.LMGetKbdType
kUCKeyActionDisplay = 3
kUCKeyTranslateNoDeadKeysBit = 0
UCKeyTranslate = \
_Carbon.UCKeyTranslate
@contextlib.contextmanager
def keycode_context():
"""Returns an opaque value representing a context for translating keycodes
to strings.
"""
keyboard_type, layout_data = None, None
for source in [
CarbonExtra.TISCopyCurrentKeyboardInputSource,
CarbonExtra.TISCopyCurrentASCIICapableKeyboardLayoutInputSource]:
with _wrapped(source()) as keyboard:
keyboard_type = CarbonExtra.LMGetKbdType()
layout = _wrap_value(CarbonExtra.TISGetInputSourceProperty(
keyboard,
CarbonExtra.kTISPropertyUnicodeKeyLayoutData))
layout_data = layout.bytes().tobytes() if layout else None
if keyboard is not None and layout_data is not None:
break
yield (keyboard_type, layout_data)
def keycode_to_string(context, keycode, modifier_state=0):
"""Converts a keycode to a string.
"""
LENGTH = 4
keyboard_type, layout_data = context
dead_key_state = ctypes.c_uint32()
length = ctypes.c_uint8()
unicode_string = (ctypes.c_uint16 * LENGTH)()
CarbonExtra.UCKeyTranslate(
layout_data,
keycode,
CarbonExtra.kUCKeyActionDisplay,
modifier_state,
keyboard_type,
CarbonExtra.kUCKeyTranslateNoDeadKeysBit,
ctypes.byref(dead_key_state),
LENGTH,
ctypes.byref(length),
unicode_string)
return u''.join(
six.unichr(unicode_string[i])
for i in range(length.value))
def get_unicode_to_keycode_map():
"""Returns a mapping from unicode strings to virtual key codes.
:return: a dict mapping key codes to strings
"""
with keycode_context() as context:
return {
keycode_to_string(context, keycode): keycode
for keycode in range(128)}
class ListenerMixin(object):
"""A mixin for *Quartz* event listeners.
Subclasses should set a value for :attr:`_EVENTS` and implement
:meth:`_handle`.
"""
#: The events that we listen to
_EVENTS = tuple()
def _run(self):
self._loop = None
try:
tap = self._create_event_tap()
if tap is None:
self._mark_ready()
return
loop_source = Quartz.CFMachPortCreateRunLoopSource(
None, tap, 0)
self._loop = Quartz.CFRunLoopGetCurrent()
Quartz.CFRunLoopAddSource(
self._loop, loop_source, Quartz.kCFRunLoopDefaultMode)
Quartz.CGEventTapEnable(tap, True)
self._mark_ready()
# pylint: disable=W0702; we want to silence errors
try:
while self.running:
result = Quartz.CFRunLoopRunInMode(
Quartz.kCFRunLoopDefaultMode, 1, False)
try:
if result != Quartz.kCFRunLoopRunTimedOut:
break
except AttributeError:
# This happens during teardown of the virtual machine
break
except:
# This exception will have been passed to the main thread
pass
# pylint: enable=W0702
finally:
self._loop = None
def _stop_platform(self):
# The base class sets the running flag to False; this will cause the
# loop around run loop invocations to terminate and set this event
try:
if self._loop is not None:
Quartz.CFRunLoopStop(self._loop)
except AttributeError:
# The loop may not have been created
pass
def _create_event_tap(self):
"""Creates the event tap used by the listener.
:return: an event tap
"""
return Quartz.CGEventTapCreate(
Quartz.kCGSessionEventTap,
Quartz.kCGHeadInsertEventTap,
Quartz.kCGEventTapOptionListenOnly if (True
and not self.suppress
and self._intercept is None)
else Quartz.kCGEventTapOptionDefault,
self._EVENTS,
self._handler,
None)
@AbstractListener._emitter
def _handler(self, proxy, event_type, event, refcon):
"""The callback registered with *Mac OSX* for mouse events.
This method will call the callbacks registered on initialisation.
"""
self._handle(proxy, event_type, event, refcon)
if self._intercept is not None:
return self._intercept(event_type, event)
elif self.suppress:
return None
def _handle(self, proxy, event_type, event, refcon):
"""The device specific callback handler.
This method calls the appropriate callback registered when this
listener was created based on the event.
"""
raise NotImplementedError()

641
pype/vendor/pynput/_util/win32.py vendored Normal file
View file

@ -0,0 +1,641 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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/>.
"""
Utility functions and classes for the *win32* backend.
"""
# pylint: disable=C0103
# We want to make it obvious how structs are related
# pylint: disable=R0903
# This module contains a number of structs
import contextlib
import ctypes
import threading
from ctypes import (
windll,
wintypes)
import six
from . import AbstractListener
# LPDWORD is not in ctypes.wintypes on Python 2
if not hasattr(wintypes, 'LPDWORD'):
wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
class MOUSEINPUT(ctypes.Structure):
"""Contains information about a simulated mouse event.
"""
MOVE = 0x0001
LEFTDOWN = 0x0002
LEFTUP = 0x0004
RIGHTDOWN = 0x0008
RIGHTUP = 0x0010
MIDDLEDOWN = 0x0020
MIDDLEUP = 0x0040
XDOWN = 0x0080
XUP = 0x0100
WHEEL = 0x0800
HWHEEL = 0x1000
ABSOLUTE = 0x8000
XBUTTON1 = 0x0001
XBUTTON2 = 0x0002
_fields_ = [
('dx', wintypes.LONG),
('dy', wintypes.LONG),
('mouseData', wintypes.DWORD),
('dwFlags', wintypes.DWORD),
('time', wintypes.DWORD),
('dwExtraInfo', ctypes.c_void_p)]
class KEYBDINPUT(ctypes.Structure):
"""Contains information about a simulated keyboard event.
"""
EXTENDEDKEY = 0x0001
KEYUP = 0x0002
SCANCODE = 0x0008
UNICODE = 0x0004
_fields_ = [
('wVk', wintypes.WORD),
('wScan', wintypes.WORD),
('dwFlags', wintypes.DWORD),
('time', wintypes.DWORD),
('dwExtraInfo', ctypes.c_void_p)]
class HARDWAREINPUT(ctypes.Structure):
"""Contains information about a simulated message generated by an input
device other than a keyboard or mouse.
"""
_fields_ = [
('uMsg', wintypes.DWORD),
('wParamL', wintypes.WORD),
('wParamH', wintypes.WORD)]
class INPUT_union(ctypes.Union):
"""Represents the union of input types in :class:`INPUT`.
"""
_fields_ = [
('mi', MOUSEINPUT),
('ki', KEYBDINPUT),
('hi', HARDWAREINPUT)]
class INPUT(ctypes.Structure):
"""Used by :attr:`SendInput` to store information for synthesizing input
events such as keystrokes, mouse movement, and mouse clicks.
"""
MOUSE = 0
KEYBOARD = 1
HARDWARE = 2
_fields_ = [
('type', wintypes.DWORD),
('value', INPUT_union)]
LPINPUT = ctypes.POINTER(INPUT)
VkKeyScan = windll.user32.VkKeyScanW
VkKeyScan.argtypes = (
wintypes.WCHAR,)
SendInput = windll.user32.SendInput
SendInput.argtypes = (
wintypes.UINT,
LPINPUT,
ctypes.c_int)
GetCurrentThreadId = windll.kernel32.GetCurrentThreadId
GetCurrentThreadId.restype = wintypes.DWORD
class MessageLoop(object):
"""A class representing a message loop.
"""
#: The message that signals this loop to terminate
WM_STOP = 0x0401
_LPMSG = ctypes.POINTER(wintypes.MSG)
_GetMessage = windll.user32.GetMessageW
_GetMessage.argtypes = (
_LPMSG,
wintypes.HWND,
wintypes.UINT,
wintypes.UINT)
_PeekMessage = windll.user32.PeekMessageW
_PeekMessage.argtypes = (
_LPMSG,
wintypes.HWND,
wintypes.UINT,
wintypes.UINT,
wintypes.UINT)
_PostThreadMessage = windll.user32.PostThreadMessageW
_PostThreadMessage.argtypes = (
wintypes.DWORD,
wintypes.UINT,
wintypes.WPARAM,
wintypes.LPARAM)
PM_NOREMOVE = 0
def __init__(self):
self._threadid = None
self._event = threading.Event()
self.thread = None
def __iter__(self):
"""Initialises the message loop and yields all messages until
:meth:`stop` is called.
:raises AssertionError: if :meth:`start` has not been called
"""
assert self._threadid is not None
try:
# Pump messages until WM_STOP
while True:
msg = wintypes.MSG()
lpmsg = ctypes.byref(msg)
r = self._GetMessage(lpmsg, None, 0, 0)
if r <= 0 or msg.message == self.WM_STOP:
break
else:
yield msg
finally:
self._threadid = None
self.thread = None
def start(self):
"""Starts the message loop.
This method must be called before iterating over messages, and it must
be called from the same thread.
"""
self._threadid = GetCurrentThreadId()
self.thread = threading.current_thread()
# Create the message loop
msg = wintypes.MSG()
lpmsg = ctypes.byref(msg)
self._PeekMessage(lpmsg, None, 0x0400, 0x0400, self.PM_NOREMOVE)
# Set the event to signal to other threads that the loop is created
self._event.set()
def stop(self):
"""Stops the message loop.
"""
self._event.wait()
if self._threadid:
self.post(self.WM_STOP, 0, 0)
def post(self, msg, wparam, lparam):
"""Posts a message to this message loop.
:param ctypes.wintypes.UINT msg: The message.
:param ctypes.wintypes.WPARAM wparam: The value of ``wParam``.
:param ctypes.wintypes.LPARAM lparam: The value of ``lParam``.
"""
self._PostThreadMessage(self._threadid, msg, wparam, lparam)
class SystemHook(object):
"""A class to handle Windows hooks.
"""
#: The hook action value for actions we should check
HC_ACTION = 0
_HOOKPROC = ctypes.WINFUNCTYPE(
wintypes.LPARAM,
ctypes.c_int32, wintypes.WPARAM, wintypes.LPARAM)
_SetWindowsHookEx = windll.user32.SetWindowsHookExW
_SetWindowsHookEx.argtypes = (
ctypes.c_int,
_HOOKPROC,
wintypes.HINSTANCE,
wintypes.DWORD)
_UnhookWindowsHookEx = windll.user32.UnhookWindowsHookEx
_UnhookWindowsHookEx.argtypes = (
wintypes.HHOOK,)
_CallNextHookEx = windll.user32.CallNextHookEx
_CallNextHookEx.argtypes = (
wintypes.HHOOK,
ctypes.c_int,
wintypes.WPARAM,
wintypes.LPARAM)
#: The registered hook procedures
_HOOKS = {}
class SuppressException(Exception):
"""An exception raised by a hook callback to suppress further
propagation of events.
"""
pass
def __init__(self, hook_id, on_hook=lambda code, msg, lpdata: None):
self.hook_id = hook_id
self.on_hook = on_hook
self._hook = None
def __enter__(self):
key = threading.current_thread().ident
assert key not in self._HOOKS
# Add ourself to lookup table and install the hook
self._HOOKS[key] = self
self._hook = self._SetWindowsHookEx(
self.hook_id,
self._handler,
None,
0)
return self
def __exit__(self, exc_type, value, traceback):
key = threading.current_thread().ident
assert key in self._HOOKS
if self._hook is not None:
# Uninstall the hook and remove ourself from lookup table
self._UnhookWindowsHookEx(self._hook)
del self._HOOKS[key]
@staticmethod
@_HOOKPROC
def _handler(code, msg, lpdata):
key = threading.current_thread().ident
self = SystemHook._HOOKS.get(key, None)
if self:
# pylint: disable=W0702; we want to silence errors
try:
self.on_hook(code, msg, lpdata)
except self.SuppressException:
# Return non-zero to stop event propagation
return 1
except:
# Ignore any errors
pass
# pylint: enable=W0702
return SystemHook._CallNextHookEx(0, code, msg, lpdata)
class ListenerMixin(object):
"""A mixin for *win32* event listeners.
Subclasses should set a value for :attr:`_EVENTS` and implement
:meth:`_handle`.
Subclasses must also be decorated with a decorator compatible with
:meth:`pynput._util.NotifierMixin._receiver` or implement the method
``_receive()``.
"""
#: The Windows hook ID for the events to capture
_EVENTS = None
#: The window message used to signal that an even should be handled
_WM_PROCESS = 0x410
def suppress_event(self):
"""Causes the currently filtered event to be suppressed.
This has a system wide effect and will generally result in no
applications receiving the event.
This method will raise an undefined exception.
"""
raise SystemHook.SuppressException()
def _run(self):
self._message_loop = MessageLoop()
with self._receive():
self._mark_ready()
self._message_loop.start()
# pylint: disable=W0702; we want to silence errors
try:
with SystemHook(self._EVENTS, self._handler):
# Just pump messages
for msg in self._message_loop:
if not self.running:
break
if msg.message == self._WM_PROCESS:
self._process(msg.wParam, msg.lParam)
except:
# This exception will have been passed to the main thread
pass
# pylint: enable=W0702
def _stop_platform(self):
try:
self._message_loop.stop()
except AttributeError:
# The loop may not have been created
pass
@AbstractListener._emitter
def _handler(self, code, msg, lpdata):
"""The callback registered with *Windows* for events.
This method will post the message :attr:`_WM_HANDLE` to the message loop
started with this listener using :meth:`MessageLoop.post`. The
parameters are retrieved with a call to :meth:`_handle`.
"""
try:
converted = self._convert(code, msg, lpdata)
if converted is not None:
self._message_loop.post(self._WM_PROCESS, *converted)
except NotImplementedError:
self._handle(code, msg, lpdata)
if self.suppress:
self.suppress_event()
def _convert(self, code, msg, lpdata):
"""The device specific callback handler.
This method converts a low-level message and data to a
``WPARAM`` / ``LPARAM`` pair.
"""
raise NotImplementedError()
def _process(self, wparam, lparam):
"""The device specific callback handler.
This method performs the actual dispatching of events.
"""
raise NotImplementedError()
def _handle(self, code, msg, lpdata):
"""The device specific callback handler.
This method calls the appropriate callback registered when this
listener was created based on the event.
This method is only called if :meth:`_convert` is not implemented.
"""
raise NotImplementedError()
class KeyTranslator(object):
"""A class to translate virtual key codes to characters.
"""
_AttachThreadInput = ctypes.windll.user32.AttachThreadInput
_AttachThreadInput.argtypes = (
wintypes.DWORD,
wintypes.DWORD,
wintypes.BOOL)
_GetForegroundWindow = ctypes.windll.user32.GetForegroundWindow
_GetKeyboardLayout = ctypes.windll.user32.GetKeyboardLayout
_GetKeyboardLayout.argtypes = (
wintypes.DWORD,)
_GetKeyboardState = ctypes.windll.user32.GetKeyboardState
_GetKeyboardState.argtypes = (
ctypes.c_voidp,)
_GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId
_GetWindowThreadProcessId.argtypes = (
wintypes.HWND,
wintypes.LPDWORD)
_MapVirtualKeyEx = ctypes.windll.user32.MapVirtualKeyExW
_MapVirtualKeyEx.argtypes = (
wintypes.UINT,
wintypes.UINT,
wintypes.HKL)
_ToUnicodeEx = ctypes.windll.user32.ToUnicodeEx
_ToUnicodeEx.argtypes = (
wintypes.UINT,
wintypes.UINT,
ctypes.c_voidp,
ctypes.c_voidp,
ctypes.c_int,
wintypes.UINT,
wintypes.HKL)
_MAPVK_VK_TO_VSC = 0
_MAPVK_VK_TO_CHAR = 2
def __init__(self):
self.__state = (ctypes.c_ubyte * 255)()
self._cache = {}
self._reinject_arguments = None
def __call__(self, vk, is_press):
"""Converts a virtual key code to a string.
:param int vk: The virtual key code.
:param bool is_press: Whether this is a press. Because the *win32*
functions used to translate the key modifies internal kernel state,
some cleanup must be performed for key presses.
:return: parameters suitable for the :class:`pynput.keyboard.KeyCode`
constructor
:raises OSError: if a call to any *win32* function fails
"""
# Get the keyboard state and layout
state, layout = self._get_state_and_layout()
# Get the scan code for the virtual key
scan = self._to_scan(vk, layout)
# Try to reuse the previous key in the cache
try:
if is_press:
return self._cache[vk]
else:
return self._cache.pop(vk)
except KeyError:
pass
# Get a string representation of the key
char, is_dead = self._to_char(vk, layout)
modified_char = self._to_char_with_modifiers(vk, layout, scan, state)
# Clear the keyboard state if the key was a dead key
if is_dead:
self._reset_state(vk, layout, scan)
# If the previous key handled was a dead key, we reinject it
if self._reinject_arguments:
self._reinject(*self._reinject_arguments)
self._reinject_arguments = None
# If the current key is a dead key, we store the current state to be
# able to reinject later
elif is_dead:
self._reinject_arguments = (
vk,
layout,
scan,
(ctypes.c_ubyte * 255)(*state))
# Otherwise we just clear any previous dead key state
else:
self._reinject_arguments = None
# Update the cache
self._cache[vk] = {
'char': modified_char or char,
'is_dead': is_dead,
'vk': vk}
return self._cache[vk]
def _get_state_and_layout(self):
"""Returns the keyboard state and layout.
The state is read from the currently active window if possible. It is
kept in a cache, so any call to this method will invalidate return
values from previous invocations.
:return: the tuple ``(state, layout)``
"""
# Get the state of the keyboard attached to the active window
with self._thread_input() as active_thread:
if not self._GetKeyboardState(ctypes.byref(self.__state)):
raise OSError(
'GetKeyboardState failed: %d',
ctypes.wintypes.get_last_error())
# Get the keyboard layout for the thread for which we retrieved the
# state
layout = self._GetKeyboardLayout(active_thread)
return (self.__state, layout)
def _to_scan(self, vk, layout):
"""Retrieves the scan code for a virtual key code.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:return: the scan code
"""
return self._MapVirtualKeyEx(
vk, self._MAPVK_VK_TO_VSC, layout)
def _to_char(self, vk, layout):
"""Converts a virtual key by simply mapping it through the keyboard
layout.
This method is stateless, so any active shift state or dead keys are
ignored.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:return: the string representation of the key, or ``None``, and whether
was dead as the tuple ``(char, is_dead)``
"""
# MapVirtualKeyEx will yield a string representation for dead keys
flags_and_codepoint = self._MapVirtualKeyEx(
vk, self._MAPVK_VK_TO_CHAR, layout)
if flags_and_codepoint:
return (
six.unichr(flags_and_codepoint & 0xFFFF),
bool(flags_and_codepoint & (1 << 31)))
else:
return (None, None)
def _to_char_with_modifiers(self, vk, layout, scan, state):
"""Converts a virtual key by mapping it through the keyboard layout and
internal kernel keyboard state.
This method is stateful, so any active shift state and dead keys are
applied. Currently active dead keys will be removed from the internal
kernel keyboard state.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:param int scan: The scan code of the key.
:param state: The keyboard state.
:return: the string representation of the key, or ``None``
"""
# This will apply any dead keys and modify the internal kernel keyboard
# state
out = (ctypes.wintypes.WCHAR * 5)()
count = self._ToUnicodeEx(
vk, scan, ctypes.byref(state), ctypes.byref(out),
len(out), 0, layout)
return out[0] if count > 0 else None
def _reset_state(self, vk, layout, scan):
"""Clears the internal kernel keyboard state.
This method will remove all dead keys from the internal state.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:param int scan: The scan code of the key.
"""
state = (ctypes.c_byte * 255)()
out = (ctypes.wintypes.WCHAR * 5)()
while self._ToUnicodeEx(
vk, scan, ctypes.byref(state), ctypes.byref(out),
len(out), 0, layout) < 0:
pass
def _reinject(self, vk, layout, scan, state):
"""Reinjects the previous dead key.
This must be called if ``ToUnicodeEx`` has been called, and the
previous key was a dead one.
:param int vk: The virtual key code.
:param layout: The keyboard layout.
:param int scan: The scan code of the key.
:param state: The keyboard state.
"""
out = (ctypes.wintypes.WCHAR * 5)()
self._ToUnicodeEx(
vk, scan, ctypes.byref(state), ctypes.byref(out),
len(out), 0, layout)
@contextlib.contextmanager
def _thread_input(self):
"""Yields the current thread ID.
"""
yield GetCurrentThreadId()

179
pype/vendor/pynput/_util/win32_vks.py vendored Normal file
View file

@ -0,0 +1,179 @@
# coding: utf-8
# pynput
# Copyright (C) 2015-2017 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/>.
# pylint: disable=C0111,C0302
LBUTTON = 1
RBUTTON = 2
CANCEL = 3
MBUTTON = 4
XBUTTON1 = 5
XBUTTON2 = 6
BACK = 8
TAB = 9
CLEAR = 12
RETURN = 13
SHIFT = 16
CONTROL = 17
MENU = 18
PAUSE = 19
CAPITAL = 20
KANA = 21
HANGEUL = 21
HANGUL = 21
JUNJA = 23
FINAL = 24
HANJA = 25
KANJI = 25
ESCAPE = 27
CONVERT = 28
NONCONVERT = 29
ACCEPT = 30
MODECHANGE = 31
SPACE = 32
PRIOR = 33
NEXT = 34
END = 35
HOME = 36
LEFT = 37
UP = 38
RIGHT = 39
DOWN = 40
SELECT = 41
PRINT = 42
EXECUTE = 43
SNAPSHOT = 44
INSERT = 45
DELETE = 46
HELP = 47
LWIN = 91
RWIN = 92
APPS = 93
SLEEP = 95
NUMPAD0 = 96
NUMPAD1 = 97
NUMPAD2 = 98
NUMPAD3 = 99
NUMPAD4 = 100
NUMPAD5 = 101
NUMPAD6 = 102
NUMPAD7 = 103
NUMPAD8 = 104
NUMPAD9 = 105
MULTIPLY = 106
ADD = 107
SEPARATOR = 108
SUBTRACT = 109
DECIMAL = 110
DIVIDE = 111
F1 = 112
F2 = 113
F3 = 114
F4 = 115
F5 = 116
F6 = 117
F7 = 118
F8 = 119
F9 = 120
F10 = 121
F11 = 122
F12 = 123
F13 = 124
F14 = 125
F15 = 126
F16 = 127
F17 = 128
F18 = 129
F19 = 130
F20 = 131
F21 = 132
F22 = 133
F23 = 134
F24 = 135
NUMLOCK = 144
SCROLL = 145
OEM_NEC_EQUAL = 146
OEM_FJ_JISHO = 146
OEM_FJ_MASSHOU = 147
OEM_FJ_TOUROKU = 148
OEM_FJ_LOYA = 149
OEM_FJ_ROYA = 150
LSHIFT = 160
RSHIFT = 161
LCONTROL = 162
RCONTROL = 163
LMENU = 164
RMENU = 165
BROWSER_BACK = 166
BROWSER_FORWARD = 167
BROWSER_REFRESH = 168
BROWSER_STOP = 169
BROWSER_SEARCH = 170
BROWSER_FAVORITES = 171
BROWSER_HOME = 172
VOLUME_MUTE = 173
VOLUME_DOWN = 174
VOLUME_UP = 175
MEDIA_NEXT_TRACK = 176
MEDIA_PREV_TRACK = 177
MEDIA_STOP = 178
MEDIA_PLAY_PAUSE = 179
LAUNCH_MAIL = 180
LAUNCH_MEDIA_SELECT = 181
LAUNCH_APP1 = 182
LAUNCH_APP2 = 183
OEM_1 = 186
OEM_PLUS = 187
OEM_COMMA = 188
OEM_MINUS = 189
OEM_PERIOD = 190
OEM_2 = 191
OEM_3 = 192
OEM_4 = 219
OEM_5 = 220
OEM_6 = 221
OEM_7 = 222
OEM_8 = 223
OEM_AX = 225
OEM_102 = 226
ICO_HELP = 227
ICO_00 = 228
PROCESSKEY = 229
ICO_CLEAR = 230
PACKET = 231
OEM_RESET = 233
OEM_JUMP = 234
OEM_PA1 = 235
OEM_PA2 = 236
OEM_PA3 = 237
OEM_WSCTRL = 238
OEM_CUSEL = 239
OEM_ATTN = 240
OEM_FINISH = 241
OEM_COPY = 242
OEM_AUTO = 243
OEM_ENLW = 244
OEM_BACKTAB = 245
ATTN = 246
CRSEL = 247
EXSEL = 248
EREOF = 249
PLAY = 250
ZOOM = 251
NONAME = 252
PA1 = 253
OEM_CLEAR = 254

480
pype/vendor/pynput/_util/xorg.py vendored Normal file
View file

@ -0,0 +1,480 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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/>.
"""
Utility functions and classes for the *Xorg* backend.
"""
# pylint: disable=R0903
# We implement stubs
import contextlib
import functools
import itertools
import operator
import Xlib.display
import Xlib.threaded
import Xlib.XK
from . import AbstractListener
from .xorg_keysyms import SYMBOLS
# Create a display to verify that we have an X connection
def _check():
display = Xlib.display.Display()
display.close()
_check()
del _check
class X11Error(Exception):
"""An error that is thrown at the end of a code block managed by a
:func:`display_manager` if an *X11* error occurred.
"""
pass
@contextlib.contextmanager
def display_manager(display):
"""Traps *X* errors and raises an :class:``X11Error`` at the end if any
error occurred.
This handler also ensures that the :class:`Xlib.display.Display` being
managed is sync'd.
:param Xlib.display.Display display: The *X* display.
:return: the display
:rtype: Xlib.display.Display
"""
errors = []
def handler(*args):
"""The *Xlib* error handler.
"""
errors.append(args)
old_handler = display.set_error_handler(handler)
try:
yield display
display.sync()
finally:
display.set_error_handler(old_handler)
if errors:
raise X11Error(errors)
def _find_mask(display, symbol):
"""Returns the mode flags to use for a modifier symbol.
:param Xlib.display.Display display: The *X* display.
:param str symbol: The name of the symbol.
:return: the modifier mask
"""
# Get the key code for the symbol
modifier_keycode = display.keysym_to_keycode(
Xlib.XK.string_to_keysym(symbol))
for index, keycodes in enumerate(display.get_modifier_mapping()):
for keycode in keycodes:
if keycode == modifier_keycode:
return 1 << index
return 0
def alt_mask(display):
"""Returns the *alt* mask flags.
The first time this function is called for a display, the value is cached.
Subsequent calls will return the cached value.
:param Xlib.display.Display display: The *X* display.
:return: the modifier mask
"""
if not hasattr(display, '__alt_mask'):
display.__alt_mask = _find_mask(display, 'Alt_L')
return display.__alt_mask
def alt_gr_mask(display):
"""Returns the *alt* mask flags.
The first time this function is called for a display, the value is cached.
Subsequent calls will return the cached value.
:param Xlib.display.Display display: The *X* display.
:return: the modifier mask
"""
if not hasattr(display, '__altgr_mask'):
display.__altgr_mask = _find_mask(display, 'Mode_switch')
return display.__altgr_mask
def numlock_mask(display):
"""Returns the *numlock* mask flags.
The first time this function is called for a display, the value is cached.
Subsequent calls will return the cached value.
:param Xlib.display.Display display: The *X* display.
:return: the modifier mask
"""
if not hasattr(display, '__numlock_mask'):
display.__numlock_mask = _find_mask(display, 'Num_Lock')
return display.__numlock_mask
def keysym_is_latin_upper(keysym):
"""Determines whether a *keysym* is an upper case *latin* character.
This is true only if ``XK_A`` <= ``keysym`` <= ` XK_Z``.
:param in keysym: The *keysym* to check.
"""
return Xlib.XK.XK_A <= keysym <= Xlib.XK.XK_Z
def keysym_is_latin_lower(keysym):
"""Determines whether a *keysym* is a lower case *latin* character.
This is true only if ``XK_a`` <= ``keysym`` <= ` XK_z``.
:param in keysym: The *keysym* to check.
"""
return Xlib.XK.XK_a <= keysym <= Xlib.XK.XK_z
def keysym_group(ks1, ks2):
"""Generates a group from two *keysyms*.
The implementation of this function comes from:
Within each group, if the second element of the group is ``NoSymbol``,
then the group should be treated as if the second element were the same
as the first element, except when the first element is an alphabetic
*KeySym* ``K`` for which both lowercase and uppercase forms are
defined.
In that case, the group should be treated as if the first element were
the lowercase form of ``K`` and the second element were the uppercase
form of ``K``.
This function assumes that *alphabetic* means *latin*; this assumption
appears to be consistent with observations of the return values from
``XGetKeyboardMapping``.
:param ks1: The first *keysym*.
:param ks2: The second *keysym*.
:return: a tuple conforming to the description above
"""
if ks2 == Xlib.XK.NoSymbol:
if keysym_is_latin_upper(ks1):
return (Xlib.XK.XK_a + ks1 - Xlib.XK.XK_A, ks1)
elif keysym_is_latin_lower(ks1):
return (ks1, Xlib.XK.XK_A + ks1 - Xlib.XK.XK_a)
else:
return (ks1, ks1)
else:
return (ks1, ks2)
def keysym_normalize(keysym):
"""Normalises a list of *keysyms*.
The implementation of this function comes from:
If the list (ignoring trailing ``NoSymbol`` entries) is a single
*KeySym* ``K``, then the list is treated as if it were the list
``K NoSymbol K NoSymbol``.
If the list (ignoring trailing ``NoSymbol`` entries) is a pair of
*KeySyms* ``K1 K2``, then the list is treated as if it were the list
``K1 K2 K1 K2``.
If the list (ignoring trailing ``NoSymbol`` entries) is a triple of
*KeySyms* ``K1 K2 K3``, then the list is treated as if it were the list
``K1 K2 K3 NoSymbol``.
This function will also group the *keysyms* using :func:`keysym_group`.
:param keysyms: A list of keysyms.
:return: the tuple ``(group_1, group_2)`` or ``None``
"""
# Remove trailing NoSymbol
stripped = list(reversed(list(
itertools.dropwhile(
lambda n: n == Xlib.XK.NoSymbol,
reversed(keysym)))))
if not stripped:
return
elif len(stripped) == 1:
return (
keysym_group(stripped[0], Xlib.XK.NoSymbol),
keysym_group(stripped[0], Xlib.XK.NoSymbol))
elif len(stripped) == 2:
return (
keysym_group(stripped[0], stripped[1]),
keysym_group(stripped[0], stripped[1]))
elif len(stripped) == 3:
return (
keysym_group(stripped[0], stripped[1]),
keysym_group(stripped[2], Xlib.XK.NoSymbol))
elif len(stripped) >= 6:
# TODO: Find out why this is necessary; using only the documented
# behaviour may lead to only a US layout being used?
return (
keysym_group(stripped[0], stripped[1]),
keysym_group(stripped[4], stripped[5]))
else:
return (
keysym_group(stripped[0], stripped[1]),
keysym_group(stripped[2], stripped[3]))
def index_to_shift(display, index):
"""Converts an index in a *key code* list to the corresponding shift state.
:param Xlib.display.Display display: The display for which to retrieve the
shift mask.
:param int index: The keyboard mapping *key code* index.
:return: a shift mask
"""
return (
(1 << 0 if index & 1 else 0) |
(alt_gr_mask(display) if index & 2 else 0))
def shift_to_index(display, shift):
"""Converts an index in a *key code* list to the corresponding shift state.
:param Xlib.display.Display display: The display for which to retrieve the
shift mask.
:param int index: The keyboard mapping *key code* index.
:retur: a shift mask
"""
return (
(1 if shift & 1 else 0) +
(2 if shift & alt_gr_mask(display) else 0))
def keyboard_mapping(display):
"""Generates a mapping from *keysyms* to *key codes* and required
modifier shift states.
:param Xlib.display.Display display: The display for which to retrieve the
keyboard mapping.
:return: the keyboard mapping
"""
mapping = {}
shift_mask = 1 << 0
group_mask = alt_gr_mask(display)
# Iterate over all keysym lists in the keyboard mapping
min_keycode = display.display.info.min_keycode
keycode_count = display.display.info.max_keycode - min_keycode + 1
for index, keysyms in enumerate(display.get_keyboard_mapping(
min_keycode, keycode_count)):
key_code = index + min_keycode
# Normalise the keysym list to yield a tuple containing the two groups
normalized = keysym_normalize(keysyms)
if not normalized:
continue
# Iterate over the groups to extract the shift and modifier state
for groups, group in zip(normalized, (False, True)):
for keysym, shift in zip(groups, (False, True)):
if not keysym:
continue
shift_state = 0 \
| (shift_mask if shift else 0) \
| (group_mask if group else 0)
# Prefer already known lesser shift states
if keysym in mapping and mapping[keysym][1] < shift_state:
continue
mapping[keysym] = (key_code, shift_state)
return mapping
def symbol_to_keysym(symbol):
"""Converts a symbol name to a *keysym*.
:param str symbol: The name of the symbol.
:return: the corresponding *keysym*, or ``0`` if it cannot be found
"""
# First try simple translation
keysym = Xlib.XK.string_to_keysym(symbol)
if keysym:
return keysym
# If that fails, try checking a module attribute of Xlib.keysymdef.xkb
if not keysym:
try:
return getattr(Xlib.keysymdef.xkb, 'XK_' + symbol, 0)
except AttributeError:
return SYMBOLS.get(symbol, (0,))[0]
class ListenerMixin(object):
"""A mixin for *X* event listeners.
Subclasses should set a value for :attr:`_EVENTS` and implement
:meth:`_handle`.
"""
#: The events for which to listen
_EVENTS = tuple()
#: We use this instance for parsing the binary data
_EVENT_PARSER = Xlib.protocol.rq.EventField(None)
def _run(self):
self._display_stop = Xlib.display.Display()
self._display_record = Xlib.display.Display()
with display_manager(self._display_stop) as dm:
self._context = dm.record_create_context(
0,
[Xlib.ext.record.AllClients],
[{
'core_requests': (0, 0),
'core_replies': (0, 0),
'ext_requests': (0, 0, 0, 0),
'ext_replies': (0, 0, 0, 0),
'delivered_events': (0, 0),
'device_events': self._EVENTS,
'errors': (0, 0),
'client_started': False,
'client_died': False}])
# pylint: disable=W0702; we want to silence errors
try:
self._initialize(self._display_stop)
self._mark_ready()
if self.suppress:
with display_manager(self._display_record) as dm:
self._suppress_start(dm)
self._display_record.record_enable_context(
self._context, self._handler)
except:
# This exception will have been passed to the main thread
pass
finally:
if self.suppress:
with display_manager(self._display_stop) as dm:
self._suppress_stop(dm)
self._display_record.record_free_context(self._context)
self._display_stop.close()
self._display_record.close()
# pylint: enable=W0702
def _stop_platform(self):
if not hasattr(self, '_context'):
self.wait()
# pylint: disable=W0702; we must ignore errors
try:
with display_manager(self._display_stop) as dm:
dm.record_disable_context(self._context)
except:
pass
# pylint: enable=W0702
def _suppress_start(self, display):
"""Starts suppressing events.
:param Xlib.display.Display display: The display for which to suppress
events.
"""
raise NotImplementedError()
def _suppress_stop(self, display):
"""Starts suppressing events.
:param Xlib.display.Display display: The display for which to suppress
events.
"""
raise NotImplementedError()
@property
def _event_mask(self):
"""The event mask.
"""
return functools.reduce(operator.__or__, self._EVENTS, 0)
@AbstractListener._emitter
def _handler(self, events):
"""The callback registered with *X* for mouse events.
This method will parse the response and call the callbacks registered
on initialisation.
:param events: The events passed by *X*. This is a binary block
parsable by :attr:`_EVENT_PARSER`.
"""
if not self.running:
raise self.StopException()
data = events.data
while data and len(data):
event, data = self._EVENT_PARSER.parse_binary_value(
data, self._display_record.display, None, None)
self._handle(self._display_stop, event)
def _initialize(self, display):
"""Initialises this listener.
This method is called immediately before the event loop, from the
handler thread.
:param display: The display being used.
"""
pass
def _handle(self, display, event):
"""The device specific callback handler.
This method calls the appropriate callback registered when this
listener was created based on the event.
:param display: The display being used.
:param event: The event.
"""
pass

1715
pype/vendor/pynput/_util/xorg_keysyms.py vendored Normal file

File diff suppressed because it is too large Load diff

57
pype/vendor/pynput/keyboard/__init__.py vendored Normal file
View file

@ -0,0 +1,57 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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 module containing keyboard classes.
See the documentation for more information.
"""
# pylint: disable=C0103
# KeyCode, Key, Controller and Listener are not constants
import os
import sys
if os.environ.get('__PYNPUT_GENERATE_DOCUMENTATION') == 'yes':
from ._base import KeyCode, Key, Controller, Listener
else:
KeyCode = None
Key = None
Controller = None
Listener = None
if sys.platform == 'darwin':
if not KeyCode and not Key and not Controller and not Listener:
from ._darwin import KeyCode, Key, Controller, Listener
elif sys.platform == 'win32':
if not KeyCode and not Key and not Controller and not Listener:
from ._win32 import KeyCode, Key, Controller, Listener
else:
if not KeyCode and not Key and not Controller and not Listener:
try:
from ._xorg import KeyCode, Key, Controller, Listener
except ImportError:
# For now, since we only support Xlib anyway, we re-raise these
# errors to allow users to determine the cause of failures to import
raise
if not KeyCode or not Key or not Controller or not Listener:
raise ImportError('this platform is not supported')

669
pype/vendor/pynput/keyboard/_base.py vendored Normal file
View file

@ -0,0 +1,669 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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/>.
"""
This module contains the base implementation.
The actual interface to keyboard classes is defined here, but the implementation
is located in a platform dependent module.
"""
# pylint: disable=R0903
# We implement stubs
import contextlib
import enum
import threading
import unicodedata
import six
from pynput._util import AbstractListener
class KeyCode(object):
"""
A :class:`KeyCode` represents the description of a key code used by the
operating system.
"""
def __init__(self, vk=None, char=None, is_dead=False):
self.vk = vk
self.char = six.text_type(char) if char is not None else None
self.is_dead = is_dead
if self.is_dead:
self.combining = unicodedata.lookup(
'COMBINING ' + unicodedata.name(self.char))
if not self.combining:
raise KeyError(char)
else:
self.combining = None
def __repr__(self):
if self.is_dead:
return '[%s]' % repr(self.char)
if self.char is not None:
return repr(self.char)
else:
return '<%d>' % self.vk
def __str__(self):
return repr(self)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
if self.char is not None and other.char is not None:
return self.char == other.char and self.is_dead == other.is_dead
else:
return self.vk == other.vk
def __hash__(self):
return hash(repr(self))
def join(self, key):
"""Applies this dead key to another key and returns the result.
Joining a dead key with space (``' '``) or itself yields the non-dead
version of this key, if one exists; for example,
``KeyCode.from_dead('~').join(KeyCode.from_char(' '))`` equals
``KeyCode.from_char('~')`` and
``KeyCode.from_dead('~').join(KeyCode.from_dead('~'))``.
:param KeyCode key: The key to join with this key.
:return: a key code
:raises ValueError: if the keys cannot be joined
"""
# A non-dead key cannot be joined
if not self.is_dead:
raise ValueError(self)
# Joining two of the same keycodes, or joining with space, yields the
# non-dead version of the key
if key.char == ' ' or self == key:
return self.from_char(self.char)
# Otherwise we combine the characters
if key.char is not None:
combined = unicodedata.normalize(
'NFC',
key.char + self.combining)
if combined:
return self.from_char(combined)
raise ValueError(key)
@classmethod
def from_vk(cls, vk, **kwargs):
"""Creates a key from a virtual key code.
:param vk: The virtual key code.
:param kwargs: Any other parameters to pass.
:return: a key code
"""
return cls(vk=vk, **kwargs)
@classmethod
def from_char(cls, char, **kwargs):
"""Creates a key from a character.
:param str char: The character.
:return: a key code
"""
return cls(char=char, **kwargs)
@classmethod
def from_dead(cls, char, **kwargs):
"""Creates a dead key.
:param char: The dead key. This should be the unicode character
representing the stand alone character, such as ``'~'`` for
*COMBINING TILDE*.
:return: a key code
"""
return cls(char=char, is_dead=True, **kwargs)
class Key(enum.Enum):
"""A class representing various buttons that may not correspond to
letters. This includes modifier keys and function keys.
The actual values for these items differ between platforms. Some platforms
may have additional buttons, but these are guaranteed to be present
everywhere.
"""
#: A generic Alt key. This is a modifier.
alt = 0
#: The left Alt key. This is a modifier.
alt_l = 0
#: The right Alt key. This is a modifier.
alt_r = 0
#: The AltGr key. This is a modifier.
alt_gr = 0
#: The Backspace key.
backspace = 0
#: The CapsLock key.
caps_lock = 0
#: A generic command button. On *PC* platforms, this corresponds to the
#: Super key or Windows key, and on *Mac* it corresponds to the Command
#: key. This may be a modifier.
cmd = 0
#: The left command button. On *PC* platforms, this corresponds to the
#: Super key or Windows key, and on *Mac* it corresponds to the Command
#: key. This may be a modifier.
cmd_l = 0
#: The right command button. On *PC* platforms, this corresponds to the
#: Super key or Windows key, and on *Mac* it corresponds to the Command
#: key. This may be a modifier.
cmd_r = 0
#: A generic Ctrl key. This is a modifier.
ctrl = 0
#: The left Ctrl key. This is a modifier.
ctrl_l = 0
#: The right Ctrl key. This is a modifier.
ctrl_r = 0
#: The Delete key.
delete = 0
#: A down arrow key.
down = 0
#: The End key.
end = 0
#: The Enter or Return key.
enter = 0
#: The Esc key.
esc = 0
#: The function keys. F1 to F20 are defined.
f1 = 0
f2 = 0
f3 = 0
f4 = 0
f5 = 0
f6 = 0
f7 = 0
f8 = 0
f9 = 0
f10 = 0
f11 = 0
f12 = 0
f13 = 0
f14 = 0
f15 = 0
f16 = 0
f17 = 0
f18 = 0
f19 = 0
f20 = 0
#: The Home key.
home = 0
#: A left arrow key.
left = 0
#: The PageDown key.
page_down = 0
#: The PageUp key.
page_up = 0
#: A right arrow key.
right = 0
#: A generic Shift key. This is a modifier.
shift = 0
#: The left Shift key. This is a modifier.
shift_l = 0
#: The right Shift key. This is a modifier.
shift_r = 0
#: The Space key.
space = 0
#: The Tab key.
tab = 0
#: An up arrow key.
up = 0
#: The Insert key. This may be undefined for some platforms.
insert = 0
#: The Menu key. This may be undefined for some platforms.
menu = 0
#: The NumLock key. This may be undefined for some platforms.
num_lock = 0
#: The Pause/Break key. This may be undefined for some platforms.
pause = 0
#: The PrintScreen key. This may be undefined for some platforms.
print_screen = 0
#: The ScrollLock key. This may be undefined for some platforms.
scroll_lock = 0
class Controller(object):
"""A controller for sending virtual keyboard events to the system.
"""
#: The virtual key codes
_KeyCode = KeyCode
#: The various keys.
_Key = Key
class InvalidKeyException(Exception):
"""The exception raised when an invalid ``key`` parameter is passed to
either :meth:`Controller.press` or :meth:`Controller.release`.
Its first argument is the ``key`` parameter.
"""
pass
class InvalidCharacterException(Exception):
"""The exception raised when an invalid character is encountered in
the string passed to :meth:`Controller.type`.
Its first argument is the index of the character in the string, and the
second the character.
"""
pass
def __init__(self):
self._modifiers_lock = threading.RLock()
self._modifiers = set()
self._caps_lock = False
self._dead_key = None
kc = self._Key
# pylint: disable=C0103; this is treated as a class scope constant, but
# we cannot set it in the class scope, as _Key is overridden by platform
# implementations
# pylint: disable=C0326; it is easier to read column aligned keys
#: The keys used as modifiers; the first value in each tuple is the
#: base modifier to use for subsequent modifiers.
self._MODIFIER_KEYS = (
(kc.alt_gr, (kc.alt_gr.value,)),
(kc.alt, (kc.alt.value, kc.alt_l.value, kc.alt_r.value)),
(kc.cmd, (kc.cmd.value, kc.cmd_l.value, kc.cmd_r.value)),
(kc.ctrl, (kc.ctrl.value, kc.ctrl_l.value, kc.ctrl_r.value)),
(kc.shift, (kc.shift.value, kc.shift_l.value, kc.shift_r.value)))
#: Control codes to transform into key codes when typing
self._CONTROL_CODES = {
'\n': kc.enter,
'\r': kc.enter,
'\t': kc.tab}
# pylint: enable=C0103,C0326
def press(self, key):
"""Presses a key.
A key may be either a string of length 1, one of the :class:`Key`
members or a :class:`KeyCode`.
Strings will be transformed to :class:`KeyCode` using
:meth:`KeyCode.char`. Members of :class:`Key` will be translated to
their :meth:`~Key.value`.
:param key: The key to press.
:raises InvalidKeyException: if the key is invalid
:raises ValueError: if ``key`` is a string, but its length is not ``1``
"""
resolved = self._resolve(key)
self._update_modifiers(resolved, True)
# Update caps lock state
if resolved == self._Key.caps_lock.value:
self._caps_lock = not self._caps_lock
# If we currently have a dead key pressed, join it with this key
original = resolved
if self._dead_key:
try:
resolved = self._dead_key.join(resolved)
except ValueError:
self._handle(self._dead_key, True)
self._handle(self._dead_key, False)
# If the key is a dead key, keep it for later
if resolved.is_dead:
self._dead_key = resolved
return
try:
self._handle(resolved, True)
except self.InvalidKeyException:
if resolved != original:
self._handle(self._dead_key, True)
self._handle(self._dead_key, False)
self._handle(original, True)
self._dead_key = None
def release(self, key):
"""Releases a key.
A key may be either a string of length 1, one of the :class:`Key`
members or a :class:`KeyCode`.
Strings will be transformed to :class:`KeyCode` using
:meth:`KeyCode.char`. Members of :class:`Key` will be translated to
their :meth:`~Key.value`.
:param key: The key to release. If this is a string, it is passed to
:meth:`touches` and the returned releases are used.
:raises InvalidKeyException: if the key is invalid
:raises ValueError: if ``key`` is a string, but its length is not ``1``
"""
resolved = self._resolve(key)
self._update_modifiers(resolved, False)
# Ignore released dead keys
if resolved.is_dead:
return
self._handle(resolved, False)
def touch(self, key, is_press):
"""Calls either :meth:`press` or :meth:`release` depending on the value
of ``is_press``.
:param key: The key to press or release.
:param bool is_press: Whether to press the key.
:raises InvalidKeyException: if the key is invalid
"""
if is_press:
self.press(key)
else:
self.release(key)
@contextlib.contextmanager
def pressed(self, *args):
"""Executes a block with some keys pressed.
:param keys: The keys to keep pressed.
"""
for key in args:
self.press(key)
try:
yield
finally:
for key in reversed(args):
self.release(key)
def type(self, string):
"""Types a string.
This method will send all key presses and releases necessary to type
all characters in the string.
:param str string: The string to type.
:raises InvalidCharacterException: if an untypable character is
encountered
"""
for i, character in enumerate(string):
key = self._CONTROL_CODES.get(character, character)
try:
self.press(key)
self.release(key)
except (ValueError, self.InvalidKeyException):
raise self.InvalidCharacterException(i, character)
@property
@contextlib.contextmanager
def modifiers(self):
"""The currently pressed modifier keys.
Please note that this reflects only the internal state of this
controller, and not the state of the operating system keyboard buffer.
This property cannot be used to determine whether a key is physically
pressed.
Only the generic modifiers will be set; when pressing either
:attr:`Key.shift_l`, :attr:`Key.shift_r` or :attr:`Key.shift`, only
:attr:`Key.shift` will be present.
Use this property within a context block thus::
with controller.modifiers as modifiers:
with_block()
This ensures that the modifiers cannot be modified by another thread.
"""
with self._modifiers_lock:
yield set(
self._as_modifier(modifier)
for modifier in self._modifiers)
@property
def alt_pressed(self):
"""Whether any *alt* key is pressed.
Please note that this reflects only the internal state of this
controller. See :attr:`modifiers` for more information.
"""
with self.modifiers as modifiers:
return self._Key.alt in modifiers
@property
def alt_gr_pressed(self):
"""Whether *altgr* is pressed.
Please note that this reflects only the internal state of this
controller. See :attr:`modifiers` for more information.
"""
with self.modifiers as modifiers:
return self._Key.alt_gr in modifiers
@property
def ctrl_pressed(self):
"""Whether any *ctrl* key is pressed.
Please note that this reflects only the internal state of this
controller. See :attr:`modifiers` for more information.
"""
with self.modifiers as modifiers:
return self._Key.ctrl in modifiers
@property
def shift_pressed(self):
"""Whether any *shift* key is pressed, or *caps lock* is toggled.
Please note that this reflects only the internal state of this
controller. See :attr:`modifiers` for more information.
"""
if self._caps_lock:
return True
with self.modifiers as modifiers:
return self._Key.shift in modifiers
def _resolve(self, key):
"""Resolves a key to a :class:`KeyCode` instance.
This method will convert any key representing a character to uppercase
if a shift modifier is active.
:param key: The key to resolve.
:return: a key code, or ``None`` if it cannot be resolved
"""
# Use the value for the key constants
if key in self._Key:
return key.value
# Convert strings to key codes
if isinstance(key, six.string_types):
if len(key) != 1:
raise ValueError(key)
return self._KeyCode.from_char(key)
# Assume this is a proper key
if isinstance(key, self._KeyCode):
if key.char is not None and self.shift_pressed:
return self._KeyCode(vk=key.vk, char=key.char.upper())
else:
return key
def _update_modifiers(self, key, is_press):
"""Updates the current modifier list.
If ``key`` is not a modifier, no action is taken.
:param key: The key being pressed or released.
"""
# Check whether the key is a modifier
if self._as_modifier(key):
with self._modifiers_lock:
if is_press:
self._modifiers.add(key)
else:
try:
self._modifiers.remove(key)
except KeyError:
pass
def _as_modifier(self, key):
"""Returns a key as the modifier used internally if defined.
This method will convert values like :attr:`Key.alt_r.value` and
:attr:`Key.shift_l.value` to :attr:`Key.alt` and :attr:`Key.shift`.
:param key: The possible modifier key.
:return: the base modifier key, or ``None`` if ``key`` is not a
modifier
"""
for base, modifiers in self._MODIFIER_KEYS:
if key in modifiers:
return base
def _handle(self, key, is_press):
"""The platform implementation of the actual emitting of keyboard
events.
This is a platform dependent implementation.
:param Key key: The key to handle.
:param bool is_press: Whether this is a key press event.
"""
raise NotImplementedError()
# pylint: disable=W0223; This is also an abstract class
class Listener(AbstractListener):
"""A listener for keyboard events.
Instances of this class can be used as context managers. This is equivalent
to the following code::
listener.start()
try:
with_statements()
finally:
listener.stop()
This class inherits from :class:`threading.Thread` and supports all its
methods. It will set :attr:`daemon` to ``True`` when created.
:param callable on_press: The callback to call when a button is pressed.
It will be called with the argument ``(key)``, where ``key`` is a
:class:`KeyCode`, a :class:`Key` or ``None`` if the key is unknown.
:param callable on_release: The callback to call when a button is release.
It will be called with the argument ``(key)``, where ``key`` is a
:class:`KeyCode`, a :class:`Key` or ``None`` if the key is unknown.
:param bool suppress: Whether to suppress events. Setting this to ``True``
will prevent the input events from being passed to the rest of the
system.
:param kwargs: Any non-standard platform dependent options. These should be
prefixed with the platform name thus: ``darwin_``, ``xorg_`` or
``win32_``.
Supported values are:
``darwin_intercept``
A callable taking the arguments ``(event_type, event)``, where
``event_type`` is ``Quartz.kCGEventKeyDown`` or
``Quartz.kCGEventKeyDown``, and ``event`` is a ``CGEventRef``.
This callable can freely modify the event using functions like
``Quartz.CGEventSetIntegerValueField``. If this callable does not
return the event, the event is suppressed system wide.
``win32_event_filter``
A callable taking the arguments ``(msg, data)``, where ``msg`` is
the current message, and ``data`` associated data as a
`KBLLHOOKSTRUCT <https://msdn.microsoft.com/en-us/library/windows/desktop/ms644967(v=vs.85).aspx>`_.
If this callback returns ``False``, the event will not be propagated
to the listener callback.
If ``self.suppress_event()`` is called, the event is suppressed
system wide.
"""
def __init__(self, on_press=None, on_release=None, suppress=False,
**kwargs):
prefix = self.__class__.__module__.rsplit('.', 1)[-1][1:] + '_'
self._options = {
key[len(prefix):]: value
for key, value in kwargs.items()
if key.startswith(prefix)}
super(Listener, self).__init__(
on_press=on_press, on_release=on_release, suppress=suppress)
# pylint: enable=W0223

250
pype/vendor/pynput/keyboard/_darwin.py vendored Normal file
View file

@ -0,0 +1,250 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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 *OSX*.
"""
# 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,
keycode_to_string,
ListenerMixin)
from . import _base
class KeyCode(_base.KeyCode):
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, 0)
result = Quartz.CGEventCreateKeyboardEvent(None, 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
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)
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 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))
#: A mapping from keysym to special key
_SPECIAL_KEYS = {
key.value.vk: key
for key in Key}
#: 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, dummy_proxy, event_type, event, dummy_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)
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)
# First try special keys...
if vk in self._SPECIAL_KEYS:
return self._SPECIAL_KEYS[vk]
# ...then try characters...
# TODO: Use Quartz.CGEventKeyboardGetUnicodeString instead
char = keycode_to_string(
self._context, vk, Quartz.CGEventGetFlags(event))
if char:
return KeyCode.from_char(char, vk=vk)
# ...and fall back on a virtual key code
return KeyCode.from_vk(vk)

284
pype/vendor/pynput/keyboard/_win32.py vendored Normal file
View file

@ -0,0 +1,284 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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 *Windows*.
"""
# pylint: disable=C0111
# The documentation is extracted from the base classes
# pylint: disable=R0903
# We implement stubs
import contextlib
import ctypes
import enum
import six
from ctypes import wintypes
import pynput._util.win32_vks as VK
from pynput._util import AbstractListener, NotifierMixin
from pynput._util.win32 import (
INPUT,
INPUT_union,
KEYBDINPUT,
KeyTranslator,
ListenerMixin,
SendInput,
SystemHook,
VkKeyScan)
from . import _base
class KeyCode(_base.KeyCode):
def _parameters(self, is_press):
"""The parameters to pass to ``SendInput`` to generate this key.
:param bool is_press: Whether to generate a press event.
:return: all arguments to pass to ``SendInput`` for this key
:rtype: dict
"""
if self.vk:
vk = self.vk
scan = 0
flags = 0
else:
res = VkKeyScan(self.char)
if (res >> 8) & 0xFF == 0:
vk = res & 0xFF
scan = 0
flags = 0
else:
vk = 0
scan = ord(self.char)
flags = KEYBDINPUT.UNICODE
return dict(
dwFlags=flags | (KEYBDINPUT.KEYUP if not is_press else 0),
wVk=vk,
wScan=scan)
class Key(enum.Enum):
alt = KeyCode.from_vk(VK.MENU)
alt_l = KeyCode.from_vk(VK.LMENU)
alt_r = KeyCode.from_vk(VK.RMENU)
alt_gr = KeyCode.from_vk(VK.RMENU)
backspace = KeyCode.from_vk(VK.BACK)
caps_lock = KeyCode.from_vk(VK.CAPITAL)
cmd = KeyCode.from_vk(VK.LWIN)
cmd_l = KeyCode.from_vk(VK.LWIN)
cmd_r = KeyCode.from_vk(VK.RWIN)
ctrl = KeyCode.from_vk(VK.CONTROL)
ctrl_l = KeyCode.from_vk(VK.LCONTROL)
ctrl_r = KeyCode.from_vk(VK.RCONTROL)
delete = KeyCode.from_vk(VK.DELETE)
down = KeyCode.from_vk(VK.DOWN)
end = KeyCode.from_vk(VK.END)
enter = KeyCode.from_vk(VK.RETURN)
esc = KeyCode.from_vk(VK.ESCAPE)
f1 = KeyCode.from_vk(VK.F1)
f2 = KeyCode.from_vk(VK.F2)
f3 = KeyCode.from_vk(VK.F3)
f4 = KeyCode.from_vk(VK.F4)
f5 = KeyCode.from_vk(VK.F5)
f6 = KeyCode.from_vk(VK.F6)
f7 = KeyCode.from_vk(VK.F7)
f8 = KeyCode.from_vk(VK.F8)
f9 = KeyCode.from_vk(VK.F9)
f10 = KeyCode.from_vk(VK.F10)
f11 = KeyCode.from_vk(VK.F11)
f12 = KeyCode.from_vk(VK.F12)
f13 = KeyCode.from_vk(VK.F13)
f14 = KeyCode.from_vk(VK.F14)
f15 = KeyCode.from_vk(VK.F15)
f16 = KeyCode.from_vk(VK.F16)
f17 = KeyCode.from_vk(VK.F17)
f18 = KeyCode.from_vk(VK.F18)
f19 = KeyCode.from_vk(VK.F19)
f20 = KeyCode.from_vk(VK.F20)
home = KeyCode.from_vk(VK.HOME)
left = KeyCode.from_vk(VK.LEFT)
page_down = KeyCode.from_vk(VK.NEXT)
page_up = KeyCode.from_vk(VK.PRIOR)
right = KeyCode.from_vk(VK.RIGHT)
shift = KeyCode.from_vk(VK.LSHIFT)
shift_l = KeyCode.from_vk(VK.LSHIFT)
shift_r = KeyCode.from_vk(VK.RSHIFT)
space = KeyCode.from_vk(VK.SPACE, char=' ')
tab = KeyCode.from_vk(VK.TAB)
up = KeyCode.from_vk(VK.UP)
insert = KeyCode.from_vk(VK.INSERT)
menu = KeyCode.from_vk(VK.APPS)
num_lock = KeyCode.from_vk(VK.NUMLOCK)
pause = KeyCode.from_vk(VK.PAUSE)
print_screen = KeyCode.from_vk(VK.SNAPSHOT)
scroll_lock = KeyCode.from_vk(VK.SCROLL)
class Controller(_base.Controller):
_KeyCode = KeyCode
_Key = Key
def _handle(self, key, is_press):
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.KEYBOARD,
value=INPUT_union(
ki=KEYBDINPUT(**key._parameters(is_press))))),
ctypes.sizeof(INPUT))
class Listener(ListenerMixin, _base.Listener):
#: The Windows hook ID for low level keyboard events, ``WH_KEYBOARD_LL``
_EVENTS = 13
_WM_KEYDOWN = 0x0100
_WM_KEYUP = 0x0101
_WM_SYSKEYDOWN = 0x0104
_WM_SYSKEYUP = 0x0105
# A bit flag attached to messages indicating that the payload is an actual
# UTF-16 character code
_UTF16_FLAG = 0x1000
# A special virtual key code designating unicode characters
_VK_PACKET = 0xE7
#: The messages that correspond to a key press
_PRESS_MESSAGES = (_WM_KEYDOWN, _WM_SYSKEYDOWN)
#: The messages that correspond to a key release
_RELEASE_MESSAGES = (_WM_KEYUP, _WM_SYSKEYUP)
#: A mapping from keysym to special key
_SPECIAL_KEYS = {
key.value.vk: key
for key in Key}
_HANDLED_EXCEPTIONS = (
SystemHook.SuppressException,)
class _KBDLLHOOKSTRUCT(ctypes.Structure):
"""Contains information about a mouse event passed to a
``WH_KEYBOARD_LL`` hook procedure, ``LowLevelKeyboardProc``.
"""
_fields_ = [
('vkCode', wintypes.DWORD),
('scanCode', wintypes.DWORD),
('flags', wintypes.DWORD),
('time', wintypes.DWORD),
('dwExtraInfo', ctypes.c_void_p)]
#: A pointer to a :class:`KBDLLHOOKSTRUCT`
_LPKBDLLHOOKSTRUCT = ctypes.POINTER(_KBDLLHOOKSTRUCT)
def __init__(self, *args, **kwargs):
super(Listener, self).__init__(*args, **kwargs)
self._translator = KeyTranslator()
self._event_filter = self._options.get(
'event_filter',
lambda msg, data: True)
def _convert(self, code, msg, lpdata):
if code != SystemHook.HC_ACTION:
return
data = ctypes.cast(lpdata, self._LPKBDLLHOOKSTRUCT).contents
is_packet = data.vkCode == self._VK_PACKET
# Suppress further propagation of the event if it is filtered
if self._event_filter(msg, data) is False:
return None
elif is_packet:
return (msg | self._UTF16_FLAG, data.scanCode)
else:
return (msg, data.vkCode)
@AbstractListener._emitter
def _process(self, wparam, lparam):
msg = wparam
vk = lparam
# If the key has the UTF-16 flag, we treat it as a unicode character,
# otherwise convert the event to a KeyCode; this may fail, and in that
# case we pass None
is_utf16 = msg & self._UTF16_FLAG
if is_utf16:
msg = msg ^ self._UTF16_FLAG
scan = vk
key = KeyCode.from_char(six.unichr(scan))
else:
try:
key = self._event_to_key(msg, vk)
except OSError:
key = None
if msg in self._PRESS_MESSAGES:
self.on_press(key)
elif msg in self._RELEASE_MESSAGES:
self.on_release(key)
# pylint: disable=R0201
@contextlib.contextmanager
def _receive(self):
"""An empty context manager; we do not need to fake keyboard events.
"""
yield
# pylint: enable=R0201
def _event_to_key(self, msg, vk):
"""Converts an :class:`_KBDLLHOOKSTRUCT` to a :class:`KeyCode`.
:param msg: The message received.
:param vk: The virtual key code to convert.
:return: a :class:`pynput.keyboard.KeyCode`
:raises OSError: if the message and data could not be converted
"""
# We must always call self._translate to keep the keyboard state up to
# date
key = KeyCode(**self._translate(
vk,
msg in self._PRESS_MESSAGES))
# If the virtual key code corresponds to a Key value, we prefer that
if vk in self._SPECIAL_KEYS:
return self._SPECIAL_KEYS[vk]
else:
return key
def _translate(self, vk, is_press):
"""Translates a virtual key code to a parameter list passable to
:class:`pynput.keyboard.KeyCode`.
:param int vk: The virtual key code.
:param bool is_press: Whether this is a press event.
:return: a paramter list to the :class:`pynput.keyboard.KeyCode`
constructor
"""
return self._translator(vk, is_press)

607
pype/vendor/pynput/keyboard/_xorg.py vendored Normal file
View file

@ -0,0 +1,607 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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 *Xorg*.
"""
# pylint: disable=C0111
# The documentation is extracted from the base classes
# pylint: disable=R0903
# We implement stubs
import enum
import threading
import Xlib.display
import Xlib.ext
import Xlib.ext.xtest
import Xlib.X
import Xlib.XK
import Xlib.protocol
import Xlib.keysymdef.xkb
from pynput._util import NotifierMixin
from pynput._util.xorg import (
alt_mask,
alt_gr_mask,
display_manager,
index_to_shift,
keyboard_mapping,
ListenerMixin,
numlock_mask,
shift_to_index,
symbol_to_keysym)
from pynput._util.xorg_keysyms import (
CHARS,
DEAD_KEYS,
KEYPAD_KEYS,
KEYSYMS,
SYMBOLS)
from . import _base
class KeyCode(_base.KeyCode):
@classmethod
def _from_symbol(cls, symbol, **kwargs):
"""Creates a key from a symbol.
:param str symbol: The symbol name.
:return: a key code
"""
# First try simple translation
keysym = Xlib.XK.string_to_keysym(symbol)
if keysym:
return cls.from_vk(keysym, **kwargs)
# If that fails, try checking a module attribute of Xlib.keysymdef.xkb
if not keysym:
# pylint: disable=W0702; we want to ignore errors
try:
return cls.from_vk(
getattr(Xlib.keysymdef.xkb, 'XK_' + symbol, 0),
**kwargs)
except:
return cls.from_vk(
SYMBOLS.get(symbol, (0,))[0],
**kwargs)
# pylint: enable=W0702
class Key(enum.Enum):
# Default keys
alt = KeyCode._from_symbol('Alt_L')
alt_l = KeyCode._from_symbol('Alt_L')
alt_r = KeyCode._from_symbol('Alt_R')
alt_gr = KeyCode._from_symbol('Mode_switch')
backspace = KeyCode._from_symbol('BackSpace')
caps_lock = KeyCode._from_symbol('Caps_Lock')
cmd = KeyCode._from_symbol('Super_L')
cmd_l = KeyCode._from_symbol('Super_L')
cmd_r = KeyCode._from_symbol('Super_R')
ctrl = KeyCode._from_symbol('Control_L')
ctrl_l = KeyCode._from_symbol('Control_L')
ctrl_r = KeyCode._from_symbol('Control_R')
delete = KeyCode._from_symbol('Delete')
down = KeyCode._from_symbol('Down')
end = KeyCode._from_symbol('End')
enter = KeyCode._from_symbol('Return')
esc = KeyCode._from_symbol('Escape')
f1 = KeyCode._from_symbol('F1')
f2 = KeyCode._from_symbol('F2')
f3 = KeyCode._from_symbol('F3')
f4 = KeyCode._from_symbol('F4')
f5 = KeyCode._from_symbol('F5')
f6 = KeyCode._from_symbol('F6')
f7 = KeyCode._from_symbol('F7')
f8 = KeyCode._from_symbol('F8')
f9 = KeyCode._from_symbol('F9')
f10 = KeyCode._from_symbol('F10')
f11 = KeyCode._from_symbol('F11')
f12 = KeyCode._from_symbol('F12')
f13 = KeyCode._from_symbol('F13')
f14 = KeyCode._from_symbol('F14')
f15 = KeyCode._from_symbol('F15')
f16 = KeyCode._from_symbol('F16')
f17 = KeyCode._from_symbol('F17')
f18 = KeyCode._from_symbol('F18')
f19 = KeyCode._from_symbol('F19')
f20 = KeyCode._from_symbol('F20')
home = KeyCode._from_symbol('Home')
left = KeyCode._from_symbol('Left')
page_down = KeyCode._from_symbol('Page_Down')
page_up = KeyCode._from_symbol('Page_Up')
right = KeyCode._from_symbol('Right')
shift = KeyCode._from_symbol('Shift_L')
shift_l = KeyCode._from_symbol('Shift_L')
shift_r = KeyCode._from_symbol('Shift_R')
space = KeyCode._from_symbol('space', char=' ')
tab = KeyCode._from_symbol('Tab')
up = KeyCode._from_symbol('Up')
insert = KeyCode._from_symbol('Insert')
menu = KeyCode._from_symbol('Menu')
num_lock = KeyCode._from_symbol('Num_Lock')
pause = KeyCode._from_symbol('Pause')
print_screen = KeyCode._from_symbol('Print')
scroll_lock = KeyCode._from_symbol('Scroll_Lock')
class Controller(NotifierMixin, _base.Controller):
_KeyCode = KeyCode
_Key = Key
#: The shift mask for :attr:`Key.ctrl`
CTRL_MASK = Xlib.X.ControlMask
#: The shift mask for :attr:`Key.shift`
SHIFT_MASK = Xlib.X.ShiftMask
def __init__(self):
super(Controller, self).__init__()
self._display = Xlib.display.Display()
self._keyboard_mapping = None
self._borrows = {}
self._borrow_lock = threading.RLock()
# pylint: disable=C0103; this is treated as a class scope constant, but
# we cannot set it in the class scope, as it requires a Display instance
self.ALT_MASK = alt_mask(self._display)
self.ALT_GR_MASK = alt_gr_mask(self._display)
# pylint: enable=C0103
def __del__(self):
if self._display:
self._display.close()
@property
def keyboard_mapping(self):
"""A mapping from *keysyms* to *key codes*.
Each value is the tuple ``(key_code, shift_state)``. By sending an
event with the specified *key code* and shift state, the specified
*keysym* will be touched.
"""
if not self._keyboard_mapping:
self._update_keyboard_mapping()
return self._keyboard_mapping
def _handle(self, key, is_press):
"""Resolves a key identifier and sends a keyboard event.
:param event: The *X* keyboard event.
:param int keysym: The keysym to handle.
"""
event = Xlib.display.event.KeyPress if is_press \
else Xlib.display.event.KeyRelease
keysym = self._keysym(key)
# Make sure to verify that the key was resolved
if keysym is None:
raise self.InvalidKeyException(key)
# If the key has a virtual key code, use that immediately with
# fake_input; fake input,being an X server extension, has access to more
# internal state that we
if key.vk is not None:
with display_manager(self._display) as dm:
Xlib.ext.xtest.fake_input(
dm,
Xlib.X.KeyPress if is_press else Xlib.X.KeyRelease,
dm.keysym_to_keycode(key.vk))
# Otherwise use XSendEvent; we need to use this in the general case to
# work around problems with keyboard layouts
else:
try:
keycode, shift_state = self.keyboard_mapping[keysym]
self._send_key(event, keycode, shift_state)
except KeyError:
with self._borrow_lock:
keycode, index, count = self._borrows[keysym]
self._send_key(
event,
keycode,
index_to_shift(self._display, index))
count += 1 if is_press else -1
self._borrows[keysym] = (keycode, index, count)
# Notify any running listeners
self._emit('_on_fake_event', key, is_press)
def _keysym(self, key):
"""Converts a key to a *keysym*.
:param KeyCode key: The key code to convert.
"""
return self._resolve_dead(key) if key.is_dead else None \
or self._resolve_special(key) \
or self._resolve_normal(key) \
or self._resolve_borrowed(key) \
or self._resolve_borrowing(key)
def _send_key(self, event, keycode, shift_state):
"""Sends a single keyboard event.
:param event: The *X* keyboard event.
:param int keycode: The calculated keycode.
:param int shift_state: The shift state. The actual value used is
:attr:`shift_state` or'd with this value.
"""
with display_manager(self._display) as dm, self.modifiers as modifiers:
# Under certain cimcumstances, such as when running under Xephyr,
# the value returned by dm.get_input_focus is an int
window = dm.get_input_focus().focus
send_event = getattr(
window,
'send_event',
lambda event: dm.send_event(window, event))
send_event(event(
detail=keycode,
state=shift_state | self._shift_mask(modifiers),
time=0,
root=dm.screen().root,
window=window,
same_screen=0,
child=Xlib.X.NONE,
root_x=0, root_y=0, event_x=0, event_y=0))
def _resolve_dead(self, key):
"""Tries to resolve a dead key.
:param str identifier: The identifier to resolve.
"""
# pylint: disable=W0702; we want to ignore errors
try:
keysym, _ = SYMBOLS[CHARS[key.combining]]
except:
return None
# pylint: enable=W0702
if keysym not in self.keyboard_mapping:
return None
return keysym
def _resolve_special(self, key):
"""Tries to resolve a special key.
A special key has the :attr:`~KeyCode.vk` attribute set.
:param KeyCode key: The key to resolve.
"""
if not key.vk:
return None
return key.vk
def _resolve_normal(self, key):
"""Tries to resolve a normal key.
A normal key exists on the keyboard, and is typed by pressing
and releasing a simple key, possibly in combination with a modifier.
:param KeyCode key: The key to resolve.
"""
keysym = self._key_to_keysym(key)
if keysym is None:
return None
if keysym not in self.keyboard_mapping:
return None
return keysym
def _resolve_borrowed(self, key):
"""Tries to resolve a key by looking up the already borrowed *keysyms*.
A borrowed *keysym* does not exist on the keyboard, but has been
temporarily added to the layout.
:param KeyCode key: The key to resolve.
"""
keysym = self._key_to_keysym(key)
if keysym is None:
return None
with self._borrow_lock:
if keysym not in self._borrows:
return None
return keysym
def _resolve_borrowing(self, key):
"""Tries to resolve a key by modifying the layout temporarily.
A borrowed *keysym* does not exist on the keyboard, but is temporarily
added to the layout.
:param KeyCode key: The key to resolve.
"""
keysym = self._key_to_keysym(key)
if keysym is None:
return None
mapping = self._display.get_keyboard_mapping(8, 255 - 8)
def i2kc(index):
return index + 8
def kc2i(keycode):
return keycode - 8
#: Finds a keycode and index by looking at already used keycodes
def reuse():
for _, (keycode, _, _) in self._borrows.items():
keycodes = mapping[kc2i(keycode)]
# Only the first four items are addressable by X
for index in range(4):
if not keycodes[index]:
return keycode, index
#: Finds a keycode and index by using a new keycode
def borrow():
for i, keycodes in enumerate(mapping):
if not any(keycodes):
return i2kc(i), 0
#: Finds a keycode and index by reusing an old, unused one
def overwrite():
for keysym, (keycode, index, count) in self._borrows.items():
if count < 1:
del self._borrows[keysym]
return keycode, index
#: Registers a keycode for a specific key and modifier state
def register(dm, keycode, index):
i = kc2i(keycode)
mapping[i][index] = keysym
dm.change_keyboard_mapping(
keycode,
mapping[i:i + 1])
self._borrows[keysym] = (keycode, index, 0)
try:
with display_manager(self._display) as dm, self._borrow_lock as _:
# First try an already used keycode, then try a new one, and
# fall back on reusing one that is not currently pressed
register(dm, *(
reuse() or
borrow() or
overwrite()))
return keysym
except TypeError:
return None
def _key_to_keysym(self, key):
"""Converts a character key code to a *keysym*.
:param KeyCode key: The key code.
:return: a keysym if found
:rtype: int or None
"""
symbol = CHARS.get(key.char, None)
if symbol is None:
return None
# pylint: disable=W0702; we want to ignore errors
try:
return symbol_to_keysym(symbol)
except:
try:
return SYMBOLS[symbol][0]
except:
return None
# pylint: enable=W0702
def _shift_mask(self, modifiers):
"""The *X* modifier mask to apply for a set of modifiers.
:param set modifiers: A set of active modifiers for which to get the
shift mask.
"""
return (
0
| (self.ALT_MASK
if Key.alt in modifiers else 0)
| (self.ALT_GR_MASK
if Key.alt_gr in modifiers else 0)
| (self.CTRL_MASK
if Key.ctrl in modifiers else 0)
| (self.SHIFT_MASK
if Key.shift in modifiers else 0))
def _update_keyboard_mapping(self):
"""Updates the keyboard mapping.
"""
with display_manager(self._display) as dm:
self._keyboard_mapping = keyboard_mapping(dm)
@Controller._receiver
class Listener(ListenerMixin, _base.Listener):
_EVENTS = (
Xlib.X.KeyPress,
Xlib.X.KeyRelease)
#: A mapping from keysym to special key
_SPECIAL_KEYS = {
key.value.vk: key
for key in Key}
#: A mapping from numeric keypad keys to keys
_KEYPAD_KEYS = {
KEYPAD_KEYS['KP_0']: KeyCode.from_char('0'),
KEYPAD_KEYS['KP_1']: KeyCode.from_char('1'),
KEYPAD_KEYS['KP_2']: KeyCode.from_char('2'),
KEYPAD_KEYS['KP_3']: KeyCode.from_char('3'),
KEYPAD_KEYS['KP_4']: KeyCode.from_char('4'),
KEYPAD_KEYS['KP_5']: KeyCode.from_char('5'),
KEYPAD_KEYS['KP_6']: KeyCode.from_char('6'),
KEYPAD_KEYS['KP_7']: KeyCode.from_char('7'),
KEYPAD_KEYS['KP_8']: KeyCode.from_char('8'),
KEYPAD_KEYS['KP_9']: KeyCode.from_char('9'),
KEYPAD_KEYS['KP_Add']: KeyCode.from_char('+'),
KEYPAD_KEYS['KP_Decimal']: KeyCode.from_char(','),
KEYPAD_KEYS['KP_Delete']: Key.delete,
KEYPAD_KEYS['KP_Divide']: KeyCode.from_char('/'),
KEYPAD_KEYS['KP_Down']: Key.down,
KEYPAD_KEYS['KP_End']: Key.end,
KEYPAD_KEYS['KP_Enter']: Key.enter,
KEYPAD_KEYS['KP_Equal']: KeyCode.from_char('='),
KEYPAD_KEYS['KP_F1']: Key.f1,
KEYPAD_KEYS['KP_F2']: Key.f2,
KEYPAD_KEYS['KP_F3']: Key.f3,
KEYPAD_KEYS['KP_F4']: Key.f4,
KEYPAD_KEYS['KP_Home']: Key.home,
KEYPAD_KEYS['KP_Insert']: Key.insert,
KEYPAD_KEYS['KP_Left']: Key.left,
KEYPAD_KEYS['KP_Multiply']: KeyCode.from_char('*'),
KEYPAD_KEYS['KP_Page_Down']: Key.page_down,
KEYPAD_KEYS['KP_Page_Up']: Key.page_up,
KEYPAD_KEYS['KP_Right']: Key.right,
KEYPAD_KEYS['KP_Space']: Key.space,
KEYPAD_KEYS['KP_Subtract']: KeyCode.from_char('-'),
KEYPAD_KEYS['KP_Tab']: Key.tab,
KEYPAD_KEYS['KP_Up']: Key.up}
def __init__(self, *args, **kwargs):
super(Listener, self).__init__(*args, **kwargs)
self._keyboard_mapping = None
def _run(self):
with self._receive():
super(Listener, self)._run()
def _initialize(self, display):
# Get the keyboard mapping to be able to translate events details to
# key codes
min_keycode = display.display.info.min_keycode
keycode_count = display.display.info.max_keycode - min_keycode + 1
self._keyboard_mapping = display.get_keyboard_mapping(
min_keycode, keycode_count)
def _handle(self, display, event):
# Convert the event to a KeyCode; this may fail, and in that case we
# pass None
try:
key = self._event_to_key(display, event)
except IndexError:
key = None
if event.type == Xlib.X.KeyPress:
self.on_press(key)
elif event.type == Xlib.X.KeyRelease:
self.on_release(key)
def _suppress_start(self, display):
display.screen().root.grab_keyboard(
self._event_mask, Xlib.X.GrabModeAsync, Xlib.X.GrabModeAsync,
Xlib.X.CurrentTime)
def _suppress_stop(self, display):
display.ungrab_keyboard(Xlib.X.CurrentTime)
def _on_fake_event(self, key, is_press):
"""The handler for fake press events sent by the controllers.
:param KeyCode key: The key pressed.
:param bool is_press: Whether this is a press event.
"""
(self.on_press if is_press else self.on_release)(
self._SPECIAL_KEYS.get(key.vk, key))
def _keycode_to_keysym(self, display, keycode, index):
"""Converts a keycode and shift state index to a keysym.
This method uses a simplified version of the *X* convention to locate
the correct keysym in the display table: since this method is only used
to locate special keys, alphanumeric keys are not treated specially.
:param display: The current *X* display.
:param keycode: The keycode.
:param index: The shift state index.
:return: a keysym
"""
keysym = display.keycode_to_keysym(keycode, index)
if keysym:
return keysym
elif index & 0x2:
return self._keycode_to_keysym(display, keycode, index & ~0x2)
elif index & 0x1:
return self._keycode_to_keysym(display, keycode, index & ~0x1)
else:
return 0
def _event_to_key(self, display, event):
"""Converts an *X* event to a :class:`KeyCode`.
:param display: The current *X* display.
:param event: The event to convert.
:return: a :class:`pynput.keyboard.KeyCode`
:raises IndexError: if the key code is invalid
"""
keycode = event.detail
index = shift_to_index(display, event.state)
# First try special keys...
keysym = self._keycode_to_keysym(display, keycode, index)
if keysym in self._SPECIAL_KEYS:
return self._SPECIAL_KEYS[keysym]
elif keysym in self._KEYPAD_KEYS:
# We must recalculate the index if numlock is active; index 1 is the
# one to use
try:
return self._KEYPAD_KEYS[
self._keycode_to_keysym(
display,
keycode,
bool(event.state & numlock_mask(display)))]
except KeyError:
# Since we recalculated the key, this may happen
pass
# ...then try characters...
name = KEYSYMS.get(keysym, None)
if name is not None and name in SYMBOLS:
char = SYMBOLS[name][1].upper() if index & 1 else SYMBOLS[name][1]
if char in DEAD_KEYS:
return KeyCode.from_dead(DEAD_KEYS[char], vk=keycode)
else:
return KeyCode.from_char(char, vk=keycode)
# ...and fall back on a virtual key code
return KeyCode.from_vk(keysym)

56
pype/vendor/pynput/mouse/__init__.py vendored Normal file
View file

@ -0,0 +1,56 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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 module containing mouse classes.
See the documentation for more information.
"""
# pylint: disable=C0103
# Button, Controller and Listener are not constants
import os
import sys
if os.environ.get('__PYNPUT_GENERATE_DOCUMENTATION') == 'yes':
from ._base import Button, Controller, Listener
else:
Button = None
Controller = None
Listener = None
if sys.platform == 'darwin':
if not Button and not Controller and not Listener:
from ._darwin import Button, Controller, Listener
elif sys.platform == 'win32':
if not Button and not Controller and not Listener:
from ._win32 import Button, Controller, Listener
else:
if not Button and not Controller and not Listener:
try:
from ._xorg import Button, Controller, Listener
except ImportError:
# For now, since we only support Xlib anyway, we re-raise these
# errors to allow users to determine the cause of failures to import
raise
if not Button or not Controller or not Listener:
raise ImportError('this platform is not supported')

255
pype/vendor/pynput/mouse/_base.py vendored Normal file
View file

@ -0,0 +1,255 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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/>.
"""
This module contains the base implementation.
The actual interface to mouse classes is defined here, but the implementation is
located in a platform dependent module.
"""
# pylint: disable=R0903
# We implement stubs
import enum
from pynput._util import AbstractListener
class Button(enum.Enum):
"""The various buttons.
The actual values for these items differ between platforms. Some
platforms may have additional buttons, but these are guaranteed to be
present everywhere.
"""
#: An unknown button was pressed
unknown = 0
#: The left button
left = 1
#: The middle button
middle = 2
#: The right button
right = 3
class Controller(object):
"""A controller for sending virtual mouse events to the system.
"""
@property
def position(self):
"""The current position of the mouse pointer.
This is the tuple ``(x, y)``, and setting it will move the pointer.
"""
return self._position_get()
@position.setter
def position(self, pos):
self._position_set(pos)
def scroll(self, dx, dy):
"""Sends scroll events.
:param int dx: The horizontal scroll. The units of scrolling is
undefined.
:param int dy: The vertical scroll. The units of scrolling is
undefined.
:raises ValueError: if the values are invalid, for example out of bounds
"""
self._scroll(dx, dy)
def press(self, button):
"""Emits a button press event at the current position.
:param Button button: The button to press.
"""
self._press(button)
def release(self, button):
"""Emits a button release event at the current position.
:param Button button: The button to release.
"""
self._release(button)
def move(self, dx, dy):
"""Moves the mouse pointer a number of pixels from its current
position.
:param int x: The horizontal offset.
:param int dy: The vertical offset.
:raises ValueError: if the values are invalid, for example out of bounds
"""
self.position = tuple(sum(i) for i in zip(self.position, (dx, dy)))
def click(self, button, count=1):
"""Emits a button click event at the current position.
The default implementation sends a series of press and release events.
:param Button button: The button to click.
:param int count: The number of clicks to send.
"""
with self as controller:
for _ in range(count):
controller.press(button)
controller.release(button)
def __enter__(self):
"""Begins a series of clicks.
In the default :meth:`click` implementation, the return value of this
method is used for the calls to :meth:`press` and :meth:`release`
instead of ``self``.
The default implementation is a no-op.
"""
return self
def __exit__(self, exc_type, value, traceback):
"""Ends a series of clicks.
"""
pass
def _position_get(self):
"""The implementation of the getter for :attr:`position`.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _position_set(self, pos):
"""The implementation of the setter for :attr:`position`.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _scroll(self, dx, dy):
"""The implementation of the :meth:`scroll` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _press(self, button):
"""The implementation of the :meth:`press` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _release(self, button):
"""The implementation of the :meth:`release` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
# pylint: disable=W0223; This is also an abstract class
class Listener(AbstractListener):
"""A listener for mouse events.
Instances of this class can be used as context managers. This is equivalent
to the following code::
listener.start()
try:
with_statements()
finally:
listener.stop()
This class inherits from :class:`threading.Thread` and supports all its
methods. It will set :attr:`daemon` to ``True`` when created.
:param callable on_move: The callback to call when mouse move events occur.
It will be called with the arguments ``(x, y)``, which is the new
pointer position. If this callback raises :class:`StopException` or
returns ``False``, the listener is stopped.
:param callable on_click: The callback to call when a mouse button is
clicked.
It will be called with the arguments ``(x, y, button, pressed)``,
where ``(x, y)`` is the new pointer position, ``button`` is one of the
:class:`Button` values and ``pressed`` is whether the button was
pressed.
If this callback raises :class:`StopException` or returns ``False``,
the listener is stopped.
:param callable on_scroll: The callback to call when mouse scroll
events occur.
It will be called with the arguments ``(x, y, dx, dy)``, where
``(x, y)`` is the new pointer position, and ``(dx, dy)`` is the scroll
vector.
If this callback raises :class:`StopException` or returns ``False``,
the listener is stopped.
:param bool suppress: Whether to suppress events. Setting this to ``True``
will prevent the input events from being passed to the rest of the
system.
:param kwargs: Any non-standard platform dependent options. These should be
prefixed with the platform name thus: ``darwin_``, ``xorg_`` or
``win32_``.
Supported values are:
``darwin_intercept``
A callable taking the arguments ``(event_type, event)``, where
``event_type`` is any mouse related event type constant, and
``event`` is a ``CGEventRef``.
This callable can freely modify the event using functions like
``Quartz.CGEventSetIntegerValueField``. If this callable does not
return the event, the event is suppressed system wide.
``win32_event_filter``
A callable taking the arguments ``(msg, data)``, where ``msg`` is
the current message, and ``data`` associated data as a
`MSLLHOOKSTRUCT <https://msdn.microsoft.com/en-us/library/windows/desktop/ms644970(v=vs.85).aspx>`_.
If this callback returns ``False``, the event will not be propagated
to the listener callback.
If ``self.suppress_event()`` is called, the event is suppressed
system wide.
"""
def __init__(self, on_move=None, on_click=None, on_scroll=None,
suppress=False, **kwargs):
prefix = self.__class__.__module__.rsplit('.', 1)[-1][1:] + '_'
self._options = {
key[len(prefix):]: value
for key, value in kwargs.items()
if key.startswith(prefix)}
super(Listener, self).__init__(
on_move=on_move, on_click=on_click, on_scroll=on_scroll,
suppress=suppress)
# pylint: enable=W0223

215
pype/vendor/pynput/mouse/_darwin.py vendored Normal file
View file

@ -0,0 +1,215 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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 *OSX*.
"""
# 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 shuld 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 = 5
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):
while dx != 0 or dy != 0:
xval = 1 if dx > 0 else -1 if dx < 0 else 0
dx -= xval
yval = 1 if dy > 0 else -1 if dy < 0 else 0
dy -= yval
Quartz.CGEventPost(
Quartz.kCGHIDEventTap,
Quartz.CGEventCreateScrollWheelEvent(
None,
Quartz.kCGScrollEventUnitPixel,
2,
yval * self._SCROLL_SPEED,
xval * 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, dummy_proxy, event_type, event, dummy_refcon):
"""The callback registered with *Mac OSX* 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)

192
pype/vendor/pynput/mouse/_win32.py vendored Normal file
View file

@ -0,0 +1,192 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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
class Button(enum.Enum):
"""The various buttons.
"""
unknown = None
left = (MOUSEINPUT.LEFTUP, MOUSEINPUT.LEFTDOWN)
middle = (MOUSEINPUT.MIDDLEUP, MOUSEINPUT.MIDDLEDOWN)
right = (MOUSEINPUT.RIGHTUP, MOUSEINPUT.RIGHTDOWN)
class Controller(NotifierMixin, _base.Controller):
__GetCursorPos = windll.user32.GetCursorPos
__SetCursorPos = windll.user32.SetCursorPos
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))))),
ctypes.sizeof(INPUT))
if dx:
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mi=MOUSEINPUT(
dwFlags=MOUSEINPUT.HWHEEL,
mouseData=int(dx))))),
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])))),
ctypes.sizeof(INPUT))
def _release(self, button):
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mi=MOUSEINPUT(
dwFlags=button.value[0])))),
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
_WHEEL_DELTA = 120
#: 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 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.SCROLL_BUTTONS:
mx, my = self.SCROLL_BUTTONS[msg]
dd = wintypes.SHORT(data.mouseData >> 16).value // self._WHEEL_DELTA
self.on_scroll(data.pt.x, data.pt.y, dd * mx, dd * my)

170
pype/vendor/pynput/mouse/_xorg.py vendored Normal file
View file

@ -0,0 +1,170 @@
# coding=utf-8
# pynput
# Copyright (C) 2015-2017 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 *Xorg*.
"""
# pylint: disable=C0111
# The documentation is extracted from the base classes
# pylint: disable=E1101,E1102
# We dynamically generate the Button class
# pylint: disable=R0903
# We implement stubs
import enum
import Xlib.display
import Xlib.ext
import Xlib.ext.xtest
import Xlib.X
import Xlib.protocol
from pynput._util.xorg import (
display_manager,
ListenerMixin)
from . import _base
# pylint: disable=C0103
Button = enum.Enum(
'Button',
module=__name__,
names=[
('unknown', None),
('left', 1),
('middle', 2),
('right', 3),
('scroll_up', 4),
('scroll_down', 5),
('scroll_left', 6),
('scroll_right', 7)] + [
('button%d' % i, i)
for i in range(8, 31)])
# pylint: enable=C0103
class Controller(_base.Controller):
def __init__(self):
self._display = Xlib.display.Display()
def __del__(self):
if hasattr(self, '_display'):
self._display.close()
def _position_get(self):
with display_manager(self._display) as dm:
qp = dm.screen().root.query_pointer()
return (qp.root_x, qp.root_y)
def _position_set(self, pos):
px, py = self._check_bounds(*pos)
with display_manager(self._display) as dm:
Xlib.ext.xtest.fake_input(dm, Xlib.X.MotionNotify, x=px, y=py)
def _scroll(self, dx, dy):
dx, dy = self._check_bounds(dx, dy)
if dy:
self.click(
button=Button.scroll_up if dy > 0 else Button.scroll_down,
count=abs(dy))
if dx:
self.click(
button=Button.scroll_right if dx > 0 else Button.scroll_left,
count=abs(dx))
def _press(self, button):
with display_manager(self._display) as dm:
Xlib.ext.xtest.fake_input(dm, Xlib.X.ButtonPress, button.value)
def _release(self, button):
with display_manager(self._display) as dm:
Xlib.ext.xtest.fake_input(dm, Xlib.X.ButtonRelease, button.value)
def _check_bounds(self, *args):
"""Checks the arguments and makes sure they are within the bounds of a
short integer.
:param args: The values to verify.
"""
if not all(
(-0x7fff - 1) <= number <= 0x7fff
for number in args):
raise ValueError(args)
else:
return tuple(int(p) for p in args)
class Listener(ListenerMixin, _base.Listener):
#: A mapping from button values to scroll directions
_SCROLL_BUTTONS = {
Button.scroll_up.value: (0, 1),
Button.scroll_down.value: (0, -1),
Button.scroll_right.value: (1, 0),
Button.scroll_left.value: (-1, 0)}
_EVENTS = (
Xlib.X.ButtonPressMask,
Xlib.X.ButtonReleaseMask)
def _handle(self, dummy_display, event):
px = event.root_x
py = event.root_y
if event.type == Xlib.X.ButtonPress:
# Scroll events are sent as button presses with the scroll
# button codes
scroll = self._SCROLL_BUTTONS.get(event.detail, None)
if scroll:
self.on_scroll(px, py, *scroll)
else:
self.on_click(px, py, self._button(event.detail), True)
elif event.type == Xlib.X.ButtonRelease:
# Send an event only if this was not a scroll event
if event.detail not in self._SCROLL_BUTTONS:
self.on_click(px, py, self._button(event.detail), False)
else:
self.on_move(px, py)
def _suppress_start(self, display):
display.screen().root.grab_pointer(
True, self._event_mask, Xlib.X.GrabModeAsync, Xlib.X.GrabModeAsync,
0, 0, Xlib.X.CurrentTime)
def _suppress_stop(self, display):
display.ungrab_pointer(Xlib.X.CurrentTime)
# pylint: disable=R0201
def _button(self, detail):
"""Creates a mouse button from an event detail.
If the button is unknown, :attr:`Button.unknown` is returned.
:param detail: The event detail.
:return: a button
"""
try:
return Button(detail)
except ValueError:
return Button.unknown
# pylint: enable=R0201