diff --git a/data/key-mapper.glade b/data/key-mapper.glade
index 466dbecc..8538dcc3 100644
--- a/data/key-mapper.glade
+++ b/data/key-mapper.glade
@@ -121,13 +121,12 @@
True
True
True
- Shortcut: shift + del
+ Shortcut: ctrl + del
To give your keys back their original mapping.
end
gtk-redo-icon
True
-
-
+
False
@@ -1442,6 +1441,123 @@ See the <a href="https://www.gnu.org/licenses/gpl-3.0.html">GNU General Pu
1
+
+
+
+ Shortcuts
+ Shortcuts
+ 2
+
+
diff --git a/keymapper/gui/helper.py b/keymapper/gui/helper.py
index 701d2bef..3a6c3365 100644
--- a/keymapper/gui/helper.py
+++ b/keymapper/gui/helper.py
@@ -38,11 +38,12 @@ from evdev.ecodes import EV_KEY, EV_ABS
from keymapper.ipc.pipe import Pipe
from keymapper.logger import logger
-from keymapper.getdevices import get_devices
+from keymapper.getdevices import get_devices, refresh_devices
from keymapper import utils
TERMINATE = 'terminate'
+GET_DEVICES = 'get_devices'
def is_helper_running():
@@ -67,11 +68,7 @@ class RootHelper:
self._results = Pipe('/tmp/key-mapper/results')
self._commands = Pipe('/tmp/key-mapper/commands')
- # the ui needs the devices first
- self._results.send({
- 'type': 'devices',
- 'message': get_devices()
- })
+ self._send_devices()
self.device_name = None
self._pipe = multiprocessing.Pipe()
@@ -82,6 +79,13 @@ class RootHelper:
self._handle_commands()
self._start_reading()
+ def _send_devices(self):
+ """Send the get_devices datastructure to the gui."""
+ self._results.send({
+ 'type': 'devices',
+ 'message': get_devices()
+ })
+
def _handle_commands(self):
"""Handle all unread commands."""
# wait for something to do
@@ -93,6 +97,9 @@ class RootHelper:
if cmd == TERMINATE:
logger.debug('Helper terminates')
sys.exit(0)
+ if cmd == GET_DEVICES:
+ refresh_devices()
+ self._send_devices()
elif cmd in get_devices():
self.device_name = cmd
else:
@@ -114,7 +121,14 @@ class RootHelper:
logger.error('device_name is None')
return
- group = get_devices()[device_name]
+ group = get_devices().get(device_name)
+
+ if group is None:
+ # a device possibly disappeared due to refresh_devices
+ self.device_name = None
+ logger.error('%s disappeared', device_name)
+ return
+
virtual_devices = []
# Watch over each one of the potentially multiple devices per
# hardware
diff --git a/keymapper/gui/reader.py b/keymapper/gui/reader.py
index 6e577f35..e40e01e0 100644
--- a/keymapper/gui/reader.py
+++ b/keymapper/gui/reader.py
@@ -32,7 +32,7 @@ from keymapper.logger import logger
from keymapper.key import Key
from keymapper.getdevices import set_devices
from keymapper.ipc.pipe import Pipe
-from keymapper.gui.helper import TERMINATE
+from keymapper.gui.helper import TERMINATE, GET_DEVICES
from keymapper import utils
from keymapper.state import custom_mapping
from keymapper.getdevices import get_devices, GAMEPAD
@@ -89,9 +89,10 @@ class Reader:
if message_type == 'devices':
# result of get_devices in the helper
- logger.debug('Received %d devices', len(message_body))
- set_devices(message_body)
- self._devices_updated = True
+ if message_body != get_devices():
+ logger.debug('Received %d devices', len(message_body))
+ set_devices(message_body)
+ self._devices_updated = True
return None
if message_type == 'event':
@@ -195,6 +196,10 @@ class Reader:
logger.debug('Sending close msg to helper')
self._commands.send(TERMINATE)
+ def get_devices(self):
+ """Ask the helper for new devices."""
+ self._commands.send(GET_DEVICES)
+
def clear(self):
"""Next time when reading don't return the previous keycode."""
logger.debug('Clearing reader')
diff --git a/keymapper/gui/window.py b/keymapper/gui/window.py
index 960814b5..e1f383be 100755
--- a/keymapper/gui/window.py
+++ b/keymapper/gui/window.py
@@ -235,13 +235,25 @@ class Window:
This has nothing to do with the keycode reader.
"""
+ _, focused = self.get_focused_row()
+ if isinstance(focused, Gtk.ToggleButton):
+ return
+
gdk_keycode = event.get_keyval()[1]
if gdk_keycode in [Gdk.KEY_Control_L, Gdk.KEY_Control_R]:
self.ctrl = True
- if gdk_keycode == Gdk.KEY_q and self.ctrl:
- self.on_close()
+ if self.ctrl:
+ # shortcuts
+ if gdk_keycode == Gdk.KEY_q:
+ self.on_close()
+
+ if gdk_keycode == Gdk.KEY_r:
+ reader.get_devices()
+
+ if gdk_keycode == Gdk.KEY_Delete:
+ self.on_restore_defaults_clicked()
def key_release(self, _, event):
"""To execute shortcuts.
@@ -345,6 +357,7 @@ class Window:
device_selection = self.get('device_selection')
with HandlerDisabled(device_selection, self.on_select_device):
+ print('clearing device_store')
self.device_store.clear()
for device in devices:
types = devices[device]['types']
@@ -443,7 +456,7 @@ class Window:
self.show_status(
CTX_WARNING,
'ctrl, alt and shift may not combine properly',
- 'Your system will probably reinterpret combinations ' +
+ 'Your system might reinterpret combinations ' +
'with those after they are injected, and by doing so ' +
'break them.'
)
@@ -454,7 +467,7 @@ class Window:
return True
@with_selected_device
- def on_apply_system_layout_clicked(self, _):
+ def on_restore_defaults_clicked(self, *_):
"""Stop injecting the mapping."""
self.dbus.stop_injecting(self.selected_device)
self.show_status(CTX_APPLY, 'Applied the system default')
diff --git a/readme/pylint.svg b/readme/pylint.svg
index 4aeb9d5c..df8fd8a5 100644
--- a/readme/pylint.svg
+++ b/readme/pylint.svg
@@ -17,7 +17,7 @@
pylint
- 9.83
- 9.83
+ 9.82
+ 9.82
\ No newline at end of file
diff --git a/readme/usage.md b/readme/usage.md
index af8d40ee..56e25141 100644
--- a/readme/usage.md
+++ b/readme/usage.md
@@ -96,8 +96,9 @@ Bear in mind that anti-cheat software might detect macros in games.
## UI Shortcuts
-- `shift` + `del` stops the injection (only works while the gui is in focus)
+- `ctrl` + `del` stops the injection (only works while the gui is in focus)
- `ctrl` + `q` closes the application
+- `ctrl` + `r` refreshes the device list
## Key Names
diff --git a/tests/test.py b/tests/test.py
index e728a0e2..466003ab 100644
--- a/tests/test.py
+++ b/tests/test.py
@@ -364,9 +364,8 @@ class InputDevice:
time.sleep(EVENT_READ_TIMEOUT)
try:
event = pending_events[self.group][1].recv()
- except UnpicklingError as error:
+ except (UnpicklingError, EOFError):
# failed in tests sometimes
- print(error)
return None
self.log(event, 'read_one')
@@ -523,7 +522,7 @@ def quick_cleanup(log=True):
try:
while pending_events[device][1].poll():
pending_events[device][1].recv()
- except EOFError:
+ except (UnpicklingError, EOFError):
# it broke, set up a new pipe
pending_events[device] = None
setup_pipe(device)
diff --git a/tests/testcases/test_integration.py b/tests/testcases/test_integration.py
index ad004759..3ed3af23 100644
--- a/tests/testcases/test_integration.py
+++ b/tests/testcases/test_integration.py
@@ -109,7 +109,7 @@ def clean_up_integration(test):
if hasattr(test, 'original_on_close'):
test.window.on_close = test.original_on_close
- test.window.on_apply_system_layout_clicked(None)
+ test.window.on_restore_defaults_clicked(None)
gtk_iteration()
test.window.on_close()
test.window.window.destroy()
@@ -125,6 +125,14 @@ def clean_up_integration(test):
original_on_select_preset = Window.on_select_preset
+class GtkKeyEvent:
+ def __init__(self, keyval):
+ self.keyval = keyval
+
+ def get_keyval(self):
+ return True, self.keyval
+
+
class TestGetDevicesFromHelper(unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -236,13 +244,6 @@ class TestIntegration(unittest.TestCase):
self.assertTrue(self.window.window.get_visible())
def test_ctrl_q(self):
- class Event:
- def __init__(self, keyval):
- self.keyval = keyval
-
- def get_keyval(self):
- return True, self.keyval
-
closed = False
def on_close():
@@ -251,22 +252,43 @@ class TestIntegration(unittest.TestCase):
self.window.on_close = on_close
- self.window.key_press(self.window, Event(Gdk.KEY_Control_L))
- self.window.key_press(self.window, Event(Gdk.KEY_a))
- self.window.key_release(self.window, Event(Gdk.KEY_Control_L))
- self.window.key_release(self.window, Event(Gdk.KEY_a))
- self.window.key_press(self.window, Event(Gdk.KEY_b))
- self.window.key_press(self.window, Event(Gdk.KEY_q))
- self.window.key_release(self.window, Event(Gdk.KEY_q))
- self.window.key_release(self.window, Event(Gdk.KEY_b))
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_a))
+ self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
+ self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_a))
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_b))
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_q))
+ self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_q))
+ self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_b))
self.assertFalse(closed)
- self.window.key_press(self.window, Event(Gdk.KEY_Control_L))
- self.window.key_press(self.window, Event(Gdk.KEY_q))
+ # while keys are being recorded no shortcut should work
+ rows = self.get_rows()
+ row = rows[-1]
+ self.window.window.set_focus(row.keycode_input)
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_q))
+ self.assertFalse(closed)
+
+ self.window.window.set_focus(None)
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_q))
self.assertTrue(closed)
- self.window.key_release(self.window, Event(Gdk.KEY_Control_L))
- self.window.key_release(self.window, Event(Gdk.KEY_q))
+ self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
+ self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_q))
+
+ def test_ctrl_r(self):
+ with patch.object(reader, 'get_devices') as reader_get_devices_patch:
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_r))
+ reader_get_devices_patch.assert_called_once()
+
+ def test_ctrl_del(self):
+ with patch.object(self.window.dbus, 'stop_injecting') as stop_injecting:
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
+ self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_Delete))
+ stop_injecting.assert_called_once()
def test_show_device_mapping_status(self):
# this function may not return True, otherwise the timeout
@@ -1293,7 +1315,7 @@ class TestIntegration(unittest.TestCase):
write_history = [pipe.recv()]
# stop
- self.window.on_apply_system_layout_clicked(None)
+ self.window.on_restore_defaults_clicked(None)
# try to receive a few of the events
time.sleep(0.2)
diff --git a/tests/testcases/test_reader.py b/tests/testcases/test_reader.py
index d25c8de1..01f2f1df 100644
--- a/tests/testcases/test_reader.py
+++ b/tests/testcases/test_reader.py
@@ -33,7 +33,7 @@ from keymapper.state import custom_mapping
from keymapper.config import BUTTONS, MOUSE
from keymapper.key import Key
from keymapper.gui.helper import RootHelper
-from keymapper.getdevices import set_devices
+from keymapper.getdevices import set_devices, get_devices, refresh_devices
from tests.test import new_event, push_events, send_event_to_reader, \
EVENT_READ_TIMEOUT, START_READING_DELAY, quick_cleanup, MAX_ABS
@@ -63,6 +63,7 @@ class TestReader(unittest.TestCase):
quick_cleanup()
if self.helper is not None:
self.helper.join()
+ refresh_devices()
def create_helper(self):
# this will cause pending events to be copied over to the helper
@@ -506,6 +507,26 @@ class TestReader(unittest.TestCase):
reader.read()
self.assertTrue(reader.are_new_devices_available())
+ # a bit weird, but it assumes the gui handled that and returns
+ # false afterwards
+ self.assertFalse(reader.are_new_devices_available())
+
+ # send the same devices again
+ reader._get_event({
+ 'type': 'devices',
+ 'message': get_devices()
+ })
+ self.assertFalse(reader.are_new_devices_available())
+
+ # send changed devices
+ message = {**get_devices()}
+ del message['device 1']
+ reader._get_event({
+ 'type': 'devices',
+ 'message': message
+ })
+ self.assertTrue(reader.are_new_devices_available())
+ self.assertFalse(reader.are_new_devices_available())
if __name__ == "__main__":