Merge remote-tracking branch 'original/main' into led_test
# Conflicts: # keymapper/injection/consumers/keycode_mapper.py # keymapper/injection/macros.pyled_test_merge
commit
8a22db48d4
@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: sezanzeb
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
@ -1,7 +1,7 @@
|
||||
Package: key-mapper
|
||||
Version: 1.0.0
|
||||
Version: 1.1.0
|
||||
Architecture: all
|
||||
Maintainer: Sezanzeb <proxima@sezanzeb.de>
|
||||
Depends: build-essential, libpython3-dev, libdbus-1-dev, python3, python3-setuptools, python3-evdev, python3-pydbus, python3-gi, gettext
|
||||
Depends: build-essential, libpython3-dev, libdbus-1-dev, python3, python3-setuptools, python3-evdev, python3-pydbus, python3-gi, gettext, python3-cairo
|
||||
Description: A tool to change the mapping of your input device buttons
|
||||
Replaces: python3-key-mapper
|
||||
|
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
|
||||
"""One mapping object for the GUI application."""
|
||||
|
||||
|
||||
from keymapper.mapping import Mapping
|
||||
|
||||
|
||||
custom_mapping = Mapping()
|
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""The injection process.
|
||||
|
||||
This folder contains all classes that are only relevant for the injection
|
||||
process. There is one process for each hardware device that is being injected
|
||||
for, and one context object per process that is being passed around for all
|
||||
classes to use.
|
||||
"""
|
@ -0,0 +1,118 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
|
||||
"""Because multiple calls to async_read_loop won't work."""
|
||||
|
||||
|
||||
import asyncio
|
||||
|
||||
import evdev
|
||||
|
||||
from keymapper.injection.consumers.event_producer import EventProducer
|
||||
from keymapper.injection.consumers.keycode_mapper import KeycodeMapper
|
||||
from keymapper.logger import logger
|
||||
|
||||
|
||||
consumer_classes = [
|
||||
KeycodeMapper,
|
||||
EventProducer,
|
||||
]
|
||||
|
||||
|
||||
class ConsumerControl:
|
||||
"""Reads input events from a single device and distributes them.
|
||||
|
||||
There is one Events object for each source, which tells multiple consumers
|
||||
that a new event is ready so that they can inject all sorts of funny
|
||||
things.
|
||||
|
||||
Other devnodes may be present for the hardware device, in which case this
|
||||
needs to be created multiple times.
|
||||
"""
|
||||
|
||||
def __init__(self, context, source, forward_to):
|
||||
"""Initialize all consumers
|
||||
|
||||
Parameters
|
||||
----------
|
||||
source : evdev.InputDevice
|
||||
where to read keycodes from
|
||||
forward_to : evdev.UInput
|
||||
where to write keycodes to that were not mapped to anything.
|
||||
Should be an UInput with capabilities that work for all forwarded
|
||||
events, so ideally they should be copied from source.
|
||||
"""
|
||||
self._source = source
|
||||
self._forward_to = forward_to
|
||||
|
||||
# add all consumers that are enabled for this particular configuration
|
||||
self._consumers = []
|
||||
for Consumer in consumer_classes:
|
||||
consumer = Consumer(context, source, forward_to)
|
||||
if consumer.is_enabled():
|
||||
self._consumers.append(consumer)
|
||||
|
||||
async def run(self):
|
||||
"""Start doing things.
|
||||
|
||||
Can be stopped by stopping the asyncio loop. This loop
|
||||
reads events from a single device only.
|
||||
"""
|
||||
for consumer in self._consumers:
|
||||
# run all of them in parallel
|
||||
asyncio.ensure_future(consumer.run())
|
||||
|
||||
logger.debug(
|
||||
"Starting to listen for events from %s, fd %s",
|
||||
self._source.path,
|
||||
self._source.fd,
|
||||
)
|
||||
|
||||
async for event in self._source.async_read_loop():
|
||||
if event.type == evdev.ecodes.EV_KEY and event.value == 2:
|
||||
# button-hold event. Environments (gnome, etc.) create them on
|
||||
# their own for the injection-fake-device if the release event
|
||||
# won't appear, no need to forward or map them.
|
||||
continue
|
||||
|
||||
handled = False
|
||||
for consumer in self._consumers:
|
||||
# copy so that the consumer doesn't screw this up for
|
||||
# all other future consumers
|
||||
event = evdev.InputEvent(
|
||||
sec=event.sec,
|
||||
usec=event.usec,
|
||||
type=event.type,
|
||||
code=event.code,
|
||||
value=event.value,
|
||||
)
|
||||
if consumer.is_handled(event):
|
||||
await consumer.notify(event)
|
||||
handled = True
|
||||
|
||||
if not handled:
|
||||
# forward the rest
|
||||
self._forward_to.write(event.type, event.code, event.value)
|
||||
# this already includes SYN events, so need to syn here again
|
||||
|
||||
# This happens all the time in tests because the async_read_loop stops when
|
||||
# there is nothing to read anymore. Otherwise tests would block.
|
||||
logger.error('The async_read_loop for "%s" stopped early', self._source.path)
|
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""Consumers
|
||||
|
||||
Each consumer can listen for events and then inject something mapped.
|
||||
"""
|
@ -0,0 +1,88 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
|
||||
"""Consumer base class.
|
||||
|
||||
Can be notified of new events so that inheriting classes can map them and
|
||||
inject new events based on them.
|
||||
"""
|
||||
|
||||
|
||||
class Consumer:
|
||||
"""Can be notified of new events to inject them. Base class."""
|
||||
|
||||
def __init__(self, context, source, forward_to=None):
|
||||
"""Initialize event consuming functionality.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : Context
|
||||
The configuration of the Injector process
|
||||
source : InputDevice
|
||||
Where events used in handle_keycode come from
|
||||
forward_to : evdev.UInput
|
||||
Where to write keycodes to that were not mapped to anything.
|
||||
Should be an UInput with capabilities that work for all forwarded
|
||||
events, so ideally they should be copied from source.
|
||||
"""
|
||||
self.context = context
|
||||
self.forward_to = forward_to
|
||||
self.source = source
|
||||
self.context.update_purposes()
|
||||
|
||||
def is_enabled(self):
|
||||
"""Check if the consumer will have work to do."""
|
||||
raise NotImplementedError
|
||||
|
||||
def write(self, key):
|
||||
"""Shorthand to write stuff."""
|
||||
self.context.uinput.write(*key)
|
||||
self.context.uinput.syn()
|
||||
|
||||
def forward(self, key):
|
||||
"""Shorthand to forward an event."""
|
||||
self.forward_to.write(*key)
|
||||
|
||||
async def notify(self, event):
|
||||
"""A new event is ready.
|
||||
|
||||
Overwrite this function if the consumer should do something each time
|
||||
a new event arrives. E.g. mapping a single button once clicked.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_handled(self, event):
|
||||
"""Check if the consumer will take care of this event.
|
||||
|
||||
If this returns true, the event will not be forwarded anymore
|
||||
automatically. If you want to forward the event after all you can
|
||||
inject it into `self.forward_to`.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def run(self):
|
||||
"""Start doing things.
|
||||
|
||||
Overwrite this function if the consumer should do something
|
||||
continuously even if no new event arrives. e.g. continuously injecting
|
||||
mouse movement events.
|
||||
"""
|
||||
raise NotImplementedError
|
@ -0,0 +1,499 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
|
||||
"""Executes more complex patterns of keystrokes.
|
||||
|
||||
To keep it short on the UI, basic functions are one letter long.
|
||||
|
||||
The outermost macro (in the examples below the one created by 'r',
|
||||
'r' and 'w') will be started, which triggers a chain reaction to execute
|
||||
all of the configured stuff.
|
||||
|
||||
Examples
|
||||
--------
|
||||
r(3, k(a).w(10)): a <10ms> a <10ms> a
|
||||
r(2, k(a).k(-)).k(b): a - a - b
|
||||
w(1000).m(Shift_L, r(2, k(a))).w(10).k(b): <1s> A A <10ms> b
|
||||
"""
|
||||
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
|
||||
from evdev.ecodes import ecodes, EV_KEY, EV_REL, REL_X, REL_Y, REL_WHEEL, REL_HWHEEL
|
||||
|
||||
from keymapper.logger import logger
|
||||
from keymapper.system_mapping import system_mapping
|
||||
from keymapper.ipc.shared_dict import SharedDict
|
||||
from keymapper.utils import PRESS, PRESS_NEGATIVE
|
||||
|
||||
|
||||
macro_variables = SharedDict()
|
||||
|
||||
|
||||
def type_check(display_name, value, allowed_types, position):
|
||||
"""Validate a parameter used in a macro."""
|
||||
for allowed_type in allowed_types:
|
||||
if allowed_type is None:
|
||||
if value is None:
|
||||
return value
|
||||
else:
|
||||
continue
|
||||
|
||||
# try to parse "1" as 1 if possible
|
||||
try:
|
||||
return allowed_type(value)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
if isinstance(value, allowed_type):
|
||||
return value
|
||||
|
||||
raise TypeError(
|
||||
f"Expected parameter {position} for {display_name} to be "
|
||||
f"one of {allowed_types}, but got {value}"
|
||||
)
|
||||
|
||||
|
||||
class Macro:
|
||||
"""Supports chaining and preparing actions.
|
||||
|
||||
Calling functions like keycode on Macro doesn't inject any events yet,
|
||||
it means that once .run is used it will be executed along with all other
|
||||
queued tasks.
|
||||
|
||||
Those functions need to construct an asyncio coroutine and append it to
|
||||
self.tasks. This makes parameter checking during compile time possible.
|
||||
Coroutines receive a handler as argument, which is a function that can be
|
||||
used to inject input events into the system.
|
||||
"""
|
||||
|
||||
def __init__(self, code, context):
|
||||
"""Create a macro instance that can be populated with tasks.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
code : string or None
|
||||
The original parsed code, for logging purposes.
|
||||
context : Context
|
||||
"""
|
||||
self.code = code
|
||||
self.context = context
|
||||
|
||||
# List of coroutines that will be called sequentially.
|
||||
# This is the compiled code
|
||||
self.tasks = []
|
||||
|
||||
# can be used to wait for the release of the event
|
||||
self._holding_event = asyncio.Event()
|
||||
self._holding_event.set() # released by default
|
||||
|
||||
self.running = False
|
||||
|
||||
# all required capabilities, without those of child macros
|
||||
self.capabilities = {
|
||||
EV_KEY: set(),
|
||||
EV_REL: set(),
|
||||
}
|
||||
|
||||
self.child_macros = []
|
||||
|
||||
self.keystroke_sleep_ms = None
|
||||
|
||||
self._new_event_arrived = asyncio.Event()
|
||||
self._newest_event = None
|
||||
self._newest_action = None
|
||||
|
||||
def notify(self, event, action):
|
||||
"""Tell the macro about the newest event."""
|
||||
for macro in self.child_macros:
|
||||
macro.notify(event, action)
|
||||
|
||||
self._newest_event = event
|
||||
self._newest_action = action
|
||||
self._new_event_arrived.set()
|
||||
|
||||
async def wait_for_event(self, filter=None):
|
||||
"""Wait until a specific event arrives.
|
||||
|
||||
The parameters can be used to provide a filter. It will block
|
||||
until an event arrives that matches them.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filter : function
|
||||
Receives the event. Stop waiting if it returns true.
|
||||
"""
|
||||
while True:
|
||||
await self._new_event_arrived.wait()
|
||||
self._new_event_arrived.clear()
|
||||
|
||||
if filter is not None:
|
||||
if not filter(self._newest_event, self._newest_action):
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
def is_holding(self):
|
||||
"""Check if the macro is waiting for a key to be released."""
|
||||
return not self._holding_event.is_set()
|
||||
|
||||
def get_capabilities(self):
|
||||
"""Resolve all capabilities of the macro and those of its children."""
|
||||
capabilities = copy.deepcopy(self.capabilities)
|
||||
|
||||
for macro in self.child_macros:
|
||||
macro_capabilities = macro.get_capabilities()
|
||||
for ev_type in macro_capabilities:
|
||||
if ev_type not in capabilities:
|
||||
capabilities[ev_type] = set()
|
||||
|
||||
capabilities[ev_type].update(macro_capabilities[ev_type])
|
||||
|
||||
return capabilities
|
||||
|
||||
async def run(self, handler):
|
||||
"""Run the macro.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
handler : function
|
||||
Will receive int type, code and value for an event to write
|
||||
"""
|
||||
if self.running:
|
||||
logger.error('Tried to run already running macro "%s"', self.code)
|
||||
return
|
||||
|
||||
# newly arriving events are only interesting if they arrive after the
|
||||
# macro started
|
||||
self._new_event_arrived.clear()
|
||||
|
||||
self.keystroke_sleep_ms = self.context.mapping.get("macros.keystroke_sleep_ms")
|
||||
|
||||
self.running = True
|
||||
for task in self.tasks:
|
||||
# one could call tasks the compiled macros. it's lambda functions
|
||||
# that receive the handler as an argument, so that they know
|
||||
# where to send the event to.
|
||||
coroutine = task(handler)
|
||||
if asyncio.iscoroutine(coroutine):
|
||||
await coroutine
|
||||
|
||||
# done
|
||||
self.running = False
|
||||
|
||||
def press_key(self):
|
||||
"""The user pressed the key down."""
|
||||
if self.is_holding():
|
||||
logger.error("Already holding")
|
||||
return
|
||||
|
||||
self._holding_event.clear()
|
||||
|
||||
for macro in self.child_macros:
|
||||
macro.press_key()
|
||||
|
||||
def release_key(self):
|
||||
"""The user released the key."""
|
||||
self._holding_event.set()
|
||||
|
||||
for macro in self.child_macros:
|
||||
macro.release_key()
|
||||
|
||||
def hold(self, macro=None):
|
||||
"""Loops the execution until key release."""
|
||||
if macro is None:
|
||||
self.tasks.append(lambda _: self._holding_event.wait())
|
||||
return
|
||||
|
||||
if not isinstance(macro, Macro):
|
||||
# if macro is a key name, hold down the key while the
|
||||
# keyboard key is physically held down
|
||||
symbol = str(macro)
|
||||
code = system_mapping.get(symbol)
|
||||
|
||||
if code is None:
|
||||
raise KeyError(f'Unknown key "{symbol}"')
|
||||
|
||||
self.capabilities[EV_KEY].add(code)
|
||||
self.tasks.append(lambda handler: handler(EV_KEY, code, 1))
|
||||
self.tasks.append(lambda _: self._holding_event.wait())
|
||||
self.tasks.append(lambda handler: handler(EV_KEY, code, 0))
|
||||
return
|
||||
|
||||
if isinstance(macro, Macro):
|
||||
# repeat the macro forever while the key is held down
|
||||
async def task(handler):
|
||||
while self.is_holding():
|
||||
# run the child macro completely to avoid
|
||||
# not-releasing any key
|
||||
await macro.run(handler)
|
||||
|
||||
self.tasks.append(task)
|
||||
self.child_macros.append(macro)
|
||||
|
||||
def modify(self, modifier, macro):
|
||||
"""Do stuff while a modifier is activated.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
modifier : str
|
||||
macro : Macro
|
||||
"""
|
||||
type_check("m (modify)", macro, [Macro], 2)
|
||||
|
||||
modifier = str(modifier)
|
||||
code = system_mapping.get(modifier)
|
||||
|
||||
if code is None:
|
||||
raise KeyError(f'Unknown modifier "{modifier}"')
|
||||
|
||||
self.capabilities[EV_KEY].add(code)
|
||||
|
||||
self.child_macros.append(macro)
|
||||
|
||||
self.tasks.append(lambda handler: handler(EV_KEY, code, 1))
|
||||
self.tasks.append(self._keycode_pause)
|
||||
self.tasks.append(macro.run)
|
||||
self.tasks.append(self._keycode_pause)
|
||||
self.tasks.append(lambda handler: handler(EV_KEY, code, 0))
|
||||
self.tasks.append(self._keycode_pause)
|
||||
|
||||
def repeat(self, repeats, macro):
|
||||
"""Repeat actions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
repeats : int or Macro
|
||||
macro : Macro
|
||||
"""
|
||||
repeats = type_check("r (repeat)", repeats, [int], 1)
|
||||
type_check("r (repeat)", macro, [Macro], 2)
|
||||
|
||||
async def repeat(handler):
|
||||
for _ in range(repeats):
|
||||
await macro.run(handler)
|
||||
|
||||
self.tasks.append(repeat)
|
||||
self.child_macros.append(macro)
|
||||
|
||||
async def _keycode_pause(self, _=None):
|
||||
"""To add a pause between keystrokes."""
|
||||
await asyncio.sleep(self.keystroke_sleep_ms / 1000)
|
||||
|
||||
def keycode(self, symbol):
|
||||
"""Write the symbol."""
|
||||
symbol = str(symbol)
|
||||
code = system_mapping.get(symbol)
|
||||
|
||||
if code is None:
|
||||
raise KeyError(f'Unknown key "{symbol}"')
|
||||
|
||||
self.capabilities[EV_KEY].add(code)
|
||||
|
||||
async def keycode(handler):
|
||||
handler(EV_KEY, code, 1)
|
||||
await self._keycode_pause()
|
||||
handler(EV_KEY, code, 0)
|
||||
await self._keycode_pause()
|
||||
|
||||
self.tasks.append(keycode)
|
||||
|
||||
def event(self, ev_type, code, value):
|
||||
"""Write any event.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ev_type: str or int
|
||||
examples: 2, 'EV_KEY'
|
||||
code : int or int
|
||||
examples: 52, 'KEY_A'
|
||||
value : int
|
||||
"""
|
||||
if isinstance(ev_type, str):
|
||||
ev_type = ecodes[ev_type.upper()]
|
||||
if isinstance(code, str):
|
||||
code = ecodes[code.upper()]
|
||||
|
||||
if ev_type not in self.capabilities:
|
||||
self.capabilities[ev_type] = set()
|
||||
|
||||
if ev_type == EV_REL:
|
||||
# add all capabilities that are required for the display server
|
||||
# to recognize the device as mouse
|
||||
self.capabilities[EV_REL].add(REL_X)
|
||||
self.capabilities[EV_REL].add(REL_Y)
|
||||
self.capabilities[EV_REL].add(REL_WHEEL)
|
||||
|
||||
self.capabilities[ev_type].add(code)
|
||||
|
||||
self.tasks.append(lambda handler: handler(ev_type, code, value))
|
||||
self.tasks.append(self._keycode_pause)
|
||||
|
||||
def mouse(self, direction, speed):
|
||||
"""Shortcut for h(e(...))."""
|
||||
type_check("mouse", direction, [str], 1)
|
||||
speed = type_check("mouse", speed, [int], 2)
|
||||
|
||||
code, value = {
|
||||
"up": (REL_Y, -1),
|
||||
"down": (REL_Y, 1),
|
||||
"left": (REL_X, -1),
|
||||
"right": (REL_X, 1),
|
||||
}[direction.lower()]
|
||||
value *= speed
|
||||
child_macro = Macro(None, self.context)
|
||||
child_macro.event(EV_REL, code, value)
|
||||
self.hold(child_macro)
|
||||
|
||||
def wheel(self, direction, speed):
|
||||
"""Shortcut for h(e(...))."""
|
||||
type_check("wheel", direction, [str], 1)
|
||||
speed = type_check("wheel", speed, [int], 2)
|
||||
|
||||
code, value = {
|
||||
"up": (REL_WHEEL, 1),
|
||||
"down": (REL_WHEEL, -1),
|
||||
"left": (REL_HWHEEL, 1),
|
||||
"right": (REL_HWHEEL, -1),
|
||||
}[direction.lower()]
|
||||
child_macro = Macro(None, self.context)
|
||||
child_macro.event(EV_REL, code, value)
|
||||
child_macro.wait(100 / speed)
|
||||
self.hold(child_macro)
|
||||
|
||||
def wait(self, sleeptime):
|
||||
"""Wait time in milliseconds."""
|
||||
sleeptime = type_check("wait", sleeptime, [int, float], 1) / 1000
|
||||
|
||||
async def sleep(_):
|
||||
await asyncio.sleep(sleeptime)
|
||||
|
||||
self.tasks.append(sleep)
|
||||
|
||||
def set(self, variable, value):
|
||||
"""Set a variable to a certain value."""
|
||||
|
||||
async def set(_):
|
||||
logger.debug('"%s" set to "%s"', variable, value)
|
||||
macro_variables[variable] = value
|
||||
|
||||
self.tasks.append(set)
|
||||
|
||||
def ifeq(self, variable, value, then, otherwise=None):
|
||||
"""Perform an equality check.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
variable : string
|
||||
value : string | number
|
||||
then : Macro | None
|
||||
otherwise : Macro | None
|
||||
"""
|
||||
type_check("ifeq", then, [Macro, None], 1)
|
||||
type_check("ifeq", otherwise, [Macro, None], 2)
|
||||
|
||||
async def ifeq(handler):
|
||||
set_value = macro_variables.get(variable)
|
||||
logger.debug('"%s" is "%s"', variable, set_value)
|
||||
if set_value == value:
|
||||
if then is not None:
|
||||
await then.run(handler)
|
||||
elif otherwise is not None:
|
||||
await otherwise.run(handler)
|
||||
|
||||
if isinstance(then, Macro):
|
||||
self.child_macros.append(then)
|
||||
if isinstance(otherwise, Macro):
|
||||
self.child_macros.append(otherwise)
|
||||
|
||||
self.tasks.append(ifeq)
|
||||
|
||||
def if_tap(self, then=None, otherwise=None, timeout=300):
|
||||
"""If a key was pressed quickly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
then : Macro | None
|
||||
otherwise : Macro | None
|
||||
timeout : int
|
||||
"""
|
||||
type_check("if_tap", then, [Macro, None], 1)
|
||||
type_check("if_tap", otherwise, [Macro, None], 2)
|
||||
timeout = type_check("if_tap", timeout, [int], 3)
|
||||
|
||||
if isinstance(then, Macro):
|
||||
self.child_macros.append(then)
|
||||
if isinstance(otherwise, Macro):
|
||||
self.child_macros.append(otherwise)
|
||||
|
||||
async def if_tap(handler):
|
||||
try:
|
||||
coroutine = self._holding_event.wait()
|
||||
await asyncio.wait_for(coroutine, timeout / 1000)
|
||||
if then:
|
||||
await then.run(handler)
|
||||
except asyncio.TimeoutError:
|
||||
if otherwise:
|
||||
await otherwise.run(handler)
|
||||
|
||||
self.tasks.append(if_tap)
|
||||
|
||||
def if_single(self, then, otherwise):
|
||||
"""If a key was pressed without combining it.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
then : Macro | None
|
||||
otherwise : Macro | None
|
||||
"""
|
||||
type_check("if_single", then, [Macro, None], 1)
|
||||
type_check("if_single", otherwise, [Macro, None], 2)
|
||||
|
||||
if isinstance(then, Macro):
|
||||
self.child_macros.append(then)
|
||||
if isinstance(otherwise, Macro):
|
||||
self.child_macros.append(otherwise)
|
||||
|
||||
async def if_single(handler):
|
||||
mappable_event_1 = (self._newest_event.type, self._newest_event.code)
|
||||
|
||||
def event_filter(event, action):
|
||||
"""Which event may wake if_tap up."""
|
||||
# release event of the actual key
|
||||
if (event.type, event.code) == mappable_event_1:
|
||||
return True
|
||||
|
||||
# press event of another key
|
||||
if action in (PRESS, PRESS_NEGATIVE):
|
||||
return True
|
||||
|
||||
await self.wait_for_event(event_filter)
|
||||
|
||||
mappable_event_2 = (self._newest_event.type, self._newest_event.code)
|
||||
|
||||
combined = mappable_event_1 != mappable_event_2
|
||||
if then and not combined:
|
||||
await then.run(handler)
|
||||
elif otherwise:
|
||||
await otherwise.run(handler)
|
||||
|
||||
self.tasks.append(if_single)
|
@ -0,0 +1,280 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
|
||||
"""Parse macro code"""
|
||||
|
||||
|
||||
import re
|
||||
import traceback
|
||||
import inspect
|
||||
|
||||
from keymapper.logger import logger
|
||||
from keymapper.injection.macros.macro import Macro
|
||||
|
||||
|
||||
def is_this_a_macro(output):
|
||||
"""Figure out if this is a macro."""
|
||||
if not isinstance(output, str):
|
||||
return False
|
||||
|
||||
if "+" in output.strip():
|
||||
# for example "a + b"
|
||||
return True
|
||||
|
||||
return "(" in output and ")" in output and len(output) >= 4
|
||||
|
||||
|
||||
FUNCTIONS = {
|
||||
"m": Macro.modify,
|
||||
"r": Macro.repeat,
|
||||
"k": Macro.keycode,
|
||||
"e": Macro.event,
|
||||
"w": Macro.wait,
|
||||
"h": Macro.hold,
|
||||
"mouse": Macro.mouse,
|
||||
"wheel": Macro.wheel,
|
||||
"ifeq": Macro.ifeq,
|
||||
"set": Macro.set,
|
||||
"if_tap": Macro.if_tap,
|
||||
"if_single": Macro.if_single,
|
||||
}
|
||||
|
||||
|
||||
def get_num_parameters(function):
|
||||
"""Get the number of required parameters and the maximum number of parameters."""
|
||||
fullargspec = inspect.getfullargspec(function)
|
||||
num_args = len(fullargspec.args) - 1 # one is `self`
|
||||
return num_args - len(fullargspec.defaults or ()), num_args
|
||||
|
||||
|
||||
def _extract_params(inner):
|
||||
"""Extract parameters from the inner contents of a call.
|
||||
|
||||
This does not parse them.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
inner : string
|
||||
for example '1, r, r(2, k(a))' should result in ['1', 'r', 'r(2, k(a))']
|
||||
"""
|
||||
inner = inner.strip()
|
||||
brackets = 0
|
||||
params = []
|
||||
start = 0
|
||||
for position, char in enumerate(inner):
|
||||
if char == "(":
|
||||
brackets += 1
|
||||
if char == ")":
|
||||
brackets -= 1
|
||||
if char == "," and brackets == 0:
|
||||
# , potentially starts another parameter, but only if
|
||||
# the current brackets are all closed.
|
||||
params.append(inner[start:position].strip())
|
||||
# skip the comma
|
||||
start = position + 1
|
||||
|
||||
# one last parameter
|
||||
params.append(inner[start:].strip())
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def _count_brackets(macro):
|
||||
"""Find where the first opening bracket closes."""
|
||||
openings = macro.count("(")
|
||||
closings = macro.count(")")
|
||||
if openings != closings:
|
||||
raise Exception(
|
||||
f"You entered {openings} opening and {closings} " "closing brackets"
|
||||
)
|
||||
|
||||
brackets = 0
|
||||
position = 0
|
||||
for char in macro:
|
||||
position += 1
|
||||
if char == "(":
|
||||
brackets += 1
|
||||
continue
|
||||
|
||||
if char == ")":
|
||||
brackets -= 1
|
||||
if brackets == 0:
|
||||
# the closing bracket of the call
|
||||
break
|
||||
|
||||
return position
|
||||
|
||||
|
||||
def _parse_recurse(macro, context, macro_instance=None, depth=0):
|
||||
"""Handle a subset of the macro, e.g. one parameter or function call.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
macro : string
|
||||
Just like parse
|
||||
context : Context
|
||||
macro_instance : Macro or None
|
||||
A macro instance to add tasks to
|
||||
depth : int
|
||||
"""
|
||||
# not using eval for security reasons
|
||||
assert isinstance(macro, str)
|
||||
assert isinstance(depth, int)
|
||||
|
||||
if macro == "":
|
||||
return None
|
||||
|
||||
if macro_instance is None:
|
||||
macro_instance = Macro(macro, context)
|
||||
else:
|
||||
assert isinstance(macro_instance, Macro)
|
||||
|
||||
macro = macro.strip()
|
||||
space = " " * depth
|
||||
|
||||
# is it another macro?
|
||||
call_match = re.match(r"^(\w+)\(", macro)
|
||||
call = call_match[1] if call_match else None
|
||||
if call is not None:
|
||||
# available functions in the macro and the minimum and maximum number
|
||||
# of their parameters
|
||||
function = FUNCTIONS.get(call)
|
||||
if function is None:
|
||||
raise Exception(f"Unknown function {call}")
|
||||
|
||||
# get all the stuff inbetween
|
||||
position = _count_brackets(macro)
|
||||
|
||||
inner = macro[macro.index("(") + 1 : position - 1]
|
||||
|
||||
# split "3, k(a).w(10)" into parameters
|
||||
string_params = _extract_params(inner)
|
||||
logger.spam("%scalls %s with %s", space, call, string_params)
|
||||
# evaluate the params
|
||||
params = [
|
||||
_parse_recurse(param.strip(), context, None, depth + 1)
|
||||
for param in string_params
|
||||
]
|
||||
|
||||
logger.spam("%sadd call to %s with %s", space, call, params)
|
||||
|
||||
min_params, max_params = get_num_parameters(function)
|
||||
if len(params) < min_params or len(params) > max_params:
|
||||
if min_params != max_params:
|
||||
msg = (
|
||||
f"{call} takes between {min_params} and {max_params}, "
|
||||
f"not {len(params)} parameters"
|
||||
)
|
||||
else:
|
||||
msg = f"{call} takes {min_params}, " f"not {len(params)} parameters"
|
||||
|
||||
raise ValueError(msg)
|
||||
|
||||
function(macro_instance, *params)
|
||||
|
||||
# is after this another call? Chain it to the macro_instance
|
||||
if len(macro) > position and macro[position] == ".":
|
||||
chain = macro[position + 1 :]
|
||||
logger.spam("%sfollowed by %s", space, chain)
|
||||
_parse_recurse(chain, context, macro_instance, depth)
|
||||
|
||||
return macro_instance
|
||||
|
||||
# probably a parameter for an outer function
|
||||
try:
|
||||
# if possible, parse as int
|
||||
macro = int(macro)
|
||||
except ValueError:
|
||||
# use as string instead
|
||||
pass
|
||||
|
||||
logger.spam("%s%s %s", space, type(macro), macro)
|
||||
return macro
|
||||
|
||||
|
||||
def handle_plus_syntax(macro):
|
||||
"""transform a + b + c to m(a, m(b, m(c, h())))"""
|
||||
if "+" not in macro:
|
||||
return macro
|
||||
|
||||
if "(" in macro or ")" in macro:
|
||||
logger.error('Mixing "+" and macros is unsupported: "%s"', macro)
|
||||
return macro
|
||||
|
||||
chunks = [chunk.strip() for chunk in macro.split("+")]
|
||||
output = ""
|
||||
depth = 0
|
||||
for chunk in chunks:
|
||||
if chunk == "":
|
||||
# invalid syntax
|
||||
logger.error('Invalid syntax for "%s"', macro)
|
||||
return macro
|
||||
|
||||
depth += 1
|
||||
output += f"m({chunk},"
|
||||
|
||||
output += "h()"
|
||||
output += depth * ")"
|
||||
|
||||
logger.debug('Transformed "%s" to "%s"', macro, output)
|
||||
return output
|
||||
|
||||
|
||||
def parse(macro, context, return_errors=False):
|
||||
"""parse and generate a Macro that can be run as often as you want.
|
||||
|
||||
If it could not be parsed, possibly due to syntax errors, will log the
|
||||
error and return None.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
macro : string
|
||||
"r(3, k(a).w(10))"
|
||||
"r(2, k(a).k(-)).k(b)"
|
||||
"w(1000).m(Shift_L, r(2, k(a))).w(10, 20).k(b)"
|
||||
context : Context
|
||||
return_errors : bool
|
||||
If True, returns errors as a string or None if parsing worked.
|
||||
If False, returns the parsed macro.
|
||||
"""
|
||||
macro = handle_plus_syntax(macro)
|
||||
|
||||
# whitespaces, tabs, newlines and such don't serve a purpose. make
|
||||
# the log output clearer and the parsing easier.
|
||||
macro = re.sub(r"\s", "", macro)
|
||||
|
||||
if '"' in macro or "'" in macro:
|
||||
logger.info("Quotation marks in macros are not needed")
|
||||
macro = macro.replace('"', "").replace("'", "")
|
||||
|
||||
if return_errors:
|
||||
logger.spam("checking the syntax of %s", macro)
|
||||
else:
|
||||
logger.spam("preparing macro %s for later execution", macro)
|
||||
|
||||
try:
|
||||
macro_object = _parse_recurse(macro, context)
|
||||
return macro_object if not return_errors else None
|
||||
except Exception as error:
|
||||
logger.error('Failed to parse macro "%s": %s', macro, error.__repr__())
|
||||
# print the traceback in case this is a bug of key-mapper
|
||||
logger.debug("".join(traceback.format_tb(error.__traceback__)).strip())
|
||||
return f"{error.__class__.__name__}: {str(error)}" if return_errors else None
|
@ -1,6 +0,0 @@
|
||||
# Injection
|
||||
|
||||
This folder contains all classes that are only relevant for the injection
|
||||
process. There is one process for each hardware device that is being injected
|
||||
for, and one context object per process that is being passed around for all
|
||||
classes to use.
|
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""Since I'm not forking, I can't use multiprocessing.Pipe.
|
||||
|
||||
Processes that need privileges are spawned with pkexec, which connect to
|
||||
known pipe paths to communicate with the non-privileged parent process.
|
||||
"""
|
@ -1,7 +0,0 @@
|
||||
# IPC
|
||||
|
||||
Since I'm not forking, I can't use the handy multiprocessing.Pipe
|
||||
method anymore.
|
||||
|
||||
Processes that need privileges are spawned with pkexec, which connect to
|
||||
known pipe paths to communicate with the non-privileged parent process.
|
@ -0,0 +1,109 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
|
||||
"""Share a dictionary across processes."""
|
||||
|
||||
|
||||
import multiprocessing
|
||||
import atexit
|
||||
import select
|
||||
|
||||
from keymapper.logger import logger
|
||||
|
||||
|
||||
class SharedDict:
|
||||
"""Share a dictionary across processes."""
|
||||
|
||||
# because unittests terminate all child processes in cleanup I can't use
|
||||
# multiprocessing.Manager
|
||||
def __init__(self):
|
||||
"""Create a shared dictionary."""
|
||||
super().__init__()
|
||||
self.pipe = multiprocessing.Pipe()
|
||||
self.process = None
|
||||
atexit.register(self._stop)
|
||||
self._start()
|
||||
|
||||
# To avoid blocking forever if something goes wrong. The maximum
|
||||
# observed time communication takes was 0.001 for me on a slow pc
|
||||
self._timeout = 0.02
|
||||
|
||||
def _start(self):
|
||||
"""Ensure the process to manage the dictionary is running."""
|
||||
if self.process is not None and self.process.is_alive():
|
||||
return
|
||||
|
||||
# if the manager has already been running in the past but stopped
|
||||
# for some reason, the dictionary contents are lost
|
||||
self.process = multiprocessing.Process(target=self.manage)
|
||||
self.process.start()
|
||||
|
||||
def manage(self):
|
||||
"""Manage the dictionary, handle read and write requests."""
|
||||
shared_dict = dict()
|
||||
while True:
|
||||
message = self.pipe[0].recv()
|
||||
logger.spam("SharedDict got %s", message)
|
||||
|
||||
if message[0] == "stop":
|
||||
return
|
||||
|
||||
if message[0] == "set":
|
||||
shared_dict[message[1]] = message[2]
|
||||
|
||||
if message[0] == "get":
|
||||
self.pipe[0].send(shared_dict.get(message[1]))
|
||||
|
||||
if message[0] == "ping":
|
||||
self.pipe[0].send("pong")
|
||||
|
||||
def _stop(self):
|
||||
"""Stop the managing process."""
|
||||
self.pipe[1].send(("stop",))
|
||||
|
||||
def get(self, key):
|
||||
"""Get a value from the dictionary."""
|
||||
return self.__getitem__(key)
|
||||
|
||||
def is_alive(self, timeout=None):
|
||||
"""Check if the manager process is running."""
|
||||
self.pipe[1].send(("ping",))
|
||||
select.select([self.pipe[1]], [], [], timeout or self._timeout)
|
||||
if self.pipe[1].poll():
|
||||
return self.pipe[1].recv() == "pong"
|
||||
|
||||
return False
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.pipe[1].send(("set", key, value))
|
||||
|
||||
def __getitem__(self, key):
|
||||
self.pipe[1].send(("get", key))
|
||||
|
||||
select.select([self.pipe[1]], [], [], self._timeout)
|
||||
if self.pipe[1].poll():
|
||||
return self.pipe[1].recv()
|
||||
|
||||
logger.error("select.select timed out")
|
||||
return None
|
||||
|
||||
def __del__(self):
|
||||
self._stop()
|
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,204 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# key-mapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
|
||||
import unittest
|
||||
import asyncio
|
||||
|
||||
import evdev
|
||||
from evdev.ecodes import EV_KEY, EV_ABS, ABS_Y, EV_REL
|
||||
|
||||
from keymapper.injection.consumers.keycode_mapper import active_macros
|
||||
from keymapper.config import BUTTONS, MOUSE, WHEEL
|
||||
|
||||
from keymapper.injection.context import Context
|
||||
from keymapper.mapping import Mapping
|
||||
from keymapper.key import Key
|
||||
from keymapper.injection.consumer_control import ConsumerControl, consumer_classes
|
||||
from keymapper.injection.consumers.consumer import Consumer
|
||||
from keymapper.injection.consumers.keycode_mapper import KeycodeMapper
|
||||
from keymapper.system_mapping import system_mapping
|
||||
|
||||
from tests.test import new_event, quick_cleanup
|
||||
|
||||
|
||||
class ExampleConsumer(Consumer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def is_enabled(self):
|
||||
return True
|
||||
|
||||
async def notify(self, event):
|
||||
pass
|
||||
|
||||
def is_handled(self, event):
|
||||
pass
|
||||
|
||||
async def run(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestConsumerControl(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self):
|
||||
consumer_classes.append(ExampleConsumer)
|
||||
self.gamepad_source = evdev.InputDevice("/dev/input/event30")
|
||||
self.mapping = Mapping()
|
||||
|
||||
def tearDown(self):
|
||||
quick_cleanup()
|
||||
consumer_classes.remove(ExampleConsumer)
|
||||
|
||||
def setup(self, source, mapping):
|
||||
"""Set a a ConsumerControl up for the test and run it in the background."""
|
||||
forward_to = evdev.UInput()
|
||||
context = Context(mapping)
|
||||
context.uinput = evdev.UInput()
|
||||
consumer_control = ConsumerControl(context, source, forward_to)
|
||||
for consumer in consumer_control._consumers:
|
||||
consumer._abs_range = (-10, 10)
|
||||
asyncio.ensure_future(consumer_control.run())
|
||||
return context, consumer_control
|
||||
|
||||
async def test_no_keycode_mapper_needed(self):
|
||||
self.mapping.change(Key(EV_KEY, 1, 1), "b")
|
||||
_, consumer_control = self.setup(self.gamepad_source, self.mapping)
|
||||
consumer_types = [type(consumer) for consumer in consumer_control._consumers]
|
||||
self.assertIn(KeycodeMapper, consumer_types)
|
||||
|
||||
self.mapping.empty()
|
||||
_, consumer_control = self.setup(self.gamepad_source, self.mapping)
|
||||
consumer_types = [type(consumer) for consumer in consumer_control._consumers]
|
||||
self.assertNotIn(KeycodeMapper, consumer_types)
|
||||
|
||||
self.mapping.change(Key(EV_KEY, 1, 1), "k(a)")
|
||||
_, consumer_control = self.setup(self.gamepad_source, self.mapping)
|
||||
consumer_types = [type(consumer) for consumer in consumer_control._consumers]
|
||||
self.assertIn(KeycodeMapper, consumer_types)
|
||||
|
||||
async def test_if_single_joystick_then(self):
|
||||
# Integration test style for if_single.
|
||||
# won't care about the event, because the purpose is not set to BUTTON
|
||||
code_a = system_mapping.get("a")
|
||||
code_shift = system_mapping.get("KEY_LEFTSHIFT")
|
||||
trigger = 1
|
||||
self.mapping.change(
|
||||
Key(EV_KEY, trigger, 1), "if_single(k(a), k(KEY_LEFTSHIFT))"
|
||||
)
|
||||
self.mapping.change(Key(EV_ABS, ABS_Y, 1), "b")
|
||||
|
||||
self.mapping.set("gamepad.joystick.left_purpose", MOUSE)
|
||||
self.mapping.set("gamepad.joystick.right_purpose", WHEEL)
|
||||
context, _ = self.setup(self.gamepad_source, self.mapping)
|
||||
|
||||
self.gamepad_source.push_events(
|
||||
[
|
||||
new_event(EV_KEY, trigger, 1), # start the macro
|
||||
new_event(EV_ABS, ABS_Y, 10), # ignored
|
||||
new_event(EV_KEY, 2, 2), # ignored
|
||||
new_event(EV_KEY, 2, 0), # ignored
|
||||
new_event(EV_REL, 1, 1), # ignored
|
||||
new_event(
|
||||
EV_KEY, trigger, 0
|
||||
), # stop it, the only way to trigger `then`
|
||||
]
|
||||
)
|
||||
await asyncio.sleep(0.1)
|
||||
self.assertFalse(active_macros[(EV_KEY, 1)].running)
|
||||
history = [a.t for a in context.uinput.write_history]
|
||||
self.assertIn((EV_KEY, code_a, 1), history)
|
||||
self.assertIn((EV_KEY, code_a, 0), history)
|
||||
self.assertNotIn((EV_KEY, code_shift, 1), history)
|
||||
self.assertNotIn((EV_KEY, code_shift, 0), history)
|
||||
|
||||
async def test_if_single_joystick_else(self):
|
||||
"""triggers else + delayed_handle_keycode"""
|
||||
# Integration test style for if_single.
|
||||
# If a joystick that is mapped to a button is moved, if_single stops
|
||||
code_b = system_mapping.get("b")
|
||||
code_shift = system_mapping.get("KEY_LEFTSHIFT")
|
||||
trigger = 1
|
||||
self.mapping.change(
|
||||
Key(EV_KEY, trigger, 1), "if_single(k(a), k(KEY_LEFTSHIFT))"
|
||||
)
|
||||
self.mapping.change(Key(EV_ABS, ABS_Y, 1), "b")
|
||||
|
||||
self.mapping.set("gamepad.joystick.left_purpose", BUTTONS)
|
||||
self.mapping.set("gamepad.joystick.right_purpose", BUTTONS)
|
||||
context, _ = self.setup(self.gamepad_source, self.mapping)
|
||||
|
||||
self.gamepad_source.push_events(
|
||||
[
|
||||
new_event(EV_KEY, trigger, 1), # start the macro
|
||||
new_event(EV_ABS, ABS_Y, 10), # not ignored, stops it
|
||||
]
|
||||
)
|
||||
await asyncio.sleep(0.1)
|
||||
self.assertFalse(active_macros[(EV_KEY, 1)].running)
|
||||
history = [a.t for a in context.uinput.write_history]
|
||||
|
||||
# the key that triggered if_single should be injected after
|
||||
# if_single had a chance to inject keys (if the macro is fast enough),
|
||||
# so that if_single can inject a modifier to e.g. capitalize the
|
||||
# triggering key. This is important for the space cadet shift
|
||||
self.assertListEqual(
|
||||
history,
|
||||
[
|
||||
(EV_KEY, code_shift, 1),
|
||||
(EV_KEY, code_b, 1), # would be capitalized now
|
||||
(EV_KEY, code_shift, 0),
|
||||
],
|
||||
)
|
||||
|
||||
async def test_if_single_joystick_under_threshold(self):
|
||||
"""triggers then because the joystick events value is too low."""
|
||||
code_a = system_mapping.get("a")
|
||||
trigger = 1
|
||||
self.mapping.change(
|
||||
Key(EV_KEY, trigger, 1), "if_single(k(a), k(KEY_LEFTSHIFT))"
|
||||
)
|
||||
self.mapping.change(Key(EV_ABS, ABS_Y, 1), "b")
|
||||
|
||||
self.mapping.set("gamepad.joystick.left_purpose", BUTTONS)
|
||||
self.mapping.set("gamepad.joystick.right_purpose", BUTTONS)
|
||||
context, _ = self.setup(self.gamepad_source, self.mapping)
|
||||
|
||||
self.gamepad_source.push_events(
|
||||
[
|
||||
new_event(EV_KEY, trigger, 1), # start the macro
|
||||
new_event(EV_ABS, ABS_Y, 1), # ignored because value too low
|
||||
new_event(EV_KEY, trigger, 0), # stop, only way to trigger `then`
|
||||
]
|
||||
)
|
||||
await asyncio.sleep(0.1)
|
||||
self.assertFalse(active_macros[(EV_KEY, 1)].running)
|
||||
history = [a.t for a in context.uinput.write_history]
|
||||
|
||||
# the key that triggered if_single should be injected after
|
||||
# if_single had a chance to inject keys (if the macro is fast enough),
|
||||
# so that if_single can inject a modifier to e.g. capitalize the
|
||||
# triggering key. This is important for the space cadet shift
|
||||
self.assertListEqual(
|
||||
history,
|
||||
[
|
||||
(EV_KEY, code_a, 1),
|
||||
(EV_KEY, code_a, 0),
|
||||
],
|
||||
)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,108 +1,108 @@
|
||||
xmodmap = (
|
||||
b'keycode 8 =\nkeycode 9 = Escape NoSymbol Escape\nkeycode 10 = 1 exclam 1 exclam onesuperior exclamdown ones'
|
||||
b'uperior\nkeycode 11 = 2 quotedbl 2 quotedbl twosuperior oneeighth twosuperior\nkeycode 12 = 3 section 3 sectio'
|
||||
b'n threesuperior sterling threesuperior\nkeycode 13 = 4 dollar 4 dollar onequarter currency onequarter\nkeycode '
|
||||
b' 14 = 5 percent 5 percent onehalf threeeighths onehalf\nkeycode 15 = 6 ampersand 6 ampersand notsign fiveeighth'
|
||||
b's notsign\nkeycode 16 = 7 slash 7 slash braceleft seveneighths braceleft\nkeycode 17 = 8 parenleft 8 parenleft'
|
||||
b' bracketleft trademark bracketleft\nkeycode 18 = 9 parenright 9 parenright bracketright plusminus bracketright'
|
||||
b'\nkeycode 19 = 0 equal 0 equal braceright degree braceright\nkeycode 20 = ssharp question ssharp question back'
|
||||
b'slash questiondown U1E9E\nkeycode 21 = dead_acute dead_grave dead_acute dead_grave dead_cedilla dead_ogonek dea'
|
||||
b'd_cedilla\nkeycode 22 = BackSpace BackSpace BackSpace BackSpace\nkeycode 23 = Tab ISO_Left_Tab Tab ISO_Left_Ta'
|
||||
b'b\nkeycode 24 = q Q q Q at Greek_OMEGA at\nkeycode 25 = w W w W lstroke Lstroke lstroke\nkeycode 26 = e E e E'
|
||||
b' EuroSign EuroSign EuroSign\nkeycode 27 = r R r R paragraph registered paragraph\nkeycode 28 = t T t T tslash '
|
||||
b'Tslash tslash\nkeycode 29 = z Z z Z leftarrow yen leftarrow\nkeycode 30 = u U u U downarrow uparrow downarrow'
|
||||
b'\nkeycode 31 = i I i I rightarrow idotless rightarrow\nkeycode 32 = o O o O oslash Oslash oslash\nkeycode 33 '
|
||||
b'= p P p P thorn THORN thorn\nkeycode 34 = udiaeresis Udiaeresis udiaeresis Udiaeresis dead_diaeresis dead_above'
|
||||
b'ring dead_diaeresis\nkeycode 35 = plus asterisk plus asterisk asciitilde macron asciitilde\nkeycode 36 = Retur'
|
||||
b'n NoSymbol Return\nkeycode 37 = Control_L NoSymbol Control_L\nkeycode 38 = a A a A ae AE ae\nkeycode 39 = s S'
|
||||
b' s S U017F U1E9E U017F\nkeycode 40 = d D d D eth ETH eth\nkeycode 41 = f F f F dstroke ordfeminine dstroke\nke'
|
||||
b'ycode 42 = g G g G eng ENG eng\nkeycode 43 = h H h H hstroke Hstroke hstroke\nkeycode 44 = j J j J dead_below'
|
||||
b'dot dead_abovedot dead_belowdot\nkeycode 45 = k K k K kra ampersand kra\nkeycode 46 = l L l L lstroke Lstroke '
|
||||
b'lstroke\nkeycode 47 = odiaeresis Odiaeresis odiaeresis Odiaeresis dead_doubleacute dead_belowdot dead_doubleacu'
|
||||
b'te\nkeycode 48 = adiaeresis Adiaeresis adiaeresis Adiaeresis dead_circumflex dead_caron dead_circumflex\nkeycod'
|
||||
b'e 49 = dead_circumflex degree dead_circumflex degree U2032 U2033 U2032\nkeycode 50 = Shift_L NoSymbol Shift_L'
|
||||
b'\nkeycode 51 = numbersign apostrophe numbersign apostrophe rightsinglequotemark dead_breve rightsinglequotemark'
|
||||
b'\nkeycode 52 = y Y y Y guillemotright U203A guillemotright\nkeycode 53 = x X x X guillemotleft U2039 guillemot'
|
||||
b'left\nkeycode 54 = c C c C cent copyright cent\nkeycode 55 = v V v V doublelowquotemark singlelowquotemark dou'
|
||||
b'blelowquotemark\nkeycode 56 = b B b B leftdoublequotemark leftsinglequotemark leftdoublequotemark\nkeycode 57 '
|
||||
b'= n N n N rightdoublequotemark rightsinglequotemark rightdoublequotemark\nkeycode 58 = m M m M mu masculine mu'
|
||||
b'\nkeycode 59 = comma semicolon comma semicolon periodcentered multiply periodcentered\nkeycode 60 = period col'
|
||||
b'on period colon U2026 division U2026\nkeycode 61 = minus underscore minus underscore endash emdash endash\nkeyc'
|
||||
b'ode 62 = Shift_R NoSymbol Shift_R\nkeycode 63 = KP_Multiply KP_Multiply KP_Multiply KP_Multiply KP_Multiply KP'
|
||||
b'_Multiply XF86ClearGrab\nkeycode 64 = Alt_L Meta_L Alt_L Meta_L\nkeycode 65 = space NoSymbol space\nkeycode 6'
|
||||
b'6 = Caps_Lock NoSymbol Caps_Lock\nkeycode 67 = F1 F1 F1 F1 F1 F1 XF86Switch_VT_1\nkeycode 68 = F2 F2 F2 F2 F2 '
|
||||
b'F2 XF86Switch_VT_2\nkeycode 69 = F3 F3 F3 F3 F3 F3 XF86Switch_VT_3\nkeycode 70 = F4 F4 F4 F4 F4 F4 XF86Switch_'
|
||||
b'VT_4\nkeycode 71 = F5 F5 F5 F5 F5 F5 XF86Switch_VT_5\nkeycode 72 = F6 F6 F6 F6 F6 F6 XF86Switch_VT_6\nkeycode '
|
||||
b' 73 = F7 F7 F7 F7 F7 F7 XF86Switch_VT_7\nkeycode 74 = F8 F8 F8 F8 F8 F8 XF86Switch_VT_8\nkeycode 75 = F9 F9 F9'
|
||||
b' F9 F9 F9 XF86Switch_VT_9\nkeycode 76 = F10 F10 F10 F10 F10 F10 XF86Switch_VT_10\nkeycode 77 = Num_Lock NoSymb'
|
||||
b'ol Num_Lock\nkeycode 78 = Scroll_Lock NoSymbol Scroll_Lock\nkeycode 79 = KP_Home KP_7 KP_Home KP_7\nkeycode 8'
|
||||
b'0 = KP_Up KP_8 KP_Up KP_8\nkeycode 81 = KP_Prior KP_9 KP_Prior KP_9\nkeycode 82 = KP_Subtract KP_Subtract KP_S'
|
||||
b'ubtract KP_Subtract KP_Subtract KP_Subtract XF86Prev_VMode\nkeycode 83 = KP_Left KP_4 KP_Left KP_4\nkeycode 84'
|
||||
b' = KP_Begin KP_5 KP_Begin KP_5\nkeycode 85 = KP_Right KP_6 KP_Right KP_6\nkeycode 86 = KP_Add KP_Add KP_Add KP'
|
||||
b'_Add KP_Add KP_Add XF86Next_VMode\nkeycode 87 = KP_End KP_1 KP_End KP_1\nkeycode 88 = KP_Down KP_2 KP_Down KP_'
|
||||
b'2\nkeycode 89 = KP_Next KP_3 KP_Next KP_3\nkeycode 90 = KP_Insert KP_0 KP_Insert KP_0\nkeycode 91 = KP_Delete'
|
||||
b' KP_Separator KP_Delete KP_Separator\nkeycode 92 = ISO_Level3_Shift NoSymbol ISO_Level3_Shift\nkeycode 93 =\nk'
|
||||
b'eycode 94 = less greater less greater bar dead_belowmacron bar\nkeycode 95 = F11 F11 F11 F11 F11 F11 XF86Switc'
|
||||
b'h_VT_11\nkeycode 96 = F12 F12 F12 F12 F12 F12 XF86Switch_VT_12\nkeycode 97 =\nkeycode 98 = Katakana NoSymbol '
|
||||
b'Katakana\nkeycode 99 = Hiragana NoSymbol Hiragana\nkeycode 100 = Henkan_Mode NoSymbol Henkan_Mode\nkeycode 101 '
|
||||
b'= Hiragana_Katakana NoSymbol Hiragana_Katakana\nkeycode 102 = Muhenkan NoSymbol Muhenkan\nkeycode 103 =\nkeycode'
|
||||
b' 104 = KP_Enter NoSymbol KP_Enter\nkeycode 105 = Control_R NoSymbol Control_R\nkeycode 106 = KP_Divide KP_Divide'
|
||||
b' KP_Divide KP_Divide KP_Divide KP_Divide XF86Ungrab\nkeycode 107 = Print Sys_Req Print Sys_Req\nkeycode 108 = IS'
|
||||
b'O_Level3_Shift NoSymbol ISO_Level3_Shift\nkeycode 109 = Linefeed NoSymbol Linefeed\nkeycode 110 = Home NoSymbol '
|
||||
b'Home\nkeycode 111 = Up NoSymbol Up\nkeycode 112 = Prior NoSymbol Prior\nkeycode 113 = Left NoSymbol Left\nkeycod'
|
||||
b'e 114 = Right NoSymbol Right\nkeycode 115 = End NoSymbol End\nkeycode 116 = Down NoSymbol Down\nkeycode 117 = Ne'
|
||||
b'xt NoSymbol Next\nkeycode 118 = Insert NoSymbol Insert\nkeycode 119 = Delete NoSymbol Delete\nkeycode 120 =\nkey'
|
||||
b'code 121 = XF86AudioMute NoSymbol XF86AudioMute\nkeycode 122 = XF86AudioLowerVolume NoSymbol XF86AudioLowerVolum'
|
||||
b'e\nkeycode 123 = XF86AudioRaiseVolume NoSymbol XF86AudioRaiseVolume\nkeycode 124 = XF86PowerOff NoSymbol XF86Pow'
|
||||
b'erOff\nkeycode 125 = KP_Equal NoSymbol KP_Equal\nkeycode 126 = plusminus NoSymbol plusminus\nkeycode 127 = Pause'
|
||||
b' Break Pause Break\nkeycode 128 = XF86LaunchA NoSymbol XF86LaunchA\nkeycode 129 = KP_Decimal KP_Decimal KP_Decim'
|
||||
b'al KP_Decimal\nkeycode 130 = Hangul NoSymbol Hangul\nkeycode 131 = Hangul_Hanja NoSymbol Hangul_Hanja\nkeycode 1'
|
||||
b'32 =\nkeycode 133 = Super_L NoSymbol Super_L\nkeycode 134 = Super_R NoSymbol Super_R\nkeycode 135 = Menu NoSymbo'
|
||||
b'l Menu\nkeycode 136 = Cancel NoSymbol Cancel\nkeycode 137 = Redo NoSymbol Redo\nkeycode 138 = SunProps NoSymbol '
|
||||
b'SunProps\nkeycode 139 = Undo NoSymbol Undo\nkeycode 140 = SunFront NoSymbol SunFront\nkeycode 141 = XF86Copy NoS'
|
||||
b'ymbol XF86Copy\nkeycode 142 = XF86Open NoSymbol XF86Open\nkeycode 143 = XF86Paste NoSymbol XF86Paste\nkeycode 14'
|
||||
b'4 = Find NoSymbol Find\nkeycode 145 = XF86Cut NoSymbol XF86Cut\nkeycode 146 = Help NoSymbol Help\nkeycode 147 = '
|
||||
b'XF86MenuKB NoSymbol XF86MenuKB\nkeycode 148 = XF86Calculator NoSymbol XF86Calculator\nkeycode 149 =\nkeycode 150'
|
||||
b' = XF86Sleep NoSymbol XF86Sleep\nkeycode 151 = XF86WakeUp NoSymbol XF86WakeUp\nkeycode 152 = XF86Explorer NoSymb'
|
||||
b'ol XF86Explorer\nkeycode 153 = XF86Send NoSymbol XF86Send\nkeycode 154 =\nkeycode 155 = XF86Xfer NoSymbol XF86Xf'
|
||||
b'er\nkeycode 156 = XF86Launch1 NoSymbol XF86Launch1\nkeycode 157 = XF86Launch2 NoSymbol XF86Launch2\nkeycode 158 '
|
||||
b'= XF86WWW NoSymbol XF86WWW\nkeycode 159 = XF86DOS NoSymbol XF86DOS\nkeycode 160 = XF86ScreenSaver NoSymbol XF86S'
|
||||
b'creenSaver\nkeycode 161 = XF86RotateWindows NoSymbol XF86RotateWindows\nkeycode 162 = XF86TaskPane NoSymbol XF86'
|
||||
b'TaskPane\nkeycode 163 = XF86Mail NoSymbol XF86Mail\nkeycode 164 = XF86Favorites NoSymbol XF86Favorites\nkeycode '
|
||||
b'165 = XF86MyComputer NoSymbol XF86MyComputer\nkeycode 166 = XF86Back NoSymbol XF86Back\nkeycode 167 = XF86Forwar'
|
||||
b'd NoSymbol XF86Forward\nkeycode 168 =\nkeycode 169 = XF86Eject NoSymbol XF86Eject\nkeycode 170 = XF86Eject XF86E'
|
||||
b'ject XF86Eject XF86Eject\nkeycode 171 = XF86AudioNext NoSymbol XF86AudioNext\nkeycode 172 = XF86AudioPlay XF86Au'
|
||||
b'dioPause XF86AudioPlay XF86AudioPause\nkeycode 173 = XF86AudioPrev NoSymbol XF86AudioPrev\nkeycode 174 = XF86Aud'
|
||||
b'ioStop XF86Eject XF86AudioStop XF86Eject\nkeycode 175 = XF86AudioRecord NoSymbol XF86AudioRecord\nkeycode 176 = '
|
||||
b'XF86AudioRewind NoSymbol XF86AudioRewind\nkeycode 177 = XF86Phone NoSymbol XF86Phone\nkeycode 178 =\nkeycode 179'
|
||||
b' = XF86Tools NoSymbol XF86Tools\nkeycode 180 = XF86HomePage NoSymbol XF86HomePage\nkeycode 181 = XF86Reload NoSy'
|
||||
b'mbol XF86Reload\nkeycode 182 = XF86Close NoSymbol XF86Close\nkeycode 183 =\nkeycode 184 =\nkeycode 185 = XF86Scr'
|
||||
b'ollUp NoSymbol XF86ScrollUp\nkeycode 186 = XF86ScrollDown NoSymbol XF86ScrollDown\nkeycode 187 = parenleft NoSym'
|
||||
b'bol parenleft\nkeycode 188 = parenright NoSymbol parenright\nkeycode 189 = XF86New NoSymbol XF86New\nkeycode 190'
|
||||
b' = Redo NoSymbol Redo\nkeycode 191 = XF86Tools NoSymbol XF86Tools\nkeycode 192 = XF86Launch5 NoSymbol XF86Launch'
|
||||
b'5\nkeycode 193 = XF86Launch6 NoSymbol XF86Launch6\nkeycode 194 = XF86Launch7 NoSymbol XF86Launch7\nkeycode 195 ='
|
||||
b' XF86Launch8 NoSymbol XF86Launch8\nkeycode 196 = XF86Launch9 NoSymbol XF86Launch9\nkeycode 197 =\nkeycode 198 = '
|
||||
b'XF86AudioMicMute NoSymbol XF86AudioMicMute\nkeycode 199 = XF86TouchpadToggle NoSymbol XF86TouchpadToggle\nkeycod'
|
||||
b'e 200 = XF86TouchpadOn NoSymbol XF86TouchpadOn\nkeycode 201 = XF86TouchpadOff NoSymbol XF86TouchpadOff\nkeycode '
|
||||
b'202 =\nkeycode 203 = Mode_switch NoSymbol Mode_switch\nkeycode 204 = NoSymbol Alt_L NoSymbol Alt_L\nkeycode 205 '
|
||||
b'= NoSymbol Meta_L NoSymbol Meta_L\nkeycode 206 = NoSymbol Super_L NoSymbol Super_L\nkeycode 207 = NoSymbol Hyper'
|
||||
b'_L NoSymbol Hyper_L\nkeycode 208 = XF86AudioPlay NoSymbol XF86AudioPlay\nkeycode 209 = XF86AudioPause NoSymbol X'
|
||||
b'F86AudioPause\nkeycode 210 = XF86Launch3 NoSymbol XF86Launch3\nkeycode 211 = XF86Launch4 NoSymbol XF86Launch4\nk'
|
||||
b'eycode 212 = XF86LaunchB NoSymbol XF86LaunchB\nkeycode 213 = XF86Suspend NoSymbol XF86Suspend\nkeycode 214 = XF8'
|
||||
b'6Close NoSymbol XF86Close\nkeycode 215 = XF86AudioPlay NoSymbol XF86AudioPlay\nkeycode 216 = XF86AudioForward No'
|
||||
b'Symbol XF86AudioForward\nkeycode 217 =\nkeycode 218 = Print NoSymbol Print\nkeycode 219 =\nkeycode 220 = XF86Web'
|
||||
b'Cam NoSymbol XF86WebCam\nkeycode 221 = XF86AudioPreset NoSymbol XF86AudioPreset\nkeycode 222 =\nkeycode 223 = XF'
|
||||
b'86Mail NoSymbol XF86Mail\nkeycode 224 = XF86Messenger NoSymbol XF86Messenger\nkeycode 225 = XF86Search NoSymbol '
|
||||
b'XF86Search\nkeycode 226 = XF86Go NoSymbol XF86Go\nkeycode 227 = XF86Finance NoSymbol XF86Finance\nkeycode 228 = '
|
||||
b'XF86Game NoSymbol XF86Game\nkeycode 229 = XF86Shop NoSymbol XF86Shop\nkeycode 230 =\nkeycode 231 = Cancel NoSymb'
|
||||
b'ol Cancel\nkeycode 232 = XF86MonBrightnessDown NoSymbol XF86MonBrightnessDown\nkeycode 233 = XF86MonBrightnessUp'
|
||||
b' NoSymbol XF86MonBrightnessUp\nkeycode 234 = XF86AudioMedia NoSymbol XF86AudioMedia\nkeycode 235 = XF86Display N'
|
||||
b'oSymbol XF86Display\nkeycode 236 = XF86KbdLightOnOff NoSymbol XF86KbdLightOnOff\nkeycode 237 = XF86KbdBrightness'
|
||||
b'Down NoSymbol XF86KbdBrightnessDown\nkeycode 238 = XF86KbdBrightnessUp NoSymbol XF86KbdBrightnessUp\nkeycode 239'
|
||||
b' = XF86Send NoSymbol XF86Send\nkeycode 240 = XF86Reply NoSymbol XF86Reply\nkeycode 241 = XF86MailForward NoSymbo'
|
||||
b'l XF86MailForward\nkeycode 242 = XF86Save NoSymbol XF86Save\nkeycode 243 = XF86Documents NoSymbol XF86Documents'
|
||||
b'\nkeycode 244 = XF86Battery NoSymbol XF86Battery\nkeycode 245 = XF86Bluetooth NoSymbol XF86Bluetooth\nkeycode 24'
|
||||
b'6 = XF86WLAN NoSymbol XF86WLAN\nkeycode 247 =\nkeycode 248 =\nkeycode 249 =\nkeycode 250 =\nkeycode 251 = XF86Mo'
|
||||
b'nBrightnessCycle NoSymbol XF86MonBrightnessCycle\nkeycode 252 =\nkeycode 253 =\nkeycode 254 = XF86WWAN NoSymbol '
|
||||
b'XF86WWAN\nkeycode 255 = XF86RFKill NoSymbol XF86RFKill\n'
|
||||
b"keycode 8 =\nkeycode 9 = Escape NoSymbol Escape\nkeycode 10 = 1 exclam 1 exclam onesuperior exclamdown ones"
|
||||
b"uperior\nkeycode 11 = 2 quotedbl 2 quotedbl twosuperior oneeighth twosuperior\nkeycode 12 = 3 section 3 sectio"
|
||||
b"n threesuperior sterling threesuperior\nkeycode 13 = 4 dollar 4 dollar onequarter currency onequarter\nkeycode "
|
||||
b" 14 = 5 percent 5 percent onehalf threeeighths onehalf\nkeycode 15 = 6 ampersand 6 ampersand notsign fiveeighth"
|
||||
b"s notsign\nkeycode 16 = 7 slash 7 slash braceleft seveneighths braceleft\nkeycode 17 = 8 parenleft 8 parenleft"
|
||||
b" bracketleft trademark bracketleft\nkeycode 18 = 9 parenright 9 parenright bracketright plusminus bracketright"
|
||||
b"\nkeycode 19 = 0 equal 0 equal braceright degree braceright\nkeycode 20 = ssharp question ssharp question back"
|
||||
b"slash questiondown U1E9E\nkeycode 21 = dead_acute dead_grave dead_acute dead_grave dead_cedilla dead_ogonek dea"
|
||||
b"d_cedilla\nkeycode 22 = BackSpace BackSpace BackSpace BackSpace\nkeycode 23 = Tab ISO_Left_Tab Tab ISO_Left_Ta"
|
||||
b"b\nkeycode 24 = q Q q Q at Greek_OMEGA at\nkeycode 25 = w W w W lstroke Lstroke lstroke\nkeycode 26 = e E e E"
|
||||
b" EuroSign EuroSign EuroSign\nkeycode 27 = r R r R paragraph registered paragraph\nkeycode 28 = t T t T tslash "
|
||||
b"Tslash tslash\nkeycode 29 = z Z z Z leftarrow yen leftarrow\nkeycode 30 = u U u U downarrow uparrow downarrow"
|
||||
b"\nkeycode 31 = i I i I rightarrow idotless rightarrow\nkeycode 32 = o O o O oslash Oslash oslash\nkeycode 33 "
|
||||
b"= p P p P thorn THORN thorn\nkeycode 34 = udiaeresis Udiaeresis udiaeresis Udiaeresis dead_diaeresis dead_above"
|
||||
b"ring dead_diaeresis\nkeycode 35 = plus asterisk plus asterisk asciitilde macron asciitilde\nkeycode 36 = Retur"
|
||||
b"n NoSymbol Return\nkeycode 37 = Control_L NoSymbol Control_L\nkeycode 38 = a A a A ae AE ae\nkeycode 39 = s S"
|
||||
b" s S U017F U1E9E U017F\nkeycode 40 = d D d D eth ETH eth\nkeycode 41 = f F f F dstroke ordfeminine dstroke\nke"
|
||||
b"ycode 42 = g G g G eng ENG eng\nkeycode 43 = h H h H hstroke Hstroke hstroke\nkeycode 44 = j J j J dead_below"
|
||||
b"dot dead_abovedot dead_belowdot\nkeycode 45 = k K k K kra ampersand kra\nkeycode 46 = l L l L lstroke Lstroke "
|
||||
b"lstroke\nkeycode 47 = odiaeresis Odiaeresis odiaeresis Odiaeresis dead_doubleacute dead_belowdot dead_doubleacu"
|
||||
b"te\nkeycode 48 = adiaeresis Adiaeresis adiaeresis Adiaeresis dead_circumflex dead_caron dead_circumflex\nkeycod"
|
||||
b"e 49 = dead_circumflex degree dead_circumflex degree U2032 U2033 U2032\nkeycode 50 = Shift_L NoSymbol Shift_L"
|
||||
b"\nkeycode 51 = numbersign apostrophe numbersign apostrophe rightsinglequotemark dead_breve rightsinglequotemark"
|
||||
b"\nkeycode 52 = y Y y Y guillemotright U203A guillemotright\nkeycode 53 = x X x X guillemotleft U2039 guillemot"
|
||||
b"left\nkeycode 54 = c C c C cent copyright cent\nkeycode 55 = v V v V doublelowquotemark singlelowquotemark dou"
|
||||
b"blelowquotemark\nkeycode 56 = b B b B leftdoublequotemark leftsinglequotemark leftdoublequotemark\nkeycode 57 "
|
||||
b"= n N n N rightdoublequotemark rightsinglequotemark rightdoublequotemark\nkeycode 58 = m M m M mu masculine mu"
|
||||
b"\nkeycode 59 = comma semicolon comma semicolon periodcentered multiply periodcentered\nkeycode 60 = period col"
|
||||
b"on period colon U2026 division U2026\nkeycode 61 = minus underscore minus underscore endash emdash endash\nkeyc"
|
||||
b"ode 62 = Shift_R NoSymbol Shift_R\nkeycode 63 = KP_Multiply KP_Multiply KP_Multiply KP_Multiply KP_Multiply KP"
|
||||
b"_Multiply XF86ClearGrab\nkeycode 64 = Alt_L Meta_L Alt_L Meta_L\nkeycode 65 = space NoSymbol space\nkeycode 6"
|
||||
b"6 = Caps_Lock NoSymbol Caps_Lock\nkeycode 67 = F1 F1 F1 F1 F1 F1 XF86Switch_VT_1\nkeycode 68 = F2 F2 F2 F2 F2 "
|
||||
b"F2 XF86Switch_VT_2\nkeycode 69 = F3 F3 F3 F3 F3 F3 XF86Switch_VT_3\nkeycode 70 = F4 F4 F4 F4 F4 F4 XF86Switch_"
|
||||
b"VT_4\nkeycode 71 = F5 F5 F5 F5 F5 F5 XF86Switch_VT_5\nkeycode 72 = F6 F6 F6 F6 F6 F6 XF86Switch_VT_6\nkeycode "
|
||||
b" 73 = F7 F7 F7 F7 F7 F7 XF86Switch_VT_7\nkeycode 74 = F8 F8 F8 F8 F8 F8 XF86Switch_VT_8\nkeycode 75 = F9 F9 F9"
|
||||
b" F9 F9 F9 XF86Switch_VT_9\nkeycode 76 = F10 F10 F10 F10 F10 F10 XF86Switch_VT_10\nkeycode 77 = Num_Lock NoSymb"
|
||||
b"ol Num_Lock\nkeycode 78 = Scroll_Lock NoSymbol Scroll_Lock\nkeycode 79 = KP_Home KP_7 KP_Home KP_7\nkeycode 8"
|
||||
b"0 = KP_Up KP_8 KP_Up KP_8\nkeycode 81 = KP_Prior KP_9 KP_Prior KP_9\nkeycode 82 = KP_Subtract KP_Subtract KP_S"
|
||||
b"ubtract KP_Subtract KP_Subtract KP_Subtract XF86Prev_VMode\nkeycode 83 = KP_Left KP_4 KP_Left KP_4\nkeycode 84"
|
||||
b" = KP_Begin KP_5 KP_Begin KP_5\nkeycode 85 = KP_Right KP_6 KP_Right KP_6\nkeycode 86 = KP_Add KP_Add KP_Add KP"
|
||||
b"_Add KP_Add KP_Add XF86Next_VMode\nkeycode 87 = KP_End KP_1 KP_End KP_1\nkeycode 88 = KP_Down KP_2 KP_Down KP_"
|
||||
b"2\nkeycode 89 = KP_Next KP_3 KP_Next KP_3\nkeycode 90 = KP_Insert KP_0 KP_Insert KP_0\nkeycode 91 = KP_Delete"
|
||||
b" KP_Separator KP_Delete KP_Separator\nkeycode 92 = ISO_Level3_Shift NoSymbol ISO_Level3_Shift\nkeycode 93 =\nk"
|
||||
b"eycode 94 = less greater less greater bar dead_belowmacron bar\nkeycode 95 = F11 F11 F11 F11 F11 F11 XF86Switc"
|
||||
b"h_VT_11\nkeycode 96 = F12 F12 F12 F12 F12 F12 XF86Switch_VT_12\nkeycode 97 =\nkeycode 98 = Katakana NoSymbol "
|
||||
b"Katakana\nkeycode 99 = Hiragana NoSymbol Hiragana\nkeycode 100 = Henkan_Mode NoSymbol Henkan_Mode\nkeycode 101 "
|
||||
b"= Hiragana_Katakana NoSymbol Hiragana_Katakana\nkeycode 102 = Muhenkan NoSymbol Muhenkan\nkeycode 103 =\nkeycode"
|
||||
b" 104 = KP_Enter NoSymbol KP_Enter\nkeycode 105 = Control_R NoSymbol Control_R\nkeycode 106 = KP_Divide KP_Divide"
|
||||
b" KP_Divide KP_Divide KP_Divide KP_Divide XF86Ungrab\nkeycode 107 = Print Sys_Req Print Sys_Req\nkeycode 108 = IS"
|
||||
b"O_Level3_Shift NoSymbol ISO_Level3_Shift\nkeycode 109 = Linefeed NoSymbol Linefeed\nkeycode 110 = Home NoSymbol "
|
||||
b"Home\nkeycode 111 = Up NoSymbol Up\nkeycode 112 = Prior NoSymbol Prior\nkeycode 113 = Left NoSymbol Left\nkeycod"
|
||||
b"e 114 = Right NoSymbol Right\nkeycode 115 = End NoSymbol End\nkeycode 116 = Down NoSymbol Down\nkeycode 117 = Ne"
|
||||
b"xt NoSymbol Next\nkeycode 118 = Insert NoSymbol Insert\nkeycode 119 = Delete NoSymbol Delete\nkeycode 120 =\nkey"
|
||||
b"code 121 = XF86AudioMute NoSymbol XF86AudioMute\nkeycode 122 = XF86AudioLowerVolume NoSymbol XF86AudioLowerVolum"
|
||||
b"e\nkeycode 123 = XF86AudioRaiseVolume NoSymbol XF86AudioRaiseVolume\nkeycode 124 = XF86PowerOff NoSymbol XF86Pow"
|
||||
b"erOff\nkeycode 125 = KP_Equal NoSymbol KP_Equal\nkeycode 126 = plusminus NoSymbol plusminus\nkeycode 127 = Pause"
|
||||
b" Break Pause Break\nkeycode 128 = XF86LaunchA NoSymbol XF86LaunchA\nkeycode 129 = KP_Decimal KP_Decimal KP_Decim"
|
||||
b"al KP_Decimal\nkeycode 130 = Hangul NoSymbol Hangul\nkeycode 131 = Hangul_Hanja NoSymbol Hangul_Hanja\nkeycode 1"
|
||||
b"32 =\nkeycode 133 = Super_L NoSymbol Super_L\nkeycode 134 = Super_R NoSymbol Super_R\nkeycode 135 = Menu NoSymbo"
|
||||
b"l Menu\nkeycode 136 = Cancel NoSymbol Cancel\nkeycode 137 = Redo NoSymbol Redo\nkeycode 138 = SunProps NoSymbol "
|
||||
b"SunProps\nkeycode 139 = Undo NoSymbol Undo\nkeycode 140 = SunFront NoSymbol SunFront\nkeycode 141 = XF86Copy NoS"
|
||||
b"ymbol XF86Copy\nkeycode 142 = XF86Open NoSymbol XF86Open\nkeycode 143 = XF86Paste NoSymbol XF86Paste\nkeycode 14"
|
||||
b"4 = Find NoSymbol Find\nkeycode 145 = XF86Cut NoSymbol XF86Cut\nkeycode 146 = Help NoSymbol Help\nkeycode 147 = "
|
||||
b"XF86MenuKB NoSymbol XF86MenuKB\nkeycode 148 = XF86Calculator NoSymbol XF86Calculator\nkeycode 149 =\nkeycode 150"
|
||||
b" = XF86Sleep NoSymbol XF86Sleep\nkeycode 151 = XF86WakeUp NoSymbol XF86WakeUp\nkeycode 152 = XF86Explorer NoSymb"
|
||||
b"ol XF86Explorer\nkeycode 153 = XF86Send NoSymbol XF86Send\nkeycode 154 =\nkeycode 155 = XF86Xfer NoSymbol XF86Xf"
|
||||
b"er\nkeycode 156 = XF86Launch1 NoSymbol XF86Launch1\nkeycode 157 = XF86Launch2 NoSymbol XF86Launch2\nkeycode 158 "
|
||||
b"= XF86WWW NoSymbol XF86WWW\nkeycode 159 = XF86DOS NoSymbol XF86DOS\nkeycode 160 = XF86ScreenSaver NoSymbol XF86S"
|
||||
b"creenSaver\nkeycode 161 = XF86RotateWindows NoSymbol XF86RotateWindows\nkeycode 162 = XF86TaskPane NoSymbol XF86"
|
||||
b"TaskPane\nkeycode 163 = XF86Mail NoSymbol XF86Mail\nkeycode 164 = XF86Favorites NoSymbol XF86Favorites\nkeycode "
|
||||
b"165 = XF86MyComputer NoSymbol XF86MyComputer\nkeycode 166 = XF86Back NoSymbol XF86Back\nkeycode 167 = XF86Forwar"
|
||||
b"d NoSymbol XF86Forward\nkeycode 168 =\nkeycode 169 = XF86Eject NoSymbol XF86Eject\nkeycode 170 = XF86Eject XF86E"
|
||||
b"ject XF86Eject XF86Eject\nkeycode 171 = XF86AudioNext NoSymbol XF86AudioNext\nkeycode 172 = XF86AudioPlay XF86Au"
|
||||
b"dioPause XF86AudioPlay XF86AudioPause\nkeycode 173 = XF86AudioPrev NoSymbol XF86AudioPrev\nkeycode 174 = XF86Aud"
|
||||
b"ioStop XF86Eject XF86AudioStop XF86Eject\nkeycode 175 = XF86AudioRecord NoSymbol XF86AudioRecord\nkeycode 176 = "
|
||||
b"XF86AudioRewind NoSymbol XF86AudioRewind\nkeycode 177 = XF86Phone NoSymbol XF86Phone\nkeycode 178 =\nkeycode 179"
|
||||
b" = XF86Tools NoSymbol XF86Tools\nkeycode 180 = XF86HomePage NoSymbol XF86HomePage\nkeycode 181 = XF86Reload NoSy"
|
||||
b"mbol XF86Reload\nkeycode 182 = XF86Close NoSymbol XF86Close\nkeycode 183 =\nkeycode 184 =\nkeycode 185 = XF86Scr"
|
||||
b"ollUp NoSymbol XF86ScrollUp\nkeycode 186 = XF86ScrollDown NoSymbol XF86ScrollDown\nkeycode 187 = parenleft NoSym"
|
||||
b"bol parenleft\nkeycode 188 = parenright NoSymbol parenright\nkeycode 189 = XF86New NoSymbol XF86New\nkeycode 190"
|
||||
b" = Redo NoSymbol Redo\nkeycode 191 = XF86Tools NoSymbol XF86Tools\nkeycode 192 = XF86Launch5 NoSymbol XF86Launch"
|
||||
b"5\nkeycode 193 = XF86Launch6 NoSymbol XF86Launch6\nkeycode 194 = XF86Launch7 NoSymbol XF86Launch7\nkeycode 195 ="
|
||||
b" XF86Launch8 NoSymbol XF86Launch8\nkeycode 196 = XF86Launch9 NoSymbol XF86Launch9\nkeycode 197 =\nkeycode 198 = "
|
||||
b"XF86AudioMicMute NoSymbol XF86AudioMicMute\nkeycode 199 = XF86TouchpadToggle NoSymbol XF86TouchpadToggle\nkeycod"
|
||||
b"e 200 = XF86TouchpadOn NoSymbol XF86TouchpadOn\nkeycode 201 = XF86TouchpadOff NoSymbol XF86TouchpadOff\nkeycode "
|
||||
b"202 =\nkeycode 203 = Mode_switch NoSymbol Mode_switch\nkeycode 204 = NoSymbol Alt_L NoSymbol Alt_L\nkeycode 205 "
|
||||
b"= NoSymbol Meta_L NoSymbol Meta_L\nkeycode 206 = NoSymbol Super_L NoSymbol Super_L\nkeycode 207 = NoSymbol Hyper"
|
||||
b"_L NoSymbol Hyper_L\nkeycode 208 = XF86AudioPlay NoSymbol XF86AudioPlay\nkeycode 209 = XF86AudioPause NoSymbol X"
|
||||
b"F86AudioPause\nkeycode 210 = XF86Launch3 NoSymbol XF86Launch3\nkeycode 211 = XF86Launch4 NoSymbol XF86Launch4\nk"
|
||||
b"eycode 212 = XF86LaunchB NoSymbol XF86LaunchB\nkeycode 213 = XF86Suspend NoSymbol XF86Suspend\nkeycode 214 = XF8"
|
||||
b"6Close NoSymbol XF86Close\nkeycode 215 = XF86AudioPlay NoSymbol XF86AudioPlay\nkeycode 216 = XF86AudioForward No"
|
||||
b"Symbol XF86AudioForward\nkeycode 217 =\nkeycode 218 = Print NoSymbol Print\nkeycode 219 =\nkeycode 220 = XF86Web"
|
||||
b"Cam NoSymbol XF86WebCam\nkeycode 221 = XF86AudioPreset NoSymbol XF86AudioPreset\nkeycode 222 =\nkeycode 223 = XF"
|
||||
b"86Mail NoSymbol XF86Mail\nkeycode 224 = XF86Messenger NoSymbol XF86Messenger\nkeycode 225 = XF86Search NoSymbol "
|
||||
b"XF86Search\nkeycode 226 = XF86Go NoSymbol XF86Go\nkeycode 227 = XF86Finance NoSymbol XF86Finance\nkeycode 228 = "
|
||||
b"XF86Game NoSymbol XF86Game\nkeycode 229 = XF86Shop NoSymbol XF86Shop\nkeycode 230 =\nkeycode 231 = Cancel NoSymb"
|
||||
b"ol Cancel\nkeycode 232 = XF86MonBrightnessDown NoSymbol XF86MonBrightnessDown\nkeycode 233 = XF86MonBrightnessUp"
|
||||
b" NoSymbol XF86MonBrightnessUp\nkeycode 234 = XF86AudioMedia NoSymbol XF86AudioMedia\nkeycode 235 = XF86Display N"
|
||||
b"oSymbol XF86Display\nkeycode 236 = XF86KbdLightOnOff NoSymbol XF86KbdLightOnOff\nkeycode 237 = XF86KbdBrightness"
|
||||
b"Down NoSymbol XF86KbdBrightnessDown\nkeycode 238 = XF86KbdBrightnessUp NoSymbol XF86KbdBrightnessUp\nkeycode 239"
|
||||
b" = XF86Send NoSymbol XF86Send\nkeycode 240 = XF86Reply NoSymbol XF86Reply\nkeycode 241 = XF86MailForward NoSymbo"
|
||||
b"l XF86MailForward\nkeycode 242 = XF86Save NoSymbol XF86Save\nkeycode 243 = XF86Documents NoSymbol XF86Documents"
|
||||
b"\nkeycode 244 = XF86Battery NoSymbol XF86Battery\nkeycode 245 = XF86Bluetooth NoSymbol XF86Bluetooth\nkeycode 24"
|
||||
b"6 = XF86WLAN NoSymbol XF86WLAN\nkeycode 247 =\nkeycode 248 =\nkeycode 249 =\nkeycode 250 =\nkeycode 251 = XF86Mo"
|
||||
b"nBrightnessCycle NoSymbol XF86MonBrightnessCycle\nkeycode 252 =\nkeycode 253 =\nkeycode 254 = XF86WWAN NoSymbol "
|
||||
b"XF86WWAN\nkeycode 255 = XF86RFKill NoSymbol XF86RFKill\n"
|
||||
)
|
||||
|
Loading…
Reference in New Issue