platypush/platypush/plugins/joystick/_inputs.py

3661 lines
117 KiB
Python

"""Inputs - user input for humans.
Inputs aims to provide easy to use, cross-platform, user input device
support for Python. I.e. keyboards, mice, gamepads, etc.
Currently supported platforms are the Raspberry Pi, Linux, Windows and
Mac OS X.
"""
# Forked by Fabio Manganiello <fabio@manganiello.tech> from:
# Copyright (c) 2016, 2018: Zeth
# All rights reserved.
#
# BSD Licence
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import print_function
from __future__ import division
import os
import sys
import io
import glob
import struct
import platform
import math
import time
import codecs
from warnings import warn
from itertools import count
from operator import itemgetter
from multiprocessing import Process, Pipe
import ctypes
__version__ = "0.5"
WIN = platform.system() == 'Windows'
MAC = platform.system() == 'Darwin'
NIX = platform.system() == 'Linux'
if WIN:
# pylint: disable=wrong-import-position
import ctypes.wintypes
DWORD = ctypes.wintypes.DWORD
HANDLE = ctypes.wintypes.HANDLE
WPARAM = ctypes.wintypes.WPARAM
LPARAM = ctypes.wintypes.WPARAM
MSG = ctypes.wintypes.MSG
else:
DWORD = ctypes.c_ulong
HANDLE = ctypes.c_void_p
WPARAM = ctypes.c_ulonglong
LPARAM = ctypes.c_ulonglong
MSG = ctypes.Structure
if NIX:
from fcntl import ioctl
OLD = sys.version_info < (3, 4)
PERMISSIONS_ERROR_TEXT = (
"The user (that this program is being run as) does "
"not have permission to access the input events, "
"check groups and permissions, for example, on "
"Debian, the user needs to be in the input group."
)
# Standard event format for most devices.
# long, long, unsigned short, unsigned short, int
EVENT_FORMAT = str('llHHi')
EVENT_SIZE = struct.calcsize(EVENT_FORMAT)
def chunks(raw):
"""Yield successive EVENT_SIZE sized chunks from raw."""
for i in range(0, len(raw), EVENT_SIZE):
yield struct.unpack(EVENT_FORMAT, raw[i : i + EVENT_SIZE])
if OLD:
def iter_unpack(raw):
"""Yield successive EVENT_SIZE chunks from message."""
return chunks(raw)
else:
def iter_unpack(raw):
"""Yield successive EVENT_SIZE chunks from message."""
return struct.iter_unpack(EVENT_FORMAT, raw)
def convert_timeval(seconds_since_epoch):
"""Convert time into C style timeval."""
frac, whole = math.modf(seconds_since_epoch)
microseconds = math.floor(frac * 1000000)
seconds = math.floor(whole)
return seconds, microseconds
SPECIAL_DEVICES = (
(
"Raspberry Pi Sense HAT Joystick",
"/dev/input/by-id/gpio-Raspberry_Pi_Sense_HAT_Joystick-event-kbd",
),
(
"Nintendo Wii Remote",
"/dev/input/by-id/bluetooth-Nintendo_Wii_Remote-event-joystick",
),
(
"FT5406 memory based driver",
"/dev/input/by-id/gpio-Raspberry_Pi_Touchscreen_Display-event-mouse",
),
)
XINPUT_MAPPING = (
(1, 0x11),
(2, 0x11),
(3, 0x10),
(4, 0x10),
(5, 0x13A),
(6, 0x13B),
(7, 0x13D),
(8, 0x13E),
(9, 0x136),
(10, 0x137),
(13, 0x130),
(14, 0x131),
(15, 0x134),
(16, 0x133),
(17, 0x11),
('l_thumb_x', 0x00),
('l_thumb_y', 0x01),
('left_trigger', 0x02),
('r_thumb_x', 0x03),
('r_thumb_y', 0x04),
('right_trigger', 0x05),
)
XINPUT_DLL_NAMES = (
"XInput1_4.dll",
"XInput9_1_0.dll",
"XInput1_3.dll",
"XInput1_2.dll",
"XInput1_1.dll",
)
XINPUT_ERROR_DEVICE_NOT_CONNECTED = 1167
XINPUT_ERROR_SUCCESS = 0
XBOX_STYLE_LED_CONTROL = {
0: 'off',
1: 'all blink, then previous setting',
2: '1/top-left blink, then on',
3: '2/top-right blink, then on',
4: '3/bottom-left blink, then on',
5: '4/bottom-right blink, then on',
6: '1/top-left on',
7: '2/top-right on',
8: '3/bottom-left on',
9: '4/bottom-right on',
10: 'rotate',
11: 'blink, based on previous setting',
12: 'slow blink, based on previous setting',
13: 'rotate with two lights',
14: 'persistent slow all blink',
15: 'blink once, then previous setting',
}
DEVICE_PROPERTIES = (
(0x00, "INPUT_PROP_POINTER"), # needs a pointer
(0x01, "INPUT_PROP_DIRECT"), # direct input devices
(0x02, "INPUT_PROP_BUTTONPAD"), # has button(s) under pad
(0x03, "INPUT_PROP_SEMI_MT"), # touch rectangle only
(0x04, "INPUT_PROP_TOPBUTTONPAD"), # softbuttons at top of pad
(0x05, "INPUT_PROP_POINTING_STICK"), # is a pointing stick
(0x06, "INPUT_PROP_ACCELEROMETER"), # has accelerometer
(0x1F, "INPUT_PROP_MAX"),
(0x1F + 1, "INPUT_PROP_CNT"),
)
EVENT_TYPES = (
(0x00, "Sync"),
(0x01, "Key"),
(0x02, "Relative"),
(0x03, "Absolute"),
(0x04, "Misc"),
(0x05, "Switch"),
(0x11, "LED"),
(0x12, "Sound"),
(0x14, "Repeat"),
(0x15, "ForceFeedback"),
(0x16, "Power"),
(0x17, "ForceFeedbackStatus"),
(0x1F, "Max"),
(0x1F + 1, "Current"),
)
SYNCHRONIZATION_EVENTS = (
(0, "SYN_REPORT"),
(1, "SYN_CONFIG"),
(2, "SYN_MT_REPORT"),
(3, "SYN_DROPPED"),
(0xF, "SYN_MAX"),
(0xF + 1, "SYN_CNT"),
)
KEYS_AND_BUTTONS = (
(0, "KEY_RESERVED"),
(1, "KEY_ESC"),
(2, "KEY_1"),
(3, "KEY_2"),
(4, "KEY_3"),
(5, "KEY_4"),
(6, "KEY_5"),
(7, "KEY_6"),
(8, "KEY_7"),
(9, "KEY_8"),
(10, "KEY_9"),
(11, "KEY_0"),
(12, "KEY_MINUS"),
(13, "KEY_EQUAL"),
(14, "KEY_BACKSPACE"),
(15, "KEY_TAB"),
(16, "KEY_Q"),
(17, "KEY_W"),
(18, "KEY_E"),
(19, "KEY_R"),
(20, "KEY_T"),
(21, "KEY_Y"),
(22, "KEY_U"),
(23, "KEY_I"),
(24, "KEY_O"),
(25, "KEY_P"),
(26, "KEY_LEFTBRACE"),
(27, "KEY_RIGHTBRACE"),
(28, "KEY_ENTER"),
(29, "KEY_LEFTCTRL"),
(30, "KEY_A"),
(31, "KEY_S"),
(32, "KEY_D"),
(33, "KEY_F"),
(34, "KEY_G"),
(35, "KEY_H"),
(36, "KEY_J"),
(37, "KEY_K"),
(38, "KEY_L"),
(39, "KEY_SEMICOLON"),
(40, "KEY_APOSTROPHE"),
(41, "KEY_GRAVE"),
(42, "KEY_LEFTSHIFT"),
(43, "KEY_BACKSLASH"),
(44, "KEY_Z"),
(45, "KEY_X"),
(46, "KEY_C"),
(47, "KEY_V"),
(48, "KEY_B"),
(49, "KEY_N"),
(50, "KEY_M"),
(51, "KEY_COMMA"),
(52, "KEY_DOT"),
(53, "KEY_SLASH"),
(54, "KEY_RIGHTSHIFT"),
(55, "KEY_KPASTERISK"),
(56, "KEY_LEFTALT"),
(57, "KEY_SPACE"),
(58, "KEY_CAPSLOCK"),
(59, "KEY_F1"),
(60, "KEY_F2"),
(61, "KEY_F3"),
(62, "KEY_F4"),
(63, "KEY_F5"),
(64, "KEY_F6"),
(65, "KEY_F7"),
(66, "KEY_F8"),
(67, "KEY_F9"),
(68, "KEY_F10"),
(69, "KEY_NUMLOCK"),
(70, "KEY_SCROLLLOCK"),
(71, "KEY_KP7"),
(72, "KEY_KP8"),
(73, "KEY_KP9"),
(74, "KEY_KPMINUS"),
(75, "KEY_KP4"),
(76, "KEY_KP5"),
(77, "KEY_KP6"),
(78, "KEY_KPPLUS"),
(79, "KEY_KP1"),
(80, "KEY_KP2"),
(81, "KEY_KP3"),
(82, "KEY_KP0"),
(83, "KEY_KPDOT"),
(85, "KEY_ZENKAKUHANKAKU"),
(86, "KEY_102ND"),
(87, "KEY_F11"),
(88, "KEY_F12"),
(89, "KEY_RO"),
(90, "KEY_KATAKANA"),
(91, "KEY_HIRAGANA"),
(92, "KEY_HENKAN"),
(93, "KEY_KATAKANAHIRAGANA"),
(94, "KEY_MUHENKAN"),
(95, "KEY_KPJPCOMMA"),
(96, "KEY_KPENTER"),
(97, "KEY_RIGHTCTRL"),
(98, "KEY_KPSLASH"),
(99, "KEY_SYSRQ"),
(100, "KEY_RIGHTALT"),
(101, "KEY_LINEFEED"),
(102, "KEY_HOME"),
(103, "KEY_UP"),
(104, "KEY_PAGEUP"),
(105, "KEY_LEFT"),
(106, "KEY_RIGHT"),
(107, "KEY_END"),
(108, "KEY_DOWN"),
(109, "KEY_PAGEDOWN"),
(110, "KEY_INSERT"),
(111, "KEY_DELETE"),
(112, "KEY_MACRO"),
(113, "KEY_MUTE"),
(114, "KEY_VOLUMEDOWN"),
(115, "KEY_VOLUMEUP"),
(116, "KEY_POWER"), # SC System Power Down
(117, "KEY_KPEQUAL"),
(118, "KEY_KPPLUSMINUS"),
(119, "KEY_PAUSE"),
(120, "KEY_SCALE"), # AL Compiz Scale (Expose)
(121, "KEY_KPCOMMA"),
(122, "KEY_HANGEUL"),
(123, "KEY_HANJA"),
(124, "KEY_YEN"),
(125, "KEY_LEFTMETA"),
(126, "KEY_RIGHTMETA"),
(127, "KEY_COMPOSE"),
(128, "KEY_STOP"), # AC Stop
(129, "KEY_AGAIN"),
(130, "KEY_PROPS"), # AC Properties
(131, "KEY_UNDO"), # AC Undo
(132, "KEY_FRONT"),
(133, "KEY_COPY"), # AC Copy
(134, "KEY_OPEN"), # AC Open
(135, "KEY_PASTE"), # AC Paste
(136, "KEY_FIND"), # AC Search
(137, "KEY_CUT"), # AC Cut
(138, "KEY_HELP"), # AL Integrated Help Center
(139, "KEY_MENU"), # Menu (show menu)
(140, "KEY_CALC"), # AL Calculator
(141, "KEY_SETUP"),
(142, "KEY_SLEEP"), # SC System Sleep
(143, "KEY_WAKEUP"), # System Wake Up
(144, "KEY_FILE"), # AL Local Machine Browser
(145, "KEY_SENDFILE"),
(146, "KEY_DELETEFILE"),
(147, "KEY_XFER"),
(148, "KEY_PROG1"),
(149, "KEY_PROG2"),
(150, "KEY_WWW"), # AL Internet Browser
(151, "KEY_MSDOS"),
(152, "KEY_COFFEE"), # AL Terminal Lock/Screensaver
(153, "KEY_ROTATE_DISPLAY"), # Display orientation for e.g. tablets
(154, "KEY_CYCLEWINDOWS"),
(155, "KEY_MAIL"),
(156, "KEY_BOOKMARKS"), # AC Bookmarks
(157, "KEY_COMPUTER"),
(158, "KEY_BACK"), # AC Back
(159, "KEY_FORWARD"), # AC Forward
(160, "KEY_CLOSECD"),
(161, "KEY_EJECTCD"),
(162, "KEY_EJECTCLOSECD"),
(163, "KEY_NEXTSONG"),
(164, "KEY_PLAYPAUSE"),
(165, "KEY_PREVIOUSSONG"),
(166, "KEY_STOPCD"),
(167, "KEY_RECORD"),
(168, "KEY_REWIND"),
(169, "KEY_PHONE"), # Media Select Telephone
(170, "KEY_ISO"),
(171, "KEY_CONFIG"), # AL Consumer Control Configuration
(172, "KEY_HOMEPAGE"), # AC Home
(173, "KEY_REFRESH"), # AC Refresh
(174, "KEY_EXIT"), # AC Exit
(175, "KEY_MOVE"),
(176, "KEY_EDIT"),
(177, "KEY_SCROLLUP"),
(178, "KEY_SCROLLDOWN"),
(179, "KEY_KPLEFTPAREN"),
(180, "KEY_KPRIGHTPAREN"),
(181, "KEY_NEW"), # AC New
(182, "KEY_REDO"), # AC Redo/Repeat
(183, "KEY_F13"),
(184, "KEY_F14"),
(185, "KEY_F15"),
(186, "KEY_F16"),
(187, "KEY_F17"),
(188, "KEY_F18"),
(189, "KEY_F19"),
(190, "KEY_F20"),
(191, "KEY_F21"),
(192, "KEY_F22"),
(193, "KEY_F23"),
(194, "KEY_F24"),
(200, "KEY_PLAYCD"),
(201, "KEY_PAUSECD"),
(202, "KEY_PROG3"),
(203, "KEY_PROG4"),
(204, "KEY_DASHBOARD"), # AL Dashboard
(205, "KEY_SUSPEND"),
(206, "KEY_CLOSE"), # AC Close
(207, "KEY_PLAY"),
(208, "KEY_FASTFORWARD"),
(209, "KEY_BASSBOOST"),
(210, "KEY_PRINT"), # AC Print
(211, "KEY_HP"),
(212, "KEY_CAMERA"),
(213, "KEY_SOUND"),
(214, "KEY_QUESTION"),
(215, "KEY_EMAIL"),
(216, "KEY_CHAT"),
(217, "KEY_SEARCH"),
(218, "KEY_CONNECT"),
(219, "KEY_FINANCE"), # AL Checkbook/Finance
(220, "KEY_SPORT"),
(221, "KEY_SHOP"),
(222, "KEY_ALTERASE"),
(223, "KEY_CANCEL"), # AC Cancel
(224, "KEY_BRIGHTNESSDOWN"),
(225, "KEY_BRIGHTNESSUP"),
(226, "KEY_MEDIA"),
(227, "KEY_SWITCHVIDEOMODE"), # Cycle between available video
(228, "KEY_KBDILLUMTOGGLE"),
(229, "KEY_KBDILLUMDOWN"),
(230, "KEY_KBDILLUMUP"),
(231, "KEY_SEND"), # AC Send
(232, "KEY_REPLY"), # AC Reply
(233, "KEY_FORWARDMAIL"), # AC Forward Msg
(234, "KEY_SAVE"), # AC Save
(235, "KEY_DOCUMENTS"),
(236, "KEY_BATTERY"),
(237, "KEY_BLUETOOTH"),
(238, "KEY_WLAN"),
(239, "KEY_UWB"),
(240, "KEY_UNKNOWN"),
(241, "KEY_VIDEO_NEXT"), # drive next video source
(242, "KEY_VIDEO_PREV"), # drive previous video source
(243, "KEY_BRIGHTNESS_CYCLE"), # brightness up, after max is min
(244, "KEY_BRIGHTNESS_AUTO"), # Set Auto Brightness: manual
(245, "KEY_DISPLAY_OFF"), # display device to off state
(246, "KEY_WWAN"), # Wireless WAN (LTE, UMTS, GSM, etc.)
(247, "KEY_RFKILL"), # Key that controls all radios
(248, "KEY_MICMUTE"), # Mute / unmute the microphone
(0x100, "BTN_MISC"),
(0x100, "BTN_0"),
(0x101, "BTN_1"),
(0x102, "BTN_2"),
(0x103, "BTN_3"),
(0x104, "BTN_4"),
(0x105, "BTN_5"),
(0x106, "BTN_6"),
(0x107, "BTN_7"),
(0x108, "BTN_8"),
(0x109, "BTN_9"),
(0x110, "BTN_MOUSE"),
(0x110, "BTN_LEFT"),
(0x111, "BTN_RIGHT"),
(0x112, "BTN_MIDDLE"),
(0x113, "BTN_SIDE"),
(0x114, "BTN_EXTRA"),
(0x115, "BTN_FORWARD"),
(0x116, "BTN_BACK"),
(0x117, "BTN_TASK"),
(0x120, "BTN_JOYSTICK"),
(0x120, "BTN_TRIGGER"),
(0x121, "BTN_THUMB"),
(0x122, "BTN_THUMB2"),
(0x123, "BTN_TOP"),
(0x124, "BTN_TOP2"),
(0x125, "BTN_PINKIE"),
(0x126, "BTN_BASE"),
(0x127, "BTN_BASE2"),
(0x128, "BTN_BASE3"),
(0x129, "BTN_BASE4"),
(0x12A, "BTN_BASE5"),
(0x12B, "BTN_BASE6"),
(0x12F, "BTN_DEAD"),
(0x130, "BTN_GAMEPAD"),
(0x130, "BTN_SOUTH"),
(0x131, "BTN_EAST"),
(0x132, "BTN_C"),
(0x133, "BTN_NORTH"),
(0x134, "BTN_WEST"),
(0x135, "BTN_Z"),
(0x136, "BTN_TL"),
(0x137, "BTN_TR"),
(0x138, "BTN_TL2"),
(0x139, "BTN_TR2"),
(0x13A, "BTN_SELECT"),
(0x13B, "BTN_START"),
(0x13C, "BTN_MODE"),
(0x13D, "BTN_THUMBL"),
(0x13E, "BTN_THUMBR"),
(0x140, "BTN_DIGI"),
(0x140, "BTN_TOOL_PEN"),
(0x141, "BTN_TOOL_RUBBER"),
(0x142, "BTN_TOOL_BRUSH"),
(0x143, "BTN_TOOL_PENCIL"),
(0x144, "BTN_TOOL_AIRBRUSH"),
(0x145, "BTN_TOOL_FINGER"),
(0x146, "BTN_TOOL_MOUSE"),
(0x147, "BTN_TOOL_LENS"),
(0x148, "BTN_TOOL_QUINTTAP"), # Five fingers on trackpad
(0x14A, "BTN_TOUCH"),
(0x14B, "BTN_STYLUS"),
(0x14C, "BTN_STYLUS2"),
(0x14D, "BTN_TOOL_DOUBLETAP"),
(0x14E, "BTN_TOOL_TRIPLETAP"),
(0x14F, "BTN_TOOL_QUADTAP"), # Four fingers on trackpad
(0x150, "BTN_WHEEL"),
(0x150, "BTN_GEAR_DOWN"),
(0x151, "BTN_GEAR_UP"),
(0x160, "KEY_OK"),
(0x161, "KEY_SELECT"),
(0x162, "KEY_GOTO"),
(0x163, "KEY_CLEAR"),
(0x164, "KEY_POWER2"),
(0x165, "KEY_OPTION"),
(0x166, "KEY_INFO"), # AL OEM Features/Tips/Tutorial
(0x167, "KEY_TIME"),
(0x168, "KEY_VENDOR"),
(0x169, "KEY_ARCHIVE"),
(0x16A, "KEY_PROGRAM"), # Media Select Program Guide
(0x16B, "KEY_CHANNEL"),
(0x16C, "KEY_FAVORITES"),
(0x16D, "KEY_EPG"),
(0x16E, "KEY_PVR"), # Media Select Home
(0x16F, "KEY_MHP"),
(0x170, "KEY_LANGUAGE"),
(0x171, "KEY_TITLE"),
(0x172, "KEY_SUBTITLE"),
(0x173, "KEY_ANGLE"),
(0x174, "KEY_ZOOM"),
(0x175, "KEY_MODE"),
(0x176, "KEY_KEYBOARD"),
(0x177, "KEY_SCREEN"),
(0x178, "KEY_PC"), # Media Select Computer
(0x179, "KEY_TV"), # Media Select TV
(0x17A, "KEY_TV2"), # Media Select Cable
(0x17B, "KEY_VCR"), # Media Select VCR
(0x17C, "KEY_VCR2"), # VCR Plus
(0x17D, "KEY_SAT"), # Media Select Satellite
(0x17E, "KEY_SAT2"),
(0x17F, "KEY_CD"), # Media Select CD
(0x180, "KEY_TAPE"), # Media Select Tape
(0x181, "KEY_RADIO"),
(0x182, "KEY_TUNER"), # Media Select Tuner
(0x183, "KEY_PLAYER"),
(0x184, "KEY_TEXT"),
(0x185, "KEY_DVD"), # Media Select DVD
(0x186, "KEY_AUX"),
(0x187, "KEY_MP3"),
(0x188, "KEY_AUDIO"), # AL Audio Browser
(0x189, "KEY_VIDEO"), # AL Movie Browser
(0x18A, "KEY_DIRECTORY"),
(0x18B, "KEY_LIST"),
(0x18C, "KEY_MEMO"), # Media Select Messages
(0x18D, "KEY_CALENDAR"),
(0x18E, "KEY_RED"),
(0x18F, "KEY_GREEN"),
(0x190, "KEY_YELLOW"),
(0x191, "KEY_BLUE"),
(0x192, "KEY_CHANNELUP"), # Channel Increment
(0x193, "KEY_CHANNELDOWN"), # Channel Decrement
(0x194, "KEY_FIRST"),
(0x195, "KEY_LAST"), # Recall Last
(0x196, "KEY_AB"),
(0x197, "KEY_NEXT"),
(0x198, "KEY_RESTART"),
(0x199, "KEY_SLOW"),
(0x19A, "KEY_SHUFFLE"),
(0x19B, "KEY_BREAK"),
(0x19C, "KEY_PREVIOUS"),
(0x19D, "KEY_DIGITS"),
(0x19E, "KEY_TEEN"),
(0x19F, "KEY_TWEN"),
(0x1A0, "KEY_VIDEOPHONE"), # Media Select Video Phone
(0x1A1, "KEY_GAMES"), # Media Select Games
(0x1A2, "KEY_ZOOMIN"), # AC Zoom In
(0x1A3, "KEY_ZOOMOUT"), # AC Zoom Out
(0x1A4, "KEY_ZOOMRESET"), # AC Zoom
(0x1A5, "KEY_WORDPROCESSOR"), # AL Word Processor
(0x1A6, "KEY_EDITOR"), # AL Text Editor
(0x1A7, "KEY_SPREADSHEET"), # AL Spreadsheet
(0x1A8, "KEY_GRAPHICSEDITOR"), # AL Graphics Editor
(0x1A9, "KEY_PRESENTATION"), # AL Presentation App
(0x1AA, "KEY_DATABASE"), # AL Database App
(0x1AB, "KEY_NEWS"), # AL Newsreader
(0x1AC, "KEY_VOICEMAIL"), # AL Voicemail
(0x1AD, "KEY_ADDRESSBOOK"), # AL Contacts/Address Book
(0x1AE, "KEY_MESSENGER"), # AL Instant Messaging
(0x1AF, "KEY_DISPLAYTOGGLE"), # Turn display (LCD) on and off
(0x1B0, "KEY_SPELLCHECK"), # AL Spell Check
(0x1B1, "KEY_LOGOFF"), # AL Logoff
(0x1B2, "KEY_DOLLAR"),
(0x1B3, "KEY_EURO"),
(0x1B4, "KEY_FRAMEBACK"), # Consumer - transport controls
(0x1B5, "KEY_FRAMEFORWARD"),
(0x1B6, "KEY_CONTEXT_MENU"), # GenDesc - system context menu
(0x1B7, "KEY_MEDIA_REPEAT"), # Consumer - transport control
(0x1B8, "KEY_10CHANNELSUP"), # 10 channels up (10+)
(0x1B9, "KEY_10CHANNELSDOWN"), # 10 channels down (10-)
(0x1BA, "KEY_IMAGES"), # AL Image Browser
(0x1C0, "KEY_DEL_EOL"),
(0x1C1, "KEY_DEL_EOS"),
(0x1C2, "KEY_INS_LINE"),
(0x1C3, "KEY_DEL_LINE"),
(0x1D0, "KEY_FN"),
(0x1D1, "KEY_FN_ESC"),
(0x1D2, "KEY_FN_F1"),
(0x1D3, "KEY_FN_F2"),
(0x1D4, "KEY_FN_F3"),
(0x1D5, "KEY_FN_F4"),
(0x1D6, "KEY_FN_F5"),
(0x1D7, "KEY_FN_F6"),
(0x1D8, "KEY_FN_F7"),
(0x1D9, "KEY_FN_F8"),
(0x1DA, "KEY_FN_F9"),
(0x1DB, "KEY_FN_F10"),
(0x1DC, "KEY_FN_F11"),
(0x1DD, "KEY_FN_F12"),
(0x1DE, "KEY_FN_1"),
(0x1DF, "KEY_FN_2"),
(0x1E0, "KEY_FN_D"),
(0x1E1, "KEY_FN_E"),
(0x1E2, "KEY_FN_F"),
(0x1E3, "KEY_FN_S"),
(0x1E4, "KEY_FN_B"),
(0x1F1, "KEY_BRL_DOT1"),
(0x1F2, "KEY_BRL_DOT2"),
(0x1F3, "KEY_BRL_DOT3"),
(0x1F4, "KEY_BRL_DOT4"),
(0x1F5, "KEY_BRL_DOT5"),
(0x1F6, "KEY_BRL_DOT6"),
(0x1F7, "KEY_BRL_DOT7"),
(0x1F8, "KEY_BRL_DOT8"),
(0x1F9, "KEY_BRL_DOT9"),
(0x1FA, "KEY_BRL_DOT10"),
(0x200, "KEY_NUMERIC_0"), # used by phones, remote controls,
(0x201, "KEY_NUMERIC_1"), # and other keypads
(0x202, "KEY_NUMERIC_2"),
(0x203, "KEY_NUMERIC_3"),
(0x204, "KEY_NUMERIC_4"),
(0x205, "KEY_NUMERIC_5"),
(0x206, "KEY_NUMERIC_6"),
(0x207, "KEY_NUMERIC_7"),
(0x208, "KEY_NUMERIC_8"),
(0x209, "KEY_NUMERIC_9"),
(0x20A, "KEY_NUMERIC_STAR"),
(0x20B, "KEY_NUMERIC_POUND"),
(0x20C, "KEY_NUMERIC_A"), # Phone key A - HUT Telephony 0xb9
(0x20D, "KEY_NUMERIC_B"),
(0x20E, "KEY_NUMERIC_C"),
(0x20F, "KEY_NUMERIC_D"),
(0x210, "KEY_CAMERA_FOCUS"),
(0x211, "KEY_WPS_BUTTON"), # WiFi Protected Setup key
(0x212, "KEY_TOUCHPAD_TOGGLE"), # Request switch touchpad on or off
(0x213, "KEY_TOUCHPAD_ON"),
(0x214, "KEY_TOUCHPAD_OFF"),
(0x215, "KEY_CAMERA_ZOOMIN"),
(0x216, "KEY_CAMERA_ZOOMOUT"),
(0x217, "KEY_CAMERA_UP"),
(0x218, "KEY_CAMERA_DOWN"),
(0x219, "KEY_CAMERA_LEFT"),
(0x21A, "KEY_CAMERA_RIGHT"),
(0x21B, "KEY_ATTENDANT_ON"),
(0x21C, "KEY_ATTENDANT_OFF"),
(0x21D, "KEY_ATTENDANT_TOGGLE"), # Attendant call on or off
(0x21E, "KEY_LIGHTS_TOGGLE"), # Reading light on or off
(0x220, "BTN_DPAD_UP"),
(0x221, "BTN_DPAD_DOWN"),
(0x222, "BTN_DPAD_LEFT"),
(0x223, "BTN_DPAD_RIGHT"),
(0x230, "KEY_ALS_TOGGLE"), # Ambient light sensor
(0x240, "KEY_BUTTONCONFIG"), # AL Button Configuration
(0x241, "KEY_TASKMANAGER"), # AL Task/Project Manager
(0x242, "KEY_JOURNAL"), # AL Log/Journal/Timecard
(0x243, "KEY_CONTROLPANEL"), # AL Control Panel
(0x244, "KEY_APPSELECT"), # AL Select Task/Application
(0x245, "KEY_SCREENSAVER"), # AL Screen Saver
(0x246, "KEY_VOICECOMMAND"), # Listening Voice Command
(0x250, "KEY_BRIGHTNESS_MIN"), # Set Brightness to Minimum
(0x251, "KEY_BRIGHTNESS_MAX"), # Set Brightness to Maximum
(0x260, "KEY_KBDINPUTASSIST_PREV"),
(0x261, "KEY_KBDINPUTASSIST_NEXT"),
(0x262, "KEY_KBDINPUTASSIST_PREVGROUP"),
(0x263, "KEY_KBDINPUTASSIST_NEXTGROUP"),
(0x264, "KEY_KBDINPUTASSIST_ACCEPT"),
(0x265, "KEY_KBDINPUTASSIST_CANCEL"),
(0x2C0, "BTN_TRIGGER_HAPPY"),
(0x2C0, "BTN_TRIGGER_HAPPY1"),
(0x2C1, "BTN_TRIGGER_HAPPY2"),
(0x2C2, "BTN_TRIGGER_HAPPY3"),
(0x2C3, "BTN_TRIGGER_HAPPY4"),
(0x2C4, "BTN_TRIGGER_HAPPY5"),
(0x2C5, "BTN_TRIGGER_HAPPY6"),
(0x2C6, "BTN_TRIGGER_HAPPY7"),
(0x2C7, "BTN_TRIGGER_HAPPY8"),
(0x2C8, "BTN_TRIGGER_HAPPY9"),
(0x2C9, "BTN_TRIGGER_HAPPY10"),
(0x2CA, "BTN_TRIGGER_HAPPY11"),
(0x2CB, "BTN_TRIGGER_HAPPY12"),
(0x2CC, "BTN_TRIGGER_HAPPY13"),
(0x2CD, "BTN_TRIGGER_HAPPY14"),
(0x2CE, "BTN_TRIGGER_HAPPY15"),
(0x2CF, "BTN_TRIGGER_HAPPY16"),
(0x2D0, "BTN_TRIGGER_HAPPY17"),
(0x2D1, "BTN_TRIGGER_HAPPY18"),
(0x2D2, "BTN_TRIGGER_HAPPY19"),
(0x2D3, "BTN_TRIGGER_HAPPY20"),
(0x2D4, "BTN_TRIGGER_HAPPY21"),
(0x2D5, "BTN_TRIGGER_HAPPY22"),
(0x2D6, "BTN_TRIGGER_HAPPY23"),
(0x2D7, "BTN_TRIGGER_HAPPY24"),
(0x2D8, "BTN_TRIGGER_HAPPY25"),
(0x2D9, "BTN_TRIGGER_HAPPY26"),
(0x2DA, "BTN_TRIGGER_HAPPY27"),
(0x2DB, "BTN_TRIGGER_HAPPY28"),
(0x2DC, "BTN_TRIGGER_HAPPY29"),
(0x2DD, "BTN_TRIGGER_HAPPY30"),
(0x2DE, "BTN_TRIGGER_HAPPY31"),
(0x2DF, "BTN_TRIGGER_HAPPY32"),
(0x2E0, "BTN_TRIGGER_HAPPY33"),
(0x2E1, "BTN_TRIGGER_HAPPY34"),
(0x2E2, "BTN_TRIGGER_HAPPY35"),
(0x2E3, "BTN_TRIGGER_HAPPY36"),
(0x2E4, "BTN_TRIGGER_HAPPY37"),
(0x2E5, "BTN_TRIGGER_HAPPY38"),
(0x2E6, "BTN_TRIGGER_HAPPY39"),
(0x2E7, "BTN_TRIGGER_HAPPY40"),
(0x2FF, "KEY_MAX"),
(0x2FF + 1, "KEY_CNT"),
)
RELATIVE_AXES = (
(0x00, "REL_X"),
(0x01, "REL_Y"),
(0x02, "REL_Z"),
(0x03, "REL_RX"),
(0x04, "REL_RY"),
(0x05, "REL_RZ"),
(0x06, "REL_HWHEEL"),
(0x07, "REL_DIAL"),
(0x08, "REL_WHEEL"),
(0x09, "REL_MISC"),
(0x0F, "REL_MAX"),
(0x0F + 1, "REL_CNT"),
)
ABSOLUTE_AXES = (
(0x00, "ABS_X"),
(0x01, "ABS_Y"),
(0x02, "ABS_Z"),
(0x03, "ABS_RX"),
(0x04, "ABS_RY"),
(0x05, "ABS_RZ"),
(0x06, "ABS_THROTTLE"),
(0x07, "ABS_RUDDER"),
(0x08, "ABS_WHEEL"),
(0x09, "ABS_GAS"),
(0x0A, "ABS_BRAKE"),
(0x10, "ABS_HAT0X"),
(0x11, "ABS_HAT0Y"),
(0x12, "ABS_HAT1X"),
(0x13, "ABS_HAT1Y"),
(0x14, "ABS_HAT2X"),
(0x15, "ABS_HAT2Y"),
(0x16, "ABS_HAT3X"),
(0x17, "ABS_HAT3Y"),
(0x18, "ABS_PRESSURE"),
(0x19, "ABS_DISTANCE"),
(0x1A, "ABS_TILT_X"),
(0x1B, "ABS_TILT_Y"),
(0x1C, "ABS_TOOL_WIDTH"),
(0x20, "ABS_VOLUME"),
(0x28, "ABS_MISC"),
(0x2F, "ABS_MT_SLOT"), # MT slot being modified
(0x30, "ABS_MT_TOUCH_MAJOR"), # Major axis of touching ellipse
(0x31, "ABS_MT_TOUCH_MINOR"), # Minor axis (omit if circular)
(0x32, "ABS_MT_WIDTH_MAJOR"), # Major axis of approaching ellipse
(0x33, "ABS_MT_WIDTH_MINOR"), # Minor axis (omit if circular)
(0x34, "ABS_MT_ORIENTATION"), # Ellipse orientation
(0x35, "ABS_MT_POSITION_X"), # Center X touch position
(0x36, "ABS_MT_POSITION_Y"), # Center Y touch position
(0x37, "ABS_MT_TOOL_TYPE"), # Type of touching device
(0x38, "ABS_MT_BLOB_ID"), # Group a set of packets as a blob
(0x39, "ABS_MT_TRACKING_ID"), # Unique ID of initiated contact
(0x3A, "ABS_MT_PRESSURE"), # Pressure on contact area
(0x3B, "ABS_MT_DISTANCE"), # Contact hover distance
(0x3C, "ABS_MT_TOOL_X"), # Center X tool position
(0x3D, "ABS_MT_TOOL_Y"), # Center Y tool position
(0x3F, "ABS_MAX"),
(0x3F + 1, "ABS_CNT"),
)
SWITCH_EVENTS = (
(0x00, "SW_LID"), # set = lid shut
(0x01, "SW_TABLET_MODE"), # set = tablet mode
(0x02, "SW_HEADPHONE_INSERT"), # set = inserted
(0x03, "SW_RFKILL_ALL"), # rfkill master switch, type "any"
(0x04, "SW_MICROPHONE_INSERT"), # set = inserted
(0x05, "SW_DOCK"), # set = plugged into dock
(0x06, "SW_LINEOUT_INSERT"), # set = inserted
(0x07, "SW_JACK_PHYSICAL_INSERT"), # set = mechanical switch set
(0x08, "SW_VIDEOOUT_INSERT"), # set = inserted
(0x09, "SW_CAMERA_LENS_COVER"), # set = lens covered
(0x0A, "SW_KEYPAD_SLIDE"), # set = keypad slide out
(0x0B, "SW_FRONT_PROXIMITY"), # set = front proximity sensor active
(0x0C, "SW_ROTATE_LOCK"), # set = rotate locked/disabled
(0x0D, "SW_LINEIN_INSERT"), # set = inserted
(0x0E, "SW_MUTE_DEVICE"), # set = device disabled
(0x0F, "SW_MAX"),
(0x0F + 1, "SW_CNT"),
)
MISC_EVENTS = (
(0x00, "MSC_SERIAL"),
(0x01, "MSC_PULSELED"),
(0x02, "MSC_GESTURE"),
(0x03, "MSC_RAW"),
(0x04, "MSC_SCAN"),
(0x05, "MSC_TIMESTAMP"),
(0x07, "MSC_MAX"),
(0x07 + 1, "MSC_CNT"),
)
LEDS = (
(0x00, "LED_NUML"),
(0x01, "LED_CAPSL"),
(0x02, "LED_SCROLLL"),
(0x03, "LED_COMPOSE"),
(0x04, "LED_KANA"),
(0x05, "LED_SLEEP"),
(0x06, "LED_SUSPEND"),
(0x07, "LED_MUTE"),
(0x08, "LED_MISC"),
(0x09, "LED_MAIL"),
(0x0A, "LED_CHARGING"),
(0x0F, "LED_MAX"),
(0x0F + 1, "LED_CNT"),
)
LED_TYPE_CODES = (
('numlock', 0x00),
('capslock', 0x01),
('scrolllock', 0x02),
('compose', 0x03),
('kana', 0x04),
('sleep', 0x05),
('suspend', 0x06),
('mute', 0x07),
('misc', 0x08),
('mail', 0x09),
('charging', 0x0A),
('max', 0x0F),
('cnt', 0x0F + 1),
)
AUTOREPEAT_VALUES = (
(0x00, "REP_DELAY"),
(0x01, "REP_PERIOD"),
(0x01, "REP_MAX"),
(0x01 + 1, "REP_CNT"),
)
SOUNDS = (
(0x00, "SND_CLICK"),
(0x01, "SND_BELL"),
(0x02, "SND_TONE"),
(0x07, "SND_MAX"),
(0x07 + 1, "SND_CNT"),
)
WIN_KEYBOARD_CODES = {
0x0100: 1,
0x0101: 0,
0x104: 1,
0x105: 0,
}
WIN_MOUSE_CODES = {
0x0201: (0x110, 1, 589825), # WM_LBUTTONDOWN --> BTN_LEFT
0x0202: (0x110, 0, 589825), # WM_LBUTTONUP --> BTN_LEFT
0x0204: (0x111, 1, 589826), # WM_RBUTTONDOWN --> BTN_RIGHT
0x0205: (0x111, 0, 589826), # WM_RBUTTONUP --> BTN_RIGHT
0x0207: (0x112, 1, 589827), # WM_MBUTTONDOWN --> BTN_MIDDLE
0x0208: (0x112, 0, 589827), # WM_MBUTTONU --> BTN_MIDDLE
0x020B: (0x113, 1, 589828), # WM_XBUTTONDOWN --> BTN_SIDE
0x020C: (0x113, 0, 589828), # WM_XBUTTONUP --> BTN_SIDE
0x020B2: (0x114, 1, 589829), # WM_XBUTTONDOWN --> BTN_EXTRA
0x020C2: (0x114, 0, 589829), # WM_XBUTTONUP --> BTN_EXTRA
}
# THING SING That thing can sing!
# SONG LONG A long, long song.
# Good-bye, Thing. You sing too long.
# pylint: disable=too-many-lines
WINCODES = (
(0x01, 0x110), # Left mouse button
(0x02, 0x111), # Right mouse button
(0x03, 0), # Control-break processing
(0x04, 0x112), # Middle mouse button (three-button mouse)
(0x05, 0x113), # X1 mouse button
(0x06, 0x114), # X2 mouse button
(0x07, 0), # Undefined
(0x08, 14), # BACKSPACE key
(0x09, 15), # TAB key
(0x0A, 0), # Reserved
(0x0B, 0), # Reserved
(0x0C, 0x163), # CLEAR key
(0x0D, 28), # ENTER key
(0x0E, 0), # Undefined
(0x0F, 0), # Undefined
(0x10, 42), # SHIFT key
(0x11, 29), # CTRL key
(0x12, 56), # ALT key
(0x13, 119), # PAUSE key
(0x14, 58), # CAPS LOCK key
(0x15, 90), # IME Kana mode
(0x15, 91), # IME Hanguel mode (maintained for compatibility; use
# VK_HANGUL)
(0x15, 91), # IME Hangul mode
(0x16, 0), # Undefined
(0x17, 92), # IME Junja mode - These all need to be fixed
(0x18, 93), # IME final mode - By someone who
(0x19, 94), # IME Hanja mode - Knows how
(0x19, 95), # IME Kanji mode - Japanese Keyboards work
(0x1A, 0), # Undefined
(0x1B, 1), # ESC key
(0x1C, 0), # IME convert
(0x1D, 0), # IME nonconvert
(0x1E, 0), # IME accept
(0x1F, 0), # IME mode change request
(0x20, 57), # SPACEBAR
(0x21, 104), # PAGE UP key
(0x22, 109), # PAGE DOWN key
(0x23, 107), # END key
(0x24, 102), # HOME key
(0x25, 105), # LEFT ARROW key
(0x26, 103), # UP ARROW key
(0x27, 106), # RIGHT ARROW key
(0x28, 108), # DOWN ARROW key
(0x29, 0x161), # SELECT key
(0x2A, 210), # PRINT key
(0x2B, 28), # EXECUTE key
(0x2C, 99), # PRINT SCREEN key
(0x2D, 110), # INS key
(0x2E, 111), # DEL key
(0x2F, 138), # HELP key
(0x30, 11), # 0 key
(0x31, 2), # 1 key
(0x32, 3), # 2 key
(0x33, 4), # 3 key
(0x34, 5), # 4 key
(0x35, 6), # 5 key
(0x36, 7), # 6 key
(0x37, 8), # 7 key
(0x38, 9), # 8 key
(0x39, 10), # 9 key
# (0x3A-40, 0), # Undefined
(0x41, 30), # A key
(0x42, 48), # B key
(0x43, 46), # C key
(0x44, 32), # D key
(0x45, 18), # E key
(0x46, 33), # F key
(0x47, 34), # G key
(0x48, 35), # H key
(0x49, 23), # I key
(0x4A, 36), # J key
(0x4B, 37), # K key
(0x4C, 38), # L key
(0x4D, 50), # M key
(0x4E, 49), # N key
(0x4F, 24), # O key
(0x50, 25), # P key
(0x51, 16), # Q key
(0x52, 19), # R key
(0x53, 31), # S key
(0x54, 20), # T key
(0x55, 22), # U key
(0x56, 47), # V key
(0x57, 17), # W key
(0x58, 45), # X key
(0x59, 21), # Y key
(0x5A, 44), # Z key
(0x5B, 125), # Left Windows key (Natural keyboard)
(0x5C, 126), # Right Windows key (Natural keyboard)
(0x5D, 139), # Applications key (Natural keyboard)
(0x5E, 0), # Reserved
(0x5F, 142), # Computer Sleep key
(0x60, 82), # Numeric keypad 0 key
(0x61, 79), # Numeric keypad 1 key
(0x62, 80), # Numeric keypad 2 key
(0x63, 81), # Numeric keypad 3 key
(0x64, 75), # Numeric keypad 4 key
(0x65, 76), # Numeric keypad 5 key
(0x66, 77), # Numeric keypad 6 key
(0x67, 71), # Numeric keypad 7 key
(0x68, 72), # Numeric keypad 8 key
(0x69, 73), # Numeric keypad 9 key
(0x6A, 55), # Multiply key
(0x6B, 78), # Add key
(0x6C, 96), # Separator key
(0x6D, 74), # Subtract key
(0x6E, 83), # Decimal key
(0x6F, 98), # Divide key
(0x70, 59), # F1 key
(0x71, 60), # F2 key
(0x72, 61), # F3 key
(0x73, 62), # F4 key
(0x74, 63), # F5 key
(0x75, 64), # F6 key
(0x76, 65), # F7 key
(0x77, 66), # F8 key
(0x78, 67), # F9 key
(0x79, 68), # F10 key
(0x7A, 87), # F11 key
(0x7B, 88), # F12 key
(0x7C, 183), # F13 key
(0x7D, 184), # F14 key
(0x7E, 185), # F15 key
(0x7F, 186), # F16 key
(0x80, 187), # F17 key
(0x81, 188), # F18 key
(0x82, 189), # F19 key
(0x83, 190), # F20 key
(0x84, 191), # F21 key
(0x85, 192), # F22 key
(0x86, 192), # F23 key
(0x87, 194), # F24 key
# (0x88-8F, 0), # Unassigned
(0x90, 69), # NUM LOCK key
(0x91, 70), # SCROLL LOCK key
# (0x92-96, 0), # OEM specific
# (0x97-9F, 0), # Unassigned
(0xA0, 42), # Left SHIFT key
(0xA1, 54), # Right SHIFT key
(0xA2, 29), # Left CONTROL key
(0xA3, 97), # Right CONTROL key
(0xA4, 125), # Left MENU key
(0xA5, 126), # Right MENU key
(0xA6, 158), # Browser Back key
(0xA7, 159), # Browser Forward key
(0xA8, 173), # Browser Refresh key
(0xA9, 128), # Browser Stop key
(0xAA, 217), # Browser Search key
(0xAB, 0x16C), # Browser Favorites key
(0xAC, 150), # Browser Start and Home key
(0xAD, 113), # Volume Mute key
(0xAE, 114), # Volume Down key
(0xAF, 115), # Volume Up key
(0xB0, 163), # Next Track key
(0xB1, 165), # Previous Track key
(0xB2, 166), # Stop Media key
(0xB3, 164), # Play/Pause Media key
(0xB4, 155), # Start Mail key
(0xB5, 0x161), # Select Media key
(0xB6, 148), # Start Application 1 key
(0xB7, 149), # Start Application 2 key
# (0xB8-B9, 0), # Reserved
(0xBA, 39), # Used for miscellaneous characters; it can vary by keyboard.
(0xBB, 13), # For any country/region, the '+' key
(0xBC, 51), # For any country/region, the ',' key
(0xBD, 12), # For any country/region, the '-' key
(0xBE, 52), # For any country/region, the '.' key
(0xBF, 53), # Slash
(0xC0, 40), # Apostrophe
# (0xC1-D7, 0), # Reserved
# (0xD8-DA, 0), # Unassigned
(0xDB, 26), # [
(0xDC, 86), # \
(0xDD, 27), # ]
(0xDE, 43), # '
(0xDF, 119), # VK_OFF - What's that?
(0xE0, 0), # Reserved
(0xE1, 0), # OEM Specific
(0xE2, 43), # Either the angle bracket key or the backslash key
# on the RT 102-key keyboard (0xE3-E4, 0), # OEM
# specific
(0xE5, 0), # IME PROCESS key
(0xE6, 0), # OEM specific
(0xE7, 0), # Used to pass Unicode characters as if they were
# keystrokes. The VK_PACKET key is the low word of a
# 32-bit Virtual Key value used for non-keyboard input
# methods. For more information, see Remark in
# KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP
(0xE8, 0), # Unassigned
# (0xE9-F5, 0), # OEM specific
(0xF6, 0), # Attn key
(0xF7, 0), # CrSel key
(0xF8, 0), # ExSel key
(0xF9, 222), # Erase EOF key
(0xFA, 207), # Play key
(0xFB, 0x174), # Zoom key
(0xFC, 0), # Reserved
(0xFD, 0x19B), # PA1 key
(0xFE, 0x163), # Clear key
(0xFF, 185),
)
MAC_EVENT_CODES = (
# NSLeftMouseDown Quartz.kCGEventLeftMouseDown
(1, ("Key", 0x110, 1, 589825)),
# NSLeftMouseUp Quartz.kCGEventLeftMouseUp
(2, ("Key", 0x110, 0, 589825)),
# NSRightMouseDown Quartz.kCGEventRightMouseDown
(3, ("Key", 0x111, 1, 589826)),
# NSRightMouseUp Quartz.kCGEventRightMouseUp
(4, ("Key", 0x111, 0, 589826)),
(5, (None, 0, 0, 0)), # NSMouseMoved Quartz.kCGEventMouseMoved
(6, (None, 0, 0, 0)), # NSLeftMouseDragged Quartz.kCGEventLeftMouseDragged
# NSRightMouseDragged Quartz.kCGEventRightMouseDragged
(7, (None, 0, 0, 0)),
(8, (None, 0, 0, 0)), # NSMouseEntered
(9, (None, 0, 0, 0)), # NSMouseExited
(10, (None, 0, 0, 0)), # NSKeyDown
(11, (None, 0, 0, 0)), # NSKeyUp
(12, (None, 0, 0, 0)), # NSFlagsChanged
(13, (None, 0, 0, 0)), # NSAppKitDefined
(14, (None, 0, 0, 0)), # NSSystemDefined
(15, (None, 0, 0, 0)), # NSApplicationDefined
(16, (None, 0, 0, 0)), # NSPeriodic
(17, (None, 0, 0, 0)), # NSCursorUpdate
(22, (None, 0, 0, 0)), # NSScrollWheel Quartz.kCGEventScrollWheel
(23, (None, 0, 0, 0)), # NSTabletPoint Quartz.kCGEventTabletPointer
(24, (None, 0, 0, 0)), # NSTabletProximity Quartz.kCGEventTabletProximity
(25, (None, 0, 0, 0)), # NSOtherMouseDown Quartz.kCGEventOtherMouseDown
(25.2, ("Key", 0x112, 1, 589827)), # BTN_MIDDLE
(25.3, ("Key", 0x113, 1, 589828)), # BTN_SIDE
(25.4, ("Key", 0x114, 1, 589829)), # BTN_EXTRA
(26, (None, 0, 0, 0)), # NSOtherMouseUp Quartz.kCGEventOtherMouseUp
(26.2, ("Key", 0x112, 0, 589827)), # BTN_MIDDLE
(26.3, ("Key", 0x113, 0, 589828)), # BTN_SIDE
(26.4, ("Key", 0x114, 0, 589829)), # BTN_EXTRA
(27, (None, 0, 0, 0)), # NSOtherMouseDragged
(29, (None, 0, 0, 0)), # NSEventTypeGesture
(30, (None, 0, 0, 0)), # NSEventTypeMagnify
(31, (None, 0, 0, 0)), # NSEventTypeSwipe
(18, (None, 0, 0, 0)), # NSEventTypeRotate
(19, (None, 0, 0, 0)), # NSEventTypeBeginGesture
(20, (None, 0, 0, 0)), # NSEventTypeEndGesture
(27, (None, 0, 0, 0)), # Quartz.kCGEventOtherMouseDragged
(32, (None, 0, 0, 0)), # NSEventTypeSmartMagnify
(33, (None, 0, 0, 0)), # NSEventTypeQuickLook
(34, (None, 0, 0, 0)), # NSEventTypePressure
)
MAC_KEYS = (
(0x00, 30), # kVK_ANSI_A
(0x01, 31), # kVK_ANSI_S (0x02, 32), # kVK_ANSI_D
(0x03, 33), # kVK_ANSI_F
(0x04, 35), # kVK_ANSI_H
(0x05, 34), # kVK_ANSI_G
(0x06, 44), # kVK_ANSI_Z
(0x07, 45), # kVK_ANSI_X
(0x08, 46), # kVK_ANSI_C
(0x09, 47), # kVK_ANSI_V
(0x0B, 48), # kVK_ANSI_B
(0x0C, 16), # kVK_ANSI_Q
(0x0D, 17), # kVK_ANSI_W
(0x0E, 18), # kVK_ANSI_E
(0x0F, 33), # kVK_ANSI_R
(0x10, 21), # kVK_ANSI_Y
(0x11, 20), # kVK_ANSI_T
(0x12, 2), # kVK_ANSI_1
(0x13, 3), # kVK_ANSI_2
(0x14, 4), # kVK_ANSI_3
(0x15, 5), # kVK_ANSI_4
(0x16, 7), # kVK_ANSI_6
(0x17, 6), # kVK_ANSI_5
(0x18, 13), # kVK_ANSI_Equal
(0x19, 10), # kVK_ANSI_9
(0x1A, 8), # kVK_ANSI_7
(0x1B, 12), # kVK_ANSI_Minus
(0x1C, 9), # kVK_ANSI_8
(0x1D, 11), # kVK_ANSI_0
(0x1E, 27), # kVK_ANSI_RightBracket
(0x1F, 24), # kVK_ANSI_O
(0x20, 22), # kVK_ANSI_U
(0x21, 26), # kVK_ANSI_LeftBracket
(0x22, 23), # kVK_ANSI_I
(0x23, 25), # kVK_ANSI_P
(0x25, 38), # kVK_ANSI_L
(0x26, 36), # kVK_ANSI_J
(0x27, 40), # kVK_ANSI_Quote
(0x28, 37), # kVK_ANSI_K
(0x29, 39), # kVK_ANSI_Semicolon
(0x2A, 43), # kVK_ANSI_Backslash
(0x2B, 51), # kVK_ANSI_Comma
(0x2C, 53), # kVK_ANSI_Slash
(0x2D, 49), # kVK_ANSI_N
(0x2E, 50), # kVK_ANSI_M
(0x2F, 52), # kVK_ANSI_Period
(0x32, 41), # kVK_ANSI_Grave
(0x41, 83), # kVK_ANSI_KeypadDecimal
(0x43, 55), # kVK_ANSI_KeypadMultiply
(0x45, 78), # kVK_ANSI_KeypadPlus
(0x47, 69), # kVK_ANSI_KeypadClear
(0x4B, 98), # kVK_ANSI_KeypadDivide
(0x4C, 96), # kVK_ANSI_KeypadEnter
(0x4E, 74), # kVK_ANSI_KeypadMinus
(0x51, 117), # kVK_ANSI_KeypadEquals
(0x52, 82), # kVK_ANSI_Keypad0
(0x53, 79), # kVK_ANSI_Keypad1
(0x54, 80), # kVK_ANSI_Keypad2
(0x55, 81), # kVK_ANSI_Keypad3
(0x56, 75), # kVK_ANSI_Keypad4
(0x57, 76), # kVK_ANSI_Keypad5
(0x58, 77), # kVK_ANSI_Keypad6
(0x59, 71), # kVK_ANSI_Keypad7
(0x5B, 72), # kVK_ANSI_Keypad8
(0x5C, 73), # kVK_ANSI_Keypad9
(0x24, 28), # kVK_Return
(0x30, 15), # kVK_Tab
(0x31, 57), # kVK_Space
(0x33, 111), # kVK_Delete
(0x35, 1), # kVK_Escape
(0x37, 125), # kVK_Command
(0x38, 42), # kVK_Shift
(0x39, 58), # kVK_CapsLock
(0x3A, 56), # kVK_Option
(0x3B, 29), # kVK_Control
(0x3C, 54), # kVK_RightShift
(0x3D, 100), # kVK_RightOption
(0x3E, 126), # kVK_RightControl
(0x36, 126), # Right Meta
(0x3F, 0x1D0), # kVK_Function
(0x40, 187), # kVK_F17
(0x48, 115), # kVK_VolumeUp
(0x49, 114), # kVK_VolumeDown
(0x4A, 113), # kVK_Mute
(0x4F, 188), # kVK_F18
(0x50, 189), # kVK_F19
(0x5A, 190), # kVK_F20
(0x60, 63), # kVK_F5
(0x61, 64), # kVK_F6
(0x62, 65), # kVK_F7
(0x63, 61), # kVK_F3
(0x64, 66), # kVK_F8
(0x65, 67), # kVK_F9
(0x67, 87), # kVK_F11
(0x69, 183), # kVK_F13
(0x6A, 186), # kVK_F16
(0x6B, 184), # kVK_F14
(0x6D, 68), # kVK_F10
(0x6F, 88), # kVK_F12
(0x71, 185), # kVK_F15
(0x72, 138), # kVK_Help
(0x73, 102), # kVK_Home
(0x74, 104), # kVK_PageUp
(0x75, 111), # kVK_ForwardDelete
(0x76, 62), # kVK_F4
(0x77, 107), # kVK_End
(0x78, 60), # kVK_F2
(0x79, 109), # kVK_PageDown
(0x7A, 59), # kVK_F1
(0x7B, 105), # kVK_LeftArrow
(0x7C, 106), # kVK_RightArrow
(0x7D, 108), # kVK_DownArrow
(0x7E, 103), # kVK_UpArrow
(0x0A, 170), # kVK_ISO_Section
(0x5D, 124), # kVK_JIS_Yen
(0x5E, 92), # kVK_JIS_Underscore
(0x5F, 95), # kVK_JIS_KeypadComma
(0x66, 94), # kVK_JIS_Eisu
(0x68, 90), # kVK_JIS_Kana
)
# We have yet to support force feedback but probably should
# eventually:
FORCE_FEEDBACK = () # Motor in gamepad
FORCE_FEEDBACK_STATUS = () # Status of motor
POWER = () # Power switch
# These two are internal workings of evdev we probably will never care
# about.
MAX = ()
CURRENT = ()
EVENT_MAP = (
('types', EVENT_TYPES),
('type_codes', ((value, key) for key, value in EVENT_TYPES)),
('wincodes', WINCODES),
('specials', SPECIAL_DEVICES),
('xpad', XINPUT_MAPPING),
('Sync', SYNCHRONIZATION_EVENTS),
('Key', KEYS_AND_BUTTONS),
('Relative', RELATIVE_AXES),
('Absolute', ABSOLUTE_AXES),
('Misc', MISC_EVENTS),
('Switch', SWITCH_EVENTS),
('LED', LEDS),
('LED_type_codes', LED_TYPE_CODES),
('Sound', SOUNDS),
('Repeat', AUTOREPEAT_VALUES),
('ForceFeedback', FORCE_FEEDBACK),
('Power', POWER),
('ForceFeedbackStatus', FORCE_FEEDBACK_STATUS),
('Max', MAX),
('Current', CURRENT),
)
# Evdev style paths for the Mac
APPKIT_KB_PATH = "/dev/input/by-id/usb-AppKit_Keyboard-event-kbd"
QUARTZ_MOUSE_PATH = "/dev/input/by-id/usb-Quartz_Mouse-event-mouse"
APPKIT_MOUSE_PATH = "/dev/input/by-id/usb-AppKit_Mouse-event-mouse"
# Now comes all the structs we need to parse the infomation coming
# from Windows.
class KBDLLHookStruct(ctypes.Structure):
"""Contains information about a low-level keyboard input event.
For full details see Microsoft's documentation:
https://msdn.microsoft.com/en-us/library/windows/desktop/
ms644967%28v=vs.85%29.aspx
"""
# pylint: disable=too-few-public-methods
_fields_ = [
("vk_code", DWORD),
("scan_code", DWORD),
("flags", DWORD),
("time", ctypes.c_int),
]
class MSLLHookStruct(ctypes.Structure):
"""Contains information about a low-level mouse input event.
For full details see Microsoft's documentation:
https://msdn.microsoft.com/en-us/library/windows/desktop/
ms644970%28v=vs.85%29.aspx
"""
# pylint: disable=too-few-public-methods
_fields_ = [
("x_pos", ctypes.c_long),
("y_pos", ctypes.c_long),
('reserved', ctypes.c_short),
('mousedata', ctypes.c_short),
("flags", DWORD),
("time", DWORD),
("extrainfo", ctypes.c_ulong),
]
class XinputGamepad(ctypes.Structure):
"""Describes the current state of the Xbox 360 Controller.
For full details see Microsoft's documentation:
https://msdn.microsoft.com/en-us/library/windows/desktop/
microsoft.directx_sdk.reference.xinput_gamepad%28v=vs.85%29.aspx
"""
# pylint: disable=too-few-public-methods
_fields_ = [
('buttons', ctypes.c_ushort), # wButtons
('left_trigger', ctypes.c_ubyte), # bLeftTrigger
('right_trigger', ctypes.c_ubyte), # bLeftTrigger
('l_thumb_x', ctypes.c_short), # sThumbLX
('l_thumb_y', ctypes.c_short), # sThumbLY
('r_thumb_x', ctypes.c_short), # sThumbRx
('r_thumb_y', ctypes.c_short), # sThumbRy
]
class XinputState(ctypes.Structure):
"""Represents the state of a controller.
For full details see Microsoft's documentation:
https://msdn.microsoft.com/en-us/library/windows/desktop/
microsoft.directx_sdk.reference.xinput_state%28v=vs.85%29.aspx
"""
# pylint: disable=too-few-public-methods
_fields_ = [
('packet_number', ctypes.c_ulong), # dwPacketNumber
('gamepad', XinputGamepad), # Gamepad
]
class XinputVibration(ctypes.Structure):
"""Specifies motor speed levels for the vibration function of a
controller.
For full details see Microsoft's documentation:
https://msdn.microsoft.com/en-us/library/windows/desktop/
microsoft.directx_sdk.reference.xinput_vibration%28v=vs.85%29.aspx
"""
# pylint: disable=too-few-public-methods
_fields_ = [
("wLeftMotorSpeed", ctypes.c_ushort),
("wRightMotorSpeed", ctypes.c_ushort),
]
if sys.version_info.major == 2:
# pylint: disable=redefined-builtin
class PermissionError(IOError):
"""Raised when trying to run an operation without the adequate access
rights - for example filesystem permissions. Corresponds to errno
EACCES and EPERM."""
class UnpluggedError(RuntimeError):
"""The device requested is not plugged in."""
pass
class NoDevicePath(RuntimeError):
"""No evdev device path was given."""
pass
class UnknownEventType(IndexError):
"""We don't know what this event is."""
pass
class UnknownEventCode(IndexError):
"""We don't know what this event is."""
pass
class InputEvent:
"""A user event."""
# pylint: disable=too-few-public-methods
def __init__(self, device, event_info):
self.device = device
self.timestamp = event_info["timestamp"]
self.code = event_info["code"]
self.state = event_info["state"]
self.ev_type = event_info["ev_type"]
class BaseListener:
"""Loosely emulate Evdev keyboard behaviour on other platforms.
Listen (hook in Windows terminology) for key events then buffer
them in a pipe.
"""
def __init__(self, pipe, events=None, codes=None):
self.pipe = pipe
self.events = events if events else []
self.codes = codes if codes else None
self.app = None
self.timeval = None
self.type_codes = dict(EVENT_TYPES)
self.install_handle_input()
def install_handle_input(self):
"""Install the input handler."""
pass
def uninstall_handle_input(self):
"""Un-install the input handler."""
pass
def __del__(self):
"""Clean up when deleted."""
self.uninstall_handle_input()
@staticmethod
def get_timeval():
"""Get the time in seconds and microseconds."""
return convert_timeval(time.time())
def update_timeval(self):
"""Update the timeval with the current time."""
self.timeval = self.get_timeval()
def create_event_object(self, event_type, code, value, timeval=None):
"""Create an evdev style structure."""
if not timeval:
self.update_timeval()
timeval = self.timeval
try:
event_code = self.type_codes[event_type]
except KeyError:
raise UnknownEventType(
"We don't know what kind of event a %s is." % event_type
)
event = struct.pack(
EVENT_FORMAT, timeval[0], timeval[1], event_code, code, value
)
return event
def write_to_pipe(self, event_list):
"""Send event back to the mouse object."""
self.pipe.send_bytes(b''.join(event_list))
def emulate_wheel(self, data, direction, timeval):
"""Emulate rel values for the mouse wheel.
In evdev, a single click forwards of the mouse wheel is 1 and
a click back is -1. Windows uses 120 and -120. We floor divide
the Windows number by 120. This is fine for the digital scroll
wheels found on the vast majority of mice. It also works on
the analogue ball on the top of the Apple mouse.
What do the analogue scroll wheels found on 200 quid high end
gaming mice do? If the lowest unit is 120 then we are okay. If
they report changes of less than 120 units Windows, then this
might be an unacceptable loss of precision. Needless to say, I
don't have such a mouse to test one way or the other.
"""
if direction == 'x':
code = 0x06
elif direction == 'z':
# Not enitely sure if this exists
code = 0x07
else:
code = 0x08
if WIN:
data = data // 120
return self.create_event_object("Relative", code, data, timeval)
def emulate_rel(self, key_code, value, timeval):
"""Emulate the relative changes of the mouse cursor."""
return self.create_event_object("Relative", key_code, value, timeval)
def emulate_press(self, key_code, scan_code, value, timeval):
"""Emulate a button press.
Currently supports 5 buttons.
The Microsoft documentation does not define what happens with
a mouse with more than five buttons, and I don't have such a
mouse.
From reading the Linux sources, I guess evdev can support up
to 255 buttons.
Therefore, I guess we could support more buttons quite easily,
if we had any useful hardware.
"""
scan_event = self.create_event_object("Misc", 0x04, scan_code, timeval)
key_event = self.create_event_object("Key", key_code, value, timeval)
return scan_event, key_event
def emulate_repeat(self, value, timeval):
"""The repeat press of a key/mouse button, e.g. double click."""
repeat_event = self.create_event_object("Repeat", 2, value, timeval)
return repeat_event
def sync_marker(self, timeval):
"""Separate groups of events."""
return self.create_event_object("Sync", 0, 0, timeval)
def emulate_abs(self, x_val, y_val, timeval):
"""Emulate the absolute co-ordinates of the mouse cursor."""
x_event = self.create_event_object("Absolute", 0x00, x_val, timeval)
y_event = self.create_event_object("Absolute", 0x01, y_val, timeval)
return x_event, y_event
class WindowsKeyboardListener(BaseListener):
"""Loosely emulate Evdev keyboard behaviour on Windows. Listen (hook
in Windows terminology) for key events then buffer them in a pipe.
"""
def __init__(self, pipe, codes=None):
self.pipe = pipe
self.hooked = None
self.pointer = None
super(WindowsKeyboardListener, self).__init__(pipe, codes)
@staticmethod
def listen():
"""Listen for keyboard input."""
msg = MSG()
ctypes.windll.user32.GetMessageA(ctypes.byref(msg), 0, 0, 0)
def get_fptr(self):
"""Get the function pointer."""
cmpfunc = ctypes.CFUNCTYPE(
ctypes.c_int, WPARAM, LPARAM, ctypes.POINTER(KBDLLHookStruct)
)
return cmpfunc(self.handle_input)
def install_handle_input(self):
"""Install the hook."""
self.pointer = self.get_fptr()
self.hooked = ctypes.windll.user32.SetWindowsHookExA(
13, self.pointer, ctypes.windll.kernel32.GetModuleHandleW(None), 0
)
if not self.hooked:
return False
return True
def uninstall_handle_input(self):
"""Remove the hook."""
if self.hooked is None:
return
ctypes.windll.user32.UnhookWindowsHookEx(self.hooked)
self.hooked = None
def handle_input(self, ncode, wparam, lparam):
"""Process the key input."""
value = WIN_KEYBOARD_CODES[wparam]
scan_code = lparam.contents.scan_code
vk_code = lparam.contents.vk_code
self.update_timeval()
events = []
# Add key event
scan_key, key_event = self.emulate_press(
vk_code, scan_code, value, self.timeval
)
events.append(scan_key)
events.append(key_event)
# End with a sync marker
events.append(self.sync_marker(self.timeval))
# We are done
self.write_to_pipe(events)
return ctypes.windll.user32.CallNextHookEx(self.hooked, ncode, wparam, lparam)
def keyboard_process(pipe):
"""Single subprocess for reading keyboard events on Windows."""
keyboard = WindowsKeyboardListener(pipe)
keyboard.listen()
class WindowsMouseListener(BaseListener):
"""Loosely emulate Evdev mouse behaviour on Windows. Listen (hook
in Windows terminology) for key events then buffer them in a pipe.
"""
def __init__(self, pipe):
self.pipe = pipe
self.hooked = None
self.pointer = None
self.mouse_codes = WIN_MOUSE_CODES
super(WindowsMouseListener, self).__init__(pipe)
@staticmethod
def listen():
"""Listen for mouse input."""
msg = MSG()
ctypes.windll.user32.GetMessageA(ctypes.byref(msg), 0, 0, 0)
def get_fptr(self):
"""Get the function pointer."""
cmpfunc = ctypes.CFUNCTYPE(
ctypes.c_int, WPARAM, LPARAM, ctypes.POINTER(MSLLHookStruct)
)
return cmpfunc(self.handle_input)
def install_handle_input(self):
"""Install the hook."""
self.pointer = self.get_fptr()
self.hooked = ctypes.windll.user32.SetWindowsHookExA(
14, self.pointer, ctypes.windll.kernel32.GetModuleHandleW(None), 0
)
if not self.hooked:
return False
return True
def uninstall_handle_input(self):
"""Remove the hook."""
if self.hooked is None:
return
ctypes.windll.user32.UnhookWindowsHookEx(self.hooked)
self.hooked = None
def handle_input(self, ncode, wparam, lparam):
"""Process the key input."""
x_pos = lparam.contents.x_pos
y_pos = lparam.contents.y_pos
data = lparam.contents.mousedata
# This is how we can distinguish mouse 1 from mouse 2
# extrainfo = lparam.contents.extrainfo
# The way windows seems to do it is there is primary mouse
# and all other mouses report as mouse 2
# Also useful later will be to support the flags field
# flags = lparam.contents.flags
# This shows if the event was from a real device or whether it
# was injected somehow via software
self.emulate_mouse(wparam, x_pos, y_pos, data)
# Give back control to Windows to wait for and process the
# next event
return ctypes.windll.user32.CallNextHookEx(self.hooked, ncode, wparam, lparam)
def emulate_mouse(self, key_code, x_val, y_val, data):
"""Emulate the ev codes using the data Windows has given us.
Note that by default in Windows, to recognise a double click,
you just notice two clicks in a row within a reasonablely
short time period.
However, if the application developer sets the application
window's class style to CS_DBLCLKS, the operating system will
notice the four button events (down, up, down, up), intercept
them and then send a single key code instead.
There are no such special double click codes on other
platforms, so not obvious what to do with them. It might be
best to just convert them back to four events.
Currently we do nothing.
((0x0203, 'WM_LBUTTONDBLCLK'),
(0x0206, 'WM_RBUTTONDBLCLK'),
(0x0209, 'WM_MBUTTONDBLCLK'),
(0x020D, 'WM_XBUTTONDBLCLK'))
"""
# Once again ignore Windows' relative time (since system
# startup) and use the absolute time (since epoch i.e. 1st Jan
# 1970).
self.update_timeval()
events = []
if key_code == 0x0200:
# We have a mouse move alone.
# So just pass through to below
pass
elif key_code == 0x020A:
# We have a vertical mouse wheel turn
events.append(self.emulate_wheel(data, 'y', self.timeval))
elif key_code == 0x020E:
# We have a horizontal mouse wheel turn
# https://msdn.microsoft.com/en-us/library/windows/desktop/
# ms645614%28v=vs.85%29.aspx
events.append(self.emulate_wheel(data, 'x', self.timeval))
else:
# We have a button press.
# Distinguish the second extra button
if key_code == 0x020B and data == 2:
key_code = 0x020B2
elif key_code == 0x020C and data == 2:
key_code = 0x020C2
# Get the mouse codes
code, value, scan_code = self.mouse_codes[key_code]
# Add in the press events
scan_event, key_event = self.emulate_press(
code, scan_code, value, self.timeval
)
events.append(scan_event)
events.append(key_event)
# Add in the absolute position of the mouse cursor
x_event, y_event = self.emulate_abs(x_val, y_val, self.timeval)
events.append(x_event)
events.append(y_event)
# End with a sync marker
events.append(self.sync_marker(self.timeval))
# We are done
self.write_to_pipe(events)
def mouse_process(pipe):
"""Single subprocess for reading mouse events on Windows."""
mouse = WindowsMouseListener(pipe)
mouse.listen()
class QuartzMouseBaseListener(BaseListener):
"""Emulate evdev mouse behaviour on mac."""
def __init__(self, pipe):
super(QuartzMouseBaseListener, self).__init__(pipe, codes=dict(MAC_EVENT_CODES))
self.active = True
self.events = []
def _get_mouse_button_number(self, event):
"""Get the mouse button number from an event."""
raise NotImplementedError
def _get_click_state(self, event):
"""The click state from an event."""
raise NotImplementedError
def _get_scroll(self, event):
"""The scroll values from an event."""
raise NotImplementedError
def _get_absolute(self, event):
"""Get abolute cursor location."""
raise NotImplementedError
def _get_relative(self, event):
"""Get the relative mouse movement."""
raise NotImplementedError
def handle_button(self, event, event_type):
"""Convert the button information from quartz into evdev format."""
# 0 for left
# 1 for right
# 2 for middle/center
# 3 for side
mouse_button_number = self._get_mouse_button_number(event)
# Identify buttons 3,4,5
if event_type in (25, 26):
event_type = event_type + (mouse_button_number * 0.1)
# Add buttons to events
event_type_string, event_code, value, scan = self.codes[event_type]
if event_type_string == "Key":
scan_event, key_event = self.emulate_press(
event_code, scan, value, self.timeval
)
self.events.append(scan_event)
self.events.append(key_event)
# doubleclick/n-click of button
click_state = self._get_click_state(event)
repeat = self.emulate_repeat(click_state, self.timeval)
self.events.append(repeat)
def handle_scrollwheel(self, event):
"""Handle the scrollwheel (it is a ball on the mighty mouse)."""
# relative Scrollwheel
scroll_x, scroll_y = self._get_scroll(event)
if scroll_x:
self.events.append(self.emulate_wheel(scroll_x, 'x', self.timeval))
if scroll_y:
self.events.append(self.emulate_wheel(scroll_y, 'y', self.timeval))
def handle_absolute(self, event):
"""Absolute mouse position on the screen."""
(x_val, y_val) = self._get_absolute(event)
x_event, y_event = self.emulate_abs(int(x_val), int(y_val), self.timeval)
self.events.append(x_event)
self.events.append(y_event)
def handle_relative(self, event):
"""Relative mouse movement."""
delta_x, delta_y = self._get_relative(event)
if delta_x:
self.events.append(self.emulate_rel(0x00, delta_x, self.timeval))
if delta_y:
self.events.append(self.emulate_rel(0x01, delta_y, self.timeval))
# pylint: disable=unused-argument
def handle_input(self, proxy, event_type, event, refcon):
"""Handle an input event."""
self.update_timeval()
self.events = []
if event_type in (1, 2, 3, 4, 25, 26, 27):
self.handle_button(event, event_type)
if event_type == 22:
self.handle_scrollwheel(event)
# Add in the absolute position of the mouse cursor
self.handle_absolute(event)
# Add in the relative position of the mouse cursor
self.handle_relative(event)
# End with a sync marker
self.events.append(self.sync_marker(self.timeval))
# We are done
self.write_to_pipe(self.events)
def quartz_mouse_process(pipe):
"""Single subprocess for reading mouse events on Mac using newer Quartz."""
# Quartz only on the mac, so don't warn about Quartz
# pylint: disable=import-error
import Quartz
# pylint: disable=no-member
class QuartzMouseListener(QuartzMouseBaseListener):
"""Loosely emulate Evdev mouse behaviour on the Macs.
Listen for key events then buffer them in a pipe.
"""
def install_handle_input(self):
"""Constants below listed at:
https://developer.apple.com/documentation/coregraphics/
cgeventtype?language=objc#topics
"""
# Keep Mac Names to make it easy to find the documentation
# pylint: disable=invalid-name
NSMachPort = Quartz.CGEventTapCreate(
Quartz.kCGSessionEventTap,
Quartz.kCGHeadInsertEventTap,
Quartz.kCGEventTapOptionDefault,
Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDown)
| Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseUp)
| Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDown)
| Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseUp)
| Quartz.CGEventMaskBit(Quartz.kCGEventMouseMoved)
| Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDragged)
| Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDragged)
| Quartz.CGEventMaskBit(Quartz.kCGEventScrollWheel)
| Quartz.CGEventMaskBit(Quartz.kCGEventTabletPointer)
| Quartz.CGEventMaskBit(Quartz.kCGEventTabletProximity)
| Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDown)
| Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseUp)
| Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDragged),
self.handle_input,
None,
)
CFRunLoopSourceRef = Quartz.CFMachPortCreateRunLoopSource(
None, NSMachPort, 0
)
CFRunLoopRef = Quartz.CFRunLoopGetCurrent()
Quartz.CFRunLoopAddSource(
CFRunLoopRef, CFRunLoopSourceRef, Quartz.kCFRunLoopDefaultMode
)
Quartz.CGEventTapEnable(NSMachPort, True)
def listen(self):
"""Listen for quartz events."""
while self.active:
Quartz.CFRunLoopRunInMode(Quartz.kCFRunLoopDefaultMode, 5, False)
def uninstall_handle_input(self):
self.active = False
def _get_mouse_button_number(self, event):
"""Get the mouse button number from an event."""
return Quartz.CGEventGetIntegerValueField(
event, Quartz.kCGMouseEventButtonNumber
)
def _get_click_state(self, event):
"""The click state from an event."""
return Quartz.CGEventGetIntegerValueField(
event, Quartz.kCGMouseEventClickState
)
def _get_scroll(self, event):
"""The scroll values from an event."""
scroll_y = Quartz.CGEventGetIntegerValueField(
event, Quartz.kCGScrollWheelEventDeltaAxis1
)
scroll_x = Quartz.CGEventGetIntegerValueField(
event, Quartz.kCGScrollWheelEventDeltaAxis2
)
return scroll_x, scroll_y
def _get_absolute(self, event):
"""Get abolute cursor location."""
return Quartz.CGEventGetLocation(event)
def _get_relative(self, event):
"""Get the relative mouse movement."""
delta_x = Quartz.CGEventGetIntegerValueField(
event, Quartz.kCGMouseEventDeltaX
)
delta_y = Quartz.CGEventGetIntegerValueField(
event, Quartz.kCGMouseEventDeltaY
)
return delta_x, delta_y
mouse = QuartzMouseListener(pipe)
mouse.listen()
class AppKitMouseBaseListener(BaseListener):
"""Emulate evdev behaviour on the the Mac."""
def __init__(self, pipe, events=None):
super(AppKitMouseBaseListener, self).__init__(
pipe, events, codes=dict(MAC_EVENT_CODES)
)
@staticmethod
def _get_mouse_button_number(event):
"""Get the button number."""
return event.buttonNumber()
@staticmethod
def _get_absolute(event):
"""Get the absolute (pixel) location of the mouse cursor."""
return event.locationInWindow()
@staticmethod
def _get_event_type(event):
"""Get the appkit event type of the event."""
return event.type()
@staticmethod
def _get_deltas(event):
"""Get the changes from the appkit event."""
delta_x = round(event.deltaX())
delta_y = round(event.deltaY())
delta_z = round(event.deltaZ())
return delta_x, delta_y, delta_z
def handle_button(self, event, event_type):
"""Handle mouse click."""
mouse_button_number = self._get_mouse_button_number(event)
# Identify buttons 3,4,5
if event_type in (25, 26):
event_type = event_type + (mouse_button_number * 0.1)
# Add buttons to events
event_type_name, event_code, value, scan = self.codes[event_type]
if event_type_name == "Key":
scan_event, key_event = self.emulate_press(
event_code, scan, value, self.timeval
)
self.events.append(scan_event)
self.events.append(key_event)
def handle_absolute(self, event):
"""Absolute mouse position on the screen."""
point = self._get_absolute(event)
x_pos = round(point.x)
y_pos = round(point.y)
x_event, y_event = self.emulate_abs(x_pos, y_pos, self.timeval)
self.events.append(x_event)
self.events.append(y_event)
def handle_scrollwheel(self, event):
"""Make endev from appkit scroll wheel event."""
delta_x, delta_y, delta_z = self._get_deltas(event)
if delta_x:
self.events.append(self.emulate_wheel(delta_x, 'x', self.timeval))
if delta_y:
self.events.append(self.emulate_wheel(delta_y, 'y', self.timeval))
if delta_z:
self.events.append(self.emulate_wheel(delta_z, 'z', self.timeval))
def handle_relative(self, event):
"""Get the position of the mouse on the screen."""
delta_x, delta_y, delta_z = self._get_deltas(event)
if delta_x:
self.events.append(self.emulate_rel(0x00, delta_x, self.timeval))
if delta_y:
self.events.append(self.emulate_rel(0x01, delta_y, self.timeval))
if delta_z:
self.events.append(self.emulate_rel(0x02, delta_z, self.timeval))
def handle_input(self, event):
"""Process the mouse event."""
self.update_timeval()
self.events = []
code = self._get_event_type(event)
# Deal with buttons
self.handle_button(event, code)
# Mouse wheel
if code == 22:
self.handle_scrollwheel(event)
# Other relative mouse movements
else:
self.handle_relative(event)
# Add in the absolute position of the mouse cursor
self.handle_absolute(event)
# End with a sync marker
self.events.append(self.sync_marker(self.timeval))
# We are done
self.write_to_pipe(self.events)
def appkit_mouse_process(pipe):
"""Single subprocess for reading mouse events on Mac using older AppKit."""
# pylint: disable=import-error,too-many-locals
# Note Objective C does not support a Unix style fork.
# So these imports have to be inside the child subprocess since
# otherwise the child process cannot use them.
# pylint: disable=no-member, no-name-in-module
from Foundation import NSObject
from AppKit import NSApplication, NSApp
from Cocoa import (
NSEvent,
NSLeftMouseDownMask,
NSLeftMouseUpMask,
NSRightMouseDownMask,
NSRightMouseUpMask,
NSMouseMovedMask,
NSLeftMouseDraggedMask,
NSRightMouseDraggedMask,
NSMouseEnteredMask,
NSMouseExitedMask,
NSScrollWheelMask,
NSOtherMouseDownMask,
NSOtherMouseUpMask,
)
from PyObjCTools import AppHelper
import objc
class MacMouseSetup(NSObject):
"""Setup the handler."""
@objc.python_method
def init_with_handler(self, handler):
"""
Init method that receives the write end of the pipe.
"""
# ALWAYS call the super's designated initializer.
# Also, make sure to re-bind "self" just in case it
# returns something else!
# pylint: disable=self-cls-assignment
self = super(MacMouseSetup, self).init()
self.handler = handler
# Unlike Python's __init__, initializers MUST return self,
# because they are allowed to return any object!
return self
# pylint: disable=invalid-name, unused-argument
def applicationDidFinishLaunching_(self, notification):
"""Bind the listen method as the handler for mouse events."""
mask = (
NSLeftMouseDownMask
| NSLeftMouseUpMask
| NSRightMouseDownMask
| NSRightMouseUpMask
| NSMouseMovedMask
| NSLeftMouseDraggedMask
| NSRightMouseDraggedMask
| NSScrollWheelMask
| NSMouseEnteredMask
| NSMouseExitedMask
| NSOtherMouseDownMask
| NSOtherMouseUpMask
)
NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(mask, self.handler)
class MacMouseListener(AppKitMouseBaseListener):
"""Loosely emulate Evdev mouse behaviour on the Macs.
Listen for key events then buffer them in a pipe.
"""
def install_handle_input(self):
"""Install the hook."""
self.app = NSApplication.sharedApplication()
# pylint: disable=no-member
delegate = MacMouseSetup.alloc().init_with_handler(self.handle_input)
NSApp().setDelegate_(delegate)
AppHelper.runEventLoop()
def __del__(self):
"""Stop the listener on deletion."""
AppHelper.stopEventLoop()
MacMouseListener(pipe, events=[])
class AppKitKeyboardListener(BaseListener):
"""Emulate an evdev keyboard on the Mac."""
def __init__(self, pipe):
super(AppKitKeyboardListener, self).__init__(pipe, codes=dict(MAC_KEYS))
@staticmethod
def _get_event_key_code(event):
"""Get the key code."""
return event.keyCode()
@staticmethod
def _get_event_type(event):
"""Get the event type."""
return event.type()
@staticmethod
def _get_flag_value(event):
"""Note, this may be able to be made more accurate,
i.e. handle two modifier keys at once."""
flags = event.modifierFlags()
if flags == 0x100:
value = 0
else:
value = 1
return value
def _get_key_value(self, event, event_type):
"""Get the key value."""
if event_type == 10:
value = 1
elif event_type == 11:
value = 0
elif event_type == 12:
value = self._get_flag_value(event)
else:
value = -1
return value
def handle_input(self, event):
"""Process they keyboard input."""
self.update_timeval()
self.events = []
code = self._get_event_key_code(event)
new_code = (self.codes or {}).get(code, 0)
event_type = self._get_event_type(event)
value = self._get_key_value(event, event_type)
scan_event, key_event = self.emulate_press(new_code, code, value, self.timeval)
self.events.append(scan_event)
self.events.append(key_event)
# End with a sync marker
self.events.append(self.sync_marker(self.timeval))
# We are done
self.write_to_pipe(self.events)
def mac_keyboard_process(pipe):
"""Single subprocesses for reading keyboard on Mac."""
# pylint: disable=import-error,too-many-locals
# Note Objective C does not support a Unix style fork.
# So these imports have to be inside the child subprocess since
# otherwise the child process cannot use them.
# pylint: disable=no-member, no-name-in-module
from AppKit import NSApplication, NSApp
from Foundation import NSObject
from Cocoa import NSEvent, NSKeyDownMask, NSKeyUpMask, NSFlagsChangedMask
from PyObjCTools import AppHelper
import objc
class MacKeyboardSetup(NSObject):
"""Setup the handler."""
@objc.python_method
def init_with_handler(self, handler):
"""
Init method that receives the write end of the pipe.
"""
# ALWAYS call the super's designated initializer.
# Also, make sure to re-bind "self" just in case it
# returns something else!
# pylint: disable=self-cls-assignment
self = super(MacKeyboardSetup, self).init()
self.handler = handler
# Unlike Python's __init__, initializers MUST return self,
# because they are allowed to return any object!
return self
# pylint: disable=invalid-name, unused-argument
def applicationDidFinishLaunching_(self, notification):
"""Bind the handler to listen to keyboard events."""
mask = NSKeyDownMask | NSKeyUpMask | NSFlagsChangedMask
NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(mask, self.handler)
class MacKeyboardListener(AppKitKeyboardListener):
"""Loosely emulate Evdev keyboard behaviour on the Mac.
Listen for key events then buffer them in a pipe.
"""
def install_handle_input(self):
"""Install the hook."""
self.app = NSApplication.sharedApplication()
# pylint: disable=no-member
delegate = MacKeyboardSetup.alloc().init_with_handler(self.handle_input)
NSApp().setDelegate_(delegate)
AppHelper.runEventLoop()
def __del__(self):
"""Stop the listener on deletion."""
AppHelper.stopEventLoop()
MacKeyboardListener(pipe)
class InputDevice:
"""A user input device."""
# pylint: disable=too-many-instance-attributes
def __init__(self, manager, device_path=None, char_path_override=None, read_size=1):
self.read_size = read_size
self.manager = manager
self.__pipe = None
self._listener = None
self.leds = None
if device_path:
self._device_path = device_path
else:
self._set_device_path()
# We should by now have a device_path
try:
if not self._device_path:
raise NoDevicePath
except AttributeError:
raise NoDevicePath
self.protocol, _, self.device_type = self._get_path_infomation()
if char_path_override:
self._character_device_path = char_path_override
else:
self._character_device_path = os.path.realpath(self._device_path)
self._character_file = None
self._evdev = False
self._set_evdev_state()
self.name = "Unknown Device"
self._set_name()
def _set_device_path(self):
"""Set the device path, overridden on the MAC and Windows."""
pass
def _set_evdev_state(self):
"""Set whether the device is a real evdev device."""
if NIX:
self._evdev = True
def _set_name(self):
if NIX:
with open(
"/sys/class/input/%s/device/name" % self.get_char_name()
) as name_file:
self.name = name_file.read().strip()
self.leds = []
def _get_path_infomation(self):
"""Get useful infomation from the device path."""
long_identifier = self._device_path.split('/')[4]
protocol, remainder = long_identifier.split('-', 1)
identifier, _, device_type = remainder.rsplit('-', 2)
return (protocol, identifier, device_type)
def get_char_name(self):
"""Get short version of char device name."""
return self._character_device_path.split('/')[-1]
def get_char_device_path(self):
"""Get the char device path."""
return self._character_device_path
def __str__(self):
try:
return self.name
except AttributeError:
return "Unknown Device"
def __repr__(self):
return '%s.%s("%s")' % (
self.__module__,
self.__class__.__name__,
self._device_path,
)
@property
def _character_device(self):
if not self._character_file:
if WIN:
self._character_file = io.BytesIO()
return self._character_file
try:
self._character_file = io.open(self._character_device_path, 'rb')
except PermissionError:
# Python 3
raise PermissionError(PERMISSIONS_ERROR_TEXT)
except IOError as err:
# Python 2
if err.errno == 13:
raise PermissionError(PERMISSIONS_ERROR_TEXT)
else:
raise
return self._character_file
def __iter__(self):
while True:
event = self._do_iter()
if event:
yield event
def _get_data(self, read_size):
"""Get data from the character device."""
return self._character_device.read(read_size)
@staticmethod
def _get_target_function():
"""Get the correct target function. This is only used by Windows
subclasses."""
return False
def _get_total_read_size(self):
"""How much event data to process at once."""
if self.read_size:
read_size = EVENT_SIZE * self.read_size
else:
read_size = EVENT_SIZE
return read_size
def _do_iter(self):
read_size = self._get_total_read_size()
data = self._get_data(read_size)
if not data:
return None
evdev_objects = iter_unpack(data)
events = [self._make_event(*event) for event in evdev_objects]
return events
# pylint: disable=too-many-arguments
def _make_event(self, tv_sec, tv_usec, ev_type, code, value):
"""Create a friendly Python object from an evdev style event."""
event_type = self.manager.get_event_type(ev_type)
eventinfo = {
"ev_type": event_type,
"state": value,
"timestamp": tv_sec + (tv_usec / 1000000),
"code": self.manager.get_event_string(event_type, code),
}
return InputEvent(self, eventinfo)
def read(self):
"""Read the next input event."""
return next(iter(self))
@property
def _pipe(self):
"""On Windows we use a pipe to emulate a Linux style character
buffer."""
if self._evdev:
return None
if not self.__pipe:
target_function = self._get_target_function()
if not target_function:
return None
self.__pipe, child_conn = Pipe(duplex=False)
self._listener = Process(target=target_function, args=(child_conn,))
self._listener.start()
return self.__pipe
def __del__(self):
if ('WIN' in globals() or 'MAC' in globals()) and (WIN or MAC) and self.__pipe:
self._listener.terminate()
class Keyboard(InputDevice):
"""A keyboard or other key-like device.
Original umapped scan code, followed by the important key info
followed by a sync.
"""
def _set_device_path(self):
super(Keyboard, self)._set_device_path()
if MAC:
self._device_path = APPKIT_KB_PATH
def _set_name(self):
super(Keyboard, self)._set_name()
if WIN:
self.name = "Microsoft Keyboard"
elif MAC:
self.name = "AppKit Keyboard"
@staticmethod
def _get_target_function():
"""Get the correct target function."""
if WIN:
return keyboard_process
if MAC:
return mac_keyboard_process
return None
def _get_data(self, read_size):
"""Get data from the character device."""
if NIX:
return super(Keyboard, self)._get_data(read_size)
return self._pipe.recv_bytes()
class Mouse(InputDevice):
"""A mouse or other pointing-like device."""
def _set_device_path(self):
super(Mouse, self)._set_device_path()
if MAC:
self._device_path = APPKIT_MOUSE_PATH
def _set_name(self):
super(Mouse, self)._set_name()
if WIN:
self.name = "Microsoft Mouse"
elif MAC:
self.name = "AppKit Mouse"
@staticmethod
def _get_target_function():
"""Get the correct target function."""
if WIN:
return mouse_process
if MAC:
return appkit_mouse_process
return None
def _get_data(self, read_size):
"""Get data from the character device."""
if NIX:
return super(Mouse, self)._get_data(read_size)
return self._pipe.recv_bytes()
class MightyMouse(Mouse):
"""A mouse or other pointing device on the Mac."""
def _set_device_path(self):
super(MightyMouse, self)._set_device_path()
if MAC:
self._device_path = QUARTZ_MOUSE_PATH
def _set_name(self):
self.name = "Quartz Mouse"
@staticmethod
def _get_target_function():
"""Get the correct target function."""
return quartz_mouse_process
def delay_and_stop(duration, dll, device_number):
"""Stop vibration aka force feedback aka rumble on
Windows after duration miliseconds."""
xinput = getattr(ctypes.windll, dll)
time.sleep(duration / 1000)
xinput_set_state = xinput.XInputSetState
xinput_set_state.argtypes = [ctypes.c_uint, ctypes.POINTER(XinputVibration)]
xinput_set_state.restype = ctypes.c_uint
vibration = XinputVibration(0, 0)
xinput_set_state(device_number, ctypes.byref(vibration))
# I made this GamePad class before Mouse and Keyboard above, and have
# learned a lot about Windows in the process. This can probably be
# simplified massively and made to match Mouse and Keyboard more.
class GamePad(InputDevice):
"""A gamepad or other joystick-like device."""
def __init__(self, manager, device_path, char_path_override=None):
super(GamePad, self).__init__(manager, device_path, char_path_override)
self._write_file = None
self.__device_number = None
if WIN and "Microsoft_Corporation_Controller" in self._device_path:
self.name = "Microsoft X-Box 360 pad"
identifier = self._get_path_infomation()[1]
self.__device_number = int(identifier.split('_')[-1])
self.__received_packets = 0
self.__missed_packets = 0
self.__last_state = self.__read_device()
if NIX:
self._number_xpad()
def _number_xpad(self):
"""Get the number of the joystick."""
js_path = self._device_path.replace('-event', '')
js_chardev = os.path.realpath(js_path)
try:
number_text = js_chardev.split('js')[1]
except IndexError:
return
try:
number = int(number_text)
except ValueError:
return
self.__device_number = number
def get_number(self):
"""Return the joystick number of the gamepad."""
return self.__device_number
def __iter__(self):
while True:
if WIN:
self.__check_state()
event = self._do_iter()
if event:
yield event
def __check_state(self):
"""On Windows, check the state and fill the event character device."""
state = self.__read_device()
if not state:
raise UnpluggedError("Gamepad %d is not connected" % self.__device_number)
if state.packet_number != self.__last_state.packet_number:
# state has changed, handle the change
self.__handle_changed_state(state)
self.__last_state = state
@staticmethod
def __get_timeval():
"""Get the time and make it into C style timeval."""
return convert_timeval(time.time())
def create_event_object(self, event_type, code, value, timeval=None):
"""Create an evdev style object."""
if not timeval:
timeval = self.__get_timeval()
try:
event_code = self.manager.codes['type_codes'][event_type]
except KeyError:
raise UnknownEventType(
"We don't know what kind of event a %s is." % event_type
)
event = struct.pack(
EVENT_FORMAT, timeval[0], timeval[1], event_code, code, value
)
return event
def __write_to_character_device(self, event_list, timeval=None):
"""Emulate the Linux character device on other platforms such as
Windows."""
# Remember the position of the stream
pos = self._character_device.tell()
# Go to the end of the stream
self._character_device.seek(0, 2)
# Write the new data to the end
for event in event_list:
self._character_device.write(event)
# Add a sync marker
sync = self.create_event_object("Sync", 0, 0, timeval)
self._character_device.write(sync)
# Put the stream back to its original position
self._character_device.seek(pos)
def __handle_changed_state(self, state):
"""
we need to pack a struct with the following five numbers:
tv_sec, tv_usec, ev_type, code, value
then write it using __write_to_character_device
seconds, mircroseconds, ev_type, code, value
time we just use now
ev_type we look up
code we look up
value is 0 or 1 for the buttons
axis value is maybe the same as Linux? Hope so!
"""
timeval = self.__get_timeval()
events = self.__get_button_events(state, timeval)
events.extend(self.__get_axis_events(state, timeval))
if events:
self.__write_to_character_device(events, timeval)
def __map_button(self, button):
"""Get the linux xpad code from the Windows xinput code."""
_, start_code, start_value = button
value = start_value
ev_type = "Key"
code = self.manager.codes['xpad'][start_code]
if 1 <= start_code <= 4:
ev_type = "Absolute"
if (start_code == 1 and start_value == 1) or (
start_code == 3 and start_value == 1
):
value = -1
return code, value, ev_type
def __map_axis(self, axis):
"""Get the linux xpad code from the Windows xinput code."""
start_code, start_value = axis
value = start_value
code = self.manager.codes['xpad'][start_code]
return code, value
def __get_button_events(self, state, timeval=None):
"""Get the button events from xinput."""
changed_buttons = self.__detect_button_events(state)
events = self.__emulate_buttons(changed_buttons, timeval)
return events
def __get_axis_events(self, state, timeval=None):
"""Get the stick events from xinput."""
axis_changes = self.__detect_axis_events(state)
events = self.__emulate_axis(axis_changes, timeval)
return events
def __emulate_axis(self, axis_changes, timeval=None):
"""Make the axis events use the Linux style format."""
events = []
for axis in axis_changes:
code, value = self.__map_axis(axis)
event = self.create_event_object("Absolute", code, value, timeval=timeval)
events.append(event)
return events
def __emulate_buttons(self, changed_buttons, timeval=None):
"""Make the button events use the Linux style format."""
events = []
for button in changed_buttons:
code, value, ev_type = self.__map_button(button)
event = self.create_event_object(ev_type, code, value, timeval=timeval)
events.append(event)
return events
@staticmethod
def __gen_bit_values(number):
"""
Return a zero or one for each bit of a numeric value up to the most
significant 1 bit, beginning with the least significant bit.
"""
number = int(number)
while number:
yield number & 0x1
number >>= 1
def __get_bit_values(self, number, size=32):
"""Get bit values as a list for a given number
>>> get_bit_values(1) == [0]*31 + [1]
True
>>> get_bit_values(0xDEADBEEF)
[1L, 1L, 0L, 1L, 1L, 1L, 1L,
0L, 1L, 0L, 1L, 0L, 1L, 1L, 0L, 1L, 1L, 0L, 1L, 1L, 1L, 1L,
1L, 0L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L]
You may override the default word size of 32-bits to match your actual
application.
>>> get_bit_values(0x3, 2)
[1L, 1L]
>>> get_bit_values(0x3, 4)
[0L, 0L, 1L, 1L]
"""
res = list(self.__gen_bit_values(number))
res.reverse()
# 0-pad the most significant bit
res = [0] * (size - len(res)) + res
return res
def __detect_button_events(self, state):
changed = state.gamepad.buttons ^ self.__last_state.gamepad.buttons
changed = self.__get_bit_values(changed, 16)
buttons_state = self.__get_bit_values(state.gamepad.buttons, 16)
changed.reverse()
buttons_state.reverse()
button_numbers = count(1)
changed_buttons = list(
filter(itemgetter(0), list(zip(changed, button_numbers, buttons_state)))
)
# returns for example [(1,15,1)] type, code, value?
return changed_buttons
def __detect_axis_events(self, state):
# axis fields are everything but the buttons
# pylint: disable=protected-access
# Attribute name _fields_ is special name set by ctypes
axis_fields = dict(XinputGamepad._fields_)
axis_fields.pop('buttons')
changed_axes = []
# Ax_type might be useful when we support high-level deadzone
# methods.
# pylint: disable=unused-variable
for axis, _ in list(axis_fields.items()):
old_val = getattr(self.__last_state.gamepad, axis)
new_val = getattr(state.gamepad, axis)
if old_val != new_val:
changed_axes.append((axis, new_val))
return changed_axes
def __read_device(self):
"""Read the state of the gamepad."""
state = XinputState()
res = self.manager.xinput.XInputGetState(
self.__device_number, ctypes.byref(state)
)
if res == XINPUT_ERROR_SUCCESS:
return state
if res != XINPUT_ERROR_DEVICE_NOT_CONNECTED:
raise RuntimeError(
"Unknown error %d attempting to get state of device %d"
% (res, self.__device_number)
)
# else (device is not connected)
return None
@property
def _write_device(self):
if not self._write_file:
if not NIX:
return None
try:
self._write_file = io.open(self._character_device_path, 'wb')
except PermissionError:
# Python 3
raise PermissionError(PERMISSIONS_ERROR_TEXT)
except IOError as err:
# Python 2
if err.errno == 13:
raise PermissionError(PERMISSIONS_ERROR_TEXT)
else:
raise
return self._write_file
def _start_vibration_win(self, left_motor, right_motor):
"""Start the vibration, which will run until stopped."""
xinput_set_state = self.manager.xinput.XInputSetState
xinput_set_state.argtypes = [ctypes.c_uint, ctypes.POINTER(XinputVibration)]
xinput_set_state.restype = ctypes.c_uint
vibration = XinputVibration(int(left_motor * 65535), int(right_motor * 65535))
xinput_set_state(self.__device_number, ctypes.byref(vibration))
def _stop_vibration_win(self):
"""Stop the vibration."""
xinput_set_state = self.manager.xinput.XInputSetState
xinput_set_state.argtypes = [ctypes.c_uint, ctypes.POINTER(XinputVibration)]
xinput_set_state.restype = ctypes.c_uint
stop_vibration = ctypes.byref(XinputVibration(0, 0))
xinput_set_state(self.__device_number, stop_vibration)
def _set_vibration_win(self, left_motor, right_motor, duration):
"""Control the motors on Windows."""
self._start_vibration_win(left_motor, right_motor)
stop_process = Process(
target=delay_and_stop,
args=(duration, self.manager.xinput_dll, self.__device_number),
)
stop_process.start()
def __get_vibration_code(self, left_motor, right_motor, duration):
"""This is some crazy voodoo, if you can simplify it, please do."""
inner_event = struct.pack(
'2h6x2h2x2H28x',
0x50,
-1,
duration,
0,
int(left_motor * 65535),
int(right_motor * 65535),
)
buf_conts = ioctl(self._write_device, 1076905344, inner_event)
return int(codecs.encode(buf_conts[1:3], 'hex'), 16)
def _set_vibration_nix(self, left_motor, right_motor, duration):
"""Control the motors on Linux.
Duration is in miliseconds."""
code = self.__get_vibration_code(left_motor, right_motor, duration)
secs, msecs = convert_timeval(time.time())
outer_event = struct.pack(EVENT_FORMAT, secs, msecs, 0x15, code, 1)
self._write_device.write(outer_event)
self._write_device.flush()
def set_vibration(self, left_motor, right_motor, duration):
"""Control the speed of both motors seperately or together.
left_motor and right_motor arguments require a number between
0 (off) and 1 (full).
duration is miliseconds, e.g. 1000 for a second."""
if WIN:
self._set_vibration_win(left_motor, right_motor, duration)
elif NIX:
self._set_vibration_nix(left_motor, right_motor, duration)
else:
raise NotImplementedError
class OtherDevice(InputDevice):
"""A device of which its is type is either undetectable or has not
been implemented yet.
"""
pass
class LED:
"""A light source."""
def __init__(self, manager, path, name):
self.manager = manager
self.path = path
self.name = name
self._write_file = None
self._character_device_path = None
self._post_init()
def _post_init(self):
"""Post init setup."""
pass
def __str__(self):
return self.name
def __repr__(self):
return '%s.%s("%s")' % (self.__module__, self.__class__.__name__, self.path)
def status(self):
"""Get the device status, i.e. the brightness level."""
status_filename = os.path.join(self.path, 'brightness')
with open(status_filename) as status_fp:
result = status_fp.read()
status_text = result.strip()
try:
status = int(status_text)
except ValueError:
return status_text
return status
def max_brightness(self):
"""Get the device's maximum brightness level."""
status_filename = os.path.join(self.path, 'max_brightness')
with open(status_filename) as status_fp:
result = status_fp.read()
status_text = result.strip()
try:
status = int(status_text)
except ValueError:
return status_text
return status
@property
def _write_device(self):
"""The output device."""
if not self._write_file:
if not NIX:
return None
try:
self._write_file = io.open(self._character_device_path, 'wb')
except PermissionError:
# Python 3
raise PermissionError(PERMISSIONS_ERROR_TEXT)
except IOError as err:
# Python 2 only
if err.errno == 13: # pragma: no cover
raise PermissionError(PERMISSIONS_ERROR_TEXT)
else:
raise
return self._write_file
def _make_event(self, event_type, code, value):
"""Make a new event and send it to the character device."""
secs, msecs = convert_timeval(time.time())
data = struct.pack(EVENT_FORMAT, secs, msecs, event_type, code, value)
self._write_device.write(data)
self._write_device.flush()
class SystemLED(LED):
"""An LED on your system e.g. caps lock."""
def __init__(self, manager, path, name):
self.code = None
self.device_path = None
self.device = None
super(SystemLED, self).__init__(manager, path, name)
def _post_init(self):
"""Set up the device path and type code."""
self._led_type_code = self.manager.get_typecode('LED')
self.device_path = os.path.realpath(os.path.join(self.path, 'device'))
if '::' in self.name:
chardev, code_name = self.name.split('::')
if code_name in self.manager.codes['LED_type_codes']:
self.code = self.manager.codes['LED_type_codes'][code_name]
try:
event_number = chardev.split('input')[1]
except IndexError:
print("Failed with", self.name)
raise
else:
self._character_device_path = '/dev/input/event' + event_number
self._match_device()
def on(self): # pylint: disable=invalid-name
"""Turn the light on."""
self._make_event(1)
def off(self):
"""Turn the light off."""
self._make_event(0)
def _make_event(self, value): # pylint: disable=arguments-differ
"""Make a new event and send it to the character device."""
super(SystemLED, self)._make_event(self._led_type_code, self.code, value)
def _match_device(self):
"""If the LED is connected to an input device,
associate the objects."""
for device in self.manager.all_devices:
if device.get_char_device_path() == self._character_device_path:
self.device = device
device.leds.append(self)
break
class GamepadLED(LED):
"""A light source on a gamepad."""
def __init__(self, manager, path, name):
self.code = None
self.device = None
self.gamepad = None
super(GamepadLED, self).__init__(manager, path, name)
def _post_init(self):
self._match_device()
self._character_device_path = (
self.gamepad.get_char_device_path() if self.gamepad else None
)
def _match_device(self):
number = int(self.name.split('xpad')[1])
for gamepad in self.manager.gamepads:
if number == gamepad.get_number():
self.gamepad = gamepad
gamepad.leds.append(self)
break
class RawInputDeviceList(ctypes.Structure):
"""
Contains information about a raw input device.
For full details see Microsoft's documentation:
http://msdn.microsoft.com/en-us/library/windows/desktop/
ms645568(v=vs.85).aspx
"""
# pylint: disable=too-few-public-methods
_fields_ = [("hDevice", HANDLE), ("dwType", DWORD)]
class DeviceManager:
"""Provides access to all connected and detectible user input
devices."""
# pylint: disable=too-many-instance-attributes
def __init__(self):
self.codes = {key: dict(value) for key, value in EVENT_MAP}
self._raw = []
self.keyboards = []
self.mice = []
self.gamepads = []
self.other_devices = []
self.all_devices = []
self.leds = []
self.microbits = []
self.xinput = None
self.xinput_dll = None
if WIN:
self._raw_device_counts = {
'mice': 0,
'keyboards': 0,
'otherhid': 0,
'unknown': 0,
}
self._post_init()
def _post_init(self):
"""Call the find devices method for the relevant platform."""
if WIN:
self._find_devices_win()
elif MAC:
self._find_devices_mac()
else:
self._find_devices()
self._update_all_devices()
if NIX:
self._find_leds()
def _update_all_devices(self):
"""Update the all_devices list."""
self.all_devices = []
self.all_devices.extend(self.keyboards)
self.all_devices.extend(self.mice)
self.all_devices.extend(self.gamepads)
self.all_devices.extend(self.other_devices)
def _parse_device_path(self, device_path, char_path_override=None):
"""Parse each device and add to the approriate list."""
# 1. Make sure that we can parse the device path.
try:
device_type = device_path.rsplit('-', 1)[1]
except IndexError:
warn(
"The following device path was skipped as it could "
"not be parsed: %s" % device_path,
RuntimeWarning,
)
return
# 2. Make sure each device is only added once.
realpath = os.path.realpath(device_path)
if realpath in self._raw:
return
self._raw.append(realpath)
# 3. All seems good, append the device to the relevant list.
if device_type == 'kbd':
self.keyboards.append(Keyboard(self, device_path, char_path_override))
elif device_type == 'mouse':
self.mice.append(Mouse(self, device_path, char_path_override))
elif device_type == 'joystick':
self.gamepads.append(GamePad(self, device_path, char_path_override))
else:
self.other_devices.append(
OtherDevice(self, device_path, char_path_override)
)
def _find_xinput(self):
"""Find most recent xinput library."""
for dll in XINPUT_DLL_NAMES:
try:
self.xinput = getattr(ctypes.windll, dll)
except OSError:
pass
else:
# We found an xinput driver
self.xinput_dll = dll
break
else:
# We didn't find an xinput library
warn("No xinput driver dll found, gamepads not supported.", RuntimeWarning)
def _find_devices_win(self):
"""Find devices on Windows."""
self._find_xinput()
self._detect_gamepads()
self._count_devices()
if self._raw_device_counts['keyboards'] > 0:
self.keyboards.append(
Keyboard(self, "/dev/input/by-id/usb-A_Nice_Keyboard-event-kbd")
)
if self._raw_device_counts['mice'] > 0:
self.mice.append(
Mouse(
self, "/dev/input/by-id/usb-A_Nice_Mouse_called_Arthur-event-mouse"
)
)
def _find_devices_mac(self):
"""Find devices on Mac."""
self.keyboards.append(Keyboard(self))
self.mice.append(MightyMouse(self))
self.mice.append(Mouse(self))
def _detect_gamepads(self):
"""Find gamepads."""
state = XinputState()
# Windows allows up to 4 gamepads.
for device_number in range(4):
res = self.xinput.XInputGetState(device_number, ctypes.byref(state))
if res == XINPUT_ERROR_SUCCESS:
# We found a gamepad
device_path = (
"/dev/input/by_id/"
+ "usb-Microsoft_Corporation_Controller_%s-event-joystick"
% device_number
)
self.gamepads.append(GamePad(self, device_path))
continue
if res != XINPUT_ERROR_DEVICE_NOT_CONNECTED:
raise RuntimeError(
"Unknown error %d attempting to get state of device %d"
% (res, device_number)
)
def _count_devices(self):
"""See what Windows' GetRawInputDeviceList wants to tell us.
For now, we are just seeing if there is at least one keyboard
and/or mouse attached.
GetRawInputDeviceList could be used to help distinguish between
different keyboards and mice on the system in the way Linux
can. However, Roma uno die non est condita.
"""
number_of_devices = ctypes.c_uint()
if (
ctypes.windll.user32.GetRawInputDeviceList(
ctypes.POINTER(ctypes.c_int)(),
ctypes.byref(number_of_devices),
ctypes.sizeof(RawInputDeviceList),
)
== -1
):
warn(
"Call to GetRawInputDeviceList was unsuccessful."
"We have no idea if a mouse or keyboard is attached.",
RuntimeWarning,
)
return
devices_found = (RawInputDeviceList * number_of_devices.value)()
if (
ctypes.windll.user32.GetRawInputDeviceList(
devices_found,
ctypes.byref(number_of_devices),
ctypes.sizeof(RawInputDeviceList),
)
== -1
):
warn(
"Call to GetRawInputDeviceList was unsuccessful."
"We have no idea if a mouse or keyboard is attached.",
RuntimeWarning,
)
return
for device in devices_found:
if device.dwType == 0:
self._raw_device_counts['mice'] += 1
elif device.dwType == 1:
self._raw_device_counts['keyboards'] += 1
elif device.dwType == 2:
self._raw_device_counts['otherhid'] += 1
else:
self._raw_device_counts['unknown'] += 1
def _find_devices(self):
"""Find available devices."""
self._find_by('id')
self._find_by('path')
self._find_special()
def _find_by(self, key):
"""Find devices."""
by_path = glob.glob('/dev/input/by-{key}/*-event-*'.format(key=key))
for device_path in by_path:
self._parse_device_path(device_path)
def _find_leds(self):
"""Find LED devices, Linux-only so far."""
for path in glob.glob('/sys/class/leds/*'):
self._parse_led_path(path)
def _parse_led_path(self, path):
name = path.rsplit('/', 1)[1]
if name.startswith('xpad'):
self.leds.append(GamepadLED(self, path, name))
elif name.startswith('input'):
self.leds.append(SystemLED(self, path, name))
else:
self.leds.append(LED(self, path, name))
def _get_char_names(self):
"""Get a list of already found devices."""
return [device.get_char_name() for device in self.all_devices]
def _find_special(self):
"""Look for special devices."""
charnames = self._get_char_names()
for eventdir in glob.glob('/sys/class/input/event*'):
char_name = os.path.split(eventdir)[1]
if char_name in charnames:
continue
name_file = os.path.join(eventdir, 'device', 'name')
with open(name_file) as name_file:
device_name = name_file.read().strip()
if device_name in self.codes['specials']:
self._parse_device_path(
self.codes['specials'][device_name],
os.path.join('/dev/input', char_name),
)
def __iter__(self):
return iter(self.all_devices)
def __getitem__(self, index):
try:
return self.all_devices[index]
except IndexError:
raise IndexError("list index out of range")
def get_event_type(self, raw_type):
"""Convert the code to a useful string name."""
try:
return self.codes['types'][raw_type]
except KeyError:
raise UnknownEventType("We don't know this event type")
def get_event_string(self, evtype, code):
"""Get the string name of the event."""
if WIN and evtype == 'Key':
# If we can map the code to a common one then do it
try:
code = self.codes['wincodes'][code]
except KeyError:
pass
try:
return self.codes[evtype][code]
except KeyError:
raise UnknownEventCode("We don't know this event.", evtype, code)
def get_typecode(self, name):
"""Returns type code for `name`."""
return self.codes['type_codes'].get(name)
def detect_microbit(self):
"""Detect a microbit."""
try:
gpad = MicroBitPad(self)
except ModuleNotFoundError:
warn(
"The microbit library could not be found in the pythonpath. \n"
"For more information, please visit \n"
"https://inputs.readthedocs.io/en/latest/user/microbit.html",
RuntimeWarning,
)
else:
self.microbits.append(gpad)
self.gamepads.append(gpad)
SPIN_UP_MOTOR = (
'00000',
'00001',
'00011',
'00111',
'01111',
'11111',
'01111',
'00011',
'00001',
'00000',
'00001',
'00011',
'00111',
'01111',
'11111',
'00000',
'11111',
'00000',
'11111',
'00000',
)
class MicroBitPad(GamePad):
"""A BBC Micro:bit flashed with bitio."""
def __init__(self, manager, device_path=None, char_path_override=None):
if not device_path:
device_path = '/dev/input/by-id/dialup-BBC_MicroBit-event-joystick'
if not char_path_override:
char_path_override = '/dev/input/microbit0'
super(MicroBitPad, self).__init__(manager, device_path, char_path_override)
# pylint: disable=no-member,import-error
import microbit
self.microbit = microbit
self.default_image = microbit.Image("00500:00500:00500:00500:00500")
self._setup_rumble()
self.set_display()
def set_display(self, index=None):
"""Show an image on the display."""
# pylint: disable=no-member
if index:
image = self.microbit.Image.STD_IMAGES[index]
else:
image = self.default_image
self.microbit.display.show(image)
def _setup_rumble(self):
"""Setup the three animations which simulate a rumble."""
self.left_rumble = self._get_ready_to('99500')
self.right_rumble = self._get_ready_to('00599')
self.double_rumble = self._get_ready_to('99599')
def _set_name(self):
self.name = "BBC microbit Gamepad"
def _set_evdev_state(self):
self._evdev = False
@staticmethod
def _get_target_function():
return microbit_process
def _get_data(self, read_size):
"""Get data from the character device."""
return self._pipe.recv_bytes()
def _get_ready_to(self, rumble):
"""Watch us wreck the mike!
PSYCHE!"""
# pylint: disable=no-member
return [
self.microbit.Image(
':'.join([rumble if char == '1' else '00500' for char in code])
)
for code in SPIN_UP_MOTOR
]
def _full_speed_rumble(self, images, duration):
"""Simulate the motors running at full."""
while duration > 0:
self.microbit.display.show(images[0]) # pylint: disable=no-member
time.sleep(0.04)
self.microbit.display.show(images[1]) # pylint: disable=no-member
time.sleep(0.04)
duration -= 0.08
def _spin_up(self, images, duration):
"""Simulate the motors getting warmed up."""
total = 0
# pylint: disable=no-member
for image in images:
self.microbit.display.show(image)
time.sleep(0.05)
total += 0.05
if total >= duration:
return
remaining = duration - total
self._full_speed_rumble(images[-2:], remaining)
self.set_display()
def set_vibration(self, left_motor, right_motor, duration):
"""Control the speed of both motors seperately or together.
left_motor and right_motor arguments require a number:
0 (off) or 1 (full).
duration is miliseconds, e.g. 1000 for a second."""
if left_motor and right_motor:
return self._spin_up(self.double_rumble, duration / 1000)
if left_motor:
return self._spin_up(self.left_rumble, duration / 1000)
if right_motor:
return self._spin_up(self.right_rumble, duration / 1000)
return -1
def microbit_process(pipe):
"""Simple subprocess for reading mouse events on the microbit."""
gamepad_listener = MicroBitListener(pipe)
gamepad_listener.listen()
class MicroBitListener(BaseListener):
"""Tracks the current state and sends changes to the MicroBitPad
device class."""
def __init__(self, pipe):
super(MicroBitListener, self).__init__(pipe)
self.active = True
self.events = []
self.state = {
('Absolute', 0x10, 0),
('Absolute', 0x11, 0),
('Key', 0x130, 0),
('Key', 0x131, 0),
('Key', 0x13A, 0),
('Key', 0x133, 0),
('Key', 0x134, 0),
}
self.dpad = True
self.sensitivity = 300
# pylint: disable=import-error
import microbit
self.microbit = microbit
def listen(self):
"""Listen while the device is active."""
while self.active:
self.handle_input()
def uninstall_handle_input(self):
"""Stop listing when active is false."""
self.active = False
def handle_new_events(self, events):
"""Add each new events to the event queue."""
for event in events:
self.events.append(
self.create_event_object(event[0], event[1], int(event[2]))
)
def handle_abs(self):
"""Gets the state as the raw abolute numbers."""
# pylint: disable=no-member
x_raw = self.microbit.accelerometer.get_x()
y_raw = self.microbit.accelerometer.get_y()
x_abs = ('Absolute', 0x00, x_raw)
y_abs = ('Absolute', 0x01, y_raw)
return x_abs, y_abs
def handle_dpad(self):
"""Gets the state of the virtual dpad."""
# pylint: disable=no-member
x_raw = self.microbit.accelerometer.get_x()
y_raw = self.microbit.accelerometer.get_y()
minus_sens = self.sensitivity * -1
if x_raw < minus_sens:
x_state = ('Absolute', 0x10, -1)
elif x_raw > self.sensitivity:
x_state = ('Absolute', 0x10, 1)
else:
x_state = ('Absolute', 0x10, 0)
if y_raw < minus_sens:
y_state = ('Absolute', 0x11, -1)
elif y_raw > self.sensitivity:
y_state = ('Absolute', 0x11, 1)
else:
y_state = ('Absolute', 0x11, 1)
return x_state, y_state
def check_state(self):
"""Tracks differences in the device state."""
if self.dpad:
x_state, y_state = self.handle_dpad()
else:
x_state, y_state = self.handle_abs()
new_state = {
x_state,
y_state,
('Key', 0x130, int(self.microbit.button_a.is_pressed())),
('Key', 0x131, int(self.microbit.button_b.is_pressed())),
('Key', 0x13A, int(self.microbit.pin0.is_touched())),
('Key', 0x133, int(self.microbit.pin1.is_touched())),
('Key', 0x134, int(self.microbit.pin2.is_touched())),
}
events = new_state - self.state
self.state = new_state
return events
def handle_input(self):
"""Sends differences in the device state to the MicroBitPad
as events."""
difference = self.check_state()
if not difference:
return
self.events = []
self.handle_new_events(difference)
self.update_timeval()
self.events.append(self.sync_marker(self.timeval))
self.write_to_pipe(self.events)
devices = DeviceManager() # pylint: disable=invalid-name
def get_key():
"""Get a single keypress from a keyboard."""
try:
keyboard = devices.keyboards[0]
except IndexError:
raise UnpluggedError("No keyboard found.")
return keyboard.read()
def get_mouse():
"""Get a single movement or click from a mouse."""
try:
mouse = devices.mice[0]
except IndexError:
raise UnpluggedError("No mice found.")
return mouse.read()
def get_gamepad():
"""Get a single action from a gamepad."""
try:
gamepad = devices.gamepads[0]
except IndexError:
raise UnpluggedError("No gamepad found.")
return gamepad.read()