Added support for multimedia keyboard button simulation for controlling the host.

pull/2/head
Paulus Schoutsen 2013-10-09 00:03:05 -07:00
parent 2b3d81d007
commit 866a3e852e
11 changed files with 1095 additions and 2 deletions

View File

@ -17,7 +17,7 @@ import dateutil.parser
from phue import Bridge
import requests
from .packages.pychromecast import pychromecast
from .packages import pychromecast, pykeyboard
from . import track_state_change
from .util import sanitize_filename
@ -35,7 +35,10 @@ EVENT_BROWSE_URL = "browse_url"
EVENT_CHROMECAST_YOUTUBE_VIDEO = "chromecast.play_youtube_video"
EVENT_TURN_LIGHT_ON = "turn_light_on"
EVENT_TURN_LIGHT_OFF = "turn_light_off"
EVENT_KEYBOARD_VOLUME_UP = "keyboard.volume_up"
EVENT_KEYBOARD_VOLUME_DOWN = "keyboard.volume_down"
EVENT_KEYBOARD_VOLUME_MUTE = "keyboard.volume_mute"
EVENT_KEYBOARD_MEDIA_PLAY_PAUSE = "keyboard.media_play_pause"
def _hue_process_transition_time(transition_seconds):
""" Transition time is in 1/10th seconds
@ -313,3 +316,20 @@ def setup_chromecast(eventbus, host):
eventbus.listen(EVENT_CHROMECAST_YOUTUBE_VIDEO,
lambda event: pychromecast.play_youtube_video(host, event.data['video']))
def setup_media_buttons(eventbus):
""" Listen for keyboard events. """
keyboard = pykeyboard.PyKeyboard()
keyboard.special_key_assignment()
eventbus.listen(EVENT_KEYBOARD_VOLUME_UP,
lambda event: keyboard.tap_key(keyboard.volume_up_key))
eventbus.listen(EVENT_KEYBOARD_VOLUME_DOWN,
lambda event: keyboard.tap_key(keyboard.volume_down_key))
eventbus.listen(EVENT_KEYBOARD_VOLUME_MUTE,
lambda event: keyboard.tap_key(keyboard.volume_mute_key))
eventbus.listen(EVENT_KEYBOARD_MEDIA_PLAY_PAUSE,
lambda event: keyboard.tap_key(keyboard.media_play_pause_key))

View File

@ -2,4 +2,15 @@
Not all external Git repositories that we depend on are
available as a package for pip. That is why we include
them here.
PyChromecast
------------
https://github.com/balloob/pychromecast
PyKeyboard
----------
Forked from https://github.com/SavinaRoja/PyUserInput
Patched with some code to get it working on OS X:
https://github.com/balloob/PyUserInput
"""

View File

@ -0,0 +1,37 @@
#Copyright 2013 Paul Barton
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The goal of PyMouse is to have a cross-platform way to control the mouse.
PyMouse should work on Windows, Mac and any Unix that has xlib.
PyKeyboard is a part of PyUserInput, along with PyMouse, for more information
about this project, see:
http://github.com/SavinaRoja/PyUserInput
"""
import sys
if sys.platform.startswith('java'):
from .java_ import PyKeyboard
elif sys.platform == 'darwin':
from .mac import PyKeyboard, PyKeyboardEvent
elif sys.platform == 'win32':
from .windows import PyKeyboard, PyKeyboardEvent
else:
from .x11 import PyKeyboard, PyKeyboardEvent

View File

@ -0,0 +1,102 @@
#Copyright 2013 Paul Barton
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
As the base file, this provides a rough operational model along with the
framework to be extended by each platform.
"""
import time
from threading import Thread
class PyKeyboardMeta(object):
"""
The base class for PyKeyboard. Represents basic operational model.
"""
def press_key(self, character=''):
"""Press a given character key."""
raise NotImplementedError
def release_key(self, character=''):
"""Release a given character key."""
raise NotImplementedError
def tap_key(self, character='', n=1, interval=0):
"""Press and release a given character key n times."""
for i in xrange(n):
self.press_key(character)
self.release_key(character)
time.sleep(interval)
def type_string(self, char_string, interval=0):
"""A convenience method for typing longer strings of characters."""
for i in char_string:
time.sleep(interval)
self.tap_key(i)
def special_key_assignment(self):
"""Makes special keys more accessible."""
raise NotImplementedError
def lookup_character_value(self, character):
"""
If necessary, lookup a valid API value for the key press from the
character.
"""
raise NotImplementedError
def is_char_shifted(self, character):
"""Returns True if the key character is uppercase or shifted."""
if character.isupper():
return True
if character in '<>?:"{}|~!@#$%^&*()_+':
return True
return False
class PyKeyboardEventMeta(Thread):
"""
The base class for PyKeyboard. Represents basic operational model.
"""
def __init__(self, capture=False):
Thread.__init__(self)
self.daemon = True
self.capture = capture
self.state = True
def run(self):
self.state = True
def stop(self):
self.state = False
def handler(self):
raise NotImplementedError
def key_press(self, key):
"""Subclass this method with your key press event handler."""
pass
def key_release(self, key):
"""Subclass this method with your key release event handler."""
pass
def escape_code(self):
"""
Defines a means to signal a stop to listening. Subclass this with your
escape behavior.
"""
escape = None
return escape

