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>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkListBox" id="key_list">
|
<object class="GtkScrolledWindow">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="selection_mode">none</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>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
// /usr/share/X11/xkb/keycodes/key-mapper
|
// /usr/share/X11/xkb/keycodes/key-mapper
|
||||||
|
|
||||||
default xkb_symbols "basic" {{
|
default xkb_symbols "basic" {{
|
||||||
|
{include}
|
||||||
name[Group1] = "{name}";
|
name[Group1] = "{name}";
|
||||||
{xkb_symbols}
|
{xkb_symbols}
|
||||||
}};
|
}};
|
||||||
|
123
keymapper/X.py
123
keymapper/X.py
@ -38,11 +38,11 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from keymapper.paths import get_home_path, get_usr_path, KEYCODES_PATH, \
|
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.logger import logger
|
||||||
from keymapper.data import get_data_path
|
from keymapper.data import get_data_path
|
||||||
from keymapper.linux import get_devices
|
from keymapper.linux import get_devices
|
||||||
from keymapper.mapping import mapping
|
from keymapper.mapping import custom_mapping, Mapping
|
||||||
|
|
||||||
|
|
||||||
def ensure_symlink():
|
def ensure_symlink():
|
||||||
@ -50,12 +50,19 @@ def ensure_symlink():
|
|||||||
|
|
||||||
It provides the configs in /home to X11 in /usr.
|
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
|
# link from /usr/share/X11/xkb/symbols/key-mapper/user to
|
||||||
# /home/user/.config/key-mapper
|
# /home/user/.config/key-mapper
|
||||||
logger.info('Linking "%s" to "%s"', SYMBOLS_PATH, CONFIG_PATH)
|
logger.info('Linking "%s" to "%s"', USERS_SYMBOLS, HOME_PATH)
|
||||||
os.makedirs(os.path.dirname(SYMBOLS_PATH), exist_ok=True)
|
os.makedirs(os.path.dirname(USERS_SYMBOLS), exist_ok=True)
|
||||||
os.symlink(CONFIG_PATH, SYMBOLS_PATH, target_is_directory=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):
|
def create_preset(device, name=None):
|
||||||
@ -78,7 +85,7 @@ def create_preset(device, name=None):
|
|||||||
|
|
||||||
# give those files to the user
|
# give those files to the user
|
||||||
user = os.getlogin()
|
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)
|
shutil.chown(root, user, user)
|
||||||
for file in files:
|
for file in files:
|
||||||
shutil.chown(os.path.join(root, file), user, user)
|
shutil.chown(os.path.join(root, file), user, user)
|
||||||
@ -102,11 +109,12 @@ def create_setxkbmap_config(device, preset):
|
|||||||
device : string
|
device : string
|
||||||
preset : string
|
preset : string
|
||||||
"""
|
"""
|
||||||
if len(mapping) == 0:
|
if len(custom_mapping) == 0:
|
||||||
logger.debug('Got empty mappings')
|
logger.debug('Got empty mappings')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
create_identity_mapping()
|
create_identity_mapping()
|
||||||
|
create_default_symbols()
|
||||||
|
|
||||||
home_device_path = get_home_path(device)
|
home_device_path = get_home_path(device)
|
||||||
if not os.path.exists(home_device_path):
|
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)
|
logger.info('Creating config file "%s"', home_preset_path)
|
||||||
os.mknod(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:
|
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:
|
if contents is not None:
|
||||||
f.write(contents)
|
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):
|
def apply_preset(device, preset):
|
||||||
"""Apply a preset to the device."""
|
"""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]
|
group = get_devices()[device]
|
||||||
|
|
||||||
# apply it to every device that hangs on the same usb port, because I
|
# 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
|
# only all virtual devices of the same hardware device
|
||||||
continue
|
continue
|
||||||
|
|
||||||
symbols = '/usr/share/X11/xkb/symbols/'
|
|
||||||
layout_path = get_usr_path(device, preset)
|
layout_path = get_usr_path(device, preset)
|
||||||
with open(layout_path, 'r') as f:
|
with open(layout_path, 'r') as f:
|
||||||
if f.read() == '':
|
if f.read() == '':
|
||||||
logger.error('Tried to load empty config')
|
logger.error('Tried to load empty config')
|
||||||
return
|
return
|
||||||
|
|
||||||
layout_name = layout_path[len(symbols):]
|
|
||||||
cmd = [
|
cmd = [
|
||||||
'setxkbmap',
|
'setxkbmap',
|
||||||
'-layout', layout_name,
|
'-layout', get_preset_name(device, preset),
|
||||||
'-keycodes', 'key-mapper',
|
'-keycodes', 'key-mapper',
|
||||||
'-device', str(xinput_id)
|
'-device', str(xinput_id)
|
||||||
]
|
]
|
||||||
@ -166,8 +184,9 @@ def create_identity_mapping():
|
|||||||
This has the added benefit that keycodes reported by xev can be
|
This has the added benefit that keycodes reported by xev can be
|
||||||
identified in the symbols file.
|
identified in the symbols file.
|
||||||
"""
|
"""
|
||||||
# TODO don't create this again if it already exists, as soon as this
|
if os.path.exists(KEYCODES_PATH):
|
||||||
# stuff is stable.
|
logger.debug('Found the keycodes file at %s', KEYCODES_PATH)
|
||||||
|
return
|
||||||
|
|
||||||
xkb_keycodes = []
|
xkb_keycodes = []
|
||||||
# the maximum specified in /usr/share/X11/xkb/keycodes is usually 255
|
# the maximum specified in /usr/share/X11/xkb/keycodes is usually 255
|
||||||
@ -195,15 +214,23 @@ def create_identity_mapping():
|
|||||||
keycodes.write(result)
|
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.
|
"""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
|
Parameters
|
||||||
----------
|
----------
|
||||||
device : string
|
name : string
|
||||||
preset : 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:
|
if len(mapping) == 0:
|
||||||
raise ValueError('Mapping is empty')
|
raise ValueError('Mapping is empty')
|
||||||
@ -212,6 +239,7 @@ def generate_symbols_content(device, preset):
|
|||||||
# the keycodes file, THE WHOLE X SESSION WILL CRASH!
|
# the keycodes file, THE WHOLE X SESSION WILL CRASH!
|
||||||
if not os.path.exists(KEYCODES_PATH):
|
if not os.path.exists(KEYCODES_PATH):
|
||||||
raise FileNotFoundError('Expected the keycodes file to exist')
|
raise FileNotFoundError('Expected the keycodes file to exist')
|
||||||
|
|
||||||
with open(KEYCODES_PATH, 'r') as f:
|
with open(KEYCODES_PATH, 'r') as f:
|
||||||
keycodes = re.findall(r'<.+?>', f.read())
|
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
|
# don't append that one, otherwise X would crash when loading
|
||||||
continue
|
continue
|
||||||
xkb_symbols.append(f'key <{keycode}> {{ [ {character} ] }};')
|
xkb_symbols.append(f'key <{keycode}> {{ [ {character} ] }};')
|
||||||
|
|
||||||
if len(xkb_symbols) == 0:
|
if len(xkb_symbols) == 0:
|
||||||
logger.error('Failed to populate xkb_symbols')
|
logger.error('Failed to populate xkb_symbols')
|
||||||
return None
|
return None
|
||||||
@ -231,8 +260,9 @@ def generate_symbols_content(device, preset):
|
|||||||
template = template_file.read()
|
template = template_file.read()
|
||||||
|
|
||||||
result = template.format(
|
result = template.format(
|
||||||
name=f'{device}/{preset}',
|
name=name,
|
||||||
xkb_symbols='\n '.join(xkb_symbols)
|
xkb_symbols='\n '.join(xkb_symbols),
|
||||||
|
include=f'include "{include}"' if include else ''
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -257,7 +287,10 @@ def get_xinput_id_mapping():
|
|||||||
|
|
||||||
|
|
||||||
def parse_symbols_file(device, preset):
|
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)
|
path = get_home_path(device, preset)
|
||||||
|
|
||||||
if not os.path.exists(path):
|
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',
|
'Tried to load non existing preset "%s" for %s',
|
||||||
preset, device
|
preset, device
|
||||||
)
|
)
|
||||||
mapping.empty()
|
custom_mapping.empty()
|
||||||
mapping.changed = False
|
custom_mapping.changed = False
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(path, 'r') as f:
|
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())
|
result = re.findall(r'\n\s+?key <(.+?)>.+?\[\s+(\w+)', f.read())
|
||||||
logger.debug('Found %d mappings in this preset', len(result))
|
logger.debug('Found %d mappings in this preset', len(result))
|
||||||
for keycode, character in result:
|
for keycode, character in result:
|
||||||
mapping.changed = False
|
custom_mapping.changed = False
|
||||||
mapping.change(None, int(keycode), character)
|
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')
|
gi.require_version('GLib', '2.0')
|
||||||
from gi.repository import Gtk, GLib
|
from gi.repository import Gtk, GLib
|
||||||
|
|
||||||
from keymapper.mapping import mapping
|
from keymapper.mapping import custom_mapping
|
||||||
from keymapper.logger import logger
|
from keymapper.logger import logger
|
||||||
from keymapper.linux import keycode_reader
|
from keymapper.linux import keycode_reader
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ class Row:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# keycode is already set by some other row
|
# 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'
|
msg = f'Keycode {new_keycode} is already mapped'
|
||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
self.window.get('status_bar').push(CTX_KEYCODE, msg)
|
self.window.get('status_bar').push(CTX_KEYCODE, msg)
|
||||||
@ -102,14 +102,14 @@ class Row:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# else, the keycode has changed, the character is set, all good
|
# 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):
|
def on_character_input_change(self, entry):
|
||||||
keycode = self.get_keycode()
|
keycode = self.get_keycode()
|
||||||
character = self.get_character()
|
character = self.get_character()
|
||||||
|
|
||||||
if keycode is not None:
|
if keycode is not None:
|
||||||
mapping.change(None, keycode, character)
|
custom_mapping.change(None, keycode, character)
|
||||||
|
|
||||||
def put_together(self, keycode, character):
|
def put_together(self, keycode, character):
|
||||||
"""Create all GTK widgets."""
|
"""Create all GTK widgets."""
|
||||||
@ -169,5 +169,5 @@ class Row:
|
|||||||
"""Destroy the row and remove it from the config."""
|
"""Destroy the row and remove it from the config."""
|
||||||
keycode = self.get_keycode()
|
keycode = self.get_keycode()
|
||||||
if keycode is not None:
|
if keycode is not None:
|
||||||
mapping.clear(keycode)
|
custom_mapping.clear(keycode)
|
||||||
self.delete_callback(self)
|
self.delete_callback(self)
|
||||||
|
@ -29,7 +29,7 @@ from gi.repository import Gtk, Gdk, GLib
|
|||||||
|
|
||||||
from keymapper.data import get_data_path
|
from keymapper.data import get_data_path
|
||||||
from keymapper.X import create_setxkbmap_config, apply_preset, \
|
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, \
|
from keymapper.presets import get_presets, find_newest_preset, \
|
||||||
delete_preset, rename_preset
|
delete_preset, rename_preset
|
||||||
from keymapper.logger import logger
|
from keymapper.logger import logger
|
||||||
@ -94,9 +94,9 @@ class Window:
|
|||||||
rows = len(self.get('key_list').get_children())
|
rows = len(self.get('key_list').get_children())
|
||||||
|
|
||||||
# verify that all mappings are displayed
|
# 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()
|
self.add_empty()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -141,7 +141,7 @@ class Window:
|
|||||||
"""Remove all rows from the mappings table."""
|
"""Remove all rows from the mappings table."""
|
||||||
key_list = self.get('key_list')
|
key_list = self.get('key_list')
|
||||||
key_list.forall(key_list.remove)
|
key_list.forall(key_list.remove)
|
||||||
mapping.empty()
|
custom_mapping.empty()
|
||||||
|
|
||||||
def on_save_preset_clicked(self, button):
|
def on_save_preset_clicked(self, button):
|
||||||
"""Save changes to a preset to the file system."""
|
"""Save changes to a preset to the file system."""
|
||||||
@ -219,7 +219,7 @@ class Window:
|
|||||||
parse_symbols_file(self.selected_device, self.selected_preset)
|
parse_symbols_file(self.selected_device, self.selected_preset)
|
||||||
|
|
||||||
key_list = self.get('key_list')
|
key_list = self.get('key_list')
|
||||||
for keycode, character in mapping:
|
for keycode, character in custom_mapping:
|
||||||
single_key_mapping = Row(
|
single_key_mapping = Row(
|
||||||
window=self,
|
window=self,
|
||||||
delete_callback=self.on_row_removed,
|
delete_callback=self.on_row_removed,
|
||||||
|
@ -104,5 +104,6 @@ class Mapping:
|
|||||||
return self._mapping.get(keycode)
|
return self._mapping.get(keycode)
|
||||||
|
|
||||||
|
|
||||||
# one mapping object for the whole application
|
# one mapping object for the whole application that holds all
|
||||||
mapping = Mapping()
|
# customizations
|
||||||
|
custom_mapping = Mapping()
|
||||||
|
@ -25,12 +25,15 @@
|
|||||||
import os
|
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
|
# 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
|
# should not contain spaces
|
||||||
SYMBOLS_PATH = os.path.join(
|
USERS_SYMBOLS = os.path.join(
|
||||||
'/usr/share/X11/xkb/symbols/key-mapper',
|
'/usr/share/X11/xkb/symbols/key-mapper',
|
||||||
os.getlogin().replace(' ', '_')
|
os.getlogin().replace(' ', '_')
|
||||||
)
|
)
|
||||||
@ -44,9 +47,12 @@ def get_home_path(device, preset=None):
|
|||||||
device = device.strip()
|
device = device.strip()
|
||||||
if preset is not None:
|
if preset is not None:
|
||||||
preset = preset.strip()
|
preset = preset.strip()
|
||||||
return os.path.join(CONFIG_PATH, device, preset).replace(' ', '_')
|
return os.path.join(HOME_PATH, device, preset).replace(' ', '_')
|
||||||
else:
|
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):
|
def get_usr_path(device, preset=None):
|
||||||
@ -59,6 +65,6 @@ def get_usr_path(device, preset=None):
|
|||||||
device = device.strip()
|
device = device.strip()
|
||||||
if preset is not None:
|
if preset is not None:
|
||||||
preset = preset.strip()
|
preset = preset.strip()
|
||||||
return os.path.join(SYMBOLS_PATH, device, preset).replace(' ', '_')
|
return os.path.join(USERS_SYMBOLS, device, preset).replace(' ', '_')
|
||||||
else:
|
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 time
|
||||||
import glob
|
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.logger import logger
|
||||||
from keymapper.linux import get_devices
|
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
|
# sort the oldest files to the front in order to use pop to get the newest
|
||||||
if device is None:
|
if device is None:
|
||||||
paths = sorted(
|
paths = sorted(
|
||||||
glob.glob(os.path.join(CONFIG_PATH, '*/*')),
|
glob.glob(os.path.join(HOME_PATH, '*/*')),
|
||||||
key=os.path.getmtime
|
key=os.path.getmtime
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -132,7 +132,7 @@ def delete_preset(device, preset):
|
|||||||
device_path = get_home_path(device)
|
device_path = get_home_path(device)
|
||||||
if os.path.exists(device_path) and len(os.listdir(device_path)) == 0:
|
if os.path.exists(device_path) and len(os.listdir(device_path)) == 0:
|
||||||
logger.debug('Removing empty dir "%s"', device_path)
|
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):
|
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
|
# quickly fake some stuff before any other file gets a chance to import
|
||||||
# the original version
|
# the original version
|
||||||
from keymapper import paths
|
from keymapper import paths
|
||||||
paths.SYMBOLS_PATH = '/tmp/key-mapper-test/symbols'
|
paths.X11_SYMBOLS = '/tmp/key-mapper-test/X11/symbols/key-mapper/user'
|
||||||
paths.CONFIG_PATH = '/tmp/key-mapper-test/.config'
|
paths.USERS_SYMBOLS = '/tmp/key-mapper-test/X11/symbols/key-mapper/user'
|
||||||
paths.KEYCODES_PATH = '/tmp/key-mapper-test/keycodes/key-mapper'
|
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
|
from keymapper import linux
|
||||||
linux._devices = {
|
linux._devices = {
|
||||||
|
@ -23,19 +23,19 @@ import os
|
|||||||
import unittest
|
import unittest
|
||||||
import shutil
|
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
|
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
|
from test import tmp
|
||||||
|
|
||||||
|
|
||||||
class TestConfig(unittest.TestCase):
|
class TestConfig(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
mapping.empty()
|
custom_mapping.empty()
|
||||||
mapping.change(None, 10, 'a')
|
custom_mapping.change(None, 10, 'a')
|
||||||
mapping.change(None, 11, 'KP_1')
|
custom_mapping.change(None, 11, 'KP_1')
|
||||||
mapping.change(None, 12, 3)
|
custom_mapping.change(None, 12, 3)
|
||||||
if os.path.exists(tmp):
|
if os.path.exists(tmp):
|
||||||
shutil.rmtree(tmp)
|
shutil.rmtree(tmp)
|
||||||
|
|
||||||
@ -43,13 +43,13 @@ class TestConfig(unittest.TestCase):
|
|||||||
create_setxkbmap_config('device a', 'preset b')
|
create_setxkbmap_config('device a', 'preset b')
|
||||||
|
|
||||||
self.assertTrue(os.path.exists(os.path.join(
|
self.assertTrue(os.path.exists(os.path.join(
|
||||||
CONFIG_PATH,
|
HOME_PATH,
|
||||||
'device_a',
|
'device_a',
|
||||||
'preset_b'
|
'preset_b'
|
||||||
)))
|
)))
|
||||||
|
|
||||||
self.assertTrue(os.path.exists(os.path.join(
|
self.assertTrue(os.path.exists(os.path.join(
|
||||||
SYMBOLS_PATH,
|
USERS_SYMBOLS,
|
||||||
'device_a',
|
'device_a',
|
||||||
'preset_b'
|
'preset_b'
|
||||||
)))
|
)))
|
||||||
@ -65,12 +65,12 @@ class TestConfig(unittest.TestCase):
|
|||||||
def test_generate_content(self):
|
def test_generate_content(self):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
FileNotFoundError,
|
FileNotFoundError,
|
||||||
generate_symbols_content,
|
generate_symbols,
|
||||||
'device', 'preset'
|
'device', 'preset'
|
||||||
)
|
)
|
||||||
|
|
||||||
# create the identity mapping, because it is required for
|
# create the identity mapping, because it is required for
|
||||||
# generate_symbols_content
|
# generate_symbols
|
||||||
create_identity_mapping()
|
create_identity_mapping()
|
||||||
self.assertTrue(os.path.exists(KEYCODES_PATH))
|
self.assertTrue(os.path.exists(KEYCODES_PATH))
|
||||||
with open(KEYCODES_PATH, 'r') as f:
|
with open(KEYCODES_PATH, 'r') as f:
|
||||||
@ -78,7 +78,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
self.assertIn('<8> = 8;', keycodes)
|
self.assertIn('<8> = 8;', keycodes)
|
||||||
self.assertIn('<255> = 255;', 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 <10> { [ a ] };', content)
|
||||||
self.assertIn('key <11> { [ KP_1 ] };', content)
|
self.assertIn('key <11> { [ KP_1 ] };', content)
|
||||||
self.assertIn('key <12> { [ 3 ] };', content)
|
self.assertIn('key <12> { [ 3 ] };', content)
|
||||||
|
@ -32,7 +32,8 @@ import shutil
|
|||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk
|
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
|
from test import tmp
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ class Integration(unittest.TestCase):
|
|||||||
rows = len(self.window.get('key_list').get_children())
|
rows = len(self.window.get('key_list').get_children())
|
||||||
self.assertEqual(rows, 1)
|
self.assertEqual(rows, 1)
|
||||||
|
|
||||||
mapping.change(None, 13, 'a')
|
custom_mapping.change(None, 13, 'a')
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
gtk_iteration()
|
gtk_iteration()
|
||||||
|
|
||||||
@ -102,18 +103,18 @@ class Integration(unittest.TestCase):
|
|||||||
self.assertEqual(rows, 2)
|
self.assertEqual(rows, 2)
|
||||||
|
|
||||||
def test_rename_and_save(self):
|
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.assertEqual(self.window.selected_preset, 'new preset')
|
||||||
self.window.on_save_preset_clicked(None)
|
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.get('preset_name_input').set_text('asdf')
|
||||||
self.window.on_save_preset_clicked(None)
|
self.window.on_save_preset_clicked(None)
|
||||||
self.assertEqual(self.window.selected_preset, 'asdf')
|
self.assertEqual(self.window.selected_preset, 'asdf')
|
||||||
self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/asdf'))
|
self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/asdf'))
|
||||||
self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/asdf'))
|
self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/asdf'))
|
||||||
self.assertEqual(mapping.get(14), 'b')
|
self.assertEqual(custom_mapping.get(14), 'b')
|
||||||
|
|
||||||
def test_select_device_and_preset(self):
|
def test_select_device_and_preset(self):
|
||||||
class FakeDropdown(Gtk.ComboBoxText):
|
class FakeDropdown(Gtk.ComboBoxText):
|
||||||
@ -125,15 +126,15 @@ class Integration(unittest.TestCase):
|
|||||||
|
|
||||||
# created on start because the first device is selected and some empty
|
# created on start because the first device is selected and some empty
|
||||||
# preset prepared.
|
# 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_device, 'device 1')
|
||||||
self.assertEqual(self.window.selected_preset, 'new preset')
|
self.assertEqual(self.window.selected_preset, 'new preset')
|
||||||
|
|
||||||
# create another one
|
# create another one
|
||||||
self.window.on_create_preset_clicked(None)
|
self.window.on_create_preset_clicked(None)
|
||||||
gtk_iteration()
|
gtk_iteration()
|
||||||
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.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/new_preset_2'))
|
self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/new_preset_2'))
|
||||||
self.assertEqual(self.window.selected_preset, 'new preset 2')
|
self.assertEqual(self.window.selected_preset, 'new preset 2')
|
||||||
|
|
||||||
self.window.on_select_preset(FakeDropdown('new preset'))
|
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.assertEqual(self.window.selected_preset, 'new preset')
|
||||||
|
|
||||||
self.assertListEqual(
|
self.assertListEqual(
|
||||||
sorted(os.listdir(f'{tmp}/symbols/device_1')),
|
sorted(os.listdir(f'{USERS_SYMBOLS}/device_1')),
|
||||||
['new_preset', 'new_preset_2']
|
['new_preset', 'new_preset_2']
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -149,20 +150,20 @@ class Integration(unittest.TestCase):
|
|||||||
self.window.get('preset_name_input').set_text('abc 123')
|
self.window.get('preset_name_input').set_text('abc 123')
|
||||||
gtk_iteration()
|
gtk_iteration()
|
||||||
self.assertEqual(self.window.selected_preset, 'new preset')
|
self.assertEqual(self.window.selected_preset, 'new preset')
|
||||||
self.assertFalse(os.path.exists(f'{tmp}/symbols/device_1/abc_123'))
|
self.assertFalse(os.path.exists(f'{USERS_SYMBOLS}/device_1/abc_123'))
|
||||||
mapping.change(None, 10, '1')
|
custom_mapping.change(None, 10, '1')
|
||||||
self.window.on_save_preset_clicked(None)
|
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()
|
gtk_iteration()
|
||||||
self.assertEqual(self.window.selected_preset, 'abc 123')
|
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(
|
self.assertListEqual(
|
||||||
sorted(os.listdir(f'{tmp}/symbols')),
|
sorted(os.listdir(USERS_SYMBOLS)),
|
||||||
['device_1']
|
['default', 'device_1']
|
||||||
)
|
)
|
||||||
self.assertListEqual(
|
self.assertListEqual(
|
||||||
sorted(os.listdir(f'{tmp}/symbols/device_1')),
|
sorted(os.listdir(f'{USERS_SYMBOLS}/device_1')),
|
||||||
['abc_123', 'new_preset_2']
|
['abc_123', 'new_preset_2']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import time
|
|||||||
|
|
||||||
from keymapper.presets import find_newest_preset, rename_preset
|
from keymapper.presets import find_newest_preset, rename_preset
|
||||||
from keymapper.X import create_preset
|
from keymapper.X import create_preset
|
||||||
|
from keymapper.paths import USERS_SYMBOLS, HOME_PATH
|
||||||
|
|
||||||
from test import tmp
|
from test import tmp
|
||||||
|
|
||||||
@ -37,24 +38,24 @@ class TestCreatePreset(unittest.TestCase):
|
|||||||
|
|
||||||
def test_create_preset_1(self):
|
def test_create_preset_1(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'{USERS_SYMBOLS}/device_1/new_preset'))
|
||||||
self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/new_preset'))
|
self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/new_preset'))
|
||||||
|
|
||||||
def test_create_preset_2(self):
|
def test_create_preset_2(self):
|
||||||
create_preset('device 1')
|
create_preset('device 1')
|
||||||
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'{USERS_SYMBOLS}/device_1/new_preset'))
|
||||||
self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/new_preset'))
|
self.assertTrue(os.path.exists(f'{HOME_PATH}/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_2'))
|
||||||
self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/new_preset_2'))
|
self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/new_preset_2'))
|
||||||
|
|
||||||
def test_create_preset_3(self):
|
def test_create_preset_3(self):
|
||||||
create_preset('device 1', 'pre set')
|
create_preset('device 1', 'pre set')
|
||||||
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'{USERS_SYMBOLS}/device_1/pre_set'))
|
||||||
self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/pre_set'))
|
self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/pre_set'))
|
||||||
self.assertTrue(os.path.exists(f'{tmp}/symbols/device_1/pre_set_2'))
|
self.assertTrue(os.path.exists(f'{USERS_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'{HOME_PATH}/device_1/pre_set_2'))
|
||||||
|
|
||||||
|
|
||||||
class TestRenamePreset(unittest.TestCase):
|
class TestRenamePreset(unittest.TestCase):
|
||||||
@ -62,9 +63,9 @@ class TestRenamePreset(unittest.TestCase):
|
|||||||
create_preset('device 1', 'preset 1')
|
create_preset('device 1', 'preset 1')
|
||||||
create_preset('device 1', 'foobar')
|
create_preset('device 1', 'foobar')
|
||||||
rename_preset('device 1', 'preset 1', 'foobar')
|
rename_preset('device 1', 'preset 1', 'foobar')
|
||||||
self.assertFalse(os.path.exists(f'{tmp}/.config/device_1/preset_1'))
|
self.assertFalse(os.path.exists(f'{HOME_PATH}/device_1/preset_1'))
|
||||||
self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/foobar'))
|
self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/foobar'))
|
||||||
self.assertTrue(os.path.exists(f'{tmp}/.config/device_1/foobar_2'))
|
self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/foobar_2'))
|
||||||
|
|
||||||
|
|
||||||
class TestFindPresets(unittest.TestCase):
|
class TestFindPresets(unittest.TestCase):
|
||||||
@ -79,17 +80,17 @@ class TestFindPresets(unittest.TestCase):
|
|||||||
self.assertEqual(find_newest_preset(), ('device 2', 'preset 2'))
|
self.assertEqual(find_newest_preset(), ('device 2', 'preset 2'))
|
||||||
|
|
||||||
def test_find_newest_preset_2(self):
|
def test_find_newest_preset_2(self):
|
||||||
os.makedirs(f'{tmp}/symbols/device_1')
|
os.makedirs(f'{USERS_SYMBOLS}/device_1')
|
||||||
os.makedirs(f'{tmp}/.config/device_1')
|
os.makedirs(f'{HOME_PATH}/device_1')
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
os.makedirs(f'{tmp}/symbols/device_2')
|
os.makedirs(f'{USERS_SYMBOLS}/device_2')
|
||||||
os.makedirs(f'{tmp}/.config/device_2')
|
os.makedirs(f'{HOME_PATH}/device_2')
|
||||||
# takes the first one that the test-fake returns
|
# takes the first one that the test-fake returns
|
||||||
self.assertEqual(find_newest_preset(), ('device 1', None))
|
self.assertEqual(find_newest_preset(), ('device 1', None))
|
||||||
|
|
||||||
def test_find_newest_preset_3(self):
|
def test_find_newest_preset_3(self):
|
||||||
os.makedirs(f'{tmp}/symbols/device_1')
|
os.makedirs(f'{USERS_SYMBOLS}/device_1')
|
||||||
os.makedirs(f'{tmp}/.config/device_1')
|
os.makedirs(f'{HOME_PATH}/device_1')
|
||||||
self.assertEqual(find_newest_preset(), ('device 1', None))
|
self.assertEqual(find_newest_preset(), ('device 1', None))
|
||||||
|
|
||||||
def test_find_newest_preset_4(self):
|
def test_find_newest_preset_4(self):
|
||||||
|
@ -22,12 +22,17 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from keymapper.linux import get_devices
|
from keymapper.linux import get_devices
|
||||||
|
from keymapper.paths import USERS_SYMBOLS, X11_SYMBOLS, DEFAULT_SYMBOLS
|
||||||
|
|
||||||
|
|
||||||
class TestTest(unittest.TestCase):
|
class TestTest(unittest.TestCase):
|
||||||
def test_stubs(self):
|
def test_stubs(self):
|
||||||
self.assertIn('device 1', get_devices())
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
Reference in New Issue
Block a user