#75 ctrl+r to refresh the device list

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

@ -121,13 +121,12 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">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> To give your keys back their original mapping.</property>
<property name="halign">end</property> <property name="halign">end</property>
<property name="image">gtk-redo-icon</property> <property name="image">gtk-redo-icon</property>
<property name="always-show-image">True</property> <property name="always-show-image">True</property>
<signal name="clicked" handler="on_apply_system_layout_clicked" swapped="no"/> <signal name="clicked" handler="on_restore_defaults_clicked" swapped="no"/>
<accelerator key="Delete" signal="activate" modifiers="GDK_SHIFT_MASK"/>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <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> <property name="position">1</property>
</packing> </packing>
</child> </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> </object>
</child> </child>
<child type="titlebar"> <child type="titlebar">

@ -38,11 +38,12 @@ from evdev.ecodes import EV_KEY, EV_ABS
from keymapper.ipc.pipe import Pipe from keymapper.ipc.pipe import Pipe
from keymapper.logger import logger from keymapper.logger import logger
from keymapper.getdevices import get_devices from keymapper.getdevices import get_devices, refresh_devices
from keymapper import utils from keymapper import utils
TERMINATE = 'terminate' TERMINATE = 'terminate'
GET_DEVICES = 'get_devices'
def is_helper_running(): def is_helper_running():
@ -67,11 +68,7 @@ class RootHelper:
self._results = Pipe('/tmp/key-mapper/results') self._results = Pipe('/tmp/key-mapper/results')
self._commands = Pipe('/tmp/key-mapper/commands') self._commands = Pipe('/tmp/key-mapper/commands')
# the ui needs the devices first self._send_devices()
self._results.send({
'type': 'devices',
'message': get_devices()
})
self.device_name = None self.device_name = None
self._pipe = multiprocessing.Pipe() self._pipe = multiprocessing.Pipe()
@ -82,6 +79,13 @@ class RootHelper:
self._handle_commands() self._handle_commands()
self._start_reading() 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): def _handle_commands(self):
"""Handle all unread commands.""" """Handle all unread commands."""
# wait for something to do # wait for something to do
@ -93,6 +97,9 @@ class RootHelper:
if cmd == TERMINATE: if cmd == TERMINATE:
logger.debug('Helper terminates') logger.debug('Helper terminates')
sys.exit(0) sys.exit(0)
if cmd == GET_DEVICES:
refresh_devices()
self._send_devices()
elif cmd in get_devices(): elif cmd in get_devices():
self.device_name = cmd self.device_name = cmd
else: else:
@ -114,7 +121,14 @@ class RootHelper:
logger.error('device_name is None') logger.error('device_name is None')
return 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 = [] virtual_devices = []
# Watch over each one of the potentially multiple devices per # Watch over each one of the potentially multiple devices per
# hardware # hardware