View File

@ -0,0 +1,14 @@
#Copyright 2013 Paul Barton
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,201 @@
#Copyright 2013 Paul Barton
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
from Quartz import *
from AppKit import NSEvent
from .base import PyKeyboardMeta, PyKeyboardEventMeta
# Taken from events.h
# /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
character_translate_table = {
'a': 0x00,
's': 0x01,
'd': 0x02,
'f': 0x03,
'h': 0x04,
'g': 0x05,
'z': 0x06,
'x': 0x07,
'c': 0x08,
'v': 0x09,
'b': 0x0b,
'q': 0x0c,
'w': 0x0d,
'e': 0x0e,
'r': 0x0f,
'y': 0x10,
't': 0x11,
'1': 0x12,
'2': 0x13,
'3': 0x14,
'4': 0x15,
'6': 0x16,
'5': 0x17,
'=': 0x18,
'9': 0x19,
'7': 0x1a,
'-': 0x1b,
'8': 0x1c,
'0': 0x1d,
']': 0x1e,
'o': 0x1f,
'u': 0x20,
'[': 0x21,
'i': 0x22,
'p': 0x23,
'l': 0x25,
'j': 0x26,
'\'': 0x27,
'k': 0x28,
';': 0x29,
'\\': 0x2a,
',': 0x2b,
'/': 0x2c,
'n': 0x2d,
'm': 0x2e,
'.': 0x2f,
'`': 0x32,
' ': 0x31,
'\r': 0x24,
'\t': 0x30,
'shift': 0x38
}
# Taken from ev_keymap.h
# http://www.opensource.apple.com/source/IOHIDFamily/IOHIDFamily-86.1/IOHIDSystem/IOKit/hidsystem/ev_keymap.h
special_key_translate_table = {
'KEYTYPE_SOUND_UP': 0,
'KEYTYPE_SOUND_DOWN': 1,
'KEYTYPE_BRIGHTNESS_UP': 2,
'KEYTYPE_BRIGHTNESS_DOWN': 3,
'KEYTYPE_CAPS_LOCK': 4,
'KEYTYPE_HELP': 5,
'POWER_KEY': 6,
'KEYTYPE_MUTE': 7,
'UP_ARROW_KEY': 8,
'DOWN_ARROW_KEY': 9,
'KEYTYPE_NUM_LOCK': 10,
'KEYTYPE_CONTRAST_UP': 11,
'KEYTYPE_CONTRAST_DOWN': 12,
'KEYTYPE_LAUNCH_PANEL': 13,
'KEYTYPE_EJECT': 14,
'KEYTYPE_VIDMIRROR': 15,
'KEYTYPE_PLAY': 16,
'KEYTYPE_NEXT': 17,
'KEYTYPE_PREVIOUS': 18,
'KEYTYPE_FAST': 19,
'KEYTYPE_REWIND': 20,
'KEYTYPE_ILLUMINATION_UP': 21,
'KEYTYPE_ILLUMINATION_DOWN': 22,
'KEYTYPE_ILLUMINATION_TOGGLE': 23
}
class PyKeyboard(PyKeyboardMeta):
def press_key(self, key):
if key in special_key_translate_table:
self._press_special_key(key, True)
else:
self._press_normal_key(key, True)
def release_key(self, key):
if key in special_key_translate_table:
self._press_special_key(key, False)
else:
self._press_normal_key(key, False)
def special_key_assignment(self):
self.volume_mute_key = 'KEYTYPE_MUTE'
self.volume_down_key = 'KEYTYPE_SOUND_DOWN'
self.volume_up_key = 'KEYTYPE_SOUND_UP'
self.media_play_pause_key = 'KEYTYPE_PLAY'
# Doesn't work :(
# self.media_next_track_key = 'KEYTYPE_NEXT'
# self.media_prev_track_key = 'KEYTYPE_PREVIOUS'
def _press_normal_key(self, key, down):
try:
if self.is_char_shifted(key):
key_code = character_translate_table[key.lower()]
event = CGEventCreateKeyboardEvent(None,
character_translate_table['shift'], down)
CGEventPost(kCGHIDEventTap, event)
# Tiny sleep to let OS X catch up on us pressing shift
time.sleep(.01)
else:
key_code = character_translate_table[key]
event = CGEventCreateKeyboardEvent(None, key_code, down)
CGEventPost(kCGHIDEventTap, event)
except KeyError:
raise RuntimeError("Key {} not implemented.".format(key))
def _press_special_key(self, key, down):
""" Helper method for special keys.
Source: http://stackoverflow.com/questions/11045814/emulate-media-key-press-on-mac
"""
key_code = special_key_translate_table[key]
ev = NSEvent.otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
NSSystemDefined, # type
(0,0), # location
0xa00 if down else 0xb00, # flags
0, # timestamp
0, # window
0, # ctx
8, # subtype
(key_code << 16) | ((0xa if down else 0xb) << 8), # data1
-1 # data2
)
CGEventPost(0, ev.CGEvent())
class PyKeyboardEvent(PyKeyboardEventMeta):
def run(self):
tap = CGEventTapCreate(
kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown) |
CGEventMaskBit(kCGEventKeyUp),
self.handler,
None)
loopsource = CFMachPortCreateRunLoopSource(None, tap, 0)
loop = CFRunLoopGetCurrent()
CFRunLoopAddSource(loop, loopsource, kCFRunLoopDefaultMode)
CGEventTapEnable(tap, True)
while self.state:
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 5, False)
def handler(self, proxy, type, event, refcon):
key = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)
if type == kCGEventKeyDown:
self.key_press(key)
elif type == kCGEventKeyUp:
self.key_release(key)
if self.capture:
CGEventSetType(event, kCGEventNull)
return event

