Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input event with origin #550

Merged
merged 37 commits into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
fdbe5d4
Update InputEvent to include origin and analog threshold
jonasBoss Nov 20, 2022
4738843
preset save file migration
jonasBoss Nov 20, 2022
df386bd
Merge branch 'beta' into input-event-with-origin
jonasBoss Nov 21, 2022
5b321f0
Use the InputEvent.analog_threshold field instead of value
jonasBoss Nov 22, 2022
fb1d407
Merge branch 'beta' into input-event-with-origin
jonasBoss Nov 24, 2022
56501b8
Make tests start in pycharm again
jonasBoss Nov 24, 2022
2fadc1f
Seperate InputEvent and InputConfiguration
jonasBoss Nov 24, 2022
db652a5
integration tests
jonasBoss Nov 26, 2022
3f6e81d
simplyfied InputEvent
jonasBoss Nov 26, 2022
af96ce1
move find_analog_input_event to InputCombination
jonasBoss Nov 26, 2022
53f2a49
rename InputConfiguration to InputConfig
jonasBoss Nov 26, 2022
b41bcce
move input_configuration.py to configs/input_config.py
jonasBoss Nov 26, 2022
0b88d79
simplified imports
jonasBoss Nov 26, 2022
e76c30b
rename event_combination to input_combination
jonasBoss Nov 26, 2022
705d97b
mypy
jonasBoss Dec 1, 2022
e518a05
use event origin information
jonasBoss Dec 2, 2022
0f724e4
fix reader-service and use md5 hashing
jonasBoss Dec 2, 2022
fc95635
Updated Mapping handlers
jonasBoss Dec 2, 2022
9da39c6
Updated Tests
jonasBoss Dec 2, 2022
2f168a1
Merge branch 'beta' into input-event-with-origin
jonasBoss Dec 6, 2022
11dce4c
fix test_injector
jonasBoss Dec 6, 2022
b7ca337
mypy
jonasBoss Dec 6, 2022
d67dd32
integration tests
jonasBoss Dec 7, 2022
c3b30cf
added unit tests
jonasBoss Dec 9, 2022
e5f0159
usage.md
jonasBoss Dec 10, 2022
9af4a59
refactor injector grab deviece logic
jonasBoss Dec 11, 2022
6c1d8f9
InputConfig docstring
jonasBoss Dec 11, 2022
6067285
refactor migrations._input_combination_from_string
jonasBoss Dec 11, 2022
f94e5e7
amend
jonasBoss Dec 11, 2022
61fe702
refactor temprary preset migration
jonasBoss Dec 11, 2022
5943fd0
made notify_callbacks private
jonasBoss Dec 12, 2022
925ffad
constrain origin to lowercase
jonasBoss Dec 12, 2022
7639422
tabs 'n spaces
jonasBoss Dec 12, 2022
7010955
mypy
jonasBoss Dec 12, 2022
aa3d868
mypy
jonasBoss Dec 13, 2022
699a6f8
Signal no longer inherits Message protocol
jonasBoss Dec 13, 2022
a1d1641
rename origin to origin_hash
jonasBoss Dec 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix reader-service and use md5 hashing
  • Loading branch information
jonasBoss committed Dec 2, 2022
commit 0f724e46e4181486d7d9f0379d58b4a046e9b08f
2 changes: 1 addition & 1 deletion inputremapper/configs/input_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class InputConfig(BaseModel):

type: int
code: int
origin: Optional[int] = None
origin: Optional[str] = None
analog_threshold: Optional[int] = None

@property
Expand Down
21 changes: 13 additions & 8 deletions inputremapper/gui/reader_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def _read(self):
# update the generator
try:
if self._recording_generator is not None:
self._recording_generator.send(InputEvent(*message_body))
self._recording_generator.send(InputEvent(**message_body))
else:
# the ReaderService should only send events while the gui
# is recording, so this is unexpected.
Expand Down Expand Up @@ -198,15 +198,20 @@ def stop_recorder(self) -> None:

@staticmethod
def _input_event_to_config(event: InputEvent):
return {"type": event.type, "code": event.code, "analog_threshold": event.value}
return {
"type": event.type,
"code": event.code,
"analog_threshold": event.value,
"origin": event.origin,
}

