got rid of this confusing symlink stuff

xkb
sezanzeb 4 years ago committed by sezanzeb
parent b68b958160
commit a381613641

@ -1,8 +1,6 @@
# Folder Structure of Key Mapper # Folder Structure of Key Mapper
Stuff has to be placed in `/usr/share/X11/xkb` to my knowledge. In order to Stuff has to be placed in `/usr/share/X11/xkb` to my knowledge.
be able to make backups of the configs, which would be expected in the
users home directory, this is symlinked to home where the actual files are.
Every user gets a path within that `/usr/...` directory which is very Every user gets a path within that `/usr/...` directory which is very
unconventional, but it works. This way the presets of multiple users unconventional, but it works. This way the presets of multiple users
@ -11,15 +9,12 @@ don't clash.
**Presets** **Presets**
- `/usr/share/X11/xkb/symbols/key-mapper/<user>/<device>/<preset>` - `/usr/share/X11/xkb/symbols/key-mapper/<user>/<device>/<preset>`
- `/home/<user>/.config/key-mapper/<device>/<preset>`
This is how a single preset is stored. The path in /usr is a symlink, the This is how a single preset is stored.
files are actually in home.
**Defaults** **Defaults**
- `/usr/share/X11/xkb/symbols/key-mapper/<user>/default` - `/usr/share/X11/xkb/symbols/key-mapper/<user>/default`
- `/home/<user>/.config/key-mapper/default`
This is where key-mapper stores the defaults. They are generated from the 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 parsed output of `xmodmap` and used to keep the unmapped keys at their system

