#75 ctrl+r to refresh the device list

xkb
sezanzeb 3 years ago committed by sezanzeb
parent dc8a74b401
commit 7d2d542d6e

@ -121,13 +121,12 @@
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Shortcut: shift + del
<property name="tooltip-text" translatable="yes">Shortcut: ctrl + del
To give your keys back their original mapping.</property>
<property name="halign">end</property>
<property name="image">gtk-redo-icon</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_apply_system_layout_clicked" swapped="no"/>
<accelerator key="Delete" signal="activate" modifiers="GDK_SHIFT_MASK"/>
<signal name="clicked" handler="on_restore_defaults_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@ -1442,6 +1441,123 @@ See the &lt;a href="https://www.gnu.org/licenses/gpl-3.0.html"&gt;GNU General Pu
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="border-width">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Shortcuts only work while keys are not being recorded and the gui is in focus.</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=3 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="column-spacing">18</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">ctrl + del</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">closes the application</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">ctrl + q</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">ctrl + r</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">refreshes the device list</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">stops the injection</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="name">Shortcuts</property>
<property name="title" translatable="yes">Shortcuts</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">

@ -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

@ -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')

@ -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')

@ -17,7 +17,7 @@
<text x="22.0" y="14">pylint</text>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="63.0" y="15" fill="#010101" fill-opacity=".3">9.83</text>
<text x="62.0" y="14">9.83</text>
<text x="63.0" y="15" fill="#010101" fill-opacity=".3">9.82</text>
<text x="62.0" y="14">9.82</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -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

@ -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)

@ -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)

@ -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__":

Loading…
Cancel
Save