@@ -6,18 +6,19 @@ var async = require("./js/async"); | |||
var udev = require("./udev"); | |||
var modules = { | |||
// display: require("./modules/display"), | |||
display: require("./modules/display"), | |||
input: require("./modules/input"), | |||
wallpaper: require("./modules/wallpaper"), | |||
process: require("./modules/process"), | |||
}; | |||
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); | |||
} | |||
var config = parseConf(process.argv[2]); | |||
var config; | |||
function createLogger(name) { | |||
function log(pre, msg) { | |||
@@ -34,7 +35,7 @@ function createLogger(name) { | |||
function startAll() { | |||
Object.keys(modules).forEach(i => { | |||
var mod = modules[i]; | |||
var conf = config[i] || {}; | |||
var conf = config[i]; | |||
if (conf instanceof Array && conf.length === 0) | |||
return; | |||
@@ -50,16 +51,30 @@ function stopAll(cb) { | |||
} | |||
function onTerm() { | |||
udev.exit(); | |||
console.error("Exiting..."); | |||
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); | |||
} |
@@ -0,0 +1,24 @@ | |||
# 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 | |||
} |
@@ -1,6 +1,9 @@ | |||
module.exports = async; | |||
function async(num, cb) { | |||
if (num === 0) | |||
return cb(); | |||
return function() { | |||
num -= 1; | |||
if (num === 0) |
@@ -7,7 +7,7 @@ var configStructure = { | |||
count: "many", | |||
props: { | |||
name: "string", | |||
resolution: "string", | |||
mode: "string", | |||
rate: "string", | |||
where: "object", | |||
}, |
@@ -0,0 +1,41 @@ | |||
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); | |||
} | |||
} |
@@ -1,22 +1,121 @@ | |||
var udev = require("../../udev"); | |||
var xrandr = require("./xrandr"); | |||
var table = require("../../js/table"); | |||
var spawn = require("child_process").spawn; | |||
exports.start = start; | |||
exports.stop = stop; | |||
exports.event = event; | |||
exports.list = list; | |||
var conf; | |||
var logger; | |||
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_) { | |||
@@ -24,6 +123,7 @@ function start(conf_, logger_, modules_) { | |||
logger = logger_ || logger; | |||
modules = modules_ || modules; | |||
onchange(); | |||
udev.monitor("drm", onchange); | |||
} | |||
@@ -38,3 +138,27 @@ function event(name, ...params) { | |||
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(); | |||
}); | |||
} |
@@ -0,0 +1,149 @@ | |||
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); | |||
}); | |||
} |
@@ -3,10 +3,12 @@ var spawn = require("child_process").spawn; | |||
var exec = require("child_process").exec; | |||
var debounce = require("../../js/debounce"); | |||
var table = require("../../js/table"); | |||
exports.start = start; | |||
exports.stop = stop; | |||
exports.event = event; | |||
exports.list = list; | |||
var conf; | |||
var logger; | |||
@@ -88,10 +90,7 @@ function nameMatches(dev, name) { | |||
return true; | |||
} | |||
function onchange(dev) { | |||
if (!filter(dev)) | |||
return; | |||
function gettype(dev) { | |||
var isKeyboard = !!dev.ID_INPUT_KEYBOARD; | |||
var isPointer = !!(dev.ID_INPUT_MOUSE || dev.ID_INPUT_TOUCHPAD); | |||
@@ -102,7 +101,16 @@ function onchange(dev) { | |||
else if (isKeyboard) | |||
inputType = "keyboard"; | |||
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 | |||
if (dev.ACTION === "add") | |||
@@ -164,3 +172,16 @@ function event(name, ...params) { | |||
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(); | |||
}); | |||
} |
@@ -130,13 +130,20 @@ class ProcessGroup { | |||
} | |||
} | |||
var procs = {}; | |||
var procs; | |||
function start(conf_, logger_, modules_) { | |||
conf = conf_ || conf; | |||
logger = logger_ || logger; | |||
modules = modules_ || modules; | |||
procs = {}; | |||
if (conf == null) { | |||
logger.info("No processes."); | |||
return; | |||
} | |||
conf.forEach(proc => { | |||
if (procs[proc.name]) | |||
return logger.warn("Igonring duplicate process: "+proc.name); |
@@ -58,8 +58,6 @@ function start(conf_, logger_, modules_) { | |||
if (!conf.path) | |||
return logger.error("Expected conf.path"); | |||
runFeh(); | |||
var dirname = pathlib.dirname(conf.path); | |||
var basename = pathlib.basename(conf.path); | |||
@@ -24,7 +24,7 @@ child.stdout.on("data", d => { | |||
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 monitors = {}; |