View File

@ -0,0 +1,14 @@
#Copyright 2013 Paul Barton
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,14 @@
#Copyright 2013 Paul Barton
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,316 @@
#Copyright 2013 Paul Barton
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
from ctypes import *
import win32api
from win32con import *
import pythoncom, pyHook
from .base import PyKeyboardMeta, PyKeyboardEventMeta
import time
class SupportError(Exception):
"""For keys not supported on this system"""
def __init__(self, value):
self.value = value
def __str__(self):
return('The {0} key is not supported in Windows'.format(self.value))
class PyKeyboard(PyKeyboardMeta):
"""
The PyKeyboard implementation for Windows systems. This allows one to
simulate keyboard input.
"""
def __init__(self):
PyKeyboardMeta.__init__(self)
self.special_key_assignment()
def press_key(self, character=''):
"""
Press a given character key.
"""
try:
shifted = self.is_char_shifted(character)
except AttributeError:
win32api.keybd_event(character, 0, 0, 0)
else:
if shifted:
win32api.keybd_event(self.shift_key, 0, 0, 0)
char_vk = win32api.VkKeyScan(character)
win32api.keybd_event(char_vk, 0, 0, 0)
def release_key(self, character=''):
"""
Release a given character key.
"""
try:
shifted = self.is_char_shifted(character)
except AttributeError:
win32api.keybd_event(character, 0, KEYEVENTF_KEYUP, 0)
else:
if shifted:
win32api.keybd_event(self.shift_key, 0, KEYEVENTF_KEYUP, 0)
char_vk = win32api.VkKeyScan(character)
win32api.keybd_event(char_vk, 0, KEYEVENTF_KEYUP, 0)
def special_key_assignment(self):
"""
Special Key assignment for windows
"""
#As defined by Microsoft, refer to:
#http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
self.backspace_key = VK_BACK
self.tab_key = VK_TAB
self.clear_key = VK_CLEAR
self.return_key = VK_RETURN
self.enter_key = self.return_key # Because many keyboards call it "Enter"
self.shift_key = VK_SHIFT
self.shift_l_key = VK_LSHIFT
self.shift_r_key = VK_RSHIFT
self.control_key = VK_CONTROL
self.control_l_key = VK_LCONTROL
self.control_r_key = VK_RCONTROL
#Windows uses "menu" to refer to Alt...
self.menu_key = VK_MENU
self.alt_l_key = VK_LMENU
self.alt_r_key = VK_RMENU
self.alt_key = self.alt_l_key
self.pause_key = VK_PAUSE
self.caps_lock_key = VK_CAPITAL
self.capital_key = self.caps_lock_key
self.num_lock_key = VK_NUMLOCK
self.scroll_lock_key = VK_SCROLL
#Windows Language Keys,
self.kana_key = VK_KANA
self.hangeul_key = VK_HANGEUL # old name - should be here for compatibility
self.hangul_key = VK_HANGUL
self.junjua_key = VK_JUNJA
self.final_key = VK_FINAL
self.hanja_key = VK_HANJA
self.kanji_key = VK_KANJI
self.convert_key = VK_CONVERT
self.nonconvert_key = VK_NONCONVERT
self.accept_key = VK_ACCEPT
self.modechange_key = VK_MODECHANGE
#More Keys
self.escape_key = VK_ESCAPE
self.space_key = VK_SPACE
self.prior_key = VK_PRIOR
self.next_key = VK_NEXT
self.page_up_key = self.prior_key
self.page_down_key = self.next_key
self.home_key = VK_HOME
self.up_key = VK_UP
self.down_key = VK_DOWN
self.left_key = VK_LEFT
self.right_key = VK_RIGHT
self.end_key = VK_END
self.select_key = VK_SELECT
self.print_key = VK_PRINT
self.snapshot_key = VK_SNAPSHOT
self.print_screen_key = self.snapshot_key
self.execute_key = VK_EXECUTE
self.insert_key = VK_INSERT
self.delete_key = VK_DELETE
self.help_key = VK_HELP
self.windows_l_key = VK_LWIN
self.super_l_key = self.windows_l_key
self.windows_r_key = VK_RWIN
self.super_r_key = self.windows_r_key
self.apps_key = VK_APPS
#Numpad
self.keypad_keys = {'Space': None,
'Tab': None,
'Enter': None, # Needs Fixing
'F1': None,
'F2': None,
'F3': None,
'F4': None,
'Home': VK_NUMPAD7,
'Left': VK_NUMPAD4,
'Up': VK_NUMPAD8,
'Right': VK_NUMPAD6,
'Down': VK_NUMPAD2,
'Prior': None,
'Page_Up': VK_NUMPAD9,
'Next': None,
'Page_Down': VK_NUMPAD3,
'End': VK_NUMPAD1,
'Begin': None,
'Insert': VK_NUMPAD0,
'Delete': VK_DECIMAL,
'Equal': None, # Needs Fixing
'Multiply': VK_MULTIPLY,
'Add': VK_ADD,
'Separator': VK_SEPARATOR,
'Subtract': VK_SUBTRACT,
'Decimal': VK_DECIMAL,
'Divide': VK_DIVIDE,
0: VK_NUMPAD0,
1: VK_NUMPAD1,
2: VK_NUMPAD2,
3: VK_NUMPAD3,
4: VK_NUMPAD4,
5: VK_NUMPAD5,
6: VK_NUMPAD6,
7: VK_NUMPAD7,
8: VK_NUMPAD8,
9: VK_NUMPAD9}
self.numpad_keys = self.keypad_keys
#FKeys
self.function_keys = [None, VK_F1, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6,
VK_F7, VK_F8, VK_F9, VK_F10, VK_F11, VK_F12,
VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18,
VK_F19, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24,
None, None, None, None, None, None, None, None,
None, None, None] #Up to 36 as in x11
#Miscellaneous
self.cancel_key = VK_CANCEL
self.break_key = self.cancel_key
self.mode_switch_key = VK_MODECHANGE
self.browser_back_key = VK_BROWSER_BACK
self.browser_forward_key = VK_BROWSER_FORWARD
self.processkey_key = VK_PROCESSKEY
self.attn_key = VK_ATTN
self.crsel_key = VK_CRSEL
self.exsel_key = VK_EXSEL
self.ereof_key = VK_EREOF
self.play_key = VK_PLAY
self.zoom_key = VK_ZOOM
self.noname_key = VK_NONAME
self.pa1_key = VK_PA1
self.oem_clear_key = VK_OEM_CLEAR
self.volume_mute_key = VK_VOLUME_MUTE
self.volume_down_key = VK_VOLUME_DOWN
self.volume_up_key = VK_VOLUME_UP
self.media_next_track_key = VK_MEDIA_NEXT_TRACK
self.media_prev_track_key = VK_MEDIA_PREV_TRACK
self.media_play_pause_key = VK_MEDIA_PLAY_PAUSE
self.begin_key = self.home_key
#LKeys - Unsupported
self.l_keys = [None] * 11
#RKeys - Unsupported
self.r_keys = [None] * 16
#Other unsupported Keys from X11
self.linefeed_key = None
self.find_key = None
self.meta_l_key = None
self.meta_r_key = None
self.sys_req_key = None
self.hyper_l_key = None
self.hyper_r_key = None
self.undo_key = None
self.redo_key = None
self.script_switch_key = None
class PyKeyboardEvent(PyKeyboardEventMeta):
"""
The PyKeyboardEvent implementation for Windows Systems. This allows one
to listen for keyboard input.
"""
def __init__(self):
PyKeyboardEventMeta.__init__(self)
self.hm = pyHook.HookManager()
self.shift_state = 0 # 0 is off, 1 is on
self.alt_state = 0 # 0 is off, 2 is on
def run(self):
"""Begin listening for keyboard input events."""
self.state = True
self.hm.KeyAll = self.handler
self.hm.HookKeyboard()
while self.state:
time.sleep(0.01)
pythoncom.PumpWaitingMessages()
def stop(self):
"""Stop listening for keyboard input events."""
self.hm.UnhookKeyboard()
self.state = False
def handler(self, reply):
"""Upper level handler of keyboard events."""
if reply.Message == pyHook.HookConstants.WM_KEYDOWN:
self._key_press(reply)
elif reply.Message == pyHook.HookConstants.WM_KEYUP:
self._key_release(reply)
elif reply.Message == pyHook.HookConstants.WM_SYSKEYDOWN:
self._key_press(reply)
elif reply.Message == pyHook.HookConstants.WM.SYSKEYUP:
self._key_release(reply)
else:
print('Keyboard event message unhandled: {0}'.format(reply.Message))
return not self.capture
def _key_press(self, event):
if self.escape_code(event): #Quit if this returns True
self.stop()
if event.GetKey() in ['Shift', 'Lshift', 'Rshift', 'Capital']:
self.toggle_shift_state()
if event.GetKey() in ['Menu', 'Lmenu', 'Rmenu']:
self.toggle_alt_state()
#print('Key Pressed!')
#print('GetKey: {0}'.format(event.GetKey())) # Name of the virtual keycode, str
#print('IsAlt: {0}'.format(event.IsAlt())) # Was the alt key depressed?, bool
#print('IsExtended: {0}'.format(event.IsExtended())) # Is this an extended key?, bool
#print('IsInjected: {0}'.format(event.IsInjected())) # Was this event generated programmatically?, bool
#print('IsTransition: {0}'.format(event.IsTransition())) #Is this a transition from up to down or vice versa?, bool
#print('ASCII: {0}'.format(event.Ascii)) # ASCII value, if one exists, str
#print('KeyID: {0}'.format(event.KeyID)) # Virtual key code, int
#print('ScanCode: {0}'.format(event.ScanCode)) # Scan code, int
def _key_release(self, event):
if event.GetKey() in ['Shift', 'Lshift', 'Rshift', 'Capital']:
self.toggle_shift_state()
if event.GetKey() in ['Menu', 'Lmenu', 'Rmenu']:
self.toggle_alt_state()
self.key_release()
#print('Key Released!')
#print('GetKey: {0}'.format(event.GetKey())) # Name of the virtual keycode, str
#print('IsAlt: {0}'.format(event.IsAlt())) # Was the alt key depressed?, bool
#print('IsExtended: {0}'.format(event.IsExtended())) # Is this an extended key?, bool
#print('IsInjected: {0}'.format(event.IsInjected())) # Was this event generated programmatically?, bool
#print('IsTransition: {0}'.format(event.IsTransition())) #Is this a transition from up to down or vice versa?, bool
#print('ASCII: {0}'.format(event.Ascii)) # ASCII value, if one exists, str
#print('KeyID: {0}'.format(event.KeyID)) # Virtual key code, int
#print('ScanCode: {0}'.format(event.ScanCode)) # Scan code, int
def escape_code(self, event):
if event.KeyID == VK_ESCAPE:
return True
return False
def toggle_shift_state(self):
'''Does toggling for the shift state.'''
if self.shift_state == 0:
self.shift_state = 1
elif self.shift_state == 1:
self.shift_state = 0
else:
return False
return True
def toggle_alt_state(self):
'''Does toggling for the alt state.'''
if self.alt_state == 0:
self.alt_state = 2
elif self.alt_state == 2:
self.alt_state = 0
else:
return False
return True

