mirror of
https://github.com/sezanzeb/input-remapper
synced 2024-11-16 06:12:58 +00:00
266 lines
9.1 KiB
Python
266 lines
9.1 KiB
Python
#!/usr/bin/python3
|
|
# -*- coding: utf-8 -*-
|
|
# input-remapper - GUI for device specific keyboard mappings
|
|
# Copyright (C) 2022 sezanzeb <proxima@sezanzeb.de>
|
|
#
|
|
# This file is part of input-remapper.
|
|
#
|
|
# input-remapper 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.
|
|
#
|
|
# input-remapper 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 input-remapper. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
"""A button or a key combination."""
|
|
|
|
|
|
import itertools
|
|
|
|
import evdev
|
|
from evdev import ecodes
|
|
|
|
from inputremapper.system_mapping import system_mapping
|
|
from inputremapper.logger import logger
|
|
|
|
|
|
def verify(key):
|
|
"""Check if the key is an int 3-tuple of type, code, value"""
|
|
if not isinstance(key, tuple) or len(key) != 3:
|
|
raise ValueError(f"Expected key to be a 3-tuple, but got {key}")
|
|
if sum([not isinstance(value, int) for value in key]) != 0:
|
|
raise ValueError(f"Can only use integers, but got {key}")
|
|
|
|
|
|
# having shift in combinations modifies the configured output,
|
|
# ctrl might not work at all
|
|
DIFFICULT_COMBINATIONS = [
|
|
ecodes.KEY_LEFTSHIFT,
|
|
ecodes.KEY_RIGHTSHIFT,
|
|
ecodes.KEY_LEFTCTRL,
|
|
ecodes.KEY_RIGHTCTRL,
|
|
ecodes.KEY_LEFTALT,
|
|
ecodes.KEY_RIGHTALT,
|
|
]
|
|
|
|
|
|
class Key:
|
|
"""Represents one or more pressed down keys.
|
|
|
|
Can be used in hashmaps/dicts as key
|
|
"""
|
|
|
|
def __init__(self, *keys):
|
|
"""
|
|
Parameters
|
|
----------
|
|
Takes an arbitrary number of tuples as arguments. Each one should
|
|
be in the format of
|
|
0: type, one of evdev.events, taken from the original source
|
|
event. Everything will be mapped to EV_KEY.
|
|
1: The source keycode, what the mouse would report without any
|
|
modification.
|
|
2. The value. 1 (down), 0 (up) or any
|
|
other value that the device reports. Gamepads use a continuous
|
|
space of values for joysticks and triggers.
|
|
|
|
or Key objects, which will flatten all of them into one combination
|
|
"""
|
|
if len(keys) == 0:
|
|
raise ValueError("At least one key is required")
|
|
|
|
if isinstance(keys[0], int):
|
|
# type, code, value was provided instead of a tuple
|
|
keys = (keys,)
|
|
|
|
# multiple objects of Key get flattened into one tuple
|
|
flattened = ()
|
|
for key in keys:
|
|
if isinstance(key, Key):
|
|
flattened += key.keys # pylint: disable=no-member
|
|
else:
|
|
flattened += (key,)
|
|
keys = flattened
|
|
|
|
for key in keys:
|
|
verify(key)
|
|
|
|
self.keys = tuple(keys)
|
|
self.release = (*self.keys[-1][:2], 0)
|
|
|
|
@classmethod
|
|
def btn_left(cls):
|
|
"""Construct a Key object representing a left click on a mouse."""
|
|
return cls(ecodes.EV_KEY, ecodes.BTN_LEFT, 1)
|
|
|
|
def __iter__(self):
|
|
return iter(self.keys)
|
|
|
|
def __getitem__(self, item):
|
|
return self.keys[item]
|
|
|
|
def __len__(self):
|
|
"""Get the number of pressed down kes."""
|
|
return len(self.keys)
|
|
|
|
def __str__(self):
|
|
return f"Key{str(self.keys)}"
|
|
|
|
def __repr__(self):
|
|
# used in the AssertionError output of tests
|
|
return self.__str__()
|
|
|
|
def __hash__(self):
|
|
if len(self.keys) == 1:
|
|
return hash(self.keys[0])
|
|
|
|
return hash(self.keys)
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, tuple):
|
|
if isinstance(other[0], tuple):
|
|
# a combination ((1, 5, 1), (1, 3, 1))
|
|
return self.keys == other
|
|
|
|
# otherwise, self needs to represent a single key as well
|
|
return len(self.keys) == 1 and self.keys[0] == other
|
|
|
|
if not isinstance(other, Key):
|
|
return False
|
|
|
|
# compare two instances of Key
|
|
return self.keys == other.keys
|
|
|
|
def is_problematic(self):
|
|
"""Is this combination going to work properly on all systems?"""
|
|
if len(self.keys) <= 1:
|
|
return False
|
|
|
|
for sub_key in self.keys:
|
|
if sub_key[0] != ecodes.EV_KEY:
|
|
continue
|
|
|
|
if sub_key[1] in DIFFICULT_COMBINATIONS:
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_permutations(self):
|
|
"""Get a list of Key objects representing all possible permutations.
|
|
|
|
combining a + b + c should have the same result as b + a + c.
|
|
Only the last key remains the same in the returned result.
|
|
"""
|
|
if len(self.keys) <= 2:
|
|
return [self]
|
|
|
|
permutations = []
|
|
for permutation in itertools.permutations(self.keys[:-1]):
|
|
permutations.append(Key(*permutation, self.keys[-1]))
|
|
|
|
return permutations
|
|
|
|
def beautify(self):
|
|
"""Get a human readable string representation."""
|
|
result = []
|
|
|
|
for sub_key in self:
|
|
if isinstance(sub_key[0], tuple):
|
|
raise Exception("deprecated stuff")
|
|
|
|
ev_type, code, value = sub_key
|
|
|
|
if ev_type not in evdev.ecodes.bytype:
|
|
logger.error("Unknown key type for %s", sub_key)
|
|
result.append(str(code))
|
|
continue
|
|
|
|
if code not in evdev.ecodes.bytype[ev_type]:
|
|
logger.error("Unknown key code for %s", sub_key)
|
|
result.append(str(code))
|
|
continue
|
|
|
|
key_name = None
|
|
|
|
# first try to find the name in xmodmap to not display wrong
|
|
# names due to the keyboard layout
|
|
if ev_type == evdev.ecodes.EV_KEY:
|
|
key_name = system_mapping.get_name(code)
|
|
|
|
if key_name is None:
|
|
# if no result, look in the linux key constants. On a german
|
|
# keyboard for example z and y are switched, which will therefore
|
|
# cause the wrong letter to be displayed.
|
|
key_name = evdev.ecodes.bytype[ev_type][code]
|
|
if isinstance(key_name, list):
|
|
key_name = key_name[0]
|
|
|
|
if ev_type != evdev.ecodes.EV_KEY:
|
|
direction = {
|
|
# D-Pad
|
|
(evdev.ecodes.ABS_HAT0X, -1): "Left",
|
|
(evdev.ecodes.ABS_HAT0X, 1): "Right",
|
|
(evdev.ecodes.ABS_HAT0Y, -1): "Up",
|
|
(evdev.ecodes.ABS_HAT0Y, 1): "Down",
|
|
(evdev.ecodes.ABS_HAT1X, -1): "Left",
|
|
(evdev.ecodes.ABS_HAT1X, 1): "Right",
|
|
(evdev.ecodes.ABS_HAT1Y, -1): "Up",
|
|
(evdev.ecodes.ABS_HAT1Y, 1): "Down",
|
|
(evdev.ecodes.ABS_HAT2X, -1): "Left",
|
|
(evdev.ecodes.ABS_HAT2X, 1): "Right",
|
|
(evdev.ecodes.ABS_HAT2Y, -1): "Up",
|
|
(evdev.ecodes.ABS_HAT2Y, 1): "Down",
|
|
# joystick
|
|
(evdev.ecodes.ABS_X, 1): "Right",
|
|
(evdev.ecodes.ABS_X, -1): "Left",
|
|
(evdev.ecodes.ABS_Y, 1): "Down",
|
|
(evdev.ecodes.ABS_Y, -1): "Up",
|
|
(evdev.ecodes.ABS_RX, 1): "Right",
|
|
(evdev.ecodes.ABS_RX, -1): "Left",
|
|
(evdev.ecodes.ABS_RY, 1): "Down",
|
|
(evdev.ecodes.ABS_RY, -1): "Up",
|
|
# wheel
|
|
(evdev.ecodes.REL_WHEEL, -1): "Down",
|
|
(evdev.ecodes.REL_WHEEL, 1): "Up",
|
|
(evdev.ecodes.REL_HWHEEL, -1): "Left",
|
|
(evdev.ecodes.REL_HWHEEL, 1): "Right",
|
|
}.get((code, value))
|
|
if direction is not None:
|
|
key_name += f" {direction}"
|
|
|
|
key_name = key_name.replace("ABS_Z", "Trigger Left")
|
|
key_name = key_name.replace("ABS_RZ", "Trigger Right")
|
|
|
|
key_name = key_name.replace("ABS_HAT0X", "DPad")
|
|
key_name = key_name.replace("ABS_HAT0Y", "DPad")
|
|
key_name = key_name.replace("ABS_HAT1X", "DPad 2")
|
|
key_name = key_name.replace("ABS_HAT1Y", "DPad 2")
|
|
key_name = key_name.replace("ABS_HAT2X", "DPad 3")
|
|
key_name = key_name.replace("ABS_HAT2Y", "DPad 3")
|
|
|
|
key_name = key_name.replace("ABS_X", "Joystick")
|
|
key_name = key_name.replace("ABS_Y", "Joystick")
|
|
key_name = key_name.replace("ABS_RX", "Joystick 2")
|
|
key_name = key_name.replace("ABS_RY", "Joystick 2")
|
|
|
|
key_name = key_name.replace("BTN_", "Button ")
|
|
key_name = key_name.replace("KEY_", "")
|
|
|
|
key_name = key_name.replace("REL_", "")
|
|
key_name = key_name.replace("HWHEEL", "Wheel")
|
|
key_name = key_name.replace("WHEEL", "Wheel")
|
|
|
|
key_name = key_name.replace("_", " ")
|
|
key_name = key_name.replace(" ", " ")
|
|
|
|
result.append(key_name)
|
|
|
|
return " + ".join(result)
|