From 52231d507ec152c3efbd9607306803ed64927be5 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Tue, 10 Nov 2020 23:14:28 +0100 Subject: [PATCH] renaming, keeping unmapped keys at their defaults --- X11PATHS.md | 29 ++++++++ data/key-mapper.glade | 18 ++++- data/xkb_symbols_template | 1 + keymapper/X.py | 123 +++++++++++++++++++++++++-------- keymapper/gtk/row.py | 10 +-- keymapper/gtk/window.py | 10 +-- keymapper/mapping.py | 5 +- keymapper/paths.py | 20 ++++-- keymapper/presets.py | 6 +- tests/test.py | 9 +-- tests/testcases/config.py | 22 +++--- tests/testcases/integration.py | 39 ++++++----- tests/testcases/presets.py | 39 ++++++----- tests/testcases/test.py | 5 ++ 14 files changed, 231 insertions(+), 105 deletions(-) create mode 100644 X11PATHS.md diff --git a/X11PATHS.md b/X11PATHS.md new file mode 100644 index 00000000..3b001a4a --- /dev/null +++ b/X11PATHS.md @@ -0,0 +1,29 @@ +# Folder Structure of Key Mapper + +Stuff has to be placed in /usr/share/X11/xkb to my knowledge. In order to +be able to make backups of the configs, which would be expected in the +users home directory, this is symlinked to home. + +Every user gets a path within that /usr directory which is very +unconventional, but it works. This way the presets of multiple users +don't clash. + +This is how a single preset is stored. The path in /usr is a symlink, the +files are actually in home. +- /usr/share/X11/xkb/symbols/key-mapper/// +- /home//.config/key-mapper// + +This is where key-mapper stores the defaults. They are generated from the +parsed output of `xmodmap` and used to keep the unmapped keys at their system +defaults. +- /usr/share/X11/xkb/symbols/key-mapper//default +- /home//.config/key-mapper/default + +Because the concept of "reasonable symbolic names" [3] doesn't apply +when mouse buttons are all over the place, an identity mapping +to make generating "symbols" files easier/possible exists. +Keycode 10 -> "<10>". This has the added benefit that keycodes reported +by xev can be identified in the symbols file. +- /usr/share/X11/xkb/keycodes/key-mapper + +[3] https://www.x.org/releases/X11R7.7/doc/xorg-docs/input/XKB-Enhancing.html diff --git a/data/key-mapper.glade b/data/key-mapper.glade index e782149f..505a3f0b 100644 --- a/data/key-mapper.glade +++ b/data/key-mapper.glade @@ -481,10 +481,22 @@ LCTL, RCTL - + True - False - none + True + + + True + False + + + True + False + none + + + + True diff --git a/data/xkb_symbols_template b/data/xkb_symbols_template index b5fcc86d..4eee587c 100644 --- a/data/xkb_symbols_template +++ b/data/xkb_symbols_template @@ -4,6 +4,7 @@ // /usr/share/X11/xkb/keycodes/key-mapper default xkb_symbols "basic" {{ + {include} name[Group1] = "{name}"; {xkb_symbols} }}; diff --git a/keymapper/X.py b/keymapper/X.py index feb6d06d..0d07f8ed 100644 --- a/keymapper/X.py +++ b/keymapper/X.py @@ -38,11 +38,11 @@ import shutil import subprocess from keymapper.paths import get_home_path, get_usr_path, KEYCODES_PATH, \ - CONFIG_PATH, SYMBOLS_PATH + HOME_PATH, USERS_SYMBOLS, DEFAULT_SYMBOLS, X11_SYMBOLS from keymapper.logger import logger from keymapper.data import get_data_path from keymapper.linux import get_devices -from keymapper.mapping import mapping +from keymapper.mapping import custom_mapping, Mapping def ensure_symlink(): @@ -50,12 +50,19 @@ def ensure_symlink(): It provides the configs in /home to X11 in /usr. """ - if not os.path.exists(SYMBOLS_PATH): + if not os.path.exists(HOME_PATH): + os.makedirs(HOME_PATH, exist_ok=True) + + if not os.path.exists(USERS_SYMBOLS): # link from /usr/share/X11/xkb/symbols/key-mapper/user to # /home/user/.config/key-mapper - logger.info('Linking "%s" to "%s"', SYMBOLS_PATH, CONFIG_PATH) - os.makedirs(os.path.dirname(SYMBOLS_PATH), exist_ok=True) - os.symlink(CONFIG_PATH, SYMBOLS_PATH, target_is_directory=True) + logger.info('Linking "%s" to "%s"', USERS_SYMBOLS, HOME_PATH) + os.makedirs(os.path.dirname(USERS_SYMBOLS), exist_ok=True) + os.symlink(HOME_PATH, USERS_SYMBOLS, target_is_directory=True) + elif not os.path.islink(USERS_SYMBOLS): + logger.error('Expected %s to be a symlink', USERS_SYMBOLS) + else: + logger.debug('Symlink %s exists', USERS_SYMBOLS) def create_preset(device, name=None): @@ -78,7 +85,7 @@ def create_preset(device, name=None): # give those files to the user user = os.getlogin() - for root, dirs, files in os.walk(CONFIG_PATH): + for root, dirs, files in os.walk(HOME_PATH): shutil.chown(root, user, user) for file in files: shutil.chown(os.path.join(root, file), user, user) @@ -102,11 +109,12 @@ def create_setxkbmap_config(device, preset): device : string preset : string """ - if len(mapping) == 0: + if len(custom_mapping) == 0: logger.debug('Got empty mappings') return None create_identity_mapping() + create_default_symbols() home_device_path = get_home_path(device) if not os.path.exists(home_device_path): @@ -120,16 +128,28 @@ def create_setxkbmap_config(device, preset): logger.info('Creating config file "%s"', home_preset_path) os.mknod(home_preset_path) - logger.info('Writing key mappings') + logger.info('Writing key mappings to %s', home_preset_path) with open(home_preset_path, 'w') as f: - contents = generate_symbols_content(device, preset) + contents = generate_symbols(get_preset_name(device, preset)) if contents is not None: f.write(contents) +def get_preset_name(device, preset=None): + """Get the name for that preset that is used for the setxkbmap command.""" + # It's the relative path starting from X11/xkb/symbols and may not + # contain spaces + name = get_usr_path(device, preset)[len(X11_SYMBOLS) + 1:] + assert ' ' not in name + return name + + +DEFAULT_PRESET = get_preset_name('default') + + def apply_preset(device, preset): """Apply a preset to the device.""" - logger.info('Applying the preset') + logger.info('Applying preset "%s" on device %s', preset, device) group = get_devices()[device] # apply it to every device that hangs on the same usb port, because I @@ -140,17 +160,15 @@ def apply_preset(device, preset): # only all virtual devices of the same hardware device continue - symbols = '/usr/share/X11/xkb/symbols/' layout_path = get_usr_path(device, preset) with open(layout_path, 'r') as f: if f.read() == '': logger.error('Tried to load empty config') return - layout_name = layout_path[len(symbols):] cmd = [ 'setxkbmap', - '-layout', layout_name, + '-layout', get_preset_name(device, preset), '-keycodes', 'key-mapper', '-device', str(xinput_id) ] @@ -166,8 +184,9 @@ def create_identity_mapping(): This has the added benefit that keycodes reported by xev can be identified in the symbols file. """ - # TODO don't create this again if it already exists, as soon as this - # stuff is stable. + if os.path.exists(KEYCODES_PATH): + logger.debug('Found the keycodes file at %s', KEYCODES_PATH) + return xkb_keycodes = [] # the maximum specified in /usr/share/X11/xkb/keycodes is usually 255 @@ -195,15 +214,23 @@ def create_identity_mapping(): keycodes.write(result) -def generate_symbols_content(device, preset): +def generate_symbols(name, include=DEFAULT_PRESET, mapping=custom_mapping): """Create config contents to be placed in /usr/share/X11/xkb/symbols. - This file contains the mapping of the preset as expected by X. + It's the mapping of the preset as expected by X. This function does not + create the file. Parameters ---------- - device : string - preset : string + name : string + Usually what `get_preset_name` returns + include : string or None + If another preset should be included. Defaults to the default + preset. Use None to avoid including. + mapping : Mapping + If you need to create a symbols file for some other mapping you can + pass it to this parameter. By default the custom mapping will be + used that is also displayed in the user interface. """ if len(mapping) == 0: raise ValueError('Mapping is empty') @@ -212,6 +239,7 @@ def generate_symbols_content(device, preset): # the keycodes file, THE WHOLE X SESSION WILL CRASH! if not os.path.exists(KEYCODES_PATH): raise FileNotFoundError('Expected the keycodes file to exist') + with open(KEYCODES_PATH, 'r') as f: keycodes = re.findall(r'<.+?>', f.read()) @@ -222,6 +250,7 @@ def generate_symbols_content(device, preset): # don't append that one, otherwise X would crash when loading continue xkb_symbols.append(f'key <{keycode}> {{ [ {character} ] }};') + if len(xkb_symbols) == 0: logger.error('Failed to populate xkb_symbols') return None @@ -231,8 +260,9 @@ def generate_symbols_content(device, preset): template = template_file.read() result = template.format( - name=f'{device}/{preset}', - xkb_symbols='\n '.join(xkb_symbols) + name=name, + xkb_symbols='\n '.join(xkb_symbols), + include=f'include "{include}"' if include else '' ) return result @@ -257,7 +287,10 @@ def get_xinput_id_mapping(): def parse_symbols_file(device, preset): - """Parse a symbols file and return the keycodes.""" + """Parse a symbols file populate the mapping. + + Existing mappings are overwritten if there are conflicts. + """ path = get_home_path(device, preset) if not os.path.exists(path): @@ -265,8 +298,8 @@ def parse_symbols_file(device, preset): 'Tried to load non existing preset "%s" for %s', preset, device ) - mapping.empty() - mapping.changed = False + custom_mapping.empty() + custom_mapping.changed = False return with open(path, 'r') as f: @@ -276,5 +309,41 @@ def parse_symbols_file(device, preset): result = re.findall(r'\n\s+?key <(.+?)>.+?\[\s+(\w+)', f.read()) logger.debug('Found %d mappings in this preset', len(result)) for keycode, character in result: - mapping.changed = False - mapping.change(None, int(keycode), character) + custom_mapping.changed = False + custom_mapping.change(None, int(keycode), character) + + +def create_default_symbols(): + """Parse the output of xmodmap and create a default symbols file. + + Since xmodmap may print mappings that have already been modified by + key-mapper, this should be done only once after the installation. + + This is needed because all our keycode aliases in the symbols files + are "", whereas the others are and such, so they are not + compatible. + """ + if os.path.exists(DEFAULT_SYMBOLS): + logger.debug('Found the default mapping at %s', DEFAULT_SYMBOLS) + return + + xmodmap = subprocess.check_output(['xmodmap', '-pke']).decode() + '\n' + mappings = re.findall(r'(\d+) = (.+)\n', xmodmap) + defaults = Mapping() + for keycode, characters in mappings: + # TODO support an array of values in mapping and test it + defaults.change(None, int(keycode), characters.split()[0]) + + ensure_symlink() + + if not os.path.exists(DEFAULT_SYMBOLS): + logger.info('Creating %s', DEFAULT_SYMBOLS) + os.mknod(DEFAULT_SYMBOLS) + + # TODO test that it is included in the config files + # TODO write test about it being created only if the path doesnt exist + with open(DEFAULT_SYMBOLS, 'w') as f: + contents = generate_symbols(DEFAULT_PRESET, None, defaults) + if contents is not None: + logger.info('Updating default mappings') + f.write(contents) diff --git a/keymapper/gtk/row.py b/keymapper/gtk/row.py index 1b96b64b..81d7a5c2 100644 --- a/keymapper/gtk/row.py +++ b/keymapper/gtk/row.py @@ -27,7 +27,7 @@ gi.require_version('Gtk', '3.0') gi.require_version('GLib', '2.0') from gi.repository import Gtk, GLib -from keymapper.mapping import mapping +from keymapper.mapping import custom_mapping from keymapper.logger import logger from keymapper.linux import keycode_reader @@ -87,7 +87,7 @@ class Row: return # keycode is already set by some other row - if mapping.get(new_keycode) is not None: + if custom_mapping.get(new_keycode) is not None: msg = f'Keycode {new_keycode} is already mapped' logger.info(msg) self.window.get('status_bar').push(CTX_KEYCODE, msg) @@ -102,14 +102,14 @@ class Row: return # else, the keycode has changed, the character is set, all good - mapping.change(previous_keycode, new_keycode, character) + custom_mapping.change(previous_keycode, new_keycode, character) def on_character_input_change(self, entry): keycode = self.get_keycode() character = self.get_character() if keycode is not None: - mapping.change(None, keycode, character) + custom_mapping.change(None, keycode, character) def put_together(self, keycode, character): """Create all GTK widgets.""" @@ -169,5 +169,5 @@ class Row: """Destroy the row and remove it from the config.""" keycode = self.get_keycode() if keycode is not None: - mapping.clear(keycode) + custom_mapping.clear(keycode) self.delete_callback(self) diff --git a/keymapper/gtk/window.py b/keymapper/gtk/window.py index 30fce9ed..c7f46cbf 100755 --- a/keymapper/gtk/window.py +++ b/keymapper/gtk/window.py @@ -29,7 +29,7 @@ from gi.repository import Gtk, Gdk, GLib from keymapper.data import get_data_path from keymapper.X import create_setxkbmap_config, apply_preset, \ - create_preset, mapping, parse_symbols_file + create_preset, custom_mapping, parse_symbols_file from keymapper.presets import get_presets, find_newest_preset, \ delete_preset, rename_preset from keymapper.logger import logger @@ -94,9 +94,9 @@ class Window: rows = len(self.get('key_list').get_children()) # verify that all mappings are displayed - assert rows >= len(mapping) + assert rows >= len(custom_mapping) - if rows == len(mapping): + if rows == len(custom_mapping): self.add_empty() return True @@ -141,7 +141,7 @@ class Window: """Remove all rows from the mappings table.""" key_list = self.get('key_list') key_list.forall(key_list.remove) - mapping.empty() + custom_mapping.empty() def on_save_preset_clicked(self, button): """Save changes to a preset to the file system.""" @@ -219,7 +219,7 @@ class Window: parse_symbols_file(self.selected_device, self.selected_preset) key_list = self.get('key_list') - for keycode, character in mapping: + for keycode, character in custom_mapping: single_key_mapping = Row( window=self, delete_callback=self.on_row_removed, diff --git a/keymapper/mapping.py b/keymapper/mapping.py index fa05ccc6..39252228 100644 --- a/keymapper/mapping.py +++ b/keymapper/mapping.py @@ -104,5 +104,6 @@ class Mapping: return self._mapping.get(keycode) -# one mapping object for the whole application -mapping = Mapping() +# one mapping object for the whole application that holds all +# customizations +custom_mapping = Mapping() diff --git a/keymapper/paths.py b/keymapper/paths.py index ad617ac0..8eff11b6 100644 --- a/keymapper/paths.py +++ b/keymapper/paths.py @@ -25,12 +25,15 @@ import os -# the path in home, is symlinked with SYMBOLS_PATH. +# the path in home, is symlinked with USERS_SYMBOLS. # getlogin gets the user who ran sudo -CONFIG_PATH = os.path.join('/home', os.getlogin(), '.config/key-mapper') +HOME_PATH = os.path.join('/home', os.getlogin(), '.config/key-mapper') + +# the path that contains ALL symbols, not just ours +X11_SYMBOLS = '/usr/share/X11/xkb/symbols' # should not contain spaces -SYMBOLS_PATH = os.path.join( +USERS_SYMBOLS = os.path.join( '/usr/share/X11/xkb/symbols/key-mapper', os.getlogin().replace(' ', '_') ) @@ -44,9 +47,12 @@ def get_home_path(device, preset=None): device = device.strip() if preset is not None: preset = preset.strip() - return os.path.join(CONFIG_PATH, device, preset).replace(' ', '_') + return os.path.join(HOME_PATH, device, preset).replace(' ', '_') else: - return os.path.join(CONFIG_PATH, device.replace(' ', '_')) + return os.path.join(HOME_PATH, device.replace(' ', '_')) + + +DEFAULT_SYMBOLS = get_home_path('default') def get_usr_path(device, preset=None): @@ -59,6 +65,6 @@ def get_usr_path(device, preset=None): device = device.strip() if preset is not None: preset = preset.strip() - return os.path.join(SYMBOLS_PATH, device, preset).replace(' ', '_') + return os.path.join(USERS_SYMBOLS, device, preset).replace(' ', '_') else: - return os.path.join(SYMBOLS_PATH, device.replace(' ', '_')) + return os.path.join(USERS_SYMBOLS, device.replace(' ', '_')) diff --git a/keymapper/presets.py b/keymapper/presets.py index 2b97d876..9e4a266a 100644 --- a/keymapper/presets.py +++ b/keymapper/presets.py @@ -26,7 +26,7 @@ import os import time import glob -from keymapper.paths import get_home_path, CONFIG_PATH +from keymapper.paths import get_home_path, HOME_PATH from keymapper.logger import logger from keymapper.linux import get_devices @@ -78,7 +78,7 @@ def find_newest_preset(device=None): # sort the oldest files to the front in order to use pop to get the newest if device is None: paths = sorted( - glob.glob(os.path.join(CONFIG_PATH, '*/*')), + glob.glob(os.path.join(HOME_PATH, '*/*')), key=os.path.getmtime ) else: @@ -132,7 +132,7 @@ def delete_preset(device, preset): device_path = get_home_path(device) if os.path.exists(device_path) and len(os.listdir(device_path)) == 0: logger.debug('Removing empty dir "%s"', device_path) - os.remove(device_path) + os.rmdir(device_path) def rename_preset(device, old_preset_name, new_preset_name): diff --git a/tests/test.py b/tests/test.py index 2b959ace..0c8e6c8e 100644 --- a/tests/test.py +++ b/tests/test.py @@ -28,10 +28,11 @@ import unittest # quickly fake some stuff before any other file gets a chance to import # the original version from keymapper import paths -paths.SYMBOLS_PATH = '/tmp/key-mapper-test/symbols' -paths.CONFIG_PATH = '/tmp/key-mapper-test/.config' -paths.KEYCODES_PATH = '/tmp/key-mapper-test/keycodes/key-mapper' - +paths.X11_SYMBOLS = '/tmp/key-mapper-test/X11/symbols/key-mapper/user' +paths.USERS_SYMBOLS = '/tmp/key-mapper-test/X11/symbols/key-mapper/user' +paths.DEFAULT_SYMBOLS = '/tmp/key-mapper-test/X11/symbols/key-mapper/user/default' +paths.HOME_PATH = '/tmp/key-mapper-test/user/.config' +paths.KEYCODES_PATH = '/tmp/key-mapper-test/X11/keycodes/key-mapper' from keymapper import linux linux._devices = { diff --git a/tests/testcases/config.py b/tests/testcases/config.py index 39ff6eac..1fb18ad5 100644 --- a/tests/testcases/config.py +++ b/tests/testcases/config.py @@ -23,19 +23,19 @@ import os import unittest import shutil -from keymapper.X import mapping, generate_symbols_content, \ +from keymapper.X import custom_mapping, generate_symbols, \ create_identity_mapping, create_setxkbmap_config, get_home_path -from keymapper.paths import KEYCODES_PATH, SYMBOLS_PATH, CONFIG_PATH +from keymapper.paths import KEYCODES_PATH, USERS_SYMBOLS, HOME_PATH from test import tmp class TestConfig(unittest.TestCase): def setUp(self): - mapping.empty() - mapping.change(None, 10, 'a') - mapping.change(None, 11, 'KP_1') - mapping.change(None, 12, 3) + custom_mapping.empty() + custom_mapping.change(None, 10, 'a') + custom_mapping.change(None, 11, 'KP_1') + custom_mapping.change(None, 12, 3) if os.path.exists(tmp): shutil.rmtree(tmp) @@ -43,13 +43,13 @@ class TestConfig(unittest.TestCase): create_setxkbmap_config('device a', 'preset b') self.assertTrue(os.path.exists(os.path.join( - CONFIG_PATH, + HOME_PATH, 'device_a', 'preset_b' ))) self.assertTrue(os.path.exists(os.path.join( - SYMBOLS_PATH, + USERS_SYMBOLS, 'device_a', 'preset_b' ))) @@ -65,12 +65,12 @@ class TestConfig(unittest.TestCase): def test_generate_content(self): self.assertRaises( FileNotFoundError, - generate_symbols_content, + generate_symbols, 'device', 'preset' ) # create the identity mapping, because it is required for - # generate_symbols_content + # generate_symbols create_identity_mapping() self.assertTrue(os.path.exists(KEYCODES_PATH)) with open(KEYCODES_PATH, 'r') as f: @@ -78,7 +78,7 @@ class TestConfig(unittest.TestCase): self.assertIn('<8> = 8;', keycodes) self.assertIn('<255> = 255;', keycodes) - content = generate_symbols_content('device', 'preset') + content = generate_symbols('device/preset') self.assertIn('key <10> { [ a ] };', content) self.assertIn('key <11> { [ KP_1 ] };', content) self.assertIn('key <12> { [ 3 ] };', content) diff --git a/tests/testcases/integration.py b/tests/testcases/integration.py index 130fbbb7..9cf3266c 100644 --- a/tests/testcases/integration.py +++ b/tests/testcases/integration.py @@ -32,7 +32,8 @@ import shutil gi.require_version('Gtk', '3.0') from gi.repository import Gtk -from keymapper.mapping import mapping +from keymapper.mapping import custom_mapping +from keymapper.paths import USERS_SYMBOLS, HOME_PATH, KEYCODES_PATH from test import tmp @@ -94,7 +95,7 @@ class Integration(unittest.TestCase): rows = len(self.window.get('key_list').get_children()) self.assertEqual(rows, 1) - mapping.change(None, 13, 'a') + custom_mapping.change(None, 13, 'a') time.sleep(0.2) gtk_iteration() @@ -102,18 +103,18 @@ class Integration(unittest.TestCase): self.assertEqual(rows, 2) def test_rename_and_save(self): - mapping.change(None, 14, 'a') + custom_mapping.change(None, 14, 'a') self.assertEqual(self.window.selected_preset, 'new preset') self.window.on_save_preset_clicked(None) - self.assertEqual(mapping.get(14), 'a') + self.assertEqual(custom_mapping.get(14), 'a') - mapping.change(None, 14, 'b') + custom_mapping.change(None, 14, 'b') self.window.get('preset_name_input').set_text('asdf') self.window.on_save_preset_clicked(None) self.assertEqual(self.window.selected_preset, 'asdf') - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/asdf')) - self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/asdf')) - self.assertEqual(mapping.get(14), 'b') + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/asdf')) + self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/asdf')) + self.assertEqual(custom_mapping.get(14), 'b') def test_select_device_and_preset(self): class FakeDropdown(Gtk.ComboBoxText): @@ -125,15 +126,15 @@ class Integration(unittest.TestCase): # created on start because the first device is selected and some empty # preset prepared. - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/new_preset')) + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/new_preset')) self.assertEqual(self.window.selected_device, 'device 1') self.assertEqual(self.window.selected_preset, 'new preset') # create another one self.window.on_create_preset_clicked(None) gtk_iteration() - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/new_preset')) - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/new_preset_2')) + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/new_preset')) + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/new_preset_2')) self.assertEqual(self.window.selected_preset, 'new preset 2') self.window.on_select_preset(FakeDropdown('new preset')) @@ -141,7 +142,7 @@ class Integration(unittest.TestCase): self.assertEqual(self.window.selected_preset, 'new preset') self.assertListEqual( - sorted(os.listdir(f'{tmp}/symbols/device_1')), + sorted(os.listdir(f'{USERS_SYMBOLS}/device_1')), ['new_preset', 'new_preset_2'] ) @@ -149,20 +150,20 @@ class Integration(unittest.TestCase): self.window.get('preset_name_input').set_text('abc 123') gtk_iteration() self.assertEqual(self.window.selected_preset, 'new preset') - self.assertFalse(os.path.exists(f'{tmp}/symbols/device_1/abc_123')) - mapping.change(None, 10, '1') + self.assertFalse(os.path.exists(f'{USERS_SYMBOLS}/device_1/abc_123')) + custom_mapping.change(None, 10, '1') self.window.on_save_preset_clicked(None) - self.assertTrue(os.path.exists(f'{tmp}/keycodes/key-mapper')) + self.assertTrue(os.path.exists(KEYCODES_PATH)) gtk_iteration() self.assertEqual(self.window.selected_preset, 'abc 123') - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/abc_123')) + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/abc_123')) self.assertListEqual( - sorted(os.listdir(f'{tmp}/symbols')), - ['device_1'] + sorted(os.listdir(USERS_SYMBOLS)), + ['default', 'device_1'] ) self.assertListEqual( - sorted(os.listdir(f'{tmp}/symbols/device_1')), + sorted(os.listdir(f'{USERS_SYMBOLS}/device_1')), ['abc_123', 'new_preset_2'] ) diff --git a/tests/testcases/presets.py b/tests/testcases/presets.py index de35d9bd..aaabf323 100644 --- a/tests/testcases/presets.py +++ b/tests/testcases/presets.py @@ -26,6 +26,7 @@ import time from keymapper.presets import find_newest_preset, rename_preset from keymapper.X import create_preset +from keymapper.paths import USERS_SYMBOLS, HOME_PATH from test import tmp @@ -37,24 +38,24 @@ class TestCreatePreset(unittest.TestCase): def test_create_preset_1(self): create_preset('device 1') - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/new_preset')) - self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/new_preset')) + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/new_preset')) + self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/new_preset')) def test_create_preset_2(self): create_preset('device 1') create_preset('device 1') - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/new_preset')) - self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/new_preset')) - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/new_preset_2')) - self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/new_preset_2')) + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/new_preset')) + self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/new_preset')) + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/new_preset_2')) + self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/new_preset_2')) def test_create_preset_3(self): create_preset('device 1', 'pre set') create_preset('device 1', 'pre set') - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/pre_set')) - self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/pre_set')) - self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/pre_set_2')) - self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/pre_set_2')) + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/pre_set')) + self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/pre_set')) + self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/pre_set_2')) + self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/pre_set_2')) class TestRenamePreset(unittest.TestCase): @@ -62,9 +63,9 @@ class TestRenamePreset(unittest.TestCase): create_preset('device 1', 'preset 1') create_preset('device 1', 'foobar') rename_preset('device 1', 'preset 1', 'foobar') - self.assertFalse(os.path.exists(f'{tmp}/.config/device_1/preset_1')) - self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/foobar')) - self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/foobar_2')) + self.assertFalse(os.path.exists(f'{HOME_PATH}/device_1/preset_1')) + self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/foobar')) + self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/foobar_2')) class TestFindPresets(unittest.TestCase): @@ -79,17 +80,17 @@ class TestFindPresets(unittest.TestCase): self.assertEqual(find_newest_preset(), ('device 2', 'preset 2')) def test_find_newest_preset_2(self): - os.makedirs(f'{tmp}/symbols/device_1') - os.makedirs(f'{tmp}/.config/device_1') + os.makedirs(f'{USERS_SYMBOLS}/device_1') + os.makedirs(f'{HOME_PATH}/device_1') time.sleep(0.01) - os.makedirs(f'{tmp}/symbols/device_2') - os.makedirs(f'{tmp}/.config/device_2') + os.makedirs(f'{USERS_SYMBOLS}/device_2') + os.makedirs(f'{HOME_PATH}/device_2') # takes the first one that the test-fake returns self.assertEqual(find_newest_preset(), ('device 1', None)) def test_find_newest_preset_3(self): - os.makedirs(f'{tmp}/symbols/device_1') - os.makedirs(f'{tmp}/.config/device_1') + os.makedirs(f'{USERS_SYMBOLS}/device_1') + os.makedirs(f'{HOME_PATH}/device_1') self.assertEqual(find_newest_preset(), ('device 1', None)) def test_find_newest_preset_4(self): diff --git a/tests/testcases/test.py b/tests/testcases/test.py index f4209477..a74e6676 100644 --- a/tests/testcases/test.py +++ b/tests/testcases/test.py @@ -22,12 +22,17 @@ import unittest from keymapper.linux import get_devices +from keymapper.paths import USERS_SYMBOLS, X11_SYMBOLS, DEFAULT_SYMBOLS class TestTest(unittest.TestCase): def test_stubs(self): self.assertIn('device 1', get_devices()) + def test_paths(self): + self.assertTrue(USERS_SYMBOLS.startswith(X11_SYMBOLS)) + self.assertTrue(DEFAULT_SYMBOLS.startswith(X11_SYMBOLS)) + if __name__ == "__main__": unittest.main()