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