diff --git a/keymapper/gtk/window.py b/keymapper/gtk/window.py index 5a90a092..17f4f59a 100755 --- a/keymapper/gtk/window.py +++ b/keymapper/gtk/window.py @@ -289,11 +289,13 @@ class Window: def can_modify_mapping(self, *args): """Show a message if changing the mapping is not possible.""" - if self.dbus.is_injecting(self.selected_device): - # because the device is in grab mode by the daemon and - # therefore the original keycode inaccessible - logger.info('Cannot change keycodes while injecting') - self.show_status(CTX_ERROR, 'Use "Restore Defaults" before editing') + if not self.dbus.is_injecting(self.selected_device): + return + + # because the device is in grab mode by the daemon and + # therefore the original keycode inaccessible + logger.info('Cannot change keycodes while injecting') + self.show_status(CTX_ERROR, 'Use "Restore Defaults" before editing') def get_focused_row(self): """Get the Row and its child that is currently in focus.""" @@ -389,8 +391,9 @@ class Window: self.check_macro_syntax() except PermissionError as error: - self.show_status(CTX_ERROR, 'Error: Permission denied!') - logger.error(str(error)) + error = str(error) + self.show_status(CTX_ERROR, 'Error: Permission denied!', error) + logger.error(error) def on_delete_preset_clicked(self, _): """Delete a preset from the file system.""" @@ -413,9 +416,9 @@ class Window: else: self.show_status(CTX_APPLY, f'Applied preset "{preset}"') - path = get_preset_path(self.selected_device, preset) + path = get_preset_path(device, preset) xmodmap = get_config_path(XMODMAP_FILENAME) - success = self.dbus.start_injecting(self.selected_device, path, xmodmap) + success = self.dbus.start_injecting(device, path, xmodmap) if not success: self.show_status(CTX_ERROR, 'Error: Could not grab devices!') @@ -479,8 +482,9 @@ class Window: self.get('preset_selection').append(new_preset, new_preset) self.get('preset_selection').set_active_id(new_preset) except PermissionError as error: - self.show_status(CTX_ERROR, 'Error: Permission denied!') - logger.error(str(error)) + error = str(error) + self.show_status(CTX_ERROR, 'Error: Permission denied!', error) + logger.error(error) def on_select_preset(self, dropdown): """Show the mappings of the preset.""" diff --git a/readme/coverage.svg b/readme/coverage.svg index 03eae130..c6421901 100644 --- a/readme/coverage.svg +++ b/readme/coverage.svg @@ -17,7 +17,7 @@ coverage - 90% - 90% + 91% + 91% \ No newline at end of file diff --git a/tests/testcases/test_daemon.py b/tests/testcases/test_daemon.py index f8fe8470..19f8942f 100644 --- a/tests/testcases/test_daemon.py +++ b/tests/testcases/test_daemon.py @@ -23,14 +23,16 @@ import os import multiprocessing import unittest import time +import subprocess import evdev from evdev.ecodes import EV_KEY, EV_ABS from gi.repository import Gtk +from pydbus import SystemBus from keymapper.state import custom_mapping, system_mapping from keymapper.config import config -from keymapper.getdevices import get_devices, refresh_devices +from keymapper.getdevices import get_devices from keymapper.paths import get_preset_path from keymapper.daemon import Daemon, get_dbus_interface, BUS_NAME @@ -72,6 +74,10 @@ class TestDBusDaemon(unittest.TestCase): self.assertEqual(self.interface.hello('foo'), 'foo') +check_output = subprocess.check_output +dbus_get = type(SystemBus()).get + + class TestDaemon(unittest.TestCase): new_fixture = '/dev/input/event9876' @@ -86,8 +92,31 @@ class TestDaemon(unittest.TestCase): self.daemon = None evdev.InputDevice.grab = self.grab + subprocess.check_output = check_output + type(SystemBus()).get = dbus_get + cleanup() + def test_get_dbus_interface(self): + # no daemon runs, should return an instance of the object instead + self.assertFalse(is_service_running()) + self.assertIsInstance(get_dbus_interface(), Daemon) + self.assertIsNone(get_dbus_interface(False)) + + subprocess.check_output = lambda *args: None + self.assertTrue(is_service_running()) + # now it actually tries to use the dbus, but it fails + # because none exists, so it returns an instance again + self.assertIsInstance(get_dbus_interface(), Daemon) + self.assertIsNone(get_dbus_interface(False)) + + class FakeConnection: + pass + + type(SystemBus()).get = lambda *args: FakeConnection() + self.assertIsInstance(get_dbus_interface(), FakeConnection) + self.assertIsInstance(get_dbus_interface(False), FakeConnection) + def test_daemon(self): ev_1 = (EV_KEY, 9) ev_2 = (EV_ABS, 12) diff --git a/tests/testcases/test_integration.py b/tests/testcases/test_integration.py index f673bb63..3263a25b 100644 --- a/tests/testcases/test_integration.py +++ b/tests/testcases/test_integration.py @@ -25,7 +25,7 @@ import grp import os import unittest import evdev -from evdev.events import EV_KEY, EV_ABS +from evdev.ecodes import EV_KEY, EV_ABS, BTN_LEFT, BTN_TOOL_DOUBLETAP import json from unittest.mock import patch from importlib.util import spec_from_loader, module_from_spec @@ -317,9 +317,17 @@ class TestIntegration(unittest.TestCase): if key: # modifies the keycode in the row not by writing into the input, # but by sending an event + + # click events are ignored because it would render the mouse + # useless. It can still be changed in the config files. + keycode_reader._pipe[1].send(InputEvent(EV_KEY, BTN_LEFT, 1)) + time.sleep(0.1) + gtk_iteration() + keycode_reader._pipe[1].send(InputEvent(*key)) time.sleep(0.1) gtk_iteration() + if expect_success: self.assertEqual(row.get_keycode(), key) css_classes = row.get_style_context().list_classes() diff --git a/tests/testcases/test_mapping.py b/tests/testcases/test_mapping.py index 794cff05..552d85d1 100644 --- a/tests/testcases/test_mapping.py +++ b/tests/testcases/test_mapping.py @@ -20,13 +20,12 @@ import os -import shutil -import json import unittest -from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X +import json +from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, KEY_A from keymapper.mapping import Mapping -from keymapper.state import SystemMapping +from keymapper.state import SystemMapping, XMODMAP_FILENAME from keymapper.config import config from keymapper.paths import get_preset_path @@ -50,6 +49,20 @@ class TestSystemMapping(unittest.TestCase): self.assertEqual(system_mapping.get('foo1'), 101) self.assertEqual(system_mapping.get('bar2'), 202) + def test_xmodmap_file(self): + system_mapping = SystemMapping() + path = os.path.join(tmp, XMODMAP_FILENAME) + os.remove(path) + + system_mapping.populate() + self.assertTrue(os.path.exists(path)) + with open(path, 'r') as file: + content = json.load(file) + self.assertEqual(content['a'], KEY_A) + # only xmodmap stuff should be present + self.assertNotIn('key_a', content) + self.assertNotIn('KEY_A', content) + def test_system_mapping(self): system_mapping = SystemMapping() self.assertGreater(len(system_mapping._mapping), 100) diff --git a/tests/testcases/test_reader.py b/tests/testcases/test_reader.py index 1cc48cb4..cde91e31 100644 --- a/tests/testcases/test_reader.py +++ b/tests/testcases/test_reader.py @@ -20,10 +20,11 @@ import unittest - -from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, KEY_COMMA import time +from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, KEY_COMMA, BTN_LEFT, \ + BTN_TOOL_DOUBLETAP + from keymapper.dev.reader import keycode_reader from tests.test import InputEvent, pending_events, EVENT_READ_TIMEOUT