blututto.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #!/usr/bin/env python3
  2. import json
  3. import logging
  4. import os
  5. import sys
  6. import time
  7. from argparse import ArgumentParser
  8. from subprocess import Popen
  9. import evdev
  10. INPUTDEV_BASEDIR = "/dev/input/"
  11. # TODO: handle sighup
  12. # TODO: prevent event to do other things
  13. def listall():
  14. for f in os.listdir(INPUTDEV_BASEDIR):
  15. try:
  16. print(f, evdev.InputDevice(os.path.join(INPUTDEV_BASEDIR, f)))
  17. except:
  18. pass
  19. def find_dev_by_name(name):
  20. for f in os.listdir(INPUTDEV_BASEDIR):
  21. try:
  22. ev = evdev.InputDevice(os.path.join(INPUTDEV_BASEDIR, f))
  23. except Exception:
  24. continue
  25. if ev.name == name:
  26. return os.path.join(INPUTDEV_BASEDIR, f)
  27. raise ValueError("device with such name not found")
  28. class KeymapTable:
  29. def __init__(self):
  30. self.table = {}
  31. @classmethod
  32. def from_config_file(cls, data):
  33. kt = cls()
  34. kt.table = {int(key): value for key, value in data.items()}
  35. kt.validate()
  36. return kt
  37. def validate(self):
  38. errors = 0
  39. for key in self.table:
  40. if type(key) is not int:
  41. logging.error("Keycode %r is not of valid type", key)
  42. errors += 1
  43. if type(self.table[key]) is str:
  44. # Autofixing key key
  45. self.table[key] = {
  46. "short": self.table[key],
  47. "long": self.table[key],
  48. }
  49. if type(self.table[key]) is not dict:
  50. logging.error("Keycode %r is not of valid type", key)
  51. errors += 1
  52. if errors:
  53. raise ValueError("%d validation errors found" % errors)
  54. def __contains__(self, x):
  55. code, pressure_type = x
  56. return code in self.table and pressure_type in self.table[code]
  57. def __getitem__(self, x):
  58. code, pressure_type = x
  59. return self.table[code][pressure_type]
  60. def loop(device, args):
  61. pressed = None
  62. for event in device.read_loop():
  63. if event.type != evdev.ecodes.EV_KEY:
  64. continue
  65. # for attr in dir(event):
  66. # if not attr.startswith("_"):
  67. # logging.debug(" ", attr, str(getattr(event, attr)))
  68. logging.debug("received %s", str(evdev.categorize(event)))
  69. if event.value == 1: # keydown
  70. pressed = {"code": event.code, "ts": event.timestamp()}
  71. if event.value == 0 and pressed is not None:
  72. pressure_time = event.timestamp() - pressed["ts"]
  73. if pressure_time * 1000 <= args.short_threshold:
  74. pressure_type = "short"
  75. else:
  76. pressure_type = "long"
  77. yield (pressed["code"], pressure_type)
  78. def main():
  79. p = ArgumentParser()
  80. p.add_argument("--config", type=open)
  81. input_p = p.add_argument_group("input device options")
  82. input_p.add_argument("-d", "--device")
  83. input_p.add_argument(
  84. "-n", "--name", help="Look at `xinput list` to have an idea"
  85. )
  86. input_p.add_argument(
  87. "--short-threshold",
  88. default=300,
  89. help="Threshold to distinguish a short and a long press, "
  90. "in milliseconds",
  91. )
  92. output_p = p.add_argument_group("output options")
  93. output_p.add_argument(
  94. "-v",
  95. "--verbose",
  96. dest="logging_level",
  97. action="store_const",
  98. const=logging.INFO,
  99. )
  100. output_p.add_argument(
  101. "--debug",
  102. dest="logging_level",
  103. action="store_const",
  104. const=logging.DEBUG,
  105. )
  106. args = p.parse_args()
  107. logging.basicConfig(level=args.logging_level)
  108. if args.config:
  109. config = json.load(args.config)
  110. else:
  111. config = {}
  112. keymaps = KeymapTable.from_config_file(config.get("keys", {}))
  113. if args.name:
  114. if args.device:
  115. print("Cannot specify both -d and -n", file=sys.stderr)
  116. sys.exit(1)
  117. while True:
  118. try:
  119. args.device = find_dev_by_name(args.name)
  120. except ValueError:
  121. logging.debug("Could not find device; trying again")
  122. time.sleep(2)
  123. continue
  124. else:
  125. logging.info("Found device: %s", args.device)
  126. break
  127. device = evdev.InputDevice(args.device)
  128. logging.debug("%s", device)
  129. logging.info("start loop")
  130. for highlevel_event in loop(device, args):
  131. logging.debug(highlevel_event)
  132. if highlevel_event not in keymaps:
  133. continue
  134. cmd = keymaps[highlevel_event]
  135. if type(cmd) is str:
  136. cmd = cmd.split()
  137. cmd[0] = os.path.expanduser(cmd[0])
  138. try:
  139. Popen(cmd)
  140. except Exception as exc:
  141. logging.error("Error running %s: %s" % (cmd, str(exc)))
  142. if __name__ == "__main__":
  143. # listall()
  144. main()