saving presets works again

xkb
sezanzeb 4 years ago committed by sezanzeb
parent a3250797c4
commit ed24708633

@ -35,8 +35,6 @@ import os
import re
import stat
from keymapper.paths import get_usr_path, KEYCODES_PATH, DEFAULT_SYMBOLS, \
X11_SYMBOLS
from keymapper.logger import logger
from keymapper.data import get_data_path
from keymapper.mapping import custom_mapping, system_mapping, Mapping
@ -47,6 +45,43 @@ permissions = stat.S_IREAD | stat.S_IWRITE | stat.S_IRGRP | stat.S_IROTH
MAX_KEYCODE = 255
MIN_KEYCODE = 8
# the path that contains ALL symbols, not just ours
X11_SYMBOLS = '/usr/share/X11/xkb/symbols'
# should not contain spaces
# getlogin gets the user who ran sudo
USERS_SYMBOLS = os.path.join(
'/usr/share/X11/xkb/symbols/key-mapper',
os.getlogin().replace(' ', '_')
)
# those are the same for every preset and user, they are needed to make the
# presets work.
KEYCODES_PATH = '/usr/share/X11/xkb/keycodes/key-mapper'
def get_usr_path(device=None, preset=None):
"""Get the path to the config file in /usr.
This folder is a symlink and the files are in ~/.config/key-mapper
If preset is omitted, returns the folder for the device.
"""
if device is None:
return USERS_SYMBOLS
device = device.strip()
if preset is not None:
preset = preset.strip()
return os.path.join(USERS_SYMBOLS, device, preset).replace(' ', '_')
if device is not None:
return os.path.join(USERS_SYMBOLS, device.replace(' ', '_'))
DEFAULT_SYMBOLS = get_usr_path('default')
def create_preset(device, name=None):
"""Create an empty preset and return the potentially incremented name.

@ -26,7 +26,6 @@ import os
import re
import subprocess
from keymapper.paths import X11_SYMBOLS
from keymapper.logger import logger, is_debug
from keymapper.getdevices import get_devices
from keymapper.mapping import system_mapping
@ -59,7 +58,7 @@ def setxkbmap(device, layout):
load the system default
"""
if layout is not None:
path = os.path.join(X11_SYMBOLS, layout)
path = os.path.join('/usr/share/X11/xkb/symbols', layout)
if not os.path.exists(path):
logger.error('Symbols %s don\'t exist', path)
return

@ -30,7 +30,7 @@ from gi.repository import Gtk, Gdk, GLib
from keymapper.data import get_data_path
from keymapper.mapping import custom_mapping
from keymapper.presets import get_presets, find_newest_preset, \
delete_preset, rename_preset
delete_preset, rename_preset, get_available_preset_name
from keymapper.logger import logger
from keymapper.linux import KeycodeReader
from keymapper.cli import setxkbmap
@ -164,9 +164,9 @@ class Window:
presets = get_presets(device)
self.get('preset_name_input').set_text('')
if len(presets) == 0:
# presets = [create_preset(device)]
# TODO create one empty preset
presets = []
new_preset = get_available_preset_name(self.selected_device)
custom_mapping.save(self.selected_device, new_preset)
presets = [new_preset]
else:
logger.debug('Presets for "%s": %s', device, ', '.join(presets))
preset_selection = self.get('preset_selection')
@ -276,10 +276,8 @@ class Window:
return
try:
# new_preset = create_preset(self.selected_device)
# TODO create a preset file, tell custom_mapping to clear itself
# and dump itself into a new file
new_preset = 'new_preset'
new_preset = get_available_preset_name(self.selected_device)
custom_mapping.save(self.selected_device, new_preset)
self.get('preset_selection').append(new_preset, new_preset)
self.get('preset_selection').set_active_id(new_preset)
except PermissionError as e:
@ -304,7 +302,7 @@ class Window:
logger.debug('Selecting preset "%s"', preset)
self.selected_preset = preset
# TODO load config into custom_mapping
custom_mapping.load(self.selected_device, self.selected_preset)
key_list = self.get('key_list')
for keycode, output in custom_mapping:
@ -312,7 +310,7 @@ class Window:
window=self,
delete_callback=self.on_row_removed,
keycode=keycode,
character=output[1]
character=output
)
key_list.insert(single_key_mapping, -1)

