From 74e7220120cdef089c050703fddca58f7dea45f3 Mon Sep 17 00:00:00 2001 From: Bob van Loosen Date: Wed, 7 Feb 2018 17:32:56 +0100 Subject: [PATCH 1/5] added: commands option, can be filled with shell commands to be executed on a keypress --- evdevremapkeys.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/evdevremapkeys.py b/evdevremapkeys.py index 90cda7f..a826fe4 100755 --- a/evdevremapkeys.py +++ b/evdevremapkeys.py @@ -37,14 +37,25 @@ @asyncio.coroutine -def handle_events(input, output, remappings): +def handle_events(input, output, remappings, commands): while True: events = yield from input.async_read() # noqa for event in events: + mapped = False + if event.type == ecodes.EV_KEY and \ event.code in remappings: + mapped = True remap_event(output, event, remappings) - else: + + if event.type == ecodes.EV_KEY and \ + event.code in commands: + mapped = True + if event.value == evdev.events.KeyEvent.key_down or\ + event.value == evdev.events.KeyEvent.key_hold: + execute_command(event, commands) + + if not mapped: output.write_event(event) output.syn() @@ -55,6 +66,9 @@ def remap_event(output, event, remappings): output.write_event(event) output.syn() +def execute_command(event, commands): + for command in commands[event.code]: + print(command) def load_config(config_override): conf_path = None @@ -73,18 +87,26 @@ def load_config(config_override): with open(conf_path.as_posix(), 'r') as fd: config = yaml.safe_load(fd) for device in config['devices']: - device['remappings'] = resolve_ecodes(device['remappings']) + device['remappings'] = resolve_ecodes_remappings(device['remappings']) + device['commands'] = resolve_ecodes_commands(device['commands']) return config -def resolve_ecodes(by_name): +def resolve_ecodes_remappings(by_name): by_id = {} for key, values in by_name.items(): by_id[ecodes.ecodes[key]] = [ecodes.ecodes[value] for value in values] return by_id +def resolve_ecodes_commands(by_name): + by_id = {} + for key, values in by_name.items(): + by_id[ecodes.ecodes[key]] = values; + return by_id + + def find_input(device): name = device.get('input_name', None); phys = device.get('input_phys', None); @@ -116,13 +138,15 @@ def register_device(device): del caps[ecodes.EV_SYN] remappings = device['remappings'] + commands = device['commands'] + extended = set(caps[ecodes.EV_KEY]) [extended.update(keys) for keys in remappings.values()] caps[ecodes.EV_KEY] = list(extended) output = UInput(caps, name=device['output_name']) - asyncio.ensure_future(handle_events(input, output, remappings)) + asyncio.ensure_future(handle_events(input, output, remappings, commands)) @asyncio.coroutine From 3f54c73a47b964a74c4552bf502f652886fdca95 Mon Sep 17 00:00:00 2001 From: Bob van Loosen Date: Wed, 7 Feb 2018 18:18:33 +0100 Subject: [PATCH 2/5] added: fork and execute shell commands --- evdevremapkeys.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/evdevremapkeys.py b/evdevremapkeys.py index a826fe4..d820f7b 100755 --- a/evdevremapkeys.py +++ b/evdevremapkeys.py @@ -27,6 +27,7 @@ import functools from pathlib import Path import signal +import os import daemon @@ -66,9 +67,13 @@ def remap_event(output, event, remappings): output.write_event(event) output.syn() + def execute_command(event, commands): for command in commands[event.code]: - print(command) + newpid = os.fork() + if newpid == 0: + os.execl('/bin/sh', '/bin/sh', '-c', command) + def load_config(config_override): conf_path = None @@ -168,6 +173,9 @@ def run_loop(args): functools.partial(asyncio.ensure_future, shutdown(loop))) + #prevent defunct processes + signal.signal(signal.SIGCHLD, signal.SIG_IGN) + try: loop.run_forever() except KeyboardInterrupt: From 78d2ba6ac116b1742dc9ff4a02aba1d58bba620a Mon Sep 17 00:00:00 2001 From: Bob van Loosen Date: Wed, 7 Feb 2018 22:52:55 +0100 Subject: [PATCH 3/5] added: command_user to options, runs the shell command as a certain user, so that root permissions can be dropped --- evdevremapkeys.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/evdevremapkeys.py b/evdevremapkeys.py index d820f7b..322f28d 100755 --- a/evdevremapkeys.py +++ b/evdevremapkeys.py @@ -28,6 +28,7 @@ from pathlib import Path import signal import os +import pwd import daemon @@ -38,7 +39,7 @@ @asyncio.coroutine -def handle_events(input, output, remappings, commands): +def handle_events(input, output, remappings, user, commands): while True: events = yield from input.async_read() # noqa for event in events: @@ -54,7 +55,7 @@ def handle_events(input, output, remappings, commands): mapped = True if event.value == evdev.events.KeyEvent.key_down or\ event.value == evdev.events.KeyEvent.key_hold: - execute_command(event, commands) + execute_command(event, user, commands) if not mapped: output.write_event(event) @@ -68,10 +69,16 @@ def remap_event(output, event, remappings): output.syn() -def execute_command(event, commands): +def execute_command(event, user, commands): for command in commands[event.code]: newpid = os.fork() if newpid == 0: + gid = pwd.getpwnam(user).pw_gid + uid = pwd.getpwnam(user).pw_uid + #group id needs to be set before the user id, + #otherwise the permissions are dropped too soon + os.setgid(gid) + os.setuid(uid) os.execl('/bin/sh', '/bin/sh', '-c', command) @@ -143,6 +150,7 @@ def register_device(device): del caps[ecodes.EV_SYN] remappings = device['remappings'] + user = device['command_user'] commands = device['commands'] extended = set(caps[ecodes.EV_KEY]) @@ -151,7 +159,7 @@ def register_device(device): output = UInput(caps, name=device['output_name']) - asyncio.ensure_future(handle_events(input, output, remappings, commands)) + asyncio.ensure_future(handle_events(input, output, remappings, user, commands)) @asyncio.coroutine From 7ba9a0e65952e02890c5337162fdcb78484b71a8 Mon Sep 17 00:00:00 2001 From: Bob van Loosen Date: Wed, 7 Feb 2018 23:13:58 +0100 Subject: [PATCH 4/5] fixed: made remappings, command_user and commands optional in the config file --- evdevremapkeys.py | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/evdevremapkeys.py b/evdevremapkeys.py index 322f28d..c76caba 100755 --- a/evdevremapkeys.py +++ b/evdevremapkeys.py @@ -45,12 +45,14 @@ def handle_events(input, output, remappings, user, commands): for event in events: mapped = False - if event.type == ecodes.EV_KEY and \ + if remappings is not None and \ + event.type == ecodes.EV_KEY and \ event.code in remappings: mapped = True remap_event(output, event, remappings) - if event.type == ecodes.EV_KEY and \ + if commands is not None and \ + event.type == ecodes.EV_KEY and \ event.code in commands: mapped = True if event.value == evdev.events.KeyEvent.key_down or\ @@ -73,12 +75,13 @@ def execute_command(event, user, commands): for command in commands[event.code]: newpid = os.fork() if newpid == 0: - gid = pwd.getpwnam(user).pw_gid - uid = pwd.getpwnam(user).pw_uid - #group id needs to be set before the user id, - #otherwise the permissions are dropped too soon - os.setgid(gid) - os.setuid(uid) + if user is not None: + gid = pwd.getpwnam(user).pw_gid + uid = pwd.getpwnam(user).pw_uid + #group id needs to be set before the user id, + #otherwise the permissions are dropped too soon + os.setgid(gid) + os.setuid(uid) os.execl('/bin/sh', '/bin/sh', '-c', command) @@ -99,23 +102,27 @@ def load_config(config_override): with open(conf_path.as_posix(), 'r') as fd: config = yaml.safe_load(fd) for device in config['devices']: - device['remappings'] = resolve_ecodes_remappings(device['remappings']) - device['commands'] = resolve_ecodes_commands(device['commands']) + if 'remappings' in device: + device['remappings'] = resolve_ecodes_remappings(device['remappings']) + if 'commands' in device: + device['commands'] = resolve_ecodes_commands(device['commands']) return config def resolve_ecodes_remappings(by_name): by_id = {} - for key, values in by_name.items(): - by_id[ecodes.ecodes[key]] = [ecodes.ecodes[value] for value in values] + if by_name is not None: + for key, values in by_name.items(): + by_id[ecodes.ecodes[key]] = [ecodes.ecodes[value] for value in values] return by_id def resolve_ecodes_commands(by_name): by_id = {} - for key, values in by_name.items(): - by_id[ecodes.ecodes[key]] = values; + if by_name is not None: + for key, values in by_name.items(): + by_id[ecodes.ecodes[key]] = values; return by_id @@ -149,9 +156,9 @@ def register_device(device): # EV_SYN is automatically added to uinput devices del caps[ecodes.EV_SYN] - remappings = device['remappings'] - user = device['command_user'] - commands = device['commands'] + remappings = device.get('remappings', None); + user = device.get('command_user', None); + commands = device.get('commands', None); extended = set(caps[ecodes.EV_KEY]) [extended.update(keys) for keys in remappings.values()] From 5c04fe3a29b3e39a7bbb3d8f24ec87283e7ba866 Mon Sep 17 00:00:00 2001 From: Bob van Loosen Date: Thu, 8 Feb 2018 15:05:13 +0100 Subject: [PATCH 5/5] added: option to disable passing unmapped keys --- evdevremapkeys.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/evdevremapkeys.py b/evdevremapkeys.py index c76caba..22df7b8 100755 --- a/evdevremapkeys.py +++ b/evdevremapkeys.py @@ -39,7 +39,7 @@ @asyncio.coroutine -def handle_events(input, output, remappings, user, commands): +def handle_events(input, output, remappings, user, commands, passunmapped): while True: events = yield from input.async_read() # noqa for event in events: @@ -59,7 +59,7 @@ def handle_events(input, output, remappings, user, commands): event.value == evdev.events.KeyEvent.key_hold: execute_command(event, user, commands) - if not mapped: + if passunmapped and not mapped: output.write_event(event) output.syn() @@ -159,6 +159,7 @@ def register_device(device): remappings = device.get('remappings', None); user = device.get('command_user', None); commands = device.get('commands', None); + passunmapped = device.get('pass_unmapped', True); extended = set(caps[ecodes.EV_KEY]) [extended.update(keys) for keys in remappings.values()] @@ -166,7 +167,7 @@ def register_device(device): output = UInput(caps, name=device['output_name']) - asyncio.ensure_future(handle_events(input, output, remappings, user, commands)) + asyncio.ensure_future(handle_events(input, output, remappings, user, commands, passunmapped)) @asyncio.coroutine