|
@@ -0,0 +1,168 @@
|
|
|
|
+#!/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()
|