@ -22,9 +22,13 @@
"""Contains and manages mappings."""
import os
import json
import shutil
from keymapper.logger import logger
from keymapper.paths import get_config_path
from keymapper.presets import get_available_preset_name
class Mapping:
@ -72,6 +76,10 @@ class Mapping:
return False
if new_keycode and character:
if isinstance(character, list):
character = [c.lower() for c in character]
else:
character = character.lower()
self._mapping[new_keycode] = character
if new_keycode != previous_keycode:
# clear previous mapping of that code, because the line
@ -100,21 +108,46 @@ class Mapping:
def load(self, device, preset):
"""Load a dumped JSON from home to overwrite the mappings."""
# TODO
# TODO test
path = get_config_path(device, preset)
logger.info('Loading preset from %s', path)
if not os.path.exists(path):
logger.error('Tried to load non-existing preset %s', path)
return
with open(path, 'r') as f:
self._mapping = json.load(f)
self.changed = False
def save(self, device, preset):
"""Dump as JSON into home."""
# TODO
# TODO test
path = get_config_path(device, preset)
logger.info('Saving preset to %s', path)
if not os.path.exists(path):
logger.debug('Creating "%s"', path)
os.makedirs(os.path.dirname(path), exist_ok=True)
os.mknod(path)
# if this is done with sudo rights, give the file to the user
shutil.chown(path, os.getlogin())
with open(path, 'w') as f:
json.dump(self._mapping, f)
self.changed = False
def get_keycode(self, character):
"""Get the keycode for that character."""
# TODO prepare this with .lower() instead to make it faster
character = character.lower()
for keycode, mapping in self._mapping.items():
# note, that stored mappings are already lowercase
if isinstance(mapping, list):
if character in [c.lower() for c in mapping]:
if character in [c for c in mapping]:
return keycode
elif mapping.lower() == character:
elif mapping == character:
return int(keycode)
return None

@ -24,40 +24,14 @@
import os
# the path that contains ALL symbols, not just ours
X11_SYMBOLS = '/usr/share/X11/xkb/symbols'
EMPTY_SYMBOLS = '/usr/share/X11/xkb/symbols/key-mapper-empty'
CONFIG = os.path.join('/home', os.getlogin(), '.config/key-mapper')
# should not contain spaces
# getlogin gets the user who ran sudo
USERS_SYMBOLS = os.path.join(
'/usr/share/X11/xkb/symbols/key-mapper',
os.getlogin().replace(' ', '_')
)
# those are the same for every preset and user, they are needed to make the
# presets work.
KEYCODES_PATH = '/usr/share/X11/xkb/keycodes/key-mapper'
def get_usr_path(device=None, preset=None):
"""Get the path to the config file in /usr.
This folder is a symlink and the files are in ~/.config/key-mapper
If preset is omitted, returns the folder for the device.
"""
def get_config_path(device=None, preset=None):
"""Get a path to the stored preset, or to store a preset to."""
if device is None:
return USERS_SYMBOLS
device = device.strip()
if preset is not None:
preset = preset.strip()
return os.path.join(USERS_SYMBOLS, device, preset).replace(' ', '_')
if device is not None:
return os.path.join(USERS_SYMBOLS, device.replace(' ', '_'))
DEFAULT_SYMBOLS = get_usr_path('default')
EMPTY_SYMBOLS = get_usr_path('empty')
return CONFIG
if preset is None:
return os.path.join(CONFIG, device)
return os.path.join(CONFIG, device, preset)

@ -26,11 +26,25 @@ import os
import time
import glob
from keymapper.paths import get_usr_path, USERS_SYMBOLS
from keymapper.paths import get_config_path
from keymapper.logger import logger
from keymapper.getdevices import get_devices
def get_available_preset_name(device, preset='new preset'):
"""Increment the preset name until it is available."""
preset = preset.strip()
# find a name that is not already taken
if os.path.exists(get_config_path(device, preset)):
i = 2
while os.path.exists(get_config_path(device, f'{preset} {i}')):
i += 1
return f'{preset} {i}'
return preset
def get_presets(device):
"""Get all configured presets for the device, sorted by modification date.
@ -38,7 +52,7 @@ def get_presets(device):
----------
device : string
"""
device_folder = get_usr_path(device)
device_folder = get_config_path(device)
if not os.path.exists(device_folder):
os.makedirs(device_folder)
presets = [
@ -78,12 +92,12 @@ 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(USERS_SYMBOLS, '*/*')),
glob.glob(os.path.join(get_config_path(), '*/*')),
key=os.path.getmtime
)
else:
paths = sorted(
glob.glob(os.path.join(get_usr_path(device), '*')),
glob.glob(os.path.join(get_config_path(device), '*')),
key=os.path.getmtime
)
@ -121,7 +135,7 @@ def find_newest_preset(device=None):
def delete_preset(device, preset):
"""Delete a preset from the file system."""
preset_path = get_usr_path(device, preset)
preset_path = get_config_path(device, preset)
if not os.path.exists(preset_path):
logger.debug('Cannot remove non existing path "%s"', preset_path)
return
@ -129,7 +143,7 @@ def delete_preset(device, preset):
logger.info('Removing "%s"', preset_path)
os.remove(preset_path)
device_path = get_usr_path(device)
device_path = get_config_path(device)
if os.path.exists(device_path) and len(os.listdir(device_path)) == 0:
logger.debug('Removing empty dir "%s"', device_path)
os.rmdir(device_path)
@ -137,18 +151,15 @@ def delete_preset(device, preset):
def rename_preset(device, old_preset_name, new_preset_name):
"""Rename a preset while avoiding name conflicts."""
new_preset_name = new_preset_name.strip()
# find a name that is not already taken
if os.path.exists(get_usr_path(device, new_preset_name)):
i = 2
while os.path.exists(get_usr_path(device, f'{new_preset_name} {i}')):
i += 1
new_preset_name = f'{new_preset_name} {i}'
if new_preset_name == old_preset_name:
return
new_preset_name = get_available_preset_name(device, new_preset_name)
logger.info('Moving "%s" to "%s"', old_preset_name, new_preset_name)
os.rename(
get_usr_path(device, old_preset_name),
get_usr_path(device, new_preset_name)
get_config_path(device, old_preset_name),
get_config_path(device, new_preset_name)
)
# set the modification date to now
now = time.time()
os.utime(get_usr_path(device, new_preset_name), (now, now))
os.utime(get_config_path(device, new_preset_name), (now, now))

