Mapping media keys (#318)

pull/329/head
Tobi 3 years ago committed by GitHub
parent 8f8800498c
commit 2badf2c5d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -801,7 +801,7 @@ Gives your keys back their original function</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<child> <child>
<object class="GtkScrolledWindow"> <object class="GtkScrolledWindow">
<property name="width-request">160</property> <property name="width-request">200</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<child> <child>

@ -53,12 +53,14 @@ class EventCombination(Tuple[InputEvent]):
events = [] events = []
for init_arg in init_args: for init_arg in init_args:
event = None event = None
for constructor in InputEvent.__get_validators__(): for constructor in InputEvent.__get_validators__():
try: try:
event = constructor(init_arg) event = constructor(init_arg)
break break
except InputEventCreationError: except InputEventCreationError:
pass pass
if event: if event:
events.append(event) events.append(event)
else: else:
@ -68,7 +70,7 @@ class EventCombination(Tuple[InputEvent]):
def __str__(self): def __str__(self):
# only used in tests and logging # only used in tests and logging
return f"EventCombination({', '.join([str(e.event_tuple) for e in self])})" return f"<EventCombination {', '.join([str(e.event_tuple) for e in self])}>"
@classmethod @classmethod
def __get_validators__(cls): def __get_validators__(cls):

@ -23,14 +23,11 @@
import re import re
import locale import time
import gettext
import os
from inputremapper.configs.data import get_data_path
from inputremapper.gui.gettext import _
from gi.repository import Gtk, GLib, Gdk from gi.repository import Gtk, GLib, Gdk
from inputremapper.gui.gettext import _
from inputremapper.gui.editor.autocompletion import Autocompletion from inputremapper.gui.editor.autocompletion import Autocompletion
from inputremapper.configs.system_mapping import system_mapping from inputremapper.configs.system_mapping import system_mapping
from inputremapper.gui.active_preset import active_preset from inputremapper.gui.active_preset import active_preset
@ -110,6 +107,9 @@ def ensure_everything_saved(func):
SET_KEY_FIRST = _("Set the key first") SET_KEY_FIRST = _("Set the key first")
RECORD_ALL = float("inf")
RECORD_NONE = 0
class Editor: class Editor:
"""Maintains the widgets of the editor.""" """Maintains the widgets of the editor."""
@ -138,14 +138,7 @@ class Editor:
# keys were not pressed yet # keys were not pressed yet
self._input_has_arrived = False self._input_has_arrived = False
toggle = self.get_recording_toggle() self.record_events_until = RECORD_NONE
toggle.connect("focus-out-event", self._reset_keycode_consumption)
toggle.connect("focus-out-event", lambda *_: toggle.set_active(False))
toggle.connect("toggled", self._on_recording_toggle_toggle)
# Don't leave the input when using arrow keys or tab. wait for the
# window to consume the keycode from the reader. I.e. a tab input should
# be recorded, instead of causing the recording to stop.
toggle.connect("key-press-event", lambda *args: Gdk.EVENT_STOP)
text_input = self.get_text_input() text_input = self.get_text_input()
text_input.connect("focus-out-event", self.on_text_input_unfocus) text_input.connect("focus-out-event", self.on_text_input_unfocus)
@ -161,6 +154,16 @@ class Editor:
GLib.source_remove(timeout) GLib.source_remove(timeout)
self.timeouts = [] self.timeouts = []
def _on_toggle_clicked(self, toggle, event=None):
if toggle.get_active():
self._show_press_key()
else:
self._show_change_key()
@ensure_everything_saved
def _on_toggle_unfocus(self, toggle, event=None):
toggle.set_active(False)
@ensure_everything_saved @ensure_everything_saved
def on_text_input_unfocus(self, *_): def on_text_input_unfocus(self, *_):
"""When unfocusing the text it saves. """When unfocusing the text it saves.
@ -184,7 +187,8 @@ class Editor:
One could debounce saving on text-change to avoid those logs, but that just One could debounce saving on text-change to avoid those logs, but that just
sounds like a huge source of race conditions and is also hard to test. sounds like a huge source of race conditions and is also hard to test.
""" """
pass print("on_text_input_unfocus")
pass # the decorator will be triggered
@ensure_everything_saved @ensure_everything_saved
def _on_target_input_changed(self, *_): def _on_target_input_changed(self, *_):
@ -229,31 +233,25 @@ class Editor:
def _setup_recording_toggle(self): def _setup_recording_toggle(self):
"""Prepare the toggle button for recording key inputs.""" """Prepare the toggle button for recording key inputs."""
toggle = self.get("key_recording_toggle") toggle = self.get_recording_toggle()
toggle.connect( toggle.connect("focus-out-event", self._show_change_key)
"focus-out-event", toggle.connect("focus-in-event", self._show_press_key)
self._show_change_key, toggle.connect("clicked", self._on_toggle_clicked)
) toggle.connect("focus-out-event", self._reset_keycode_consumption)
toggle.connect( toggle.connect("focus-out-event", self._on_toggle_unfocus)
"focus-in-event", toggle.connect("toggled", self._on_recording_toggle_toggle)
self._show_press_key, # Don't leave the input when using arrow keys or tab. wait for the
) # window to consume the keycode from the reader. I.e. a tab input should
toggle.connect( # be recorded, instead of causing the recording to stop.
"clicked", toggle.connect("key-press-event", lambda *args: Gdk.EVENT_STOP)
lambda _: (
self._show_press_key()
if toggle.get_active()
else self._show_change_key()
),
)
def _show_press_key(self, *_): def _show_press_key(self, *args):
"""Show user friendly instructions.""" """Show user friendly instructions."""
self.get("key_recording_toggle").set_label("Press Key") self.get_recording_toggle().set_label("Press Key")
def _show_change_key(self, *_): def _show_change_key(self, *args):
"""Show user friendly instructions.""" """Show user friendly instructions."""
self.get("key_recording_toggle").set_label("Change Key") self.get_recording_toggle().set_label("Change Key")
def _setup_source_view(self): def _setup_source_view(self):
"""Prepare the code editor.""" """Prepare the code editor."""
@ -322,8 +320,12 @@ class Editor:
presets accidentally before configuring the key and then it's gone. It can presets accidentally before configuring the key and then it's gone. It can
only be saved to the preset if a key is configured. This avoids that pitfall. only be saved to the preset if a key is configured. This avoids that pitfall.
""" """
logger.debug("Disabling the text input")
text_input = self.get_text_input() text_input = self.get_text_input()
# beware that this also disables event listeners like focus-out-event:
text_input.set_sensitive(False) text_input.set_sensitive(False)
text_input.set_opacity(0.5) text_input.set_opacity(0.5)
if clear or self.get_symbol_input_text() == "": if clear or self.get_symbol_input_text() == "":
@ -332,6 +334,7 @@ class Editor:
def enable_symbol_input(self): def enable_symbol_input(self):
"""Don't display help information anymore and allow changing the symbol.""" """Don't display help information anymore and allow changing the symbol."""
logger.debug("Enabling the text input")
text_input = self.get_text_input() text_input = self.get_text_input()
text_input.set_sensitive(True) text_input.set_sensitive(True)
text_input.set_opacity(1) text_input.set_opacity(1)
@ -428,6 +431,11 @@ class Editor:
"""Show what the user is currently pressing in the user interface.""" """Show what the user is currently pressing in the user interface."""
self.active_selection_label.set_combination(combination) self.active_selection_label.set_combination(combination)
if combination and len(combination) > 0:
self.enable_symbol_input()
else:
self.disable_symbol_input()
def get_combination(self): def get_combination(self):
"""Get the EventCombination object from the left column. """Get the EventCombination object from the left column.
@ -491,11 +499,16 @@ class Editor:
return True return True
def _on_recording_toggle_toggle(self, *args): def _on_recording_toggle_toggle(self, toggle):
"""Refresh useful usage information.""" """Refresh useful usage information."""
if not self.get_recording_toggle().get_active(): if not toggle.get_active():
# if more events arrive from the time when the toggle was still on,
# use them.
self.record_events_until = time.time()
return return
self.record_events_until = RECORD_ALL
self._reset_keycode_consumption() self._reset_keycode_consumption()
reader.clear() reader.clear()
if not self.user_interface.can_modify_preset(): if not self.user_interface.can_modify_preset():
@ -505,7 +518,7 @@ class Editor:
self.user_interface.show_status( self.user_interface.show_status(
CTX_ERROR, _('Use "Stop Injection" to stop before editing') CTX_ERROR, _('Use "Stop Injection" to stop before editing')
) )
self.get_recording_toggle().set_active(False) toggle.set_active(False)
def _on_delete_button_clicked(self, *_): def _on_delete_button_clicked(self, *_):
"""Destroy the row and remove it from the config.""" """Destroy the row and remove it from the config."""
@ -560,22 +573,28 @@ class Editor:
self.user_interface.save_preset() self.user_interface.save_preset()
def is_waiting_for_input(self): def is_waiting_for_input(self):
"""Check if the user is interacting with the ToggleButton for combination recording.""" """Check if the user is trying to record buttons."""
return self.get_recording_toggle().get_active() return self.get_recording_toggle().get_active()
def consume_newest_keycode(self, combination): def should_record_combination(self, combination):
"""To capture events from keyboards, mice and gamepads. """Check if the combination was written when the toggle was active."""
# At this point the toggle might already be off, because some keys that are
Parameters # used while the toggle was still on might cause the focus of the toggle to
---------- # be lost, like multimedia keys. This causes the toggle to be disabled.
combination : EventCombination or None # Yet, this event should be mapped.
""" timestamp = max([event.timestamp() for event in combination])
return timestamp < self.record_events_until
def consume_newest_keycode(self, combination: EventCombination):
"""To capture events from keyboards, mice and gamepads."""
self._switch_focus_if_complete() self._switch_focus_if_complete()
if combination is None: if combination is None:
return return
if not self.is_waiting_for_input(): if not self.should_record_combination(combination):
# the event arrived after the toggle has been deactivated
logger.debug("Recording toggle is not on")
return return
if not isinstance(combination, EventCombination): if not isinstance(combination, EventCombination):
@ -592,7 +611,7 @@ class Editor:
) )
logger.info("%s %s", combination, msg) logger.info("%s %s", combination, msg)
self.user_interface.show_status(CTX_KEYCODE, msg) self.user_interface.show_status(CTX_KEYCODE, msg)
return True return
if combination.is_problematic(): if combination.is_problematic():
self.user_interface.show_status( self.user_interface.show_status(
@ -620,8 +639,13 @@ class Editor:
symbol = self.get_symbol_input_text() symbol = self.get_symbol_input_text()
target = self.get_target_selection() target = self.get_target_selection()
# the symbol is empty and therefore the mapping is not complete if not symbol:
if not symbol or not target: # has not been entered yet
logger.debug("Symbol missing")
return
if not target:
logger.debug("Target missing")
return return
# else, the keycode has changed, the symbol is set, all good # else, the keycode has changed, the symbol is set, all good
@ -648,6 +672,7 @@ class Editor:
all_keys_released = reader.get_unreleased_keys() is None all_keys_released = reader.get_unreleased_keys() is None
if all_keys_released and self._input_has_arrived and self.get_combination(): if all_keys_released and self._input_has_arrived and self.get_combination():
logger.debug("Recording complete")
# A key was pressed and then released. # A key was pressed and then released.
# Switch to the symbol. idle_add this so that the # Switch to the symbol. idle_add this so that the
# keycode event won't write into the symbol input as well. # keycode event won't write into the symbol input as well.

@ -48,8 +48,13 @@ from inputremapper import utils
from inputremapper.user import USER from inputremapper.user import USER
TERMINATE = "terminate" # received by the helper
REFRESH_GROUPS = "refresh_groups" CMD_TERMINATE = "terminate"
CMD_REFRESH_GROUPS = "refresh_groups"
# sent by the helper to the reader
MSG_GROUPS = "groups"
MSG_EVENT = "event"
def is_helper_running(): def is_helper_running():
@ -88,7 +93,7 @@ class RootHelper:
def _send_groups(self): def _send_groups(self):
"""Send the groups to the gui.""" """Send the groups to the gui."""
self._results.send({"type": "groups", "message": groups.dumps()}) self._results.send({"type": MSG_GROUPS, "message": groups.dumps()})
def _handle_commands(self): def _handle_commands(self):
"""Handle all unread commands.""" """Handle all unread commands."""
@ -99,11 +104,11 @@ class RootHelper:
cmd = self._commands.recv() cmd = self._commands.recv()
logger.debug('Received command "%s"', cmd) logger.debug('Received command "%s"', cmd)
if cmd == TERMINATE: if cmd == CMD_TERMINATE:
logger.debug("Helper terminates") logger.debug("Helper terminates")
sys.exit(0) sys.exit(0)
if cmd == REFRESH_GROUPS: if cmd == CMD_REFRESH_GROUPS:
groups.refresh() groups.refresh()
self._send_groups() self._send_groups()
continue continue
@ -209,7 +214,7 @@ class RootHelper:
self._results.send( self._results.send(
{ {
"type": "event", "type": MSG_EVENT,
"message": (event.sec, event.usec, event.type, event.code, event.value), "message": (event.sec, event.usec, event.type, event.code, event.value),
} }
) )

@ -32,7 +32,12 @@ from inputremapper.logger import logger
from inputremapper.event_combination import EventCombination from inputremapper.event_combination import EventCombination
from inputremapper.groups import groups, GAMEPAD from inputremapper.groups import groups, GAMEPAD
from inputremapper.ipc.pipe import Pipe from inputremapper.ipc.pipe import Pipe
from inputremapper.gui.helper import TERMINATE, REFRESH_GROUPS from inputremapper.gui.helper import (
MSG_EVENT,
MSG_GROUPS,
CMD_TERMINATE,
CMD_REFRESH_GROUPS,
)
from inputremapper import utils from inputremapper import utils
from inputremapper.gui.active_preset import active_preset from inputremapper.gui.active_preset import active_preset
from inputremapper.user import USER from inputremapper.user import USER
@ -88,14 +93,14 @@ class Reader:
message_type = message["type"] message_type = message["type"]
message_body = message["message"] message_body = message["message"]
if message_type == "groups": if message_type == MSG_GROUPS:
if message_body != groups.dumps(): if message_body != groups.dumps():
groups.loads(message_body) groups.loads(message_body)
logger.debug("Received %d devices", len(groups)) logger.debug("Received %d devices", len(groups))
self._groups_updated = True self._groups_updated = True
return None return None
if message_type == "event": if message_type == MSG_EVENT:
return InputEvent(*message_body) return InputEvent(*message_body)
logger.error('Received unknown message "%s"', message) logger.error('Received unknown message "%s"', message)
@ -139,12 +144,12 @@ class Reader:
continue continue
if event.value == 0: if event.value == 0:
logger.debug_key(event.event_tuple, "release") logger.debug_key(event, "release")
self._release(event.type_and_code) self._release(event.type_and_code)
continue continue
if self._unreleased.get(event.type_and_code) == event.event_tuple: if self._unreleased.get(event.type_and_code) == event:
logger.debug_key(event.event_tuple, "duplicate key down") logger.debug_key(event, "duplicate key down")
self._debounce_start(event.event_tuple) self._debounce_start(event.event_tuple)
continue continue
@ -154,8 +159,8 @@ class Reader:
# from release to input in order to remember it. Since all release # from release to input in order to remember it. Since all release
# events have value 0, the value is not used in the combination. # events have value 0, the value is not used in the combination.
key_down_received = True key_down_received = True
logger.debug_key(event.event_tuple, "down") logger.debug_key(event, "down")
self._unreleased[event.type_and_code] = event.event_tuple self._unreleased[event.type_and_code] = event
self._debounce_start(event.event_tuple) self._debounce_start(event.event_tuple)
previous_event = event previous_event = event
@ -190,11 +195,11 @@ class Reader:
def terminate(self): def terminate(self):
"""Stop reading keycodes for good.""" """Stop reading keycodes for good."""
logger.debug("Sending close msg to helper") logger.debug("Sending close msg to helper")
self._commands.send(TERMINATE) self._commands.send(CMD_TERMINATE)
def refresh_groups(self): def refresh_groups(self):
"""Ask the helper for new device groups.""" """Ask the helper for new device groups."""
self._commands.send(REFRESH_GROUPS) self._commands.send(CMD_REFRESH_GROUPS)
def clear(self): def clear(self):
"""Next time when reading don't return the previous keycode.""" """Next time when reading don't return the previous keycode."""

@ -407,12 +407,18 @@ class UserInterface:
# letting go of one of the keys of a combination won't just make # letting go of one of the keys of a combination won't just make
# it return the leftover key, it will continue to return None because # it return the leftover key, it will continue to return None because
# they have already been read. # they have already been read.
key = reader.read() combination = reader.read()
if reader.are_new_groups_available(): if reader.are_new_groups_available():
self.populate_devices() self.populate_devices()
self.editor.consume_newest_keycode(key) # giving editor its own interval and making it call reader.read itself causes
# incredibly frustrating and miraculous problems. Do not do it. Observations:
# - test_autocomplete_key fails if the gui has been launched and closed by a
# previous test already
# Maybe it has something to do with the order of editor.consume_newest_keycode
# and user_interface.populate_devices.
self.editor.consume_newest_keycode(combination)
return True return True

@ -116,6 +116,18 @@ class InputEvent:
"""event type, code, value""" """event type, code, value"""
return self.type, self.code, self.value return self.type, self.code, self.value
def __str__(self):
if self.type == evdev.ecodes.EV_KEY:
key_name = evdev.ecodes.bytype[self.type].get(self.code, self.code)
action = "down" if self.value == 1 else "up"
return f"<InputEvent {key_name} {action}>"
return f"<InputEvent {self.event_tuple}>"
def timestamp(self):
"""Return the unix timestamp of when the event was seen."""
return self.sec + self.usec / 1000000
def modify( def modify(
self, self,
sec: int = None, sec: int = None,

@ -61,10 +61,10 @@ def debug_key(self, key, msg, *args):
msg = msg % args msg = msg % args
str_key = str(key) str_key = str(key)
str_key = str_key.replace(",)", ")") str_key = str_key.replace(",)", ")")
spacing = " " + "-" * max(0, 30 - len(str_key)) spacing = " " + "·" * max(0, 30 - len(msg))
if len(spacing) == 1: if len(spacing) == 1:
spacing = "" spacing = ""
msg = f"{str_key}{spacing} {msg}" msg = f"{msg}{spacing} {str_key}"
if msg == previous_key_debug_log: if msg == previous_key_debug_log:
# avoid some super spam from EV_ABS events # avoid some super spam from EV_ABS events

@ -288,7 +288,7 @@ class GuiTestBase(unittest.TestCase):
raise e raise e
# try again # try again
print("Test failed, trying again") print("Test failed, trying again...")
self.tearDown() self.tearDown()
self.setUp() self.setUp()
@ -335,6 +335,17 @@ class GuiTestBase(unittest.TestCase):
def tearDownClass(cls): def tearDownClass(cls):
UserInterface.start_processes = cls.original_start_processes UserInterface.start_processes = cls.original_start_processes
def activate_recording_toggle(self):
logger.info("Activating the recording toggle")
self.set_focus(self.toggle)
self.toggle.set_active(True)
def disable_recording_toggle(self):
logger.info("Deactivating the recording toggle")
self.set_focus(None)
# should happen automatically:
self.assertFalse(self.toggle.get_active())
def set_focus(self, widget): def set_focus(self, widget):
logger.info("Focusing %s", widget) logger.info("Focusing %s", widget)
@ -781,23 +792,31 @@ class TestGui(GuiTestBase):
"Button A + Button B + Button C", "Button A + Button B + Button C",
) )
def test_is_waiting_for_input(self):
self.activate_recording_toggle()
self.assertTrue(self.editor.is_waiting_for_input())
self.disable_recording_toggle()
self.assertFalse(self.editor.is_waiting_for_input())
def test_editor_simple(self): def test_editor_simple(self):
self.assertEqual(self.toggle.get_label(), "Change Key") self.assertEqual(self.toggle.get_label(), "Change Key")
self.assertEqual(len(self.selection_label_listbox.get_children()), 1) self.assertEqual(len(self.selection_label_listbox.get_children()), 1)
selection_label = self.selection_label_listbox.get_children()[0] selection_label = self.selection_label_listbox.get_children()[0]
self.set_focus(self.toggle) self.activate_recording_toggle()
self.toggle.set_active(True) self.assertTrue(self.editor.is_waiting_for_input())
self.assertEqual(self.toggle.get_label(), "Press Key") self.assertEqual(self.toggle.get_label(), "Press Key")
self.editor.consume_newest_keycode(None) self.user_interface.consume_newest_keycode()
# nothing happens # nothing happens
self.assertIsNone(selection_label.get_combination()) self.assertIsNone(selection_label.get_combination())
self.assertEqual(len(active_preset), 0) self.assertEqual(len(active_preset), 0)
self.assertEqual(self.toggle.get_label(), "Press Key") self.assertEqual(self.toggle.get_label(), "Press Key")
self.editor.consume_newest_keycode(EventCombination([EV_KEY, 30, 1])) send_event_to_reader(InputEvent.from_tuple((EV_KEY, 30, 1)))
self.user_interface.consume_newest_keycode()
# no symbol configured yet, so the active_preset remains empty # no symbol configured yet, so the active_preset remains empty
self.assertEqual(len(active_preset), 0) self.assertEqual(len(active_preset), 0)
self.assertEqual(len(selection_label.get_combination()), 1) self.assertEqual(len(selection_label.get_combination()), 1)
@ -806,9 +825,10 @@ class TestGui(GuiTestBase):
# but KEY_ is removed from the text for display purposes # but KEY_ is removed from the text for display purposes
self.assertEqual(selection_label.get_label(), "a") self.assertEqual(selection_label.get_label(), "a")
# providing the same key again (Maybe this could happen for gamepads or # providing the same key again doesn't do any harm
# something, idk) doesn't do any harm # (Maybe this could happen for gamepads or something, idk)
self.editor.consume_newest_keycode(EventCombination([EV_KEY, 30, 1])) send_event_to_reader(InputEvent.from_tuple((EV_KEY, 30, 1)))
self.user_interface.consume_newest_keycode()
self.assertEqual(len(active_preset), 0) # not released yet self.assertEqual(len(active_preset), 0) # not released yet
self.assertEqual(len(selection_label.get_combination()), 1) self.assertEqual(len(selection_label.get_combination()), 1)
self.assertEqual(selection_label.get_combination()[0], (EV_KEY, 30, 1)) self.assertEqual(selection_label.get_combination()[0], (EV_KEY, 30, 1))
@ -821,11 +841,17 @@ class TestGui(GuiTestBase):
2, 2,
) )
self.disable_recording_toggle()
self.set_focus(self.editor.get_text_input()) self.set_focus(self.editor.get_text_input())
self.assertFalse(self.editor.is_waiting_for_input())
self.editor.set_symbol_input_text("Shift_L") self.editor.set_symbol_input_text("Shift_L")
self.set_focus(None) self.set_focus(None)
self.assertFalse(self.editor.is_waiting_for_input())
self.assertEqual(len(active_preset), 1) num_mappings = len(active_preset)
self.assertEqual(num_mappings, 1)
time.sleep(0.1) time.sleep(0.1)
gtk_iteration() gtk_iteration()
@ -1970,7 +1996,7 @@ class TestGui(GuiTestBase):
self.assertTrue(os.path.exists(f"{device_path}/new preset.json")) self.assertTrue(os.path.exists(f"{device_path}/new preset.json"))
def test_enable_disable_symbol_input(self): def test_enable_disable_symbol_input(self):
self.editor.disable_symbol_input() # should be disabled by default since no key is recorded yet
self.assertEqual(self.get_unfiltered_symbol_input_text(), SET_KEY_FIRST) self.assertEqual(self.get_unfiltered_symbol_input_text(), SET_KEY_FIRST)
self.assertFalse(self.editor.get_text_input().get_sensitive()) self.assertFalse(self.editor.get_text_input().get_sensitive())
@ -1978,6 +2004,26 @@ class TestGui(GuiTestBase):
self.assertEqual(self.get_unfiltered_symbol_input_text(), "") self.assertEqual(self.get_unfiltered_symbol_input_text(), "")
self.assertTrue(self.editor.get_text_input().get_sensitive()) self.assertTrue(self.editor.get_text_input().get_sensitive())
# disable it
self.editor.disable_symbol_input()
self.assertFalse(self.editor.get_text_input().get_sensitive())
# try to enable it by providing a key via set_combination
self.editor.set_combination(EventCombination((1, 201, 1)))
self.assertEqual(self.get_unfiltered_symbol_input_text(), "")
self.assertTrue(self.editor.get_text_input().get_sensitive())
# disable it again
self.editor.set_combination(None)
self.assertFalse(self.editor.get_text_input().get_sensitive())
# try to enable it via the reader
self.activate_recording_toggle()
send_event_to_reader(InputEvent.from_tuple((EV_KEY, 101, 1)))
self.user_interface.consume_newest_keycode()
self.assertEqual(self.get_unfiltered_symbol_input_text(), "")
self.assertTrue(self.editor.get_text_input().get_sensitive())
# it wouldn't clear user input, if for whatever reason (a bug?) there is user # it wouldn't clear user input, if for whatever reason (a bug?) there is user
# input in there when enable_symbol_input is called. # input in there when enable_symbol_input is called.
self.editor.set_symbol_input_text("foo") self.editor.set_symbol_input_text("foo")

