renaming, keeping unmapped keys at their defaults

This commit is contained in:
sezanzeb 2020-11-10 23:14:28 +01:00
parent 96fe81a3cd
commit 52231d507e
14 changed files with 231 additions and 105 deletions

29
X11PATHS.md Normal file
View File

@ -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/<user>/<device>/<preset>
- /home/<user>/.config/key-mapper/<device>/<preset>
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/<user>/default
- /home/<user>/.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

View File

@ -481,10 +481,22 @@ LCTL, RCTL</property>
</packing>
</child>
<child>
<object class="GtkListBox" id="key_list">
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="selection_mode">none</property>
<property name="can_focus">True</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkListBox" id="key_list">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="selection_mode">none</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>

View File

@ -4,6 +4,7 @@
// /usr/share/X11/xkb/keycodes/key-mapper
default xkb_symbols "basic" {{
{include}
name[Group1] = "{name}";
{xkb_symbols}
}};

View File

@ -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 "<int>", whereas the others are <AB01> 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)

View File

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

View File

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

View File

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

View File

@ -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(' ', '_'))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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