def test_syntax_error(self): for combo_string in ( # Invalid character. 'Return,', 'Return&', 'Ret. urn <', 'exclam ! foo', 'shift[a]', # Unbalanced ) ') arg', 'arg )', 'arg())', 'arg(x) )', # Unbalanced ( 'test(', '( grr', 'foo ( bar', 'foo (bar ( ', 'foo ((', ): msg = 'parse_key_combo(%r): SyntaxError not raised' % ( combo_string, ) with self.assertRaisesWithMessage(SyntaxError, msg): parse_key_combo(combo_string)
def test_key_combo(key_name_to_key_code, instructions): def repr_expected(result): assert isinstance(result, str) return [s.strip() for s in result.split()] def repr_key_events(events): assert isinstance(events, list) return [ '%s%s' % ('+' if pressed else '-', key) for key, pressed in events ] for action, *args in instructions: if action == 'parse': combo_string, key_events = args if inspect.isclass(key_events): with pytest.raises(key_events): parse_key_combo(combo_string, key_name_to_key_code=key_name_to_key_code) else: assert repr_key_events( parse_key_combo(combo_string, key_name_to_key_code=key_name_to_key_code) ) == repr_expected(key_events) else: raise ValueError(args[0])
def test_bad_keyname(self): name2code = { c: c for c in '123abc' } combo_string = '1 (c) 2 bad 3 (a b c)' msg = 'parse_key_combo(%r): ValueError not raised' % ( combo_string, ) with self.assertRaisesWithMessage(ValueError, msg): parse_key_combo(combo_string, key_name_to_key_code=name2code.get)
def test_already_pressed(self): for combo_string in ( # Pressing an already pressed key. 'foo(foo)', 'Foo(foO)', 'foo(fOo(arg))', 'foo(bar(Foo))', 'foo(bar(foo(x)))', ): msg = 'parse_key_combo(%r): ValueError not raised' % ( combo_string, ) with self.assertRaisesWithMessage(ValueError, msg): parse_key_combo(combo_string)
def test_stacking(self): for combo_string_variants, expected in ( # + press, - release # 1 is not a valid identifier, but still a valid key name. (('1',) , '+1 -1' ), (('Shift_l', 'SHIFT_L') , '+shift_l -shift_l' ), # Case does not matter. (('a', ' A ') , '+a -a' ), (('a(b c)', 'a ( b c )') , '+a +b -b +c -c -a' ), (('a(bc)', ' a( Bc )') , '+a +bc -bc -a' ), (('a(bc(d)e f(g) h())i j',), '+a +bc +d -d -bc +e -e +f +g -g -f +h -h -a +i -i +j -j'), (('foo () bar ( foo a b c (d))', 'fOo () Bar ( FOO a B c (D))'), '+foo -foo +bar +foo -foo +a -a +b -b +c +d -d -c -bar'), ): expected = [s.strip() for s in expected.split()] for combo_string in combo_string_variants: result = ['%s%s' % ('+' if pressed else '-', key) for key, pressed in parse_key_combo(combo_string)] msg = ( 'parse_key_combo(%r):\n' ' result : %r\n' ' expected: %r\n' % (combo_string, result, expected) ) self.assertEqual(result, expected, msg=msg)
def test_stacking(self): for combo_string_variants, expected in ( # + press, - release # 1 is not a valid identifier, but still a valid key name. (('1', ), '+1 -1'), (('Shift_l', 'SHIFT_L'), '+shift_l -shift_l'), # Case does not matter. (('a', ' A '), '+a -a'), (('a(b c)', 'a ( b c )'), '+a +b -b +c -c -a'), (('a(bc)', ' a( Bc )'), '+a +bc -bc -a'), (('a(bc(d)e f(g) h())i j', ), '+a +bc +d -d -bc +e -e +f +g -g -f +h -h -a +i -i +j -j'), (('foo () bar ( foo a b c (d))', 'fOo () Bar ( FOO a B c (D))'), '+foo -foo +bar +foo -foo +a -a +b -b +c +d -d -c -bar'), ): expected = [s.strip() for s in expected.split()] for combo_string in combo_string_variants: result = [ '%s%s' % ('+' if pressed else '-', key) for key, pressed in parse_key_combo(combo_string) ] msg = ('parse_key_combo(%r):\n' ' result : %r\n' ' expected: %r\n' % (combo_string, result, expected)) self.assertEqual(result, expected, msg=msg)
def send_key_combination(self, combo_string): """Emulate a sequence of key combinations. Args: combo_string: A string representing a sequence of key combinations. Keys are represented by their names in the Xlib.XK module, without the 'XK_' prefix. For example, the left Alt key is represented by 'Alt_L'. Keys are either separated by a space or a left or right parenthesis. Parentheses must be properly formed in pairs and may be nested. A key immediately followed by a parenthetical indicates that the key is pressed down while all keys enclosed in the parenthetical are pressed and released in turn. For example, Alt_L(Tab) means to hold the left Alt key down, press and release the Tab key, and then release the left Alt key. """ def name_to_code(name): code = KEYNAME_TO_KEYCODE.get(name) if code is not None: return code char = KEYNAME_TO_CHAR.get(name, name) code, mods = mac_keycode.KeyCodeForChar(char)[0] return code # Parse and validate combo. key_events = parse_key_combo(combo_string, name_to_code) # Send events... self._send_sequence(key_events)
def test_key_combo(key_name_to_key_code, instructions): def repr_expected(result): assert isinstance(result, str) return [s.strip() for s in result.split()] def repr_key_events(events): assert isinstance(events, list) return ['%s%s' % ('+' if pressed else '-', key) for key, pressed in events] for action, *args in instructions: if action == 'parse': combo_string, key_events = args if inspect.isclass(key_events): with pytest.raises(key_events): parse_key_combo(combo_string, key_name_to_key_code=key_name_to_key_code) else: assert repr_key_events(parse_key_combo(combo_string, key_name_to_key_code=key_name_to_key_code)) == repr_expected(key_events) else: raise ValueError(args[0])
def test_aliasing(self): name2code = { '1' : 10, 'exclam': 10, } self.assertListEqual(list(parse_key_combo('1 exclam', key_name_to_key_code=name2code.get)), [(10, True), (10, False), (10, True), (10, False)]) for combo_string in ( '1 ( exclam )', 'exclam(1)', ): msg = 'parse_key_combo(%r): ValueError not raised' % ( combo_string, ) with self.assertRaisesWithMessage(ValueError, msg): # Yielding the first key event should # only happen after full validation. parse_key_combo(combo_string, key_name_to_key_code=name2code.get)
def test_aliasing(self): name2code = { '1': 10, 'exclam': 10, } self.assertListEqual( list( parse_key_combo('1 exclam', key_name_to_key_code=name2code.get)), [(10, True), (10, False), (10, True), (10, False)]) for combo_string in ( '1 ( exclam )', 'exclam(1)', ): msg = 'parse_key_combo(%r): ValueError not raised' % ( combo_string, ) with self.assertRaisesWithMessage(ValueError, msg): # Yielding the first key event should # only happen after full validation. parse_key_combo(combo_string, key_name_to_key_code=name2code.get)
def send_key_combination(self, combo_string): """Emulate a sequence of key combinations. Argument: combo_string -- A string representing a sequence of key combinations. Keys are represented by their names in the self.keyboard_layout.keyname_to_keycode above. For example, the left Alt key is represented by 'Alt_L'. Keys are either separated by a space or a left or right parenthesis. Parentheses must be properly formed in pairs and may be nested. A key immediately followed by a parenthetical indicates that the key is pressed down while all keys enclosed in the parenthetical are pressed and released in turn. For example, Alt_L(Tab) means to hold the left Alt key down, press and release the Tab key, and then release the left Alt key. """ # Make sure keyboard layout is up-to-date. self._refresh_keyboard_layout() # Parse and validate combo. key_events = parse_key_combo(combo_string, self.keyboard_layout.keyname_to_vk.get) # Send events... for keycode, pressed in key_events: self._key_event(keycode, pressed)
def send_key_combination(self, combo_string): """Emulate a sequence of key combinations. Args: combo_string: A string representing a sequence of key combinations. Keys are represented by their names in the Xlib.XK module, without the 'XK_' prefix. For example, the left Alt key is represented by 'Alt_L'. Keys are either separated by a space or a left or right parenthesis. Parentheses must be properly formed in pairs and may be nested. A key immediately followed by a parenthetical indicates that the key is pressed down while all keys enclosed in the parenthetical are pressed and released in turn. For example, Alt_L(Tab) means to hold the left Alt key down, press and release the Tab key, and then release the left Alt key. """ def name_to_code(name): # Static key codes code = KEYNAME_TO_KEYCODE.get(name) if code is not None: pass # Dead keys elif name.startswith('dead_'): code, mod = self._layout.deadkey_symbol_to_key_sequence( DEADKEY_SYMBOLS.get(name) )[0] # Normal keys else: char = KEYNAME_TO_CHAR.get(name, name) code, mods = self._layout.char_to_key_sequence(char)[0] return code # Parse and validate combo. key_events = parse_key_combo(combo_string, name_to_code) # Send events... self._send_sequence(key_events)
def test_noop(self): for combo_string in ('', ' '): self.assertEqual(parse_key_combo(combo_string), [])
def test_bad_keyname(self): name2code = {c: c for c in '123abc'} combo_string = '1 (c) 2 bad 3 (a b c)' msg = 'parse_key_combo(%r): ValueError not raised' % (combo_string, ) with self.assertRaisesWithMessage(ValueError, msg): parse_key_combo(combo_string, key_name_to_key_code=name2code.get)