@ -25,8 +25,8 @@ import shutil
from keymapper.archive.xkb import custom_mapping, generate_symbols, \
create_identity_mapping, create_setxkbmap_config, \
get_preset_name, create_default_symbols, parse_symbols_file
from keymapper.paths import get_usr_path, KEYCODES_PATH, USERS_SYMBOLS
get_preset_name, create_default_symbols, parse_symbols_file, \
get_usr_path, KEYCODES_PATH, USERS_SYMBOLS
from test import tmp

@ -33,7 +33,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from keymapper.mapping import custom_mapping
from keymapper.paths import USERS_SYMBOLS, KEYCODES_PATH
from keymapper.paths import CONFIG
from test import tmp
@ -188,7 +188,7 @@ class Integration(unittest.TestCase):
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'{USERS_SYMBOLS}/device_1/asdf'))
self.assertTrue(os.path.exists(f'{CONFIG}/device_1/asdf'))
self.assertEqual(custom_mapping.get_character(14), 'b')
def test_select_device_and_preset(self):
@ -204,15 +204,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'{USERS_SYMBOLS}/device_1/new_preset'))
self.assertTrue(os.path.exists(f'{CONFIG}/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'{USERS_SYMBOLS}/device_1/new_preset'))
self.assertTrue(os.path.exists(f'{USERS_SYMBOLS}/device_1/new_preset_2'))
self.assertTrue(os.path.exists(f'{CONFIG}/device_1/new_preset'))
self.assertTrue(os.path.exists(f'{CONFIG}/device_1/new_preset_2'))
self.assertEqual(self.window.selected_preset, 'new preset 2')
self.window.on_select_preset(FakeDropdown('new preset'))
@ -220,7 +220,7 @@ class Integration(unittest.TestCase):
self.assertEqual(self.window.selected_preset, 'new preset')
self.assertListEqual(
sorted(os.listdir(f'{USERS_SYMBOLS}/device_1')),
sorted(os.listdir(f'{CONFIG}/device_1')),
['new_preset', 'new_preset_2']
)
@ -228,19 +228,18 @@ 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'{USERS_SYMBOLS}/device_1/abc_123'))
self.assertFalse(os.path.exists(f'{CONFIG}/device_1/abc_123'))
custom_mapping.change(None, 10, '1')
self.window.on_save_preset_clicked(None)
self.assertTrue(os.path.exists(KEYCODES_PATH))
gtk_iteration()
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'{CONFIG}/device_1/abc_123'))
self.assertListEqual(
sorted(os.listdir(USERS_SYMBOLS)),
sorted(os.listdir(CONFIG)),
['default', 'device_1']
)
self.assertListEqual(
sorted(os.listdir(f'{USERS_SYMBOLS}/device_1')),
sorted(os.listdir(f'{CONFIG}/device_1')),
['abc_123', 'new_preset_2']
)

@ -26,8 +26,7 @@ import time
from keymapper.presets import find_newest_preset, rename_preset, \
get_any_preset, delete_preset
from keymapper.archive.xkb import create_preset
from keymapper.paths import USERS_SYMBOLS
from keymapper.archive.xkb import create_preset, USERS_SYMBOLS
from test import tmp

@ -22,7 +22,7 @@
import unittest
from keymapper.getdevices import get_devices
from keymapper.paths import USERS_SYMBOLS, X11_SYMBOLS, \
from keymapper.archive.xkb import USERS_SYMBOLS, X11_SYMBOLS, \
DEFAULT_SYMBOLS

Loading…
Cancel
Save