fixed stuff, dummy configs actually work now

first
sezanzeb 4 years ago
parent 5d72771083
commit dcf9462321

@ -32,7 +32,8 @@ gi.require_version('GLib', '2.0')
from gi.repository import Gtk from gi.repository import Gtk
from keymapper.data import get_data_path from keymapper.data import get_data_path
from keymapper.X import find_devices, generate_setxkbmap_config from keymapper.X import find_devices, create_setxkbmap_config, \
create_identity_mapping
from keymapper.presets import get_presets, get_mappings, \ from keymapper.presets import get_presets, get_mappings, \
find_newest_preset, create_preset find_newest_preset, create_preset
from keymapper.logger import logger, update_verbosity, log_info from keymapper.logger import logger, update_verbosity, log_info
@ -97,7 +98,7 @@ class Window:
self.populate_devices() self.populate_devices()
# find an select the newest preset based on file modification dates # find and select the newest preset based on file modification dates
device, preset = find_newest_preset() device, preset = find_newest_preset()
if device is not None: if device is not None:
self.get('device_selection').set_active_id(device) self.get('device_selection').set_active_id(device)
@ -121,7 +122,12 @@ class Window:
def populate_presets(self): def populate_presets(self):
"""Show the available presets for the selected device.""" """Show the available presets for the selected device."""
presets = get_presets(self.selected_device) device = self.selected_device
presets = get_presets(device)
if len(presets) == 0:
create_preset(device)
else:
logger.debug('Presets for "%s": %s', device, ', '.join(presets))
preset_selection = self.get('preset_selection') preset_selection = self.get('preset_selection')
preset_selection.remove_all() preset_selection.remove_all()
for preset in presets: for preset in presets:
@ -139,11 +145,6 @@ class Window:
self.selected_preset = None self.selected_preset = None
self.mappings = [] self.mappings = []
presets = get_presets(device)
if len(presets) == 0:
create_preset(device)
else:
logger.debug('Presets for "%s": %s', device, ', '.join(presets))
self.populate_presets() self.populate_presets()
def on_create_preset_clicked(self, button): def on_create_preset_clicked(self, button):
@ -217,7 +218,11 @@ class Window:
self.selected_device, self.selected_device,
self.selected_preset self.selected_preset
) )
generate_setxkbmap_config(
# TODO use user defined mapping
self.mappings = [(10, 'z')]
create_setxkbmap_config(
self.selected_device, self.selected_device,
self.selected_preset, self.selected_preset,
self.mappings self.mappings

@ -0,0 +1,7 @@
// keycodes configuration for key-mapper presets
default xkb_keycodes "basic" {{
minimum = {minimum};
maximum = {maximum};
{xkb_keycodes}
}};

@ -0,0 +1,10 @@
// key-mapper symbols config file.
// the corresponding keycodes configuration is in
// /usr/share/X11/xkb/keycodes/key-mapper
default xkb_symbols "basic" {{
include "{system_default}"
name[Group1] = "{name}";
{xkb_symbols}
}};

@ -35,8 +35,9 @@ import os
import re import re
import subprocess import subprocess
from keymapper.paths import CONFIG_PATH, SYMBOLS_PATH from keymapper.paths import CONFIG_PATH, SYMBOLS_PATH, KEYCODES_PATH
from keymapper.logger import logger from keymapper.logger import logger
from keymapper.data import get_data_path
def get_keycode(device, letter): def get_keycode(device, letter):
@ -46,7 +47,7 @@ def get_keycode(device, letter):
return '' return ''
def generate_setxkbmap_config(device, preset, mappings): def create_setxkbmap_config(device, preset, mappings):
"""Generate a config file for setxkbmap. """Generate a config file for setxkbmap.
The file is created in ~/.config/key-mapper/<device>/<preset> and, The file is created in ~/.config/key-mapper/<device>/<preset> and,
@ -56,6 +57,8 @@ def generate_setxkbmap_config(device, preset, mappings):
The file in home doesn't have underscore to be more beautiful on the The file in home doesn't have underscore to be more beautiful on the
frontend, while the symlink doesn't contain any whitespaces. frontend, while the symlink doesn't contain any whitespaces.
""" """
create_identity_mapping()
config_path = os.path.join(CONFIG_PATH, device, preset) config_path = os.path.join(CONFIG_PATH, device, preset)
# setxkbmap cannot handle spaces # setxkbmap cannot handle spaces
usr_path = os.path.join(SYMBOLS_PATH, device, preset).replace(' ', '_') usr_path = os.path.join(SYMBOLS_PATH, device, preset).replace(' ', '_')
@ -69,44 +72,89 @@ def generate_setxkbmap_config(device, preset, mappings):
os.makedirs(os.path.dirname(usr_path), exist_ok=True) os.makedirs(os.path.dirname(usr_path), exist_ok=True)
os.symlink(config_path, usr_path) os.symlink(config_path, usr_path)
logger.info('Writing key mappings')
with open(config_path, 'w') as f: with open(config_path, 'w') as f:
f.write(generate_symbols_file_content(device, preset, mappings)) f.write(generate_symbols_file_content(device, preset, mappings))
logger.debug('Wrote key mappings')
def apply_preset(device, preset): def apply_preset(device, preset):
# setxkbmap key-mapper/Razer_Razer_Naga_Trinity/new_preset -v 10 -device 12 # setxkbmap -layout key-mapper/Razer_Razer_Naga_Trinity/new_preset -keycodes key-mapper -v 10 -device 13
# TODO device 12 is from `xinput list` but currently there is no function # TODO device 12 is from `xinput list` but currently there is no function
# to obtain that. And that cli tool outputs all those extra devices. # to obtain that. And that cli tool outputs all those extra devices.
# 1. get all names in the group (similar to parse_libinput_list) # 1. get all names in the group (similar to parse_libinput_list)
# 2. get all ids from xinput list for each name # 2. get all ids from xinput list for each name
# 3. apply preset to all of them # 3. apply preset to all of them
pass
def generate_symbols_file_content(device, preset, mappings): def create_identity_mapping():
"""Create config contents to be placed in /usr/share/X11/xkb/symbols.""" """Because the concept of "reasonable symbolic names" [3] doesn't apply
system_default = 'us' # TODO get the system default when mouse buttons are all over the place. Create an identity mapping
to make generating "symbols" files easier. Keycode 10 -> "<10>"
# 10 is what the mouse reports This has the added benefit that keycodes reported by xev can be
result = """ identified in the symbols file.
default xkb_symbols "basic" {{
include "us"
name[Group1]="{name}";
key <AE01> { [ 3 ] };
}};
default xkb_keycodes "basic" {{
minimum= 8;
maximum= 255;
<AE01> = 10;
}};
""" """
result = result.format(name=f'{device}/{preset}') # TODO don't create this again if it already exists, as soon as this
# stuff is stable.
xkb_keycodes = []
# the maximum specified in /usr/share/X11/xkb/keycodes is usually 255
# and the minimum 8
maximum = 255
minimum = 8
for code in range(minimum, maximum + 1):
xkb_keycodes.append(f'<{code}> = {code};')
template_path = os.path.join(get_data_path(), 'xkb_keycodes_template')
with open(template_path, 'r') as template_file:
template = template_file.read()
result = template.format(
minimum=minimum,
maximum=maximum,
xkb_keycodes='\n '.join(xkb_keycodes)
)
logger.info('Creating "%s"', KEYCODES_PATH)
with open(KEYCODES_PATH, 'w') as keycodes:
keycodes.write(result)
for mapping in mappings: def generate_symbols_file_content(device, preset, mappings):
key = mapping.key """Create config contents to be placed in /usr/share/X11/xkb/symbols.
keycode = get_keycode(device, key)
target = mapping.target Parameters
----------
device : string
preset : string
mappings : array
tuples of code, character
"""
system_default = 'us' # TODO get the system default
# WARNING if the symbols file contains key codes that are not present in
# the keycodes file, the whole X session will crash!
if not os.path.exists(KEYCODES_PATH):
raise ValueError('Expected the keycodes file to exist.')
with open(KEYCODES_PATH, 'r') as f:
keycodes = re.findall(r'<.+?>', f.read())
xkb_symbols = []
for code, character in mappings:
if f'<{code}>' not in keycodes:
logger.error(f'Unknown keycode <{code}> for "{character}"')
xkb_symbols.append(f'key <{code}> {{ [ {character} ] }};')
template_path = os.path.join(get_data_path(), 'xkb_symbols_template')
with open(template_path, 'r') as template_file:
template = template_file.read()
result = template.format(
name=f'{device}/{preset}',
xkb_symbols='\n '.join(xkb_symbols),
system_default=system_default
)
return result return result

@ -31,6 +31,8 @@ import subprocess
SYMBOLS_PATH = '/usr/share/X11/xkb/symbols/key-mapper' SYMBOLS_PATH = '/usr/share/X11/xkb/symbols/key-mapper'
KEYCODES_PATH = '/usr/share/X11/xkb/keycodes/key-mapper'
# since this needs to run as sudo, # since this needs to run as sudo,
# get the home dir of the user who called sudo. # get the home dir of the user who called sudo.
who = subprocess.check_output('who').decode().split()[0] who = subprocess.check_output('who').decode().split()[0]

@ -27,7 +27,7 @@ import glob
from keymapper.paths import CONFIG_PATH from keymapper.paths import CONFIG_PATH
from keymapper.logger import logger from keymapper.logger import logger
from keymapper.X import find_devices, generate_setxkbmap_config from keymapper.X import find_devices, create_setxkbmap_config
def get_presets(device): def get_presets(device):
@ -40,7 +40,15 @@ def get_presets(device):
device_folder = os.path.join(CONFIG_PATH, device) device_folder = os.path.join(CONFIG_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 = os.listdir(device_folder) presets = [
os.path.basename(path)
for path in sorted(
glob.glob(os.path.join(device_folder, '*')),
key=os.path.getmtime
)
]
# the highest timestamp to the front
presets.reverse()
return presets return presets
@ -51,12 +59,13 @@ 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
i = 1 if name in existing_names:
while name in existing_names: i = 2
while f'{name} {i}' in existing_names:
i += 1 i += 1
name = f'{name} {i}' name = f'{name} {i}'
generate_setxkbmap_config(device, name, []) create_setxkbmap_config(device, name, [])
return name return name
@ -83,7 +92,6 @@ def find_newest_preset():
If no device has been configured yet, return arbitrarily. If no device has been configured yet, return arbitrarily.
""" """
# sort the oldest files to the front # sort the oldest files to the front
paths = sorted( paths = sorted(
glob.glob(os.path.join(CONFIG_PATH, '*/*')), glob.glob(os.path.join(CONFIG_PATH, '*/*')),
@ -110,4 +118,6 @@ def find_newest_preset():
logger.debug('None of the configured devices is currently online.') logger.debug('None of the configured devices is currently online.')
return get_any_preset() return get_any_preset()
logger.debug('The newest preset is "%s", "%s"', device, preset)
return device, preset return device, preset

@ -29,5 +29,6 @@ DistUtilsExtra.auto.setup(
license='GPL-3.0', license='GPL-3.0',
data_files=[ data_files=[
('share/applications/', ['data/key-mapper.desktop']), ('share/applications/', ['data/key-mapper.desktop']),
('share/key-mapper/', ['data/xkb_symbols_template']),
], ],
) )

@ -31,6 +31,10 @@ tmp = '/tmp/key-mapper-test'
class TestCreatePreset(unittest.TestCase): class TestCreatePreset(unittest.TestCase):
def setUp(self):
if os.path.exists(tmp):
shutil.rmtree(tmp)
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'{tmp}/symbols/device_1/new_preset'))
@ -55,7 +59,8 @@ class TestCreatePreset(unittest.TestCase):
class TestFindPresets(unittest.TestCase): class TestFindPresets(unittest.TestCase):
def setUp(self): def setUp(self):
shutil.rmtree(f'{tmp}') if os.path.exists(tmp):
shutil.rmtree(tmp)
def test_find_newest_preset_1(self): def test_find_newest_preset_1(self):
print('test_find_newest_preset_1') print('test_find_newest_preset_1')

Loading…
Cancel
Save