View File

@ -0,0 +1,363 @@
#Copyright 2013 Paul Barton
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
from Xlib.display import Display
from Xlib import X
from Xlib.ext.xtest import fake_input
from Xlib.XK import string_to_keysym, keysym_to_string
from Xlib.ext import record
from Xlib.protocol import rq
from .base import PyKeyboardMeta, PyKeyboardEventMeta
import time
special_X_keysyms = {
' ': "space",
'\t': "Tab",
'\n': "Return", # for some reason this needs to be cr, not lf
'\r': "Return",
'\e': "Escape",
'!': "exclam",
'#': "numbersign",
'%': "percent",
'$': "dollar",
'&': "ampersand",
'"': "quotedbl",
'\'': "apostrophe",
'(': "parenleft",
')': "parenright",
'*': "asterisk",
'=': "equal",
'+': "plus",
',': "comma",
'-': "minus",
'.': "period",
'/': "slash",
':': "colon",
';': "semicolon",
'<': "less",
'>': "greater",
'?': "question",
'@': "at",
'[': "bracketleft",
']': "bracketright",
'\\': "backslash",
'^': "asciicircum",
'_': "underscore",
'`': "grave",
'{': "braceleft",
'|': "bar",
'}': "braceright",
'~': "asciitilde"
}
class PyKeyboard(PyKeyboardMeta):
"""
The PyKeyboard implementation for X11 systems (mostly linux). This
allows one to simulate keyboard input.
"""
def __init__(self, display=None):
PyKeyboardMeta.__init__(self)
self.display = Display(display)
self.display2 = Display(display)
self.special_key_assignment()
def press_key(self, character=''):
"""
Press a given character key. Also works with character keycodes as
integers, but not keysyms.
"""
try: # Detect uppercase or shifted character
shifted = self.is_char_shifted(character)
except AttributeError: # Handle the case of integer keycode argument
fake_input(self.display, X.KeyPress, character)
self.display.sync()
else:
if shifted:
fake_input(self.display, X.KeyPress, self.shift_key)
char_val = self.lookup_character_value(character)
fake_input(self.display, X.KeyPress, char_val)
self.display.sync()
def release_key(self, character=''):
"""
Release a given character key. Also works with character keycodes as
integers, but not keysyms.
"""
try: # Detect uppercase or shifted character
shifted = self.is_char_shifted(character)
except AttributeError: # Handle the case of integer keycode argument
fake_input(self.display, X.KeyRelease, character)
self.display.sync()
else:
if shifted:
fake_input(self.display, X.KeyRelease, self.shift_key)
char_val = self.lookup_character_value(character)
fake_input(self.display, X.KeyRelease, char_val)
self.display.sync()
def special_key_assignment(self):
"""
Determines the keycodes for common special keys on the keyboard. These
are integer values and can be passed to the other key methods.
Generally speaking, these are non-printable codes.
"""
#This set of keys compiled using the X11 keysymdef.h file as reference
#They comprise a relatively universal set of keys, though there may be
#exceptions which may come up for other OSes and vendors. Countless
#special cases exist which are not handled here, but may be extended.
#TTY Function Keys
self.backspace_key = self.lookup_character_value('BackSpace')
self.tab_key = self.lookup_character_value('Tab')
self.linefeed_key = self.lookup_character_value('Linefeed')
self.clear_key = self.lookup_character_value('Clear')
self.return_key = self.lookup_character_value('Return')
self.enter_key = self.return_key # Because many keyboards call it "Enter"
self.pause_key = self.lookup_character_value('Pause')
self.scroll_lock_key = self.lookup_character_value('Scroll_Lock')
self.sys_req_key = self.lookup_character_value('Sys_Req')
self.escape_key = self.lookup_character_value('Escape')
self.delete_key = self.lookup_character_value('Delete')
#Modifier Keys
self.shift_l_key = self.lookup_character_value('Shift_L')
self.shift_r_key = self.lookup_character_value('Shift_R')
self.shift_key = self.shift_l_key # Default Shift is left Shift
self.alt_l_key = self.lookup_character_value('Alt_L')
self.alt_r_key = self.lookup_character_value('Alt_R')
self.alt_key = self.alt_l_key # Default Alt is left Alt
self.control_l_key = self.lookup_character_value('Control_L')
self.control_r_key = self.lookup_character_value('Control_R')
self.control_key = self.control_l_key # Default Ctrl is left Ctrl
self.caps_lock_key = self.lookup_character_value('Caps_Lock')
self.capital_key = self.caps_lock_key # Some may know it as Capital
self.shift_lock_key = self.lookup_character_value('Shift_Lock')
self.meta_l_key = self.lookup_character_value('Meta_L')
self.meta_r_key = self.lookup_character_value('Meta_R')
self.super_l_key = self.lookup_character_value('Super_L')
self.windows_l_key = self.super_l_key # Cross-support; also it's printed there
self.super_r_key = self.lookup_character_value('Super_R')
self.windows_r_key = self.super_r_key # Cross-support; also it's printed there
self.hyper_l_key = self.lookup_character_value('Hyper_L')
self.hyper_r_key = self.lookup_character_value('Hyper_R')
#Cursor Control and Motion
self.home_key = self.lookup_character_value('Home')
self.up_key = self.lookup_character_value('Up')
self.down_key = self.lookup_character_value('Down')
self.left_key = self.lookup_character_value('Left')
self.right_key = self.lookup_character_value('Right')
self.end_key = self.lookup_character_value('End')
self.begin_key = self.lookup_character_value('Begin')
self.page_up_key = self.lookup_character_value('Page_Up')
self.page_down_key = self.lookup_character_value('Page_Down')
self.prior_key = self.lookup_character_value('Prior')
self.next_key = self.lookup_character_value('Next')
#Misc Functions
self.select_key = self.lookup_character_value('Select')
self.print_key = self.lookup_character_value('Print')
self.print_screen_key = self.print_key # Seems to be the same thing
self.snapshot_key = self.print_key # Another name for printscreen
self.execute_key = self.lookup_character_value('Execute')
self.insert_key = self.lookup_character_value('Insert')
self.undo_key = self.lookup_character_value('Undo')
self.redo_key = self.lookup_character_value('Redo')
self.menu_key = self.lookup_character_value('Menu')
self.apps_key = self.menu_key # Windows...
self.find_key = self.lookup_character_value('Find')
self.cancel_key = self.lookup_character_value('Cancel')
self.help_key = self.lookup_character_value('Help')
self.break_key = self.lookup_character_value('Break')
self.mode_switch_key = self.lookup_character_value('Mode_switch')
self.script_switch_key = self.lookup_character_value('script_switch')
self.num_lock_key = self.lookup_character_value('Num_Lock')
#Keypad Keys: Dictionary structure
keypad = ['Space', 'Tab', 'Enter', 'F1', 'F2', 'F3', 'F4', 'Home',
'Left', 'Up', 'Right', 'Down', 'Prior', 'Page_Up', 'Next',
'Page_Down', 'End', 'Begin', 'Insert', 'Delete', 'Equal',
'Multiply', 'Add', 'Separator', 'Subtract', 'Decimal',
'Divide', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
self.keypad_keys = {k: self.lookup_character_value('KP_'+str(k)) for k in keypad}
self.numpad_keys = self.keypad_keys
#Function Keys/ Auxilliary Keys
#FKeys
self.function_keys = [None] + [self.lookup_character_value('F'+str(i)) for i in xrange(1,36)]
#LKeys
self.l_keys = [None] + [self.lookup_character_value('L'+str(i)) for i in xrange(1,11)]
#RKeys
self.r_keys = [None] + [self.lookup_character_value('R'+str(i)) for i in xrange(1,16)]
#Unsupported keys from windows
self.kana_key = None
self.hangeul_key = None # old name - should be here for compatibility
self.hangul_key = None
self.junjua_key = None
self.final_key = None
self.hanja_key = None
self.kanji_key = None
self.convert_key = None
self.nonconvert_key = None
self.accept_key = None
self.modechange_key = None
self.sleep_key = None
def lookup_character_value(self, character):
"""
Looks up the keysym for the character then returns the keycode mapping
for that keysym.
"""
ch_keysym = string_to_keysym(character)
if ch_keysym == 0:
ch_keysym = string_to_keysym(special_X_keysyms[character])
return self.display.keysym_to_keycode(ch_keysym)
class PyKeyboardEvent(PyKeyboardEventMeta):
"""
The PyKeyboardEvent implementation for X11 systems (mostly linux). This
allows one to listen for keyboard input.
"""
def __init__(self, display=None):
PyKeyboardEventMeta.__init__(self)
self.display = Display(display)
self.display2 = Display(display)
self.ctx = self.display2.record_create_context(
0,
[record.AllClients],
[{
'core_requests': (0, 0),
'core_replies': (0, 0),
'ext_requests': (0, 0, 0, 0),
'ext_replies': (0, 0, 0, 0),
'delivered_events': (0, 0),
'device_events': (X.KeyPress, X.KeyRelease),
'errors': (0, 0),
'client_started': False,
'client_died': False,
}])
self.shift_state = 0 # 0 is off, 1 is on
self.alt_state = 0 # 0 is off, 2 is on
self.mod_keycodes = self.get_mod_keycodes()
def run(self):
"""Begin listening for keyboard input events."""
self.state = True
if self.capture:
self.display2.screen().root.grab_keyboard(True, X.KeyPressMask | X.KeyReleaseMask, X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime)
self.display2.record_enable_context(self.ctx, self.handler)
self.display2.record_free_context(self.ctx)
def stop(self):
"""Stop listening for keyboard input events."""
self.state = False
self.display.record_disable_context(self.ctx)
self.display.ungrab_keyboard(X.CurrentTime)
self.display.flush()
self.display2.record_disable_context(self.ctx)
self.display2.ungrab_keyboard(X.CurrentTime)
self.display2.flush()
def handler(self, reply):
"""Upper level handler of keyboard events."""
data = reply.data
while len(data):
event, data = rq.EventField(None).parse_binary_value(data, self.display.display, None, None)
if event.type == X.KeyPress:
if self.escape_code(event): # Quit if this returns True
self.stop()
else:
self._key_press(event.detail)
elif event.type == X.KeyRelease:
self._key_release(event.detail)
else:
print('WTF: {0}'.format(event.type))
def _key_press(self, keycode):
"""A key has been pressed, do stuff."""
#Alter modification states
if keycode in self.mod_keycodes['Shift'] or keycode in self.mod_keycodes['Lock']:
self.toggle_shift_state()
elif keycode in self.mod_keycodes['Alt']:
self.toggle_alt_state()
else:
self.key_press(keycode)
def _key_release(self, keycode):
"""A key has been released, do stuff."""
#Alter modification states
if keycode in self.mod_keycodes['Shift']:
self.toggle_shift_state()
elif keycode in self.mod_keycodes['Alt']:
self.toggle_alt_state()
else:
self.key_release(keycode)
def escape_code(self, event):
if event.detail == self.lookup_character_value('Escape'):
return True
return False
def lookup_char_from_keycode(self, keycode):
keysym =self.display.keycode_to_keysym(keycode, self.shift_state + self.alt_state)
if keysym:
char = self.display.lookup_string(keysym)
return char
else:
return None
def get_mod_keycodes(self):
"""
Detects keycodes for modifiers and parses them into a dictionary
for easy access.
"""
modifier_mapping = self.display.get_modifier_mapping()
modifier_dict = {}
nti = [('Shift', X.ShiftMapIndex),
('Control', X.ControlMapIndex), ('Mod1', X.Mod1MapIndex),
('Alt', X.Mod1MapIndex), ('Mod2', X.Mod2MapIndex),
('Mod3', X.Mod3MapIndex), ('Mod4', X.Mod4MapIndex),
('Mod5', X.Mod5MapIndex), ('Lock', X.LockMapIndex)]
for n, i in nti:
modifier_dict[n] = list(modifier_mapping[i])
return modifier_dict
def lookup_character_value(self, character):
"""
Looks up the keysym for the character then returns the keycode mapping
for that keysym.
"""
ch_keysym = string_to_keysym(character)
if ch_keysym == 0:
ch_keysym = string_to_keysym(special_X_keysyms[character])
return self.display.keysym_to_keycode(ch_keysym)
def toggle_shift_state(self):
'''Does toggling for the shift state.'''
if self.shift_state == 0:
self.shift_state = 1
elif self.shift_state == 1:
self.shift_state = 0
else:
return False
return True
def toggle_alt_state(self):
'''Does toggling for the alt state.'''
if self.alt_state == 0:
self.alt_state = 2
elif self.alt_state == 2:
self.alt_state = 0
else:
return False
return True

View File

@ -35,6 +35,7 @@ actors.LightTrigger(eventbus, statemachine,
actors.setup_chromecast(eventbus, config.get("chromecast", "host"))
actors.setup_file_downloader(eventbus, config.get("downloader", "download_dir"))
actors.setup_webbrowser(eventbus)
actors.setup_media_buttons(eventbus)
# Init HTTP interface
HTTPInterface(eventbus, statemachine, config.get("common","api_password"))