input-remapper/keymapper/state.py

146 lines
4.9 KiB
Python
Raw Normal View History

2020-11-18 19:03:37 +00:00
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# key-mapper - GUI for device specific keyboard mappings
2021-02-22 18:48:20 +00:00
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
2020-11-18 19:03:37 +00:00
#
# This file is part of key-mapper.
#
# key-mapper is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# key-mapper is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with key-mapper. If not, see <https://www.gnu.org/licenses/>.
"""Create some singleton objects that are needed for the app to work."""
2020-11-18 19:03:37 +00:00
import stat
2020-11-18 21:06:54 +00:00
import re
import json
2020-11-18 21:06:54 +00:00
import subprocess
import evdev
2020-11-18 19:03:37 +00:00
from keymapper.logger import logger
2021-01-01 13:09:28 +00:00
from keymapper.mapping import Mapping, DISABLE_NAME, DISABLE_CODE
from keymapper.paths import get_config_path, touch, USER
2020-11-18 21:06:54 +00:00
# xkb uses keycodes that are 8 higher than those from evdev
XKB_KEYCODE_OFFSET = 8
XMODMAP_FILENAME = 'xmodmap.json'
2020-12-04 13:38:41 +00:00
class SystemMapping:
"""Stores information about all available keycodes."""
def __init__(self):
"""Construct the system_mapping."""
self._mapping = {}
2021-03-21 18:15:20 +00:00
self.xmodmap = {}
2020-12-04 13:38:41 +00:00
self.populate()
2020-12-05 18:38:38 +00:00
def list_names(self):
"""Return an array of all possible names in the mapping."""
return self._mapping.keys()
2020-12-04 13:38:41 +00:00
def populate(self):
"""Get a mapping of all available names to their keycodes."""
logger.debug('Gathering available keycodes')
2020-12-04 13:38:41 +00:00
self.clear()
xmodmap_dict = {}
2020-12-19 21:24:23 +00:00
try:
2021-01-10 00:36:59 +00:00
xmodmap = subprocess.check_output(
['xmodmap', '-pke'],
stderr=subprocess.STDOUT
).decode()
xmodmap = xmodmap.lower()
2021-03-21 18:15:20 +00:00
self.xmodmap = re.findall(r'(\d+) = (.+)\n', xmodmap + '\n')
for keycode, names in self.xmodmap:
2020-12-19 21:24:23 +00:00
# there might be multiple, like:
# keycode 64 = Alt_L Meta_L Alt_L Meta_L
# keycode 204 = NoSymbol Alt_L NoSymbol Alt_L
# Alt_L should map to code 64. Writing code 204 only works
# if a modifier is applied at the same time. So take the first
# one.
name = names.split()[0]
xmodmap_dict[name] = int(keycode) - XKB_KEYCODE_OFFSET
2020-12-19 21:24:23 +00:00
2021-03-21 18:15:20 +00:00
for keycode, names in self.xmodmap:
2020-12-19 21:24:23 +00:00
# but since KP may be mapped like KP_Home KP_7 KP_Home KP_7,
# make another pass and add all of them if they don't already
# exist. don't overwrite any keycodes.
for name in names.split():
if xmodmap_dict.get(name) is None:
xmodmap_dict[name] = int(keycode) - XKB_KEYCODE_OFFSET
2020-12-19 21:24:23 +00:00
except (subprocess.CalledProcessError, FileNotFoundError):
# might be within a tty
pass
2020-12-04 13:38:41 +00:00
if USER != 'root':
# write this stuff into the key-mapper config directory, because
2020-12-25 14:00:57 +00:00
# the systemd service won't know the user sessions xmodmap
path = get_config_path(XMODMAP_FILENAME)
touch(path)
with open(path, 'w') as file:
2021-01-09 16:44:24 +00:00
logger.debug('Writing "%s"', path)
json.dump(xmodmap_dict, file, indent=4)
self._mapping.update(xmodmap_dict)
2020-12-04 13:38:41 +00:00
for name, ecode in evdev.ecodes.ecodes.items():
2020-12-06 11:54:14 +00:00
if name.startswith('KEY') or name.startswith('BTN'):
2020-12-05 18:38:38 +00:00
self._set(name, ecode)
2020-12-04 13:38:41 +00:00
2021-01-01 13:09:28 +00:00
self._set(DISABLE_NAME, DISABLE_CODE)
def update(self, mapping):
"""Update this with new keys.
Parameters
----------
mapping : dict
maps from name to code. Make sure your keys are lowercase.
"""
self._mapping.update(mapping)
2020-12-04 13:38:41 +00:00
def _set(self, name, code):
"""Map name to code."""
self._mapping[str(name).lower()] = code
def get(self, name):
"""Return the code mapped to the key."""
return self._mapping.get(str(name).lower())
def clear(self):
"""Remove all mapped keys. Only needed for tests."""
keys = list(self._mapping.keys())
for key in keys:
del self._mapping[key]
2020-11-18 19:03:37 +00:00
2021-03-21 18:15:20 +00:00
def get_name(self, code):
"""Get the first matching name for the code."""
for entry in self.xmodmap:
if int(entry[0]) - XKB_KEYCODE_OFFSET == code:
return entry[1].split()[0]
return None
2020-11-18 19:03:37 +00:00
# one mapping object for the GUI application
2020-11-18 19:03:37 +00:00
custom_mapping = Mapping()
2020-11-18 21:06:54 +00:00
# this mapping represents the xmodmap output, which stays constant
2020-12-04 13:38:41 +00:00
system_mapping = SystemMapping()
2020-11-18 19:03:37 +00:00
# permissions for files created in /usr
_PERMISSIONS = stat.S_IREAD | stat.S_IWRITE | stat.S_IRGRP | stat.S_IROTH