You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. var udev = require("udev");
  2. var spawn = require("child_process").spawn;
  3. var exec = require("child_process").exec;
  4. var debounce = require("../../js/debounce");
  5. exports.start = start;
  6. exports.stop = stop;
  7. exports.event = event;
  8. var conf;
  9. var logger;
  10. var modules;
  11. var monitor;
  12. // Set an xinput property
  13. function setProp(name, val, suppressWarnings) {
  14. var args = [ "--set-prop", name ];
  15. val.forEach(v => args.push(v));
  16. // We need to wait for X to recognize the device
  17. setTimeout(function() {
  18. var child = spawn("xinput", args)
  19. child.on("close", code => {
  20. if (code !== 0 && !suppressWarnings)
  21. logger.warn("Xinput command failed:", args.join(", "));
  22. });
  23. if (!suppressWarnings) {
  24. child.stderr.on("data", d => {
  25. logger.warn(
  26. "Xinput command for", name, "failed:",
  27. d.toString().trim());
  28. });
  29. }
  30. }, 500);
  31. }
  32. // Queued commands will be run once runCmds is called
  33. var queuedCmds = [];
  34. function queueCmd(command) {
  35. if (queuedCmds.indexOf(command) === -1)
  36. queuedCmds.push(command);
  37. }
  38. // Run queued commands, debounced
  39. var runCmds = debounce(function() {
  40. queuedCmds.forEach(cmd => {
  41. var child = exec(cmd);
  42. child.on("close", code => {
  43. if (code !== 0)
  44. logger.warn("Command failed:", cmd);
  45. });
  46. });
  47. queuedCmds = [];
  48. });
  49. // Devices which aren't keyboards or mice with names aren't interesting
  50. function filter(dev) {
  51. return dev.NAME &&
  52. (dev.ID_INPUT_KEYBOARD || dev.ID_INPUT_MOUSE || dev.ID_INPUT_TOUCHPAD);
  53. }
  54. // name can be either an array or a string
  55. function nameMatches(dev, name) {
  56. // Remove quotes form device name
  57. var devname = dev.NAME.substring(1, dev.NAME.length - 1);
  58. if (typeof name === "string") {
  59. if (name !== "*" && name !== devname)
  60. return false;
  61. } else if (name instanceof Array) {
  62. var matched = false;
  63. for (var i in name) {
  64. if (name === devname) {
  65. matched = true;
  66. break;
  67. }
  68. }
  69. if (!matched)
  70. return false;
  71. } else {
  72. logger.warning("Expected name to be string or array");
  73. return false;
  74. }
  75. return true;
  76. }
  77. function onchange(dev, evt) {
  78. if (!filter(dev))
  79. return;
  80. var isKeyboard = !!dev.ID_INPUT_KEYBOARD;
  81. var isPointer = !!(dev.ID_INPUT_MOUSE || dev.ID_INPUT_TOUCHPAD);
  82. // Find out what to log
  83. var inputType;
  84. if (isKeyboard && isPointer)
  85. inputType = "keyboard/pointer";
  86. else if (isKeyboard)
  87. inputType = "keyboard";
  88. else if (isPointer)
  89. inputType = "mouse";
  90. // Log add/change
  91. if (evt === "add")
  92. logger.info(inputType, dev.NAME, "added");
  93. else if (evt === "change")
  94. logger.info(inputType, dev.NAME, "changed");
  95. // Run through and apply relevant rules
  96. conf.forEach(entry => {
  97. if (entry.type === "pointer" && !isPointer)
  98. return;
  99. if (entry.type === "keyboard" && !isKeyboard)
  100. return;
  101. if (!nameMatches(dev, entry.name))
  102. return;
  103. // Add pointer: or keyboard: to name, and remove quotes
  104. var name = dev.NAME.substring(1, dev.NAME.length - 1);
  105. if (entry.type === "pointer")
  106. name = "pointer:"+name;
  107. else if (entry.type === "keyboard")
  108. name = "keyboard:"+name
  109. else
  110. return log.error("Invalid input type: "+entry.type);
  111. // If the entry matches everything, we don't need to print xinput warnings
  112. var suppressWarnings = entry.name === "*";
  113. // Set xinput options
  114. if (entry.options)
  115. entry.options.forEach(prop => setProp(name, prop, suppressWarnings));
  116. // Run commands
  117. if (entry.commands)
  118. entry.commands.forEach(queueCmd);
  119. });
  120. // Run queued commands
  121. runCmds();
  122. }
  123. function start(conf_, logger_, modules_) {
  124. conf = conf_ || conf;
  125. logger = logger_ || logger;
  126. modules = modules_ || modules;
  127. udev.list("input").forEach(dev => onchange(dev, "init"));
  128. monitor = udev.monitor("input");
  129. monitor.on("add", dev => onchange(dev, "add"));
  130. monitor.on("change", dev => onchange(dev, "change"));
  131. }
  132. function stop(cb) {
  133. monitor.close();
  134. cb();
  135. }
  136. function event(name, ...params) {
  137. switch (name) {
  138. default:
  139. logger.warn("Unknown event: "+name);
  140. }
  141. }