diff --git a/evdevremapkeys.py b/evdevremapkeys.py index 90cda7f..22df7b8 100755 --- a/evdevremapkeys.py +++ b/evdevremapkeys.py @@ -27,6 +27,8 @@ import functools from pathlib import Path import signal +import os +import pwd import daemon @@ -37,14 +39,27 @@ @asyncio.coroutine -def handle_events(input, output, remappings): +def handle_events(input, output, remappings, user, commands, passunmapped): while True: events = yield from input.async_read() # noqa for event in events: - if event.type == ecodes.EV_KEY and \ + mapped = False + + if remappings is not None and \ + event.type == ecodes.EV_KEY and \ event.code in remappings: + mapped = True remap_event(output, event, remappings) - else: + + 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\ + event.value == evdev.events.KeyEvent.key_hold: + execute_command(event, user, commands) + + if passunmapped and not mapped: output.write_event(event) output.syn() @@ -56,6 +71,20 @@ def remap_event(output, event, remappings): output.syn() +def execute_command(event, user, commands): + for command in commands[event.code]: + newpid = os.fork() + if newpid == 0: + 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) + + def load_config(config_override): conf_path = None if config_override is None: @@ -73,15 +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(device['remappings']) + 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(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] + 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 = {} + if by_name is not None: + for key, values in by_name.items(): + by_id[ecodes.ecodes[key]] = values; return by_id @@ -115,14 +156,18 @@ def register_device(device): # EV_SYN is automatically added to uinput devices del caps[ecodes.EV_SYN] - remappings = device['remappings'] + 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()] 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, user, commands, passunmapped)) @asyncio.coroutine @@ -144,6 +189,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: