diff options
author | PMR <pmr@pmr-lander> | 2019-04-23 22:05:27 +0000 |
---|---|---|
committer | PMR <pmr@pmr-lander> | 2019-04-23 22:05:27 +0000 |
commit | 0a494e59a5fab963d4058b984eb5b17307708b76 (patch) | |
tree | f4483d998746eb2c8fd33baebeb2daa5f7150f9a /bin | |
parent | 70f70c49f0a509983d61affc13c1f9efc0414294 (diff) | |
parent | dc41bc87e81b6dfd33311aff5af5ca62ae819722 (diff) |
Merge #366343 from ~kissiel/plainbox-provider-checkbox:hotkey-testing
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/hotkey_tests.py | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/bin/hotkey_tests.py b/bin/hotkey_tests.py new file mode 100755 index 0000000..2a9282b --- /dev/null +++ b/bin/hotkey_tests.py @@ -0,0 +1,439 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 Canonical Ltd. +# Written by: +# Maciej Kisielewski <maciej.kisielewski@canonical.com> +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, +# as published by the Free Software Foundation. +# +# This file 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 file. If not, see <http://www.gnu.org/licenses/>. +import datetime +import enum +import os +import re +import struct +import subprocess +import sys +import time + + +class KeyCodes(enum.Enum): + KEY_RESERVED = 0 + KEY_ESC = 1 + KEY_1 = 2 + KEY_2 = 3 + KEY_3 = 4 + KEY_4 = 5 + KEY_5 = 6 + KEY_6 = 7 + KEY_7 = 8 + KEY_8 = 9 + KEY_9 = 10 + KEY_0 = 11 + KEY_MINUS = 12 + KEY_EQUAL = 13 + KEY_BACKSPACE = 14 + KEY_TAB = 15 + KEY_Q = 16 + KEY_W = 17 + KEY_E = 18 + KEY_R = 19 + KEY_T = 20 + KEY_Y = 21 + KEY_U = 22 + KEY_I = 23 + KEY_O = 24 + KEY_P = 25 + KEY_LEFTBRACE = 26 + KEY_RIGHTBRACE = 27 + KEY_ENTER = 28 + KEY_LEFTCTRL = 29 + KEY_A = 30 + KEY_S = 31 + KEY_D = 32 + KEY_F = 33 + KEY_G = 34 + KEY_H = 35 + KEY_J = 36 + KEY_K = 37 + KEY_L = 38 + KEY_SEMICOLON = 39 + KEY_APOSTROPHE = 40 + KEY_GRAVE = 41 + KEY_LEFTSHIFT = 42 + KEY_BACKSLASH = 43 + KEY_Z = 44 + KEY_X = 45 + KEY_C = 46 + KEY_V = 47 + KEY_B = 48 + KEY_N = 49 + KEY_M = 50 + KEY_COMMA = 51 + KEY_DOT = 52 + KEY_SLASH = 53 + KEY_RIGHTSHIFT = 54 + KEY_KPASTERISK = 55 + KEY_LEFTALT = 56 + KEY_SPACE = 57 + KEY_CAPSLOCK = 58 + KEY_F1 = 59 + KEY_F2 = 60 + KEY_F3 = 61 + KEY_F4 = 62 + KEY_F5 = 63 + KEY_F6 = 64 + KEY_F7 = 65 + KEY_F8 = 66 + KEY_F9 = 67 + KEY_F10 = 68 + KEY_NUMLOCK = 69 + KEY_SCROLLLOCK = 70 + KEY_KP7 = 71 + KEY_KP8 = 72 + KEY_KP9 = 73 + KEY_KPMINUS = 74 + KEY_KP4 = 75 + KEY_KP5 = 76 + KEY_KP6 = 77 + KEY_KPPLUS = 78 + KEY_KP1 = 79 + KEY_KP2 = 80 + KEY_KP3 = 81 + KEY_KP0 = 82 + KEY_KPDOT = 83 + KEY_ZENKAKUHANKAKU = 85 + KEY_102ND = 86 + KEY_F11 = 87 + KEY_F12 = 88 + KEY_RO = 89 + KEY_KATAKANA = 90 + KEY_HIRAGANA = 91 + KEY_HENKAN = 92 + KEY_KATAKANAHIRAGANA = 93 + KEY_MUHENKAN = 94 + KEY_KPJPCOMMA = 95 + KEY_KPENTER = 96 + KEY_RIGHTCTRL = 97 + KEY_KPSLASH = 98 + KEY_SYSRQ = 99 + KEY_RIGHTALT = 100 + KEY_LINEFEED = 101 + KEY_HOME = 102 + KEY_UP = 103 + KEY_PAGEUP = 104 + KEY_LEFT = 105 + KEY_RIGHT = 106 + KEY_END = 107 + KEY_DOWN = 108 + KEY_PAGEDOWN = 109 + KEY_INSERT = 110 + KEY_DELETE = 111 + KEY_MACRO = 112 + KEY_MUTE = 113 + KEY_VOLUMEDOWN = 114 + KEY_VOLUMEUP = 115 + KEY_POWER = 116 # SC System Power Down + KEY_KPEQUAL = 117 + KEY_KPPLUSMINUS = 118 + KEY_PAUSE = 119 + KEY_SCALE = 120 # AL Compiz Scale (Expose) + + KEY_KPCOMMA = 121 + KEY_HANGEUL = 122 + KEY_HANGUEL = KEY_HANGEUL + KEY_HANJA = 123 + KEY_YEN = 124 + KEY_LEFTMETA = 125 + KEY_RIGHTMETA = 126 + KEY_COMPOSE = 127 + + KEY_STOP = 128 # AC Stop + KEY_AGAIN = 129 + KEY_PROPS = 130 # AC Properties + KEY_UNDO = 131 # AC Undo + KEY_FRONT = 132 + KEY_COPY = 133 # AC Copy + KEY_OPEN = 134 # AC Open + KEY_PASTE = 135 # AC Paste + KEY_FIND = 136 # AC Search + KEY_CUT = 137 # AC Cut + KEY_HELP = 138 # AL Integrated Help Center + KEY_MENU = 139 # Menu (show menu) + KEY_CALC = 140 # AL Calculator + KEY_SETUP = 141 + KEY_SLEEP = 142 # SC System Sleep + KEY_WAKEUP = 143 # System Wake Up + KEY_FILE = 144 # AL Local Machine Browser + KEY_SENDFILE = 145 + KEY_DELETEFILE = 146 + KEY_XFER = 147 + KEY_PROG1 = 148 + KEY_PROG2 = 149 + KEY_WWW = 150 # AL Internet Browser + KEY_MSDOS = 151 + KEY_COFFEE = 152 # AL Terminal Lock/Screensaver + KEY_SCREENLOCK = KEY_COFFEE + KEY_ROTATE_DISPLAY = 153 # Display orientation for e.g. tablets + KEY_DIRECTION = KEY_ROTATE_DISPLAY + KEY_CYCLEWINDOWS = 154 + KEY_MAIL = 155 + KEY_BOOKMARKS = 156 # AC Bookmarks + KEY_COMPUTER = 157 + KEY_BACK = 158 # AC Back + KEY_FORWARD = 159 # AC Forward + KEY_CLOSECD = 160 + KEY_EJECTCD = 161 + KEY_EJECTCLOSECD = 162 + KEY_NEXTSONG = 163 + KEY_PLAYPAUSE = 164 + KEY_PREVIOUSSONG = 165 + KEY_STOPCD = 166 + KEY_RECORD = 167 + KEY_REWIND = 168 + KEY_PHONE = 169 # Media Select Telephone + KEY_ISO = 170 + KEY_CONFIG = 171 # AL Consumer Control Configuration + KEY_HOMEPAGE = 172 # AC Home + KEY_REFRESH = 173 # AC Refresh + KEY_EXIT = 174 # AC Exit + KEY_MOVE = 175 + KEY_EDIT = 176 + KEY_SCROLLUP = 177 + KEY_SCROLLDOWN = 178 + KEY_KPLEFTPAREN = 179 + KEY_KPRIGHTPAREN = 180 + KEY_NEW = 181 # AC New + KEY_REDO = 182 # AC Redo/Repeat + + KEY_F13 = 183 + KEY_F14 = 184 + KEY_F15 = 185 + KEY_F16 = 186 + KEY_F17 = 187 + KEY_F18 = 188 + KEY_F19 = 189 + KEY_F20 = 190 + KEY_F21 = 191 + KEY_F22 = 192 + KEY_F23 = 193 + KEY_F24 = 194 + + KEY_PLAYCD = 200 + KEY_PAUSECD = 201 + KEY_PROG3 = 202 + KEY_PROG4 = 203 + KEY_DASHBOARD = 204 # AL Dashboard + KEY_SUSPEND = 205 + KEY_CLOSE = 206 # AC Close + KEY_PLAY = 207 + KEY_FASTFORWARD = 208 + KEY_BASSBOOST = 209 + KEY_PRINT = 210 # AC Print + KEY_HP = 211 + KEY_CAMERA = 212 + KEY_SOUND = 213 + KEY_QUESTION = 214 + KEY_EMAIL = 215 + KEY_CHAT = 216 + KEY_SEARCH = 217 + KEY_CONNECT = 218 + KEY_FINANCE = 219 # AL Checkbook/Finance + KEY_SPORT = 220 + KEY_SHOP = 221 + KEY_ALTERASE = 222 + KEY_CANCEL = 223 # AC Cancel + KEY_BRIGHTNESSDOWN = 224 + KEY_BRIGHTNESSUP = 225 + KEY_MEDIA = 226 + # Cycle between available video outputs (Monitor/LCD/TV-out/etc) + KEY_SWITCHVIDEOMODE = 227 + KEY_KBDILLUMTOGGLE = 228 + KEY_KBDILLUMDOWN = 229 + KEY_KBDILLUMUP = 230 + KEY_SEND = 231 # AC Send + KEY_REPLY = 232 # AC Reply + KEY_FORWARDMAIL = 233 # AC Forward Msg + KEY_SAVE = 234 # AC Save + KEY_DOCUMENTS = 235 + KEY_BATTERY = 236 + KEY_BLUETOOTH = 237 + KEY_WLAN = 238 + KEY_UWB = 239 + KEY_UNKNOWN = 240 + KEY_VIDEO_NEXT = 241 # drive next video source + KEY_VIDEO_PREV = 242 # drive previous video source + KEY_BRIGHTNESS_CYCLE = 243 # brightness up, after max is min + # Set Auto Brightness = manual brightness control is off, rely on ambient + KEY_BRIGHTNESS_AUTO = 244 + KEY_BRIGHTNESS_ZERO = KEY_BRIGHTNESS_AUTO + KEY_DISPLAY_OFF = 245 # display device to off state + KEY_WWAN = 246 # Wireless WAN (LTE, UMTS, GSM, etc.) + KEY_WIMAX = KEY_WWAN + KEY_RFKILL = 247 # Key that controls all radios + KEY_MICMUTE = 248 # Mute / unmute the microphone + + def from_char(c): + obvious_keys = { + '/': KeyCodes.KEY_SLASH, + ' ': KeyCodes.KEY_SPACE, + '-': KeyCodes.KEY_MINUS, + } + if c in obvious_keys.keys(): + return obvious_keys[c] + keycode_name = 'KEY_{}'.format(c.upper()) + try: + return KeyCodes[keycode_name] + except KeyError: + raise SystemExit( + 'One does not simply convert {} to a keycode'.format(c)) + + +def guess_kb_dev_path(): + """ + Guess the path of the keyboard device that we can send events to. + """ + # most intel-based devices I tested had platform-i8042-serio-0-event-kbd + # device, so let's use that if it is present + intel_generic = '/dev/input/by-path/platform-i8042-serio-0-event-kbd' + + if os.path.exists(intel_generic): + return intel_generic + raise SystemExit("Couldn't guess a proper keyboard device") + + +class FauxKeyboard(): + def __init__(self, dev_path=guess_kb_dev_path()): + self.kb_dev_file = open(dev_path, 'wb') + self.event_struct = struct.Struct('llHHi') + + def __del__(self): + self.kb_dev_file.close() + + def type_text(self, text): + for c in text: + self.press_key(KeyCodes.from_char(c)) + + def press_key(self, key_code, modifiers=set(), repetitions=1, delay=0.05): + # simple key press actions contains four events: + # EV_MSC, MSC_SCAN, {KEY_CODE} + # EV_KEY, {KEY_CODE}, 1 + # EV_SYN, 0, 0 + # EV_KEY, {KEY_CODE}, 0 + # XXX: ATM there's no distinction between left and right modifiers + assert(repetitions >= 0) + while repetitions: + if not modifiers.issubset({'alt', 'ctrl', 'shift', 'meta'}): + raise SystemExit('Unknown modifier') + if type(key_code) == KeyCodes: + key_code = key_code.value + data = bytes() + data += self.event_struct.pack(0, 0, 4, 4, key_code) + for mod in modifiers: + mod_code = KeyCodes['KEY_LEFT{}'.format(mod.upper())].value + data += self.event_struct.pack(0, 0, 1, mod_code, 1) + data += self.event_struct.pack(0, 0, 1, key_code, 1) + data += self.event_struct.pack(0, 0, 0, 0, 0) + data += self.event_struct.pack(0, 10, 1, key_code, 0) + for mod in modifiers: + mod_code = KeyCodes['KEY_LEFT{}'.format(mod.upper())].value + data += self.event_struct.pack(0, 10, 1, mod_code, 0) + data += self.event_struct.pack(0, 10, 0, 0, 0) + self.kb_dev_file.write(data) + self.kb_dev_file.flush() + time.sleep(delay) + repetitions -= 1 + + +class HotKeyTesting: + + def __init__(self): + self.kb = FauxKeyboard() + + def check_volume_media_keys(self): + """ + Check if the volume media keys have an effect on ALSA + """ + # if the volume is already on max, then raising it won't make any + # difference, so first, let's lower it before establishing the baseline + self.kb.press_key(KeyCodes.KEY_VOLUMEDOWN, repetitions=4, delay=0.2) + self.kb.press_key(KeyCodes.KEY_VOLUMEUP) + # let's grab output of alsa mixer to establish what is the baseline + # before we start raising the volume + before = subprocess.check_output('amixer').decode( + sys.stdout.encoding).splitlines() + self.kb.press_key(KeyCodes.KEY_VOLUMEUP, repetitions=3, delay=0.2) + after = subprocess.check_output('amixer').decode( + sys.stdout.encoding).splitlines() + temp = before.copy() + for line in temp: + if line in after: + before.remove(line) + after.remove(line) + if not before: + # all lines removed from before, so there's no state change + # of the stuff reported bevore hitting volume up, so let's fail + # the test + return False + # we expect that the lines that changed are status information about + # the output devices. Percentage volume is in square brackets so let's + # search for those and see if they got higher + regex = re.compile(r'\[(\d*)%\]') + if len(before) != len(after): + # more of an assertion - the lines diff should match + return False + for b, a in zip(before, after): + vol_b = regex.search(b).groups() + vol_a = regex.search(a).groups() + if vol_a and vol_b: + if int(vol_b[0]) < int(vol_a[0]): + return True + return False + + def check_terminal_hotkey(self): + # spawn a terminal window using ctrl+alt+t + # touch a unique temporary file, and check if it got created + timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + filename = os.path.join('/tmp/hotkey-testing-{}'.format(timestamp)) + self.kb.press_key(KeyCodes.KEY_T, {'ctrl', 'alt'}) + # wait for the terminal window to appear + assert(not os.path.exists(filename)) + time.sleep(2) + self.kb.type_text('touch {}'.format(filename)) + self.kb.press_key(KeyCodes.KEY_ENTER) + result = os.path.exists(filename) + if not result: + return result + self.kb.press_key(KeyCodes.KEY_D, {'ctrl'}) + os.unlink(filename) + return result + + +def main(): + if not (os.geteuid() == 0): + raise SystemExit('Must be run as root.') + hkt = HotKeyTesting() + failed = False + for member in dir(hkt): + attr = getattr(hkt, member) + if not member.startswith('check_') or not callable(attr): + continue + print('{}... '.format(member), end='', flush=True) + if not attr(): + print("FAIL") + failed = True + else: + print("PASS") + if failed: + raise SystemExit('Some test failed!') + +if __name__ == '__main__': + main() |