|
|
@ -39,7 +39,7 @@ import subprocess
|
|
|
|
|
|
|
|
|
|
|
|
from keymapper.paths import get_usr_path, KEYCODES_PATH, DEFAULT_SYMBOLS, \
|
|
|
|
from keymapper.paths import get_usr_path, KEYCODES_PATH, DEFAULT_SYMBOLS, \
|
|
|
|
X11_SYMBOLS
|
|
|
|
X11_SYMBOLS
|
|
|
|
from keymapper.logger import logger
|
|
|
|
from keymapper.logger import logger, is_debug
|
|
|
|
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, system_mapping, \
|
|
|
|
from keymapper.mapping import custom_mapping, system_mapping, \
|
|
|
@ -80,13 +80,6 @@ def create_preset(device, name=None):
|
|
|
|
def create_setxkbmap_config(device, preset):
|
|
|
|
def create_setxkbmap_config(device, preset):
|
|
|
|
"""Generate a config file for setxkbmap.
|
|
|
|
"""Generate a config file for setxkbmap.
|
|
|
|
|
|
|
|
|
|
|
|
The file is created in ~/.config/key-mapper/<device>/<preset> and,
|
|
|
|
|
|
|
|
in order to find all presets in the home dir to make backing them up
|
|
|
|
|
|
|
|
more intuitive, a symlink is created in
|
|
|
|
|
|
|
|
/usr/share/X11/xkb/symbols/key-mapper/<device>/<preset> to point to it.
|
|
|
|
|
|
|
|
The file in home doesn't have underscore to be more beautiful on the
|
|
|
|
|
|
|
|
frontend, while the symlink doesn't contain any whitespaces.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
----------
|
|
|
|
device : string
|
|
|
|
device : string
|
|
|
@ -194,7 +187,7 @@ def setxkbmap(device, layout):
|
|
|
|
|
|
|
|
|
|
|
|
device_cmd = cmd + ['-device', str(xinput_id)]
|
|
|
|
device_cmd = cmd + ['-device', str(xinput_id)]
|
|
|
|
logger.debug('Running `%s`', ' '.join(device_cmd))
|
|
|
|
logger.debug('Running `%s`', ' '.join(device_cmd))
|
|
|
|
subprocess.run(device_cmd, capture_output=True)
|
|
|
|
subprocess.run(device_cmd, capture_output=(not is_debug()))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_identity_mapping():
|
|
|
|
def create_identity_mapping():
|
|
|
@ -210,8 +203,6 @@ def create_identity_mapping():
|
|
|
|
return
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
xkb_keycodes = []
|
|
|
|
xkb_keycodes = []
|
|
|
|
# the maximum specified in /usr/share/X11/xkb/keycodes is usually 255
|
|
|
|
|
|
|
|
# and the minimum 8 TODO update comment
|
|
|
|
|
|
|
|
maximum = MAX_KEYCODE
|
|
|
|
maximum = MAX_KEYCODE
|
|
|
|
minimum = MIN_KEYCODE
|
|
|
|
minimum = MIN_KEYCODE
|
|
|
|
for keycode in range(minimum, maximum + 1):
|
|
|
|
for keycode in range(minimum, maximum + 1):
|
|
|
@ -237,7 +228,9 @@ def create_identity_mapping():
|
|
|
|
keycodes.write(result)
|
|
|
|
keycodes.write(result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_symbols(name, include=DEFAULT_SYMBOLS_NAME, mapping=custom_mapping):
|
|
|
|
def generate_symbols(
|
|
|
|
|
|
|
|
name, include=DEFAULT_SYMBOLS_NAME, 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.
|
|
|
|
|
|
|
|
|
|
|
|
It's the mapping of the preset as expected by X. This function does not
|
|
|
|
It's the mapping of the preset as expected by X. This function does not
|
|
|
@ -267,12 +260,32 @@ def generate_symbols(name, include=DEFAULT_SYMBOLS_NAME, mapping=custom_mapping)
|
|
|
|
keycodes = re.findall(r'<.+?>', f.read())
|
|
|
|
keycodes = re.findall(r'<.+?>', f.read())
|
|
|
|
|
|
|
|
|
|
|
|
xkb_symbols = []
|
|
|
|
xkb_symbols = []
|
|
|
|
for _, (keycode, character) in mapping:
|
|
|
|
for system_keycode, (target_keycode, character) in mapping:
|
|
|
|
if f'<{keycode}>' not in keycodes:
|
|
|
|
if f'<{system_keycode}>' not in keycodes:
|
|
|
|
logger.error(f'Unknown keycode <{keycode}> for "{character}"')
|
|
|
|
logger.error(f'Unknown code <{system_keycode}> for "{character}"')
|
|
|
|
# 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} ] }};')
|
|
|
|
|
|
|
|
|
|
|
|
# key-mapper will write target_keycode into /dev, while
|
|
|
|
|
|
|
|
# system_keycode should do nothing to avoid a duplicate keystroke.
|
|
|
|
|
|
|
|
print('writing', system_keycode, target_keycode, character)
|
|
|
|
|
|
|
|
if target_keycode is not None:
|
|
|
|
|
|
|
|
if f'<{target_keycode}>' not in keycodes:
|
|
|
|
|
|
|
|
logger.error(f'Unknown code <{target_keycode}> for "{character}"')
|
|
|
|
|
|
|
|
# don't append that one, otherwise X would crash when loading
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
xkb_symbols.append(
|
|
|
|
|
|
|
|
f'key <{system_keycode}> {{ [ ] }}; '
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
xkb_symbols.append(
|
|
|
|
|
|
|
|
f'key <{target_keycode}> {{ [ {character} ] }}; '
|
|
|
|
|
|
|
|
f'// {system_keycode}'
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
xkb_symbols.append(
|
|
|
|
|
|
|
|
f'key <{system_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')
|
|
|
@ -329,13 +342,22 @@ def parse_symbols_file(device, preset):
|
|
|
|
# from "key <12> { [ 1 ] };" extract 12 and 1,
|
|
|
|
# from "key <12> { [ 1 ] };" extract 12 and 1,
|
|
|
|
# from "key <12> { [ a, A ] };" extract 12 and [a, A]
|
|
|
|
# from "key <12> { [ a, A ] };" extract 12 and [a, A]
|
|
|
|
# avoid lines that start with special characters
|
|
|
|
# avoid lines that start with special characters
|
|
|
|
# (might be comments)ś
|
|
|
|
# (might be comments)
|
|
|
|
|
|
|
|
# And only find those lines that have a system-keycode written
|
|
|
|
|
|
|
|
# after them, because I need that one to show in the ui.
|
|
|
|
content = f.read()
|
|
|
|
content = f.read()
|
|
|
|
result = re.findall(r'\n\s+?key <(.+?)>.+?\[\s+(.+?)\s+\]', content)
|
|
|
|
result = re.findall(
|
|
|
|
|
|
|
|
r'\n\s+?key <(.+?)>.+?\[\s+(.+?)\s+\]\s+?}; // (\d+)',
|
|
|
|
|
|
|
|
content
|
|
|
|
|
|
|
|
)
|
|
|
|
logger.debug('Found %d mappings in preset "%s"', len(result), preset)
|
|
|
|
logger.debug('Found %d mappings in preset "%s"', len(result), preset)
|
|
|
|
for keycode, character in result:
|
|
|
|
for target_keycode, character, system_keycode in result:
|
|
|
|
keycode = int(keycode)
|
|
|
|
custom_mapping.change(
|
|
|
|
custom_mapping.write_from_keymapper_symbols(keycode, character)
|
|
|
|
previous_keycode=None,
|
|
|
|
|
|
|
|
new_keycode=system_keycode,
|
|
|
|
|
|
|
|
character=character,
|
|
|
|
|
|
|
|
target_keycode=int(target_keycode)
|
|
|
|
|
|
|
|
)
|
|
|
|
custom_mapping.changed = False
|
|
|
|
custom_mapping.changed = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -343,8 +365,14 @@ def parse_xmodmap():
|
|
|
|
"""Read the output of xmodmap as a Mapping object."""
|
|
|
|
"""Read the output of xmodmap as a Mapping object."""
|
|
|
|
xmodmap = subprocess.check_output(['xmodmap', '-pke']).decode() + '\n'
|
|
|
|
xmodmap = subprocess.check_output(['xmodmap', '-pke']).decode() + '\n'
|
|
|
|
mappings = re.findall(r'(\d+) = (.+)\n', xmodmap)
|
|
|
|
mappings = re.findall(r'(\d+) = (.+)\n', xmodmap)
|
|
|
|
|
|
|
|
# TODO is this tested?
|
|
|
|
for keycode, characters in mappings:
|
|
|
|
for keycode, characters in mappings:
|
|
|
|
system_mapping.change(None, int(keycode), characters.split())
|
|
|
|
system_mapping.change(
|
|
|
|
|
|
|
|
previous_keycode=None,
|
|
|
|
|
|
|
|
new_keycode=int(keycode),
|
|
|
|
|
|
|
|
character=', '.join(characters.split()),
|
|
|
|
|
|
|
|
target_keycode=None
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# TODO verify that this is the system default and not changed when I
|
|
|
|
# TODO verify that this is the system default and not changed when I
|
|
|
|