| var udev = require("./udev"); | var udev = require("./udev"); | ||||
| var modules = { | var modules = { | ||||
| // display: require("./modules/display"), | |||||
| display: require("./modules/display"), | |||||
| input: require("./modules/input"), | input: require("./modules/input"), | ||||
| wallpaper: require("./modules/wallpaper"), | wallpaper: require("./modules/wallpaper"), | ||||
| process: require("./modules/process"), | process: require("./modules/process"), | ||||
| }; | }; | ||||
| if (!process.argv[2]) { | if (!process.argv[2]) { | ||||
| console.error("Usage:", process.argv[1], "<config>"); | |||||
| console.error("Usage:", process.argv[1], "<config file>"); | |||||
| console.error(" ", process.argv[1], "list"); | |||||
| process.exit(1); | process.exit(1); | ||||
| } | } | ||||
| var config = parseConf(process.argv[2]); | |||||
| var config; | |||||
| function createLogger(name) { | function createLogger(name) { | ||||
| function log(pre, msg) { | function log(pre, msg) { | ||||
| function startAll() { | function startAll() { | ||||
| Object.keys(modules).forEach(i => { | Object.keys(modules).forEach(i => { | ||||
| var mod = modules[i]; | var mod = modules[i]; | ||||
| var conf = config[i] || {}; | |||||
| var conf = config[i]; | |||||
| if (conf instanceof Array && conf.length === 0) | if (conf instanceof Array && conf.length === 0) | ||||
| return; | return; | ||||
| } | } | ||||
| function onTerm() { | function onTerm() { | ||||
| udev.exit(); | |||||
| console.error("Exiting..."); | |||||
| stopAll(() => process.exit(1)); | stopAll(() => process.exit(1)); | ||||
| udev.exit(); | |||||
| } | } | ||||
| syscheck(ok => { | |||||
| if (ok) | |||||
| startAll(); | |||||
| else | |||||
| console.error("Missing binaries, exiting."); | |||||
| }); | |||||
| if (process.argv[2] === "list") { | |||||
| console.error("display:"); | |||||
| modules.display.list(() => { | |||||
| console.error("input:"); | |||||
| modules.input.list(() => { | |||||
| udev.exit(); | |||||
| process.exit(0); | |||||
| }); | |||||
| }); | |||||
| } else { | |||||
| var config = parseConf(process.argv[2]); | |||||
| syscheck(ok => { | |||||
| if (ok) | |||||
| startAll(); | |||||
| else | |||||
| console.error("Missing binaries, exiting."); | |||||
| }); | |||||
| process.on("SIGTERM", onTerm); | |||||
| process.on("SIGINT", onTerm); | |||||
| process.on("SIGTERM", onTerm); | |||||
| process.on("SIGINT", onTerm); | |||||
| } |
| # display * { | |||||
| # mode max | |||||
| # rate max | |||||
| # where { left-of primary } | |||||
| # } | |||||
| input * { | |||||
| type pointer | |||||
| options [ | |||||
| [ "libinput Tapping Enabled" 1 ] | |||||
| ] | |||||
| } | |||||
| input * { | |||||
| type keyboard | |||||
| commands [ | |||||
| "xset r rate 200 60" | |||||
| "setxkbmap dvorak -option ctrl:swapcaps -option altwin:swap_alt_win" | |||||
| ] | |||||
| } | |||||
| wallpaper { | |||||
| path /home/martin/background.jpg | |||||
| } |
| module.exports = async; | module.exports = async; | ||||
| function async(num, cb) { | function async(num, cb) { | ||||
| if (num === 0) | |||||
| return cb(); | |||||
| return function() { | return function() { | ||||
| num -= 1; | num -= 1; | ||||
| if (num === 0) | if (num === 0) |
| count: "many", | count: "many", | ||||
| props: { | props: { | ||||
| name: "string", | name: "string", | ||||
| resolution: "string", | |||||
| mode: "string", | |||||
| rate: "string", | rate: "string", | ||||
| where: "object", | where: "object", | ||||
| }, | }, |
| module.exports = table; | |||||
| function table(log, data, pre, sep) { | |||||
| var sep = sep || " "; | |||||
| pre = pre || ""; | |||||
| var lengths = []; | |||||
| var i = 0; | |||||
| while (true) { | |||||
| var longest = -1; | |||||
| for (var j in data) { | |||||
| var d = data[j]; | |||||
| if (d[i] == null) | |||||
| continue; | |||||
| if (d[i].length > longest) | |||||
| longest = d[i].length; | |||||
| } | |||||
| if (longest === -1) | |||||
| break; | |||||
| else | |||||
| lengths[i] = longest; | |||||
| i += 1; | |||||
| } | |||||
| for (var i in data) { | |||||
| var d = data[i]; | |||||
| var str = ""; | |||||
| for (var j in d) { | |||||
| var s = d[j]; | |||||
| var len = lengths[j]; | |||||
| str += | |||||
| s + | |||||
| (j == d.length - 1 ? "" : sep) + | |||||
| new Array(len - s.length + 1).join(" "); | |||||
| } | |||||
| log(pre+str); | |||||
| } | |||||
| } |
| var udev = require("../../udev"); | var udev = require("../../udev"); | ||||
| var xrandr = require("./xrandr"); | |||||
| var table = require("../../js/table"); | |||||
| var spawn = require("child_process").spawn; | |||||
| exports.start = start; | exports.start = start; | ||||
| exports.stop = stop; | exports.stop = stop; | ||||
| exports.event = event; | exports.event = event; | ||||
| exports.list = list; | |||||
| var conf; | var conf; | ||||
| var logger; | var logger; | ||||
| var modules; | var modules; | ||||
| function onchange(dev) { | |||||
| if (dev.ACTION === "add") | |||||
| logger.info("display added"); | |||||
| else if (dev.ACTION === "change") | |||||
| logger.info("display changed"); | |||||
| else | |||||
| logger.info(dev); | |||||
| function findMode(display, mode) { | |||||
| if (mode === "max") | |||||
| return display.modes[0]; | |||||
| //modules.wallpaper.event("reload"); | |||||
| for (var i in display.modes) { | |||||
| if (display.modes[i].resStr == mode) | |||||
| return display.modes[i]; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| function findRate(mode, rate) { | |||||
| if (rate === "max") | |||||
| return mode.rates[0]; | |||||
| for (var i in mode.rates) { | |||||
| if (mode.rates[i] == rate) | |||||
| return mode.rates[i]; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| function applyRule(primary, rule, display) { | |||||
| var cmd = "xrandr"; | |||||
| var args = [ "--output", display.id ]; | |||||
| if (rule.rate != null && rule.mode == null) | |||||
| return logger.warn("Display "+rule.name+": 'rate' specified without 'mode'"); | |||||
| var mode; | |||||
| if (rule.mode != null) { | |||||
| mode = findMode(display, rule.mode); | |||||
| if (mode == null) | |||||
| return logger.warn( | |||||
| "Display "+rule.name+": Invalid mode: "+rule.res); | |||||
| args.push("--mode"); | |||||
| args.push(mode.resStr); | |||||
| } | |||||
| var rate; | |||||
| if (rule.rate != null) { | |||||
| rate = findRate(mode, rule.rate); | |||||
| if (rate == null) | |||||
| return logger.warn( | |||||
| "Display "+rule.name+": Invalid rate: "+rule.rate); | |||||
| args.push("--rate"); | |||||
| args.push(rate); | |||||
| } | |||||
| if (rule.where) { | |||||
| var w = rule.where; | |||||
| for (var i in w) { | |||||
| if ( | |||||
| i !== "left-of" && i !== "right-of" && | |||||
| i !== "above" && i !== "below") { | |||||
| return logger.warn( | |||||
| "Display "+rule.name+": Invalid 'where' value: "+i); | |||||
| } | |||||
| if (w[i] === "primary" && display === primary) | |||||
| continue; | |||||
| var id; | |||||
| if (w[i] === "primary") { | |||||
| id = primary.id; | |||||
| } else { | |||||
| id = w[i]; | |||||
| } | |||||
| args.push("--"+i); | |||||
| args.push(id); | |||||
| } | |||||
| } | |||||
| var child = spawn(cmd, args); | |||||
| child.stderr.on("data", d => logger.warn("xrandr:", d.toString())); | |||||
| } | |||||
| function onchange() { | |||||
| if (conf == null) { | |||||
| setTimeout(() => modules.wallpaper.event("reload"), 0); | |||||
| logger.info("No displays configured."); | |||||
| return; | |||||
| } | |||||
| xrandr.list(data => { | |||||
| var displays = data.displays.filter(d => d.connected); | |||||
| var primary = displays.filter(d => d.primary)[0]; | |||||
| conf.forEach(entry => { | |||||
| displays.forEach(disp => { | |||||
| if (entry.name !== "*" && entry.name !== disp.id) | |||||
| return; | |||||
| applyRule(primary, entry, disp); | |||||
| }); | |||||
| }); | |||||
| setTimeout(() => modules.wallpaper.event("reload"), 400); | |||||
| }); | |||||
| } | } | ||||
| function start(conf_, logger_, modules_) { | function start(conf_, logger_, modules_) { | ||||
| logger = logger_ || logger; | logger = logger_ || logger; | ||||
| modules = modules_ || modules; | modules = modules_ || modules; | ||||
| onchange(); | |||||
| udev.monitor("drm", onchange); | udev.monitor("drm", onchange); | ||||
| } | } | ||||
| logger.warn("Unknown event:", name); | logger.warn("Unknown event:", name); | ||||
| } | } | ||||
| } | } | ||||
| function list(cb) { | |||||
| xrandr.list(devs => { | |||||
| devs.displays.forEach(dev => { | |||||
| console.error( | |||||
| "\tname: "+dev.id+ | |||||
| (dev.connected ? ", connected" : "")+ | |||||
| (dev.primary ? ", primary" : "")); | |||||
| var data = []; | |||||
| for (var i in dev.modes) { | |||||
| var mode = dev.modes[i]; | |||||
| data[i] = []; | |||||
| var d = data[i]; | |||||
| d.push("mode: "+mode.resStr+","); | |||||
| d.push("rate:"); | |||||
| mode.rates.forEach(r => d.push(r)); | |||||
| } | |||||
| table(console.error.bind(console), data, "\t\t"); | |||||
| }); | |||||
| cb(); | |||||
| }); | |||||
| } |
| var spawn = require("child_process").spawn; | |||||
| exports.list = list; | |||||
| function parseScreen(line) { | |||||
| var rxId = /Screen (\d+)/; | |||||
| var rxMin = /minimum (\d+) x (\d+)/; | |||||
| var rxCurr = /current (\d+) x (\d+)/; | |||||
| var rxMax = /maximum (\d+) x (\d+)/; | |||||
| var id = line.match(rxId); | |||||
| var min = line.match(rxMin); | |||||
| var curr = line.match(rxCurr); | |||||
| var max = line.match(rxMax); | |||||
| return { | |||||
| id: parseInt(id[1]), | |||||
| minimum: { | |||||
| w: parseInt(min[1]), | |||||
| h: parseInt(min[2]), | |||||
| }, | |||||
| current: { | |||||
| w: parseInt(curr[1]), | |||||
| h: parseInt(curr[2]), | |||||
| }, | |||||
| maximum: { | |||||
| w: parseInt(max[1]), | |||||
| h: parseInt(max[2]), | |||||
| }, | |||||
| }; | |||||
| } | |||||
| function parseDisplayMode(line) { | |||||
| var rxResStr = /(\d+x\d+[^\s]+)/; | |||||
| var rxRes = /(\d+)x(\d+)/; | |||||
| var rxRate = /(\d+\.\d+)/; | |||||
| var obj = {}; | |||||
| var resStr = line.match(rxResStr); | |||||
| obj.resStr = resStr[1]; | |||||
| var res = line.match(rxRes); | |||||
| obj.res = { w: parseInt(res[1]), h: parseInt(res[2]) }; | |||||
| line = line.substr(res.index + res[0].length); | |||||
| obj.rates = []; | |||||
| while (true) { | |||||
| var rate = line.match(rxRate); | |||||
| if (rate === null) | |||||
| break; | |||||
| obj.rates.push(rate[1]); | |||||
| line = line.substr(rate.index + rate[0].length); | |||||
| } | |||||
| return obj; | |||||
| } | |||||
| function parseDisplayModes(lines) { | |||||
| var modes = []; | |||||
| for (var i = 1; i < lines.length; ++i) { | |||||
| modes.push(parseDisplayMode(lines[i])); | |||||
| } | |||||
| return modes; | |||||
| } | |||||
| function parseDisplayStart(line) { | |||||
| var rxId = /^([^ ]+)/; | |||||
| var rxPrimary = /primary/; | |||||
| var rxConnected = /(connected|disconnected)/; | |||||
| var rxRes = /(\d+)x(\d+)\+(\d+)\+(\d+)/; | |||||
| var rxDims = /(\d+)mm x (\d+)mm/; | |||||
| var id = line.match(rxId); | |||||
| var primary = line.match(rxPrimary); | |||||
| var connected = line.match(rxConnected)[1] === "connected"; | |||||
| var res = line.match(rxRes); | |||||
| var dims = line.match(rxDims); | |||||
| return { | |||||
| id: id[1], | |||||
| primary: primary ? true : false, | |||||
| connected: connected, | |||||
| res: (res == null ? null : { | |||||
| w: parseInt(res[1]), | |||||
| h: parseInt(res[2]), | |||||
| x: parseInt(res[3]), | |||||
| y: parseInt(res[4]), | |||||
| }), | |||||
| dims: (res == null ? null : { | |||||
| w: parseInt(dims[1]), | |||||
| h: parseInt(dims[2]), | |||||
| }), | |||||
| } | |||||
| } | |||||
| function parseDisplay(lines) { | |||||
| var obj = parseDisplayStart(lines[0]); | |||||
| if (obj.connected) | |||||
| obj.modes = parseDisplayModes(lines); | |||||
| return obj; | |||||
| } | |||||
| function parse(str) { | |||||
| var strings = []; | |||||
| var lines = str.split("\n"); | |||||
| var screens = []; | |||||
| var displays = []; | |||||
| var currArr; | |||||
| for (var i in lines) { | |||||
| var line = lines[i]; | |||||
| if (line === "") | |||||
| continue; | |||||
| if (line.indexOf("Screen") === 0) { | |||||
| screens.push(parseScreen(line)); | |||||
| } else if (line[0] !== " " && line[0] !== "\t") { | |||||
| if (currArr) { | |||||
| displays.push(parseDisplay(currArr)); | |||||
| } | |||||
| currArr = [line]; | |||||
| } else { | |||||
| currArr.push(line); | |||||
| } | |||||
| } | |||||
| if (currArr) { | |||||
| displays.push(parseDisplay(currArr)); | |||||
| } | |||||
| return { | |||||
| screens: screens, | |||||
| displays: displays.filter(d => d != null), | |||||
| } | |||||
| } | |||||
| function list(cb) { | |||||
| var child = spawn("xrandr", [ "--query" ]); | |||||
| var output = ""; | |||||
| child.stdout.on("data", d => output += d); | |||||
| child.on("close", () => { | |||||
| var devs = parse(output); | |||||
| cb(devs); | |||||
| }); | |||||
| } |
| var exec = require("child_process").exec; | var exec = require("child_process").exec; | ||||
| var debounce = require("../../js/debounce"); | var debounce = require("../../js/debounce"); | ||||
| var table = require("../../js/table"); | |||||
| exports.start = start; | exports.start = start; | ||||
| exports.stop = stop; | exports.stop = stop; | ||||
| exports.event = event; | exports.event = event; | ||||
| exports.list = list; | |||||
| var conf; | var conf; | ||||
| var logger; | var logger; | ||||
| return true; | return true; | ||||
| } | } | ||||
| function onchange(dev) { | |||||
| if (!filter(dev)) | |||||
| return; | |||||
| function gettype(dev) { | |||||
| var isKeyboard = !!dev.ID_INPUT_KEYBOARD; | var isKeyboard = !!dev.ID_INPUT_KEYBOARD; | ||||
| var isPointer = !!(dev.ID_INPUT_MOUSE || dev.ID_INPUT_TOUCHPAD); | var isPointer = !!(dev.ID_INPUT_MOUSE || dev.ID_INPUT_TOUCHPAD); | ||||
| else if (isKeyboard) | else if (isKeyboard) | ||||
| inputType = "keyboard"; | inputType = "keyboard"; | ||||
| else if (isPointer) | else if (isPointer) | ||||
| inputType = "mouse"; | |||||
| inputType = "pointer"; | |||||
| return { isKeyboard, isPointer, inputType }; | |||||
| } | |||||
| function onchange(dev) { | |||||
| if (!filter(dev)) | |||||
| return; | |||||
| var { isKeyboard, isPointer, inputType } = gettype(dev); | |||||
| // Log add/change | // Log add/change | ||||
| if (dev.ACTION === "add") | if (dev.ACTION === "add") | ||||
| logger.warn("Unknown event: "+name); | logger.warn("Unknown event: "+name); | ||||
| } | } | ||||
| } | } | ||||
| function list(cb) { | |||||
| udev.list("input", devs => { | |||||
| var data = []; | |||||
| devs.filter(filter).forEach((dev, i) => { | |||||
| var { inputType } = gettype(dev); | |||||
| data[i] = [ "name: "+dev.NAME+",", "type: "+inputType ]; | |||||
| }); | |||||
| table(console.error.bind(console), data, "\t"); | |||||
| cb(); | |||||
| }); | |||||
| } |
| } | } | ||||
| } | } | ||||
| var procs = {}; | |||||
| var procs; | |||||
| function start(conf_, logger_, modules_) { | function start(conf_, logger_, modules_) { | ||||
| conf = conf_ || conf; | conf = conf_ || conf; | ||||
| logger = logger_ || logger; | logger = logger_ || logger; | ||||
| modules = modules_ || modules; | modules = modules_ || modules; | ||||
| procs = {}; | |||||
| if (conf == null) { | |||||
| logger.info("No processes."); | |||||
| return; | |||||
| } | |||||
| conf.forEach(proc => { | conf.forEach(proc => { | ||||
| if (procs[proc.name]) | if (procs[proc.name]) | ||||
| return logger.warn("Igonring duplicate process: "+proc.name); | return logger.warn("Igonring duplicate process: "+proc.name); |
| if (!conf.path) | if (!conf.path) | ||||
| return logger.error("Expected conf.path"); | return logger.error("Expected conf.path"); | ||||
| runFeh(); | |||||
| var dirname = pathlib.dirname(conf.path); | var dirname = pathlib.dirname(conf.path); | ||||
| var basename = pathlib.basename(conf.path); | var basename = pathlib.basename(conf.path); | ||||
| currstr = lines[lines.length - 1]; | currstr = lines[lines.length - 1]; | ||||
| }); | }); | ||||
| child.stderr.on("data", d => console.error(d.toString())); | |||||
| child.stderr.on("data", d => console.error("udev error:", d.toString())); | |||||
| var listq = []; | var listq = []; | ||||
| var monitors = {}; | var monitors = {}; |