@ -32,7 +32,7 @@ from keymapper.logger import logger
from keymapper.key import Key from keymapper.key import Key
from keymapper.getdevices import set_devices from keymapper.getdevices import set_devices
from keymapper.ipc.pipe import Pipe 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 import utils
from keymapper.state import custom_mapping from keymapper.state import custom_mapping
from keymapper.getdevices import get_devices, GAMEPAD from keymapper.getdevices import get_devices, GAMEPAD
@ -89,9 +89,10 @@ class Reader:
if message_type == 'devices': if message_type == 'devices':
# result of get_devices in the helper # result of get_devices in the helper
logger.debug('Received %d devices', len(message_body)) if message_body != get_devices():
set_devices(message_body) logger.debug('Received %d devices', len(message_body))
self._devices_updated = True set_devices(message_body)
self._devices_updated = True
return None return None
if message_type == 'event': if message_type == 'event':
@ -195,6 +196,10 @@ class Reader:
logger.debug('Sending close msg to helper') logger.debug('Sending close msg to helper')
self._commands.send(TERMINATE) self._commands.send(TERMINATE)
def get_devices(self):
"""Ask the helper for new devices."""
self._commands.send(GET_DEVICES)
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."""
logger.debug('Clearing reader') logger.debug('Clearing reader')

@ -235,13 +235,25 @@ class Window:
This has nothing to do with the keycode reader. 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] gdk_keycode = event.get_keyval()[1]
if gdk_keycode in [Gdk.KEY_Control_L, Gdk.KEY_Control_R]: if gdk_keycode in [Gdk.KEY_Control_L, Gdk.KEY_Control_R]:
self.ctrl = True self.ctrl = True
if gdk_keycode == Gdk.KEY_q and self.ctrl: if self.ctrl:
self.on_close() # 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): def key_release(self, _, event):
"""To execute shortcuts. """To execute shortcuts.
@ -345,6 +357,7 @@ class Window:
device_selection = self.get('device_selection') device_selection = self.get('device_selection')
with HandlerDisabled(device_selection, self.on_select_device): with HandlerDisabled(device_selection, self.on_select_device):
print('clearing device_store')
self.device_store.clear() self.device_store.clear()
for device in devices: for device in devices:
types = devices[device]['types'] types = devices[device]['types']
@ -443,7 +456,7 @@ class Window:
self.show_status( self.show_status(
CTX_WARNING, CTX_WARNING,
'ctrl, alt and shift may not combine properly', '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 ' + 'with those after they are injected, and by doing so ' +
'break them.' 'break them.'
) )
@ -454,7 +467,7 @@ class Window:
return True return True
@with_selected_device @with_selected_device
def on_apply_system_layout_clicked(self, _): def on_restore_defaults_clicked(self, *_):
"""Stop injecting the mapping.""" """Stop injecting the mapping."""
self.dbus.stop_injecting(self.selected_device) self.dbus.stop_injecting(self.selected_device)
self.show_status(CTX_APPLY, 'Applied the system default') self.show_status(CTX_APPLY, 'Applied the system default')

@ -17,7 +17,7 @@
<text x="22.0" y="14">pylint</text> <text x="22.0" y="14">pylint</text>
</g> </g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <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="63.0" y="15" fill="#010101" fill-opacity=".3">9.82</text>
<text x="62.0" y="14">9.83</text> <text x="62.0" y="14">9.82</text>
</g> </g>
</svg> </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 ## 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` + `q` closes the application
- `ctrl` + `r` refreshes the device list
## Key Names ## Key Names

@ -364,9 +364,8 @@ class InputDevice:
time.sleep(EVENT_READ_TIMEOUT) time.sleep(EVENT_READ_TIMEOUT)
try: try:
event = pending_events[self.group][1].recv() event = pending_events[self.group][1].recv()
except UnpicklingError as error: except (UnpicklingError, EOFError):
# failed in tests sometimes # failed in tests sometimes
print(error)
return None return None
self.log(event, 'read_one') self.log(event, 'read_one')
@ -523,7 +522,7 @@ def quick_cleanup(log=True):
try: try:
while pending_events[device][1].poll(): while pending_events[device][1].poll():
pending_events[device][1].recv() pending_events[device][1].recv()
except EOFError: except (UnpicklingError, EOFError):
# it broke, set up a new pipe # it broke, set up a new pipe
pending_events[device] = None pending_events[device] = None
setup_pipe(device) setup_pipe(device)

@ -109,7 +109,7 @@ def clean_up_integration(test):
if hasattr(test, 'original_on_close'): if hasattr(test, 'original_on_close'):
test.window.on_close = 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() gtk_iteration()
test.window.on_close() test.window.on_close()
test.window.window.destroy() test.window.window.destroy()
@ -125,6 +125,14 @@ def clean_up_integration(test):
original_on_select_preset = Window.on_select_preset 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): class TestGetDevicesFromHelper(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -236,13 +244,6 @@ class TestIntegration(unittest.TestCase):
self.assertTrue(self.window.window.get_visible()) self.assertTrue(self.window.window.get_visible())
def test_ctrl_q(self): def test_ctrl_q(self):
class Event:
def __init__(self, keyval):
self.keyval = keyval
def get_keyval(self):
return True, self.keyval
closed = False closed = False
def on_close(): def on_close():
@ -251,22 +252,43 @@ class TestIntegration(unittest.TestCase):
self.window.on_close = on_close self.window.on_close = on_close
self.window.key_press(self.window, Event(Gdk.KEY_Control_L)) self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
self.window.key_press(self.window, Event(Gdk.KEY_a)) self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_a))
self.window.key_release(self.window, Event(Gdk.KEY_Control_L)) self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
self.window.key_release(self.window, Event(Gdk.KEY_a)) self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_a))
self.window.key_press(self.window, Event(Gdk.KEY_b)) self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_b))
self.window.key_press(self.window, Event(Gdk.KEY_q)) self.window.key_press(self.window, GtkKeyEvent(Gdk.KEY_q))
self.window.key_release(self.window, Event(Gdk.KEY_q)) self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_q))
self.window.key_release(self.window, Event(Gdk.KEY_b)) self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_b))
self.assertFalse(closed) self.assertFalse(closed)
self.window.key_press(self.window, Event(Gdk.KEY_Control_L)) # while keys are being recorded no shortcut should work
self.window.key_press(self.window, Event(Gdk.KEY_q)) 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.assertTrue(closed)
self.window.key_release(self.window, Event(Gdk.KEY_Control_L)) self.window.key_release(self.window, GtkKeyEvent(Gdk.KEY_Control_L))
self.window.key_release(self.window, Event(Gdk.KEY_q)) 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): def test_show_device_mapping_status(self):
# this function may not return True, otherwise the timeout # this function may not return True, otherwise the timeout
@ -1293,7 +1315,7 @@ class TestIntegration(unittest.TestCase):
write_history = [pipe.recv()] write_history = [pipe.recv()]
# stop # stop
self.window.on_apply_system_layout_clicked(None) self.window.on_restore_defaults_clicked(None)
# try to receive a few of the events # try to receive a few of the events
time.sleep(0.2) time.sleep(0.2)

@ -33,7 +33,7 @@ from keymapper.state import custom_mapping
from keymapper.config import BUTTONS, MOUSE from keymapper.config import BUTTONS, MOUSE
from keymapper.key import Key from keymapper.key import Key
from keymapper.gui.helper import RootHelper 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, \ from tests.test import new_event, push_events, send_event_to_reader, \
EVENT_READ_TIMEOUT, START_READING_DELAY, quick_cleanup, MAX_ABS EVENT_READ_TIMEOUT, START_READING_DELAY, quick_cleanup, MAX_ABS
@ -63,6 +63,7 @@ class TestReader(unittest.TestCase):
quick_cleanup() quick_cleanup()
if self.helper is not None: if self.helper is not None:
self.helper.join() self.helper.join()
refresh_devices()
def create_helper(self): def create_helper(self):
# this will cause pending events to be copied over to the helper # this will cause pending events to be copied over to the helper
@ -506,6 +507,26 @@ class TestReader(unittest.TestCase):
reader.read() reader.read()
self.assertTrue(reader.are_new_devices_available()) 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__": if __name__ == "__main__":

Loading…
Cancel
Save