@ -37,35 +37,14 @@ import re
import shutil import shutil
import subprocess import subprocess
from keymapper.paths import get_home_path, get_usr_path, KEYCODES_PATH, \ from keymapper.paths import get_usr_path, KEYCODES_PATH, \
HOME_PATH, USERS_SYMBOLS, DEFAULT_SYMBOLS, X11_SYMBOLS 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 custom_mapping, Mapping from keymapper.mapping import custom_mapping, Mapping
def ensure_symlink():
"""Make sure the symlink exists.
It provides the configs in /home to X11 in /usr.
"""
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"', 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:
# expected
logger.debug('Symlink %s exists', USERS_SYMBOLS)
def create_preset(device, name=None): def create_preset(device, name=None):
"""Create an empty preset and return the potentially incremented name. """Create an empty preset and return the potentially incremented name.
@ -76,26 +55,26 @@ def create_preset(device, name=None):
name = 'new preset' name = 'new preset'
# find a name that is not already taken # find a name that is not already taken
if os.path.exists(get_home_path(device, name)): if os.path.exists(get_usr_path(device, name)):
i = 2 i = 2
while os.path.exists(get_home_path(device, f'{name} {i}')): while os.path.exists(get_usr_path(device, f'{name} {i}')):
i += 1 i += 1
name = f'{name} {i}' name = f'{name} {i}'
path = get_home_path(device, name) path = get_usr_path(device, name)
if not os.path.exists(path): if not os.path.exists(path):
logger.info('Creating new file %s', path) logger.info('Creating new file %s', path)
os.makedirs(os.path.dirname(path), exist_ok=True) os.makedirs(os.path.dirname(path), exist_ok=True)
os.mknod(path) os.mknod(path)
# give those files to the user # give those files to the user
# TODO or should they stay root
user = os.getlogin() user = os.getlogin()
for root, dirs, files in os.walk(HOME_PATH): for root, dirs, files in os.walk(USERS_SYMBOLS):
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)
ensure_symlink()
return name return name
@ -121,20 +100,18 @@ def create_setxkbmap_config(device, preset):
create_identity_mapping() create_identity_mapping()
create_default_symbols() create_default_symbols()
home_device_path = get_home_path(device) device_path = get_usr_path(device)
if not os.path.exists(home_device_path): if not os.path.exists(device_path):
logger.info('Creating directory "%s"', home_device_path) logger.info('Creating directory "%s"', device_path)
os.makedirs(home_device_path, exist_ok=True) os.makedirs(device_path, exist_ok=True)
ensure_symlink()
home_preset_path = get_home_path(device, preset) preset_path = get_usr_path(device, preset)
if not os.path.exists(home_preset_path): if not os.path.exists(preset_path):
logger.info('Creating config file "%s"', home_preset_path) logger.info('Creating config file "%s"', preset_path)
os.mknod(home_preset_path) os.mknod(preset_path)
logger.info('Writing key mappings to %s', home_preset_path) logger.info('Writing key mappings to %s', preset_path)
with open(home_preset_path, 'w') as f: with open(preset_path, 'w') as f:
contents = generate_symbols(get_preset_name(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)
@ -336,7 +313,7 @@ def parse_symbols_file(device, preset):
Existing mappings are overwritten if there are conflicts. Existing mappings are overwritten if there are conflicts.
""" """
path = get_home_path(device, preset) path = get_usr_path(device, preset)
if not os.path.exists(path): if not os.path.exists(path):
logger.debug( logger.debug(
@ -379,12 +356,11 @@ def create_default_symbols():
# TODO support an array of values in mapping and test it # TODO support an array of values in mapping and test it
defaults.change(None, int(keycode), characters.split()[0]) defaults.change(None, int(keycode), characters.split()[0])
ensure_symlink()
contents = generate_symbols(DEFAULT_SYMBOLS_NAME, None, defaults) contents = generate_symbols(DEFAULT_SYMBOLS_NAME, None, defaults)
if not os.path.exists(DEFAULT_SYMBOLS): if not os.path.exists(DEFAULT_SYMBOLS):
logger.info('Creating %s', DEFAULT_SYMBOLS) logger.info('Creating %s', DEFAULT_SYMBOLS)
os.makedirs(os.path.dirname(DEFAULT_SYMBOLS), exist_ok=True)
os.mknod(DEFAULT_SYMBOLS) os.mknod(DEFAULT_SYMBOLS)
with open(DEFAULT_SYMBOLS, 'w') as f: with open(DEFAULT_SYMBOLS, 'w') as f:

@ -24,15 +24,11 @@
import os import os
# the path in home, is symlinked with USERS_SYMBOLS.
# getlogin gets the user who ran sudo
HOME_PATH = os.path.join('/home', os.getlogin(), '.config/key-mapper')
# the path that contains ALL symbols, not just ours # the path that contains ALL symbols, not just ours
X11_SYMBOLS = '/usr/share/X11/xkb/symbols' X11_SYMBOLS = '/usr/share/X11/xkb/symbols'
# should not contain spaces # should not contain spaces
# getlogin gets the user who ran sudo
USERS_SYMBOLS = 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(' ', '_')
@ -43,24 +39,6 @@ USERS_SYMBOLS = os.path.join(
KEYCODES_PATH = '/usr/share/X11/xkb/keycodes/key-mapper' KEYCODES_PATH = '/usr/share/X11/xkb/keycodes/key-mapper'
def get_home_path(device=None, preset=None):
"""Get the path to the config file in /usr."""
if device is None:
return HOME_PATH
device = device.strip()
if preset is not None:
preset = preset.strip()
return os.path.join(HOME_PATH, device, preset).replace(' ', '_')
if device is not None:
return os.path.join(HOME_PATH, device.replace(' ', '_'))
DEFAULT_SYMBOLS = get_home_path('default')
def get_usr_path(device=None, preset=None): def get_usr_path(device=None, preset=None):
"""Get the path to the config file in /usr. """Get the path to the config file in /usr.
@ -79,3 +57,6 @@ def get_usr_path(device=None, preset=None):
if device is not None: if device is not None:
return os.path.join(USERS_SYMBOLS, device.replace(' ', '_')) return os.path.join(USERS_SYMBOLS, device.replace(' ', '_'))
DEFAULT_SYMBOLS = get_usr_path('default')

@ -26,7 +26,7 @@ import os
import time import time
import glob import glob
from keymapper.paths import get_home_path, HOME_PATH from keymapper.paths import get_usr_path, USERS_SYMBOLS
from keymapper.logger import logger from keymapper.logger import logger
from keymapper.linux import get_devices from keymapper.linux import get_devices
@ -38,7 +38,7 @@ def get_presets(device):
---------- ----------
device : string device : string
""" """
device_folder = get_home_path(device) device_folder = get_usr_path(device)
if not os.path.exists(device_folder): if not os.path.exists(device_folder):
os.makedirs(device_folder) os.makedirs(device_folder)
presets = [ presets = [
@ -78,12 +78,12 @@ 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(HOME_PATH, '*/*')), glob.glob(os.path.join(USERS_SYMBOLS, '*/*')),
key=os.path.getmtime key=os.path.getmtime
) )
else: else:
paths = sorted( paths = sorted(
glob.glob(os.path.join(get_home_path(device), '*')), glob.glob(os.path.join(get_usr_path(device), '*')),
key=os.path.getmtime key=os.path.getmtime
) )
@ -121,7 +121,7 @@ def find_newest_preset(device=None):
def delete_preset(device, preset): def delete_preset(device, preset):
"""Delete a preset from the file system.""" """Delete a preset from the file system."""
preset_path = get_home_path(device, preset) preset_path = get_usr_path(device, preset)
if not os.path.exists(preset_path): if not os.path.exists(preset_path):
logger.debug('Cannot remove non existing path "%s"', preset_path) logger.debug('Cannot remove non existing path "%s"', preset_path)
return return
@ -129,7 +129,7 @@ def delete_preset(device, preset):
logger.info('Removing "%s"', preset_path) logger.info('Removing "%s"', preset_path)
os.remove(preset_path) os.remove(preset_path)
device_path = get_home_path(device) device_path = get_usr_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.rmdir(device_path) os.rmdir(device_path)
@ -139,16 +139,16 @@ def rename_preset(device, old_preset_name, new_preset_name):
"""Rename a preset while avoiding name conflicts.""" """Rename a preset while avoiding name conflicts."""
new_preset_name = new_preset_name.strip() new_preset_name = new_preset_name.strip()
# find a name that is not already taken # find a name that is not already taken
if os.path.exists(get_home_path(device, new_preset_name)): if os.path.exists(get_usr_path(device, new_preset_name)):
i = 2 i = 2
while os.path.exists(get_home_path(device, f'{new_preset_name} {i}')): while os.path.exists(get_usr_path(device, f'{new_preset_name} {i}')):
i += 1 i += 1
new_preset_name = f'{new_preset_name} {i}' new_preset_name = f'{new_preset_name} {i}'
logger.info('Moving "%s" to "%s"', old_preset_name, new_preset_name) logger.info('Moving "%s" to "%s"', old_preset_name, new_preset_name)
os.rename( os.rename(
get_home_path(device, old_preset_name), get_usr_path(device, old_preset_name),
get_home_path(device, new_preset_name) get_usr_path(device, new_preset_name)
) )
# set the modification date to now # set the modification date to now
now = time.time() now = time.time()
os.utime(get_home_path(device, new_preset_name), (now, now)) os.utime(get_usr_path(device, new_preset_name), (now, now))

@ -30,8 +30,7 @@ import unittest
from keymapper import paths from keymapper import paths
paths.X11_SYMBOLS = '/tmp/key-mapper-test/X11/symbols' paths.X11_SYMBOLS = '/tmp/key-mapper-test/X11/symbols'
paths.USERS_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/user/.config/default' 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' paths.KEYCODES_PATH = '/tmp/key-mapper-test/X11/keycodes/key-mapper'
from keymapper import linux from keymapper import linux

@ -26,8 +26,7 @@ import shutil
from keymapper.X import custom_mapping, generate_symbols, \ from keymapper.X import custom_mapping, generate_symbols, \
create_identity_mapping, create_setxkbmap_config, \ create_identity_mapping, create_setxkbmap_config, \
get_preset_name, create_default_symbols get_preset_name, create_default_symbols
from keymapper.paths import get_home_path, get_usr_path, KEYCODES_PATH, \ from keymapper.paths import get_usr_path, KEYCODES_PATH, USERS_SYMBOLS
HOME_PATH, USERS_SYMBOLS
from test import tmp from test import tmp
@ -44,12 +43,6 @@ class TestConfig(unittest.TestCase):
def test_create_setxkbmap_config(self): def test_create_setxkbmap_config(self):
create_setxkbmap_config('device a', 'preset b') create_setxkbmap_config('device a', 'preset b')
self.assertTrue(os.path.exists(os.path.join(
HOME_PATH,
'device_a',
'preset_b'
)))
self.assertTrue(os.path.exists(os.path.join( self.assertTrue(os.path.exists(os.path.join(
USERS_SYMBOLS, USERS_SYMBOLS,
'device_a', 'device_a',
@ -58,7 +51,7 @@ class TestConfig(unittest.TestCase):
self.assertTrue(os.path.exists(KEYCODES_PATH)) self.assertTrue(os.path.exists(KEYCODES_PATH))
with open(get_home_path('device_a', 'preset_b'), 'r') as f: with open(get_usr_path('device_a', 'preset_b'), 'r') as f:
content = f.read() content = f.read()
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)
@ -76,11 +69,9 @@ class TestConfig(unittest.TestCase):
create_identity_mapping() create_identity_mapping()
create_default_symbols() create_default_symbols()
self.assertTrue(os.path.exists(get_home_path('default')))
self.assertTrue(os.path.exists(get_usr_path('default'))) self.assertTrue(os.path.exists(get_usr_path('default')))
self.assertTrue(os.path.islink(get_usr_path()))
with open(get_home_path('default'), 'r') as f: with open(get_usr_path('default'), 'r') as f:
content = f.read() content = f.read()
self.assertNotIn('include', content) self.assertNotIn('include', content)
# this is pretty much the same on every keyboard # this is pretty much the same on every keyboard

@ -33,7 +33,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
from keymapper.mapping import custom_mapping from keymapper.mapping import custom_mapping
from keymapper.paths import USERS_SYMBOLS, HOME_PATH, KEYCODES_PATH from keymapper.paths import USERS_SYMBOLS, KEYCODES_PATH
from test import tmp from test import tmp
@ -189,7 +189,6 @@ class Integration(unittest.TestCase):
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'{USERS_SYMBOLS}/device_1/asdf')) 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') self.assertEqual(custom_mapping.get(14), 'b')
def test_select_device_and_preset(self): def test_select_device_and_preset(self):
@ -236,7 +235,6 @@ class Integration(unittest.TestCase):
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'{USERS_SYMBOLS}/device_1/abc_123')) self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/abc_123'))
self.assertListEqual( self.assertListEqual(
sorted(os.listdir(USERS_SYMBOLS)), sorted(os.listdir(USERS_SYMBOLS)),
['default', 'device_1'] ['default', 'device_1']

@ -26,7 +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 keymapper.paths import USERS_SYMBOLS
from test import tmp from test import tmp
@ -39,23 +39,18 @@ 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'{USERS_SYMBOLS}/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): 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'{USERS_SYMBOLS}/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'))
self.assertTrue(os.path.exists(f'{USERS_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'{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'{USERS_SYMBOLS}/device_1/pre_set')) 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'{USERS_SYMBOLS}/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):
@ -63,9 +58,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'{HOME_PATH}/device_1/preset_1')) self.assertFalse(os.path.exists(f'{USERS_SYMBOLS}/device_1/preset_1'))
self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/foobar')) self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/foobar'))
self.assertTrue(os.path.exists(f'{HOME_PATH}/device_1/foobar_2')) self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/foobar_2'))
class TestFindPresets(unittest.TestCase): class TestFindPresets(unittest.TestCase):
@ -81,16 +76,13 @@ class TestFindPresets(unittest.TestCase):
def test_find_newest_preset_2(self): def test_find_newest_preset_2(self):
os.makedirs(f'{USERS_SYMBOLS}/device_1') os.makedirs(f'{USERS_SYMBOLS}/device_1')
os.makedirs(f'{HOME_PATH}/device_1')
time.sleep(0.01) time.sleep(0.01)
os.makedirs(f'{USERS_SYMBOLS}/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 # 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'{USERS_SYMBOLS}/device_1') os.makedirs(f'{USERS_SYMBOLS}/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,7 +22,7 @@
import unittest import unittest
from keymapper.linux import get_devices from keymapper.linux import get_devices
from keymapper.paths import USERS_SYMBOLS, X11_SYMBOLS, HOME_PATH, \ from keymapper.paths import USERS_SYMBOLS, X11_SYMBOLS, \
DEFAULT_SYMBOLS DEFAULT_SYMBOLS
@ -33,9 +33,7 @@ class TestTest(unittest.TestCase):
def test_paths(self): def test_paths(self):
# stuff in /usr # stuff in /usr
self.assertTrue(USERS_SYMBOLS.startswith(X11_SYMBOLS)) self.assertTrue(USERS_SYMBOLS.startswith(X11_SYMBOLS))
self.assertTrue(DEFAULT_SYMBOLS.startswith(X11_SYMBOLS))
# stuff in /home
self.assertTrue(DEFAULT_SYMBOLS.startswith(HOME_PATH))
if __name__ == "__main__": if __name__ == "__main__":

Loading…
Cancel
Save