diff --git a/homeassistant/actors.py b/homeassistant/actors.py
index 1e22a86838f..aa38c550614 100644
--- a/homeassistant/actors.py
+++ b/homeassistant/actors.py
@@ -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))
diff --git a/homeassistant/packages/__init__.py b/homeassistant/packages/__init__.py
index 937bc365526..021eeacba10 100644
--- a/homeassistant/packages/__init__.py
+++ b/homeassistant/packages/__init__.py
@@ -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
+
"""
diff --git a/homeassistant/packages/pykeyboard/__init__.py b/homeassistant/packages/pykeyboard/__init__.py
new file mode 100644
index 00000000000..2de415d8a60
--- /dev/null
+++ b/homeassistant/packages/pykeyboard/__init__.py
@@ -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 .
+
+"""
+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
diff --git a/homeassistant/packages/pykeyboard/base.py b/homeassistant/packages/pykeyboard/base.py
new file mode 100644
index 00000000000..8a475efad9a
--- /dev/null
+++ b/homeassistant/packages/pykeyboard/base.py
@@ -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 .
+
+"""
+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
diff --git a/homeassistant/packages/pykeyboard/java_.py b/homeassistant/packages/pykeyboard/java_.py
new file mode 100644
index 00000000000..be3b9576ce6
--- /dev/null
+++ b/homeassistant/packages/pykeyboard/java_.py
@@ -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 .
diff --git a/homeassistant/packages/pykeyboard/mac.py b/homeassistant/packages/pykeyboard/mac.py
new file mode 100644
index 00000000000..8b378657fea
--- /dev/null
+++ b/homeassistant/packages/pykeyboard/mac.py
@@ -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 .
+
+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
diff --git a/homeassistant/packages/pykeyboard/mir.py b/homeassistant/packages/pykeyboard/mir.py
new file mode 100644
index 00000000000..be3b9576ce6
--- /dev/null
+++ b/homeassistant/packages/pykeyboard/mir.py
@@ -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 .
diff --git a/homeassistant/packages/pykeyboard/wayland.py b/homeassistant/packages/pykeyboard/wayland.py
new file mode 100644
index 00000000000..be3b9576ce6
--- /dev/null
+++ b/homeassistant/packages/pykeyboard/wayland.py
@@ -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 .
diff --git a/homeassistant/packages/pykeyboard/windows.py b/homeassistant/packages/pykeyboard/windows.py
new file mode 100644
index 00000000000..2ba33b93221
--- /dev/null
+++ b/homeassistant/packages/pykeyboard/windows.py
@@ -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 .
+
+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
\ No newline at end of file
diff --git a/homeassistant/packages/pykeyboard/x11.py b/homeassistant/packages/pykeyboard/x11.py
new file mode 100644
index 00000000000..c19149f20fb
--- /dev/null
+++ b/homeassistant/packages/pykeyboard/x11.py
@@ -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 .
+
+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
diff --git a/start.py b/start.py
index 018721dca88..d51419521de 100644
--- a/start.py
+++ b/start.py
@@ -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"))