diff --git a/data/key-mapper.glade b/data/key-mapper.glade
index c6fb64ac..be015975 100644
--- a/data/key-mapper.glade
+++ b/data/key-mapper.glade
@@ -238,7 +238,8 @@
- go_back
+ go_back
+ go_ahead
diff --git a/keymapper/X.py b/keymapper/X.py
index 14ce5a2f..78d63e07 100644
--- a/keymapper/X.py
+++ b/keymapper/X.py
@@ -203,8 +203,10 @@ def setxkbmap(device, layout):
logger.info('Applying layout "%s" on device %s', layout, device)
group = get_devices()[device]
- keycodes = None if layout is None else 'key-mapper'
- layout = layout or get_system_layout()
+ if layout is None:
+ cmd = ['setxkbmap', '-layout', get_system_layout()]
+ else:
+ cmd = ['setxkbmap', '-layout', layout, '-keycodes', 'key-mapper']
# apply it to every device that hangs on the same usb port, because I
# have no idea how to figure out which one of those 3 devices that are
@@ -214,17 +216,9 @@ def setxkbmap(device, layout):
# only all virtual devices of the same hardware device
continue
- cmd = [
- 'setxkbmap',
- '-device', str(xinput_id)
- ]
- if layout is not None:
- cmd += ['-layout', layout]
- if keycodes is not None:
- cmd += ['-keycodes', keycodes]
-
- logger.debug('Running `%s`', ' '.join(cmd))
- subprocess.run(cmd)
+ device_cmd = cmd + ['-device', str(xinput_id)]
+ logger.debug('Running `%s`', ' '.join(device_cmd))
+ subprocess.run(device_cmd, capture_output=True)
def create_identity_mapping():
diff --git a/keymapper/gtk/row.py b/keymapper/gtk/row.py
index 8dff06cb..e3bedc1d 100644
--- a/keymapper/gtk/row.py
+++ b/keymapper/gtk/row.py
@@ -36,20 +36,18 @@ from keymapper.linux import keycode_reader
CTX_KEYCODE = 2
-class Row:
+class Row(Gtk.ListBoxRow):
"""A single, configurable key mapping."""
+ __gtype_name__ = 'ListBoxRow'
+
def __init__(self, delete_callback, window, keycode=None, character=None):
"""Construct a row widget."""
- self.widget = None
+ super().__init__()
self.device = window.selected_device
self.window = window
self.delete_callback = delete_callback
self.put_together(keycode, character)
- def get_widget(self):
- """Return the widget that wraps all the widgets of the row."""
- return self.widget
-
def get_keycode(self):
keycode = self.keycode.get_label()
return int(keycode) if keycode else None
@@ -111,7 +109,7 @@ class Row:
def highlight(self):
"""Mark this row as changed."""
- self.widget.get_style_context().add_class('changed')
+ self.get_style_context().add_class('changed')
def on_character_input_change(self, entry):
keycode = self.get_keycode()
@@ -169,11 +167,9 @@ class Row:
box.pack_start(delete_button, expand=True, fill=False, padding=0)
box.show_all()
- row = Gtk.ListBoxRow()
- row.add(box)
- row.show_all()
+ self.add(box)
+ self.show_all()
- self.widget = row
self.character_input = character_input
self.keycode = keycode_input
diff --git a/keymapper/gtk/unsaved.py b/keymapper/gtk/unsaved.py
index 9763c150..f2848cf1 100644
--- a/keymapper/gtk/unsaved.py
+++ b/keymapper/gtk/unsaved.py
@@ -34,13 +34,16 @@ CONTINUE = True
GO_BACK = False
-def unsavedChangesDialog():
+def unsaved_changes_dialog():
+ """Blocks until the user decided about an action."""
gladefile = get_data_path('key-mapper.glade')
builder = Gtk.Builder()
builder.add_from_file(gladefile)
dialog = builder.get_object('unsaved_changes')
dialog.show()
- dialog.run()
+ response = dialog.run()
dialog.hide()
- # TODO do something meaningful
- return GO_BACK
+ if response == Gtk.ResponseType.ACCEPT:
+ return CONTINUE
+ else:
+ return GO_BACK
diff --git a/keymapper/gtk/window.py b/keymapper/gtk/window.py
index 7ba21ad1..b3c7fd22 100755
--- a/keymapper/gtk/window.py
+++ b/keymapper/gtk/window.py
@@ -35,7 +35,7 @@ from keymapper.presets import get_presets, find_newest_preset, \
from keymapper.logger import logger
from keymapper.linux import get_devices, keycode_reader
from keymapper.gtk.row import Row
-from keymapper.gtk.unsaved import unsavedChangesDialog, GO_BACK
+from keymapper.gtk.unsaved import unsaved_changes_dialog, GO_BACK
def gtk_iteration():
@@ -220,9 +220,12 @@ class Window:
def on_select_device(self, dropdown):
"""List all presets, create one if none exist yet."""
- if custom_mapping.changed:
- if unsavedChangesDialog() == GO_BACK:
- return
+ if dropdown.get_active_id() == self.selected_device:
+ return
+
+ if custom_mapping.changed and unsaved_changes_dialog() == GO_BACK:
+ dropdown.set_active_id(self.selected_device)
+ return
device = dropdown.get_active_text()
@@ -239,7 +242,7 @@ class Window:
def on_create_preset_clicked(self, button):
"""Create a new preset and select it."""
if custom_mapping.changed:
- if unsavedChangesDialog() == GO_BACK:
+ if unsaved_changes_dialog() == GO_BACK:
return
new_preset = create_preset(self.selected_device)
@@ -249,9 +252,12 @@ class Window:
def on_select_preset(self, dropdown):
"""Show the mappings of the preset."""
- if custom_mapping.changed:
- if unsavedChangesDialog() == GO_BACK:
- return
+ if dropdown.get_active_id() == self.selected_preset:
+ return
+
+ if custom_mapping.changed and unsaved_changes_dialog() == GO_BACK:
+ dropdown.set_active_id(self.selected_preset)
+ return
self.clear_mapping_table()
@@ -269,7 +275,7 @@ class Window:
keycode=keycode,
character=character
)
- key_list.insert(single_key_mapping.get_widget(), -1)
+ key_list.insert(single_key_mapping, -1)
self.add_empty()
@@ -279,7 +285,7 @@ class Window:
delete_callback=self.on_row_removed
)
key_list = self.get('key_list')
- key_list.insert(empty.get_widget(), -1)
+ key_list.insert(empty, -1)
def on_row_removed(self, single_key_mapping):
"""Stuff to do when a row was removed
@@ -290,7 +296,7 @@ class Window:
"""
key_list = self.get('key_list')
# https://stackoverflow.com/a/30329591/4417769
- key_list.remove(single_key_mapping.get_widget())
+ key_list.remove(single_key_mapping)
def save_config(self):
"""Write changes to disk"""
diff --git a/tests/test.py b/tests/test.py
index 0c8e6c8e..881a5668 100644
--- a/tests/test.py
+++ b/tests/test.py
@@ -55,6 +55,10 @@ linux._devices = {
}
linux.get_devices = lambda: linux._devices
+# don't block tests
+from keymapper.gtk import unsaved
+unsaved.unsaved_changes_dialog = lambda: unsaved.CONTINUE
+
from keymapper.logger import update_verbosity
# some class function stubs.
@@ -73,7 +77,7 @@ if __name__ == "__main__":
# in all of the available tests like unittest.main() does...,
# so provide both options.
if len(modules) > 0:
- # for example `tests/test.py ConfigTest.testFirstLine`
+ # for example `tests/test.py integration.Integration.test_can_start`
testsuite = unittest.defaultTestLoader.loadTestsFromNames(
[f'testcases.{module}' for module in modules]
)
diff --git a/tests/testcases/integration.py b/tests/testcases/integration.py
index 9cf3266c..c95b14b5 100644
--- a/tests/testcases/integration.py
+++ b/tests/testcases/integration.py
@@ -124,6 +124,9 @@ class Integration(unittest.TestCase):
def get_active_text(self):
return self.name
+ def get_active_id(self):
+ return self.name
+
# created on start because the first device is selected and some empty
# preset prepared.
self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/new_preset'))