123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- #!/usr/bin/env python3
- import json
- import logging
- import os
- import sys
- import time
- from argparse import ArgumentParser
- from subprocess import Popen
- import evdev
- INPUTDEV_BASEDIR = "/dev/input/"
- # TODO: handle sighup
- # TODO: prevent event to do other things
- def listall():
- for f in os.listdir(INPUTDEV_BASEDIR):
- try:
- print(f, evdev.InputDevice(os.path.join(INPUTDEV_BASEDIR, f)))
- except:
- pass
- def find_dev_by_name(name):
- for f in os.listdir(INPUTDEV_BASEDIR):
- try:
- ev = evdev.InputDevice(os.path.join(INPUTDEV_BASEDIR, f))
- except Exception:
- continue
- if ev.name == name:
- return os.path.join(INPUTDEV_BASEDIR, f)
- raise ValueError("device with such name not found")
- class KeymapTable:
- def __init__(self):
- self.table = {}
- @classmethod
- def from_config_file(cls, data):
- kt = cls()
- kt.table = {int(key): value for key, value in data.items()}
- kt.validate()
- return kt
- def validate(self):
- errors = 0
- for key in self.table:
- if type(key) is not int:
- logging.error("Keycode %r is not of valid type", key)
- errors += 1
- if type(self.table[key]) is str:
- # Autofixing key key
- self.table[key] = {
- "short": self.table[key],
- "long": self.table[key],
- }
- if type(self.table[key]) is not dict:
- logging.error("Keycode %r is not of valid type", key)
- errors += 1
- if errors:
- raise ValueError("%d validation errors found" % errors)
- def __contains__(self, x):
- code, pressure_type = x
- return code in self.table and pressure_type in self.table[code]
- def __getitem__(self, x):
- code, pressure_type = x
- return self.table[code][pressure_type]
- def loop(device, args):
- pressed = None
- for event in device.read_loop():
- if event.type != evdev.ecodes.EV_KEY:
- continue
- # for attr in dir(event):
- # if not attr.startswith("_"):
- # logging.debug(" ", attr, str(getattr(event, attr)))
- logging.debug("received %s", str(evdev.categorize(event)))
- if event.value == 1: # keydown
- pressed = {"code": event.code, "ts": event.timestamp()}
- if event.value == 0 and pressed is not None:
- pressure_time = event.timestamp() - pressed["ts"]
- if pressure_time * 1000 <= args.short_threshold:
- pressure_type = "short"
- else:
- pressure_type = "long"
- yield (pressed["code"], pressure_type)
- def main():
- p = ArgumentParser()
- p.add_argument("--config", type=open)
- input_p = p.add_argument_group("input device options")
- input_p.add_argument("-d", "--device")
- input_p.add_argument(
- "-n", "--name", help="Look at `xinput list` to have an idea"
- )
- input_p.add_argument(
- "--short-threshold",
- default=300,
- help="Threshold to distinguish a short and a long press, "
- "in milliseconds",
- )
- output_p = p.add_argument_group("output options")
- output_p.add_argument(
- "-v",
- "--verbose",
- dest="logging_level",
- action="store_const",
- const=logging.INFO,
- )
- output_p.add_argument(
- "--debug",
- dest="logging_level",
- action="store_const",
- const=logging.DEBUG,
- )
- args = p.parse_args()
- logging.basicConfig(level=args.logging_level)
- if args.config:
- config = json.load(args.config)
- else:
- config = {}
- keymaps = KeymapTable.from_config_file(config.get("keys", {}))
- if args.name:
- if args.device:
- print("Cannot specify both -d and -n", file=sys.stderr)
- sys.exit(1)
- while True:
- try:
- args.device = find_dev_by_name(args.name)
- except ValueError:
- logging.debug("Could not find device; trying again")
- time.sleep(2)
- continue
- else:
- logging.info("Found device: %s", args.device)
- break
- device = evdev.InputDevice(args.device)
- logging.debug("%s", device)
- logging.info("start loop")
- for highlevel_event in loop(device, args):
- logging.debug(highlevel_event)
- if highlevel_event not in keymaps:
- continue
- cmd = keymaps[highlevel_event]
- if type(cmd) is str:
- cmd = cmd.split()
- cmd[0] = os.path.expanduser(cmd[0])
- try:
- Popen(cmd)
- except Exception as exc:
- logging.error("Error running %s: %s" % (cmd, str(exc)))
- if __name__ == "__main__":
- # listall()
- main()
|