mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-18 03:25:52 +00:00
renaming, keeping unmapped keys at their defaults
This commit is contained in:
parent
96fe81a3cd
commit
52231d507e
29
X11PATHS.md
Normal file
29
X11PATHS.md
Normal 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
|
@ -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>
|
||||
|
@ -4,6 +4,7 @@
|
||||
// /usr/share/X11/xkb/keycodes/key-mapper
|
||||
|
||||
default xkb_symbols "basic" {{
|
||||
{include}
|
||||
name[Group1] = "{name}";
|
||||
{xkb_symbols}
|
||||
}};
|
||||
|
123
keymapper/X.py
123
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 "<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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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(' ', '_'))
|
||||
|
@ -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):
|
||||
|
@ -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 = {
|
||||
|
@ -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)
|
||||
|
@ -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']
|
||||
)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user