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 = {}; |