@ -565,7 +565,7 @@ def send_event_to_reader(event):
def quick_cleanup(log=True): def quick_cleanup(log=True):
"""Reset the applications state.""" """Reset the applications state."""
if log: if log:
print("quick cleanup") print("Quick cleanup...")
for device in list(pending_events.keys()): for device in list(pending_events.keys()):
try: try:
@ -648,13 +648,16 @@ def quick_cleanup(log=True):
uinput.write_count = 0 uinput.write_count = 0
uinput.write_history = [] uinput.write_history = []
if log:
print("Quick cleanup done")
def cleanup(): def cleanup():
"""Reset the applications state. """Reset the applications state.
Using this is slower, usually quick_cleanup() is sufficient. Using this is slower, usually quick_cleanup() is sufficient.
""" """
print("cleanup") print("Cleanup...")
os.system("pkill -f input-remapper-service") os.system("pkill -f input-remapper-service")
os.system("pkill -f input-remapper-control") os.system("pkill -f input-remapper-control")
@ -665,6 +668,8 @@ def cleanup():
with patch.object(sys, "argv", ["input-remapper-service"]): with patch.object(sys, "argv", ["input-remapper-service"]):
global_uinputs.prepare() global_uinputs.prepare()
print("Cleanup done")
def spy(obj, name): def spy(obj, name):
"""Convenient wrapper for patch.object(..., ..., wraps=...).""" """Convenient wrapper for patch.object(..., ..., wraps=...)."""

