fixed stuff, dummy configs actually work now

This commit is contained in:
sezanzeb 2020-11-02 19:17:28 +01:00 committed by sezanzeb
parent 15fec08332
commit 0065000114
8 changed files with 128 additions and 40 deletions

View File

@ -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

View File

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

10
data/xkb_symbols_template Normal file
View File

@ -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}
}};

View File

@ -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 create_identity_mapping():
"""Because the concept of "reasonable symbolic names" [3] doesn't apply
when mouse buttons are all over the place. Create an identity mapping
to make generating "symbols" files easier. Keycode 10 -> "<10>"
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.
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)
def generate_symbols_file_content(device, preset, mappings): def generate_symbols_file_content(device, preset, mappings):
"""Create config contents to be placed in /usr/share/X11/xkb/symbols.""" """Create config contents to be placed in /usr/share/X11/xkb/symbols.
Parameters
----------
device : string
preset : string
mappings : array
tuples of code, character
"""
system_default = 'us' # TODO get the system default system_default = 'us' # TODO get the system default
# 10 is what the mouse reports # WARNING if the symbols file contains key codes that are not present in
result = """ # the keycodes file, the whole X session will crash!
default xkb_symbols "basic" {{ if not os.path.exists(KEYCODES_PATH):
include "us" raise ValueError('Expected the keycodes file to exist.')
name[Group1]="{name}"; with open(KEYCODES_PATH, 'r') as f:
key <AE01> { [ 3 ] }; keycodes = re.findall(r'<.+?>', f.read())
}};
default xkb_keycodes "basic" {{ xkb_symbols = []
minimum= 8; for code, character in mappings:
maximum= 255; if f'<{code}>' not in keycodes:
<AE01> = 10; logger.error(f'Unknown keycode <{code}> for "{character}"')
}}; xkb_symbols.append(f'key <{code}> {{ [ {character} ] }};')
"""
result = result.format(name=f'{device}/{preset}')
for mapping in mappings: template_path = os.path.join(get_data_path(), 'xkb_symbols_template')
key = mapping.key with open(template_path, 'r') as template_file:
keycode = get_keycode(device, key) template = template_file.read()
target = mapping.target
result = template.format(
name=f'{device}/{preset}',
xkb_symbols='\n '.join(xkb_symbols),
system_default=system_default
)
return result return result

View File

@ -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]

View File

@ -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
i += 1 while f'{name} {i}' in existing_names:
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

View File

@ -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']),
], ],
) )

View File

@ -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')