def _recorder(self) -> RecordingGenerator:
"""Generator which receives InputEvents.

It accumulates them into EventCombinations and sends those on the
message_broker. It will stop once all keys or inputs are released.
"""
active: Set[Tuple[int, int]] = set()
active: Set = set()
accumulator: List[InputEvent] = []
while True:
event: InputEvent = yield
Expand All @@ -215,7 +220,7 @@ def _recorder(self) -> RecordingGenerator:

if event.value == 0:
try:
active.remove((event.type, event.code))
active.remove(event.input_match_hash)
except KeyError:
# we haven't seen this before probably a key got released which
# was pressed before we started recording. ignore it.
Expand All @@ -226,12 +231,12 @@ def _recorder(self) -> RecordingGenerator:
return
continue

active.add(event.type_and_code)
accu_type_code = [e.type_and_code for e in accumulator]
if event.type_and_code in accu_type_code and event not in accumulator:
active.add(event.input_match_hash)
accu_input_hashes = [e.input_match_hash for e in accumulator]
if event.input_match_hash in accu_input_hashes and event not in accumulator:
# the value has changed but the event is already in the accumulator
# update the event
i = accu_type_code.index(event.type_and_code)
i = accu_input_hashes.index(event.input_match_hash)
accumulator[i] = event
self.message_broker.publish(
CombinationRecorded(
Expand Down
109 changes: 47 additions & 62 deletions inputremapper/gui/reader_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

import evdev
from evdev.ecodes import EV_KEY, EV_ABS, EV_REL, REL_HWHEEL, REL_WHEEL
from inputremapper.utils import get_device_hash

from inputremapper.configs.input_config import InputCombination, InputConfig
from inputremapper.configs.mapping import UIMapping
Expand Down Expand Up @@ -267,98 +268,81 @@ def _create_event_pipeline(self, sources: List[evdev.InputDevice]) -> ContextDum
context = ContextDummy()
# create a context for each source
for device in sources:
device_hash = get_device_hash(device)
capabilities = device.capabilities(absinfo=False)

for ev_code in capabilities.get(EV_KEY) or ():
context.notify_callbacks[(EV_KEY, ev_code)].append(
input_config = InputConfig(
type=EV_KEY, code=ev_code, origin=device_hash
)
jonasBoss marked this conversation as resolved.
Show resolved Hide resolved
context.notify_callbacks[input_config.input_match_hash].append(
ForwardToUIHandler(self._results_pipe).notify
)

for ev_code in capabilities.get(EV_ABS) or ():
# positive direction
input_config = InputConfig(
type=EV_ABS, code=ev_code, analog_threshold=30, origin=device_hash
)
mapping = UIMapping(
input_combination=InputCombination(
InputConfig(type=EV_ABS, code=ev_code, analog_threshold=30)
),
input_combination=InputCombination(input_config),
target_uinput="keyboard",
)
handler: MappingHandler = AbsToBtnHandler(
InputCombination(
InputConfig(type=EV_ABS, code=ev_code, analog_threshold=30)
),
mapping,
InputCombination(input_config), mapping
)
handler.set_sub_handler(ForwardToUIHandler(self._results_pipe))
context.notify_callbacks[(EV_ABS, ev_code)].append(handler.notify)
context.notify_callbacks[input_config.input_match_hash].append(
handler.notify
)

# negative direction
input_config = input_config.modify(analog_threshold=-30)
mapping = UIMapping(
input_combination=InputCombination(
InputConfig(type=EV_ABS, code=ev_code, analog_threshold=-30)
),
input_combination=InputCombination(input_config),
target_uinput="keyboard",
)
handler = AbsToBtnHandler(
InputCombination(
InputConfig(type=EV_ABS, code=ev_code, analog_threshold=-30)
),
mapping,
)
handler = AbsToBtnHandler(InputCombination(input_config), mapping)
handler.set_sub_handler(ForwardToUIHandler(self._results_pipe))
context.notify_callbacks[(EV_ABS, ev_code)].append(handler.notify)
context.notify_callbacks[input_config.input_match_hash].append(
handler.notify
)

for ev_code in capabilities.get(EV_REL) or ():
# positive direction
input_config = InputConfig(
type=EV_REL,
code=ev_code,
analog_threshold=self.rel_xy_speed[ev_code],
origin=device_hash,
)
mapping = UIMapping(
input_combination=InputCombination(
InputConfig(
type=EV_REL,
code=ev_code,
analog_threshold=self.rel_xy_speed[ev_code],
)
),
input_combination=InputCombination(input_config),
target_uinput="keyboard",
release_timeout=0.3,
force_release_timeout=True,
)
handler = RelToBtnHandler(
InputCombination(
InputConfig(
type=EV_REL,
code=ev_code,
analog_threshold=self.rel_xy_speed[ev_code],
)
),
mapping,
)
handler = RelToBtnHandler(InputCombination(input_config), mapping)
handler.set_sub_handler(ForwardToUIHandler(self._results_pipe))
context.notify_callbacks[(EV_REL, ev_code)].append(handler.notify)
context.notify_callbacks[input_config.input_match_hash].append(
handler.notify
)

# negative direction
input_config = input_config.modify(
analog_threshold=-self.rel_xy_speed[ev_code]
)
mapping = UIMapping(
input_combination=InputCombination(
InputConfig(
type=EV_REL,
code=ev_code,
analog_threshold=-self.rel_xy_speed[ev_code],
)
),
input_combination=InputCombination(input_config),
target_uinput="keyboard",
release_timeout=0.3,
force_release_timeout=True,
)
handler = RelToBtnHandler(
InputCombination(
InputConfig(
type=EV_REL,
code=ev_code,
analog_threshold=-self.rel_xy_speed[ev_code],
)
),
mapping,
)
handler = RelToBtnHandler(InputCombination(input_config), mapping)
handler.set_sub_handler(ForwardToUIHandler(self._results_pipe))
context.notify_callbacks[(EV_REL, ev_code)].append(handler.notify)
context.notify_callbacks[input_config.input_match_hash].append(
handler.notify
)

return context

Expand Down Expand Up @@ -402,13 +386,14 @@ def notify(
self.pipe.send(
{
"type": MSG_EVENT,
"message": (
event.sec,
event.usec,
event.type,
event.code,
event.value,
),
"message": {
"sec": event.sec,
"usec": event.usec,
"type": event.type,
"code": event.code,
"value": event.value,
"origin": event.origin,
},
}
)
return True
Expand Down
3 changes: 2 additions & 1 deletion inputremapper/injection/injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ def stop_injecting(self) -> None:
def _find_input_device(
devices: List[evdev.InputDevice], input_config: InputConfig
) -> Optional[evdev.InputDevice]:
devices_by_hash = {get_device_hash: device for device in devices}
devices_by_hash = {get_device_hash(device): device for device in devices}
logger.debug(f"possible devices: {devices_by_hash}")

def has_type_and_code(device, type, code) -> bool:
return code in device.capabilities(absinfo=False).get(type, [])
Expand Down
2 changes: 1 addition & 1 deletion inputremapper/input_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class InputEvent:
code: int
value: int
actions: Tuple[EventActions, ...] = ()
origin: Optional[int] = None
origin: Optional[str] = None

def __eq__(self, other: InputEvent | evdev.InputEvent | Tuple[int, int, int]):
# useful in tests
Expand Down
12 changes: 9 additions & 3 deletions inputremapper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"""Utility functions."""

import sys
from typing import Optional, Hashable
from hashlib import md5
from typing import Optional

import evdev

Expand All @@ -30,8 +31,13 @@ def is_service() -> bool:
return sys.argv[0].endswith("input-remapper-service")


def get_device_hash(device: evdev.InputDevice) -> int:
return hash(str(device.capabilities(absinfo=False)) + device.name)
def get_device_hash(device: evdev.InputDevice) -> str:
"""get a unique hash for the given device"""
# the builtin hash() function can not be used because it is randomly
# seeded at python startup.
# a non-cryptographic hash would be faster but there is none in the standard lib
s = str(device.capabilities(absinfo=False)) + device.name
return md5(s.encode()).hexdigest()


def get_evdev_constant_name(type_: int, code: int, *_) -> Optional[str]:
Expand Down