@ -31,20 +31,17 @@ class TestKey(unittest.TestCase):
def test_key(self): def test_key(self):
# its very similar to regular tuples, but with some extra stuff # its very similar to regular tuples, but with some extra stuff
key_1 = EventCombination((1, 3, 1), (1, 5, 1)) key_1 = EventCombination((1, 3, 1), (1, 5, 1))
self.assertEqual(str(key_1), "EventCombination((1, 3, 1), (1, 5, 1))")
self.assertEqual(len(key_1), 2) self.assertEqual(len(key_1), 2)
self.assertEqual(key_1[0], (1, 3, 1)) self.assertEqual(key_1[0], (1, 3, 1))
self.assertEqual(key_1[1], (1, 5, 1)) self.assertEqual(key_1[1], (1, 5, 1))
self.assertEqual(hash(key_1), hash(((1, 3, 1), (1, 5, 1)))) self.assertEqual(hash(key_1), hash(((1, 3, 1), (1, 5, 1))))
key_2 = EventCombination((1, 3, 1)) key_2 = EventCombination((1, 3, 1))
self.assertEqual(str(key_2), "EventCombination((1, 3, 1))")
self.assertEqual(len(key_2), 1) self.assertEqual(len(key_2), 1)
self.assertNotEqual(key_2, key_1) self.assertNotEqual(key_2, key_1)
self.assertNotEqual(hash(key_2), hash(key_1)) self.assertNotEqual(hash(key_2), hash(key_1))
key_3 = EventCombination((1, 3, 1)) key_3 = EventCombination((1, 3, 1))
self.assertEqual(str(key_3), "EventCombination((1, 3, 1))")
self.assertEqual(len(key_3), 1) self.assertEqual(len(key_3), 1)
self.assertEqual(key_3, key_2) self.assertEqual(key_3, key_2)
self.assertNotEqual(key_3, (1, 3, 1)) self.assertNotEqual(key_3, (1, 3, 1))
@ -52,15 +49,11 @@ class TestKey(unittest.TestCase):
self.assertEqual(hash(key_3), hash(((1, 3, 1),))) self.assertEqual(hash(key_3), hash(((1, 3, 1),)))
key_4 = EventCombination(*key_3) key_4 = EventCombination(*key_3)
self.assertEqual(str(key_4), "EventCombination((1, 3, 1))")
self.assertEqual(len(key_4), 1) self.assertEqual(len(key_4), 1)
self.assertEqual(key_4, key_3) self.assertEqual(key_4, key_3)
self.assertEqual(hash(key_4), hash(key_3)) self.assertEqual(hash(key_4), hash(key_3))
key_5 = EventCombination(*key_4, *key_4, (1, 7, 1)) key_5 = EventCombination(*key_4, *key_4, (1, 7, 1))
self.assertEqual(
str(key_5), "EventCombination((1, 3, 1), (1, 3, 1), (1, 7, 1))"
)
self.assertEqual(len(key_5), 3) self.assertEqual(len(key_5), 3)
self.assertNotEqual(key_5, key_4) self.assertNotEqual(key_5, key_4)
self.assertNotEqual(hash(key_5), hash(key_4)) self.assertNotEqual(hash(key_5), hash(key_4))

@ -50,8 +50,14 @@ class TestLogger(unittest.TestCase):
logger.debug_key(((1, 200, -1), (1, 5, 1)), "foo %s", (1, 2)) logger.debug_key(((1, 200, -1), (1, 5, 1)), "foo %s", (1, 2))
with open(path, "r") as f: with open(path, "r") as f:
content = f.read().lower() content = f.read().lower()
self.assertIn("((1, 2, 1)) ------------------- foo 1234 bar", content) self.assertIn(
self.assertIn("((1, 200, -1), (1, 5, 1)) ----- foo (1, 2)", content) "foo 1234 bar ·················· ((1, 2, 1))",
content,
)
self.assertIn(
"foo (1, 2) ···················· ((1, 200, -1), (1, 5, 1))",
content,
)
def test_log_info(self): def test_log_info(self):
update_verbosity(debug=False) update_verbosity(debug=False)

Loading…
Cancel
Save