Browse Source

lots of stuff

master
mortie 8 years ago
parent
commit
8e0ddc4748
9 changed files with 333 additions and 64 deletions
  1. 20
    0
      README.md
  2. 21
    50
      dedaemon.js
  3. 9
    0
      js/async.js
  4. 56
    0
      js/parse-conf.js
  5. 23
    1
      modules/display/index.js
  6. 17
    11
      modules/input/index.js
  7. 183
    0
      modules/process/index.js
  8. 3
    2
      modules/wallpaper/index.js
  9. 1
    0
      package.json

+ 20
- 0
README.md View File

# DEDaemon

DEDaemon is a daemon to give some of the perks of a full desktop environment to
those of us running window managers.

## Installation

Run `npm install -g dedaemon` as root.

## Usage

`dedaemon <config file>`

e.g:

`dedaemon ~/.config/dedaemon.hcnf`

You probably want to run that on startup. If you're running i3wm, that means
adding `exec --no-startup-id dedaemon ~/.config/dedaemon.hcnf` to
`~/.i3/config`.

+ 21
- 50
dedaemon.js View File

#!/usr/bin/env node #!/usr/bin/env node


var syscheck = require("./js/syscheck"); var syscheck = require("./js/syscheck");
var parseConf = require("./js/parse-conf");
var async = require("./js/async");


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"),
}; };


var config = {
display: [
{
name: "*",
resolution: "max",
rate: "max",
where: { left_of: "primary" },
},
],

input: [
{
type: "pointer",
name: "*",
options: [,
[ "libinput Tapping Enabled", 1 ],
],
},

{
type: "pointer",
name: "Razer Razer Naga",
options: [
[ "libinput Accel Speed", "-0.8" ],
],
},

{
type: "keyboard",
name: "*",
commands: [
"xset r rate 200 60",
"setxkbmap dvorak -option ctrl:swapcaps -option altwin:swap_alt_win",
],
},
],

wallpaper: {
path: "/home/martin/background.jpg",
},
if (!process.argv[2]) {
console.log("Usage:", process.argv[1], "<config>");
process.exit(1);
} }


var config = parseConf(process.argv[2]);

function createLogger(name) { function createLogger(name) {
function log(pre, msg) { function log(pre, msg) {
console.error(pre+msg.join(" ")); console.error(pre+msg.join(" "));
var mod = modules[i]; var mod = modules[i];
var conf = config[i] || {}; var conf = config[i] || {};


if (conf instanceof Array && conf.length === 0)
return;

mod.start(conf, createLogger(i), modules); mod.start(conf, createLogger(i), modules);
}); });
} }


function stopAll(cb) { function stopAll(cb) {
var keys = Object.keys(modules); var keys = Object.keys(modules);
var next = async(keys.length, cb);
keys.forEach(i => modules[i].stop(next));
}


var cbs = keys.length;
function next() {
cbs -= 1;
if (cbs === 0)
cb();
}

keys.forEach(i => modules.stop(next));
function onTerm() {
stopAll(() => process.exit(1));
} }


syscheck(ok => { syscheck(ok => {
else else
console.error("Missing binaries, exiting."); console.error("Missing binaries, exiting.");
}); });

process.on("SIGTERM", onTerm);
process.on("SIGINT", onTerm);

+ 9
- 0
js/async.js View File

module.exports = async;

function async(num, cb) {
return function() {
num -= 1;
if (num === 0)
cb();
}
}

+ 56
- 0
js/parse-conf.js View File

var hconfig = require("hconfig");

module.exports = parse;

var configStructure = {
display: {
count: "many",
props: {
name: "string",
resolution: "string",
rate: "string",
where: "object",
},
},
input: {
count: "many",
props: {
name: "string",
type: "string",
commands: "array",
options: "array",
},
},
wallpaper: {
count: "once",
props: {
name: "null",
path: "string",
},
},
process: {
count: "many",
props: {
name: "string",
run: "array",
"in": [ "string", "null" ],
env: [ "object", "null" ],
restart: "bool",
as: [ "string", "null" ],
},
},
}

function parse(file) {
try {
return hconfig.parseConfFile(
file, configStructure);
} catch (err) {
if (err.hconfigParseError) {
console.error(err.message);
process.exit(1);
} else {
throw err;
}
}
}

+ 23
- 1
modules/display/index.js View File

var udev = require("udev");

exports.start = start; exports.start = start;
exports.stop = stop; exports.stop = stop;
exports.event = event; exports.event = event;
var logger; var logger;
var modules; var modules;


var monitor;

function onchange(dev, evt) {
if (evt === "add")
logger.info("display added");
else if (evt === "change")
logger.info("display changed");
else
logger.info(dev);

//modules.wallpaper.event("reload");
}

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;

monitor = udev.monitor("drm");
monitor.on("add", dev => onchange(dev, "add"));
monitor.on("change", dev => onchange(dev, "change"));
} }


function stop(cb) { function stop(cb) {
} }


function event(name, ...params) { function event(name, ...params) {
logger.info("Event", name, params.toString());
switch (name) {
default:
logger.warn("Unknown event:", name);
}
} }

+ 17
- 11
modules/input/index.js View File



// Devices which aren't keyboards or mice with names aren't interesting // Devices which aren't keyboards or mice with names aren't interesting
function filter(dev) { function filter(dev) {
return dev.NAME && dev.SUBSYSTEM === "input" &&
(dev.ID_INPUT_KEYBOARD || dev.ID_INPUT_MOUSE);
return dev.NAME &&
(dev.ID_INPUT_KEYBOARD || dev.ID_INPUT_MOUSE || dev.ID_INPUT_TOUCHPAD);
} }


// name can be either an array or a string // name can be either an array or a string
if (!filter(dev)) if (!filter(dev))
return; return;


var isKeyboard = !!dev.ID_INPUT_KEYBOARD;
var isPointer = !!(dev.ID_INPUT_MOUSE || dev.ID_INPUT_TOUCHPAD);

// Find out what to log // Find out what to log
var inputType; var inputType;
if (dev.ID_INPUT_KEYBOARD && dev.ID_INPUT_MOUSE)
inputType = "keyboard/mouse";
else if (dev.ID_INPUT_KEYBOARD)
if (isKeyboard && isPointer)
inputType = "keyboard/pointer";
else if (isKeyboard)
inputType = "keyboard"; inputType = "keyboard";
else if (dev.ID_INPUT_MOUSE)
else if (isPointer)
inputType = "mouse"; inputType = "mouse";


// Log add/change // Log add/change


// Run through and apply relevant rules // Run through and apply relevant rules
conf.forEach(entry => { conf.forEach(entry => {
if (entry.type === "pointer" && !dev.ID_INPUT_MOUSE)
if (entry.type === "pointer" && !isPointer)
return; return;
if (entry.type === "keyboard" && !dev.ID_INPUT_KEYBOARD)
if (entry.type === "keyboard" && !isKeyboard)
return; return;
if (!nameMatches(dev, entry.name)) if (!nameMatches(dev, entry.name))
return; return;
logger = logger_ || logger; logger = logger_ || logger;
modules = modules_ || modules; modules = modules_ || modules;


udev.list().forEach(dev => onchange(dev, "init"));
udev.list("input").forEach(dev => onchange(dev, "init"));


monitor = udev.monitor();
monitor = udev.monitor("input");
monitor.on("add", dev => onchange(dev, "add")); monitor.on("add", dev => onchange(dev, "add"));
monitor.on("change", dev => onchange(dev, "change")); monitor.on("change", dev => onchange(dev, "change"));
} }
} }


function event(name, ...params) { function event(name, ...params) {
logger.info("Event", name, params.toString());
switch (name) {
default:
logger.warn("Unknown event: "+name);
}
} }

+ 183
- 0
modules/process/index.js View File

var spawn = require("child_process").spawn;
var async = require("../../js/async");

exports.start = start;
exports.stop = stop;
exports.event = event;

var conf;
var logger;
var modules;

class Process {
constructor(id, cmd, options) {
this.id = id;
this.restart = options.restart;
this.stopping = false;
this.running = false;
this.restarts = 0;

this.name = cmd[0];
cmd.shift();
this.args = cmd;
this.childOpts = {
env: options.env,
cwd: options.cwd,
};

this.info = logger.info.bind(logger, this.id+":");
this.warn = logger.warn.bind(logger, this.id+":");
}

logOutput(stream, data) {
data.toString()
.split("\n")
.map(line => line.trim())
.forEach(line => {
if (line === "") return;
this.info(stream+":", line);
});
}

onexit() {
if (!this.restart)
return;

if (this.restarts < 2) {
this.restarts += 1;
var restarts = this.restarts;
this.info("Restarting in 2 seconds.");
setTimeout(() => {
this.start();
this.restarts = restarts;
}, 2000);
} else {
this.warn("Not restarting anymore after 2 restarts.");
}
}

start() {
this.stopping = false;
this.running = true;
this.restarts = 0;

this.child = spawn(this.name, this.args, this.childOpts);

this.child.stdout.on("data",
d => this.logOutput("stdout", d));
this.child.stderr.on("data",
d => this.logOutput("stderr", d));

this.child.once("error", err => {
if (!this.stopping)
this.warn("Failed to start:", err);

this.onexit();
});
this.child.once("close", code => {
this.running = false;
if (this.stopping)
return;

if (code === 0)
this.info("Exited with status code 0");
else
this.warn("Exited with status code", code);
this.onexit();
});
}

stop(cb) {
this.stopping = true;
if (!this.running)
return cb();

this.info("Sending SIGTERM.");
this.child.kill("SIGTERM");

setTimeout(() => {
if (this.running) {
this.info("Sending SIGKILL.");
this.child.kill("SIGKILL");
}

cb();
}, 1000);
}
}

class ProcessGroup {
constructor(id, cmds, options) {
this.procs = [];
cmds.forEach(cmd => {
var name = cmd[0];
this.procs.push(new Process(id+"("+name+")", cmd, options));
});
}

start() {
this.procs.forEach(p => p.start());
}

stop(cb) {
var next = async(this.procs.length, cb);
this.procs.forEach(p => p.stop(next));
}
}

var procs = {};

function start(conf_, logger_, modules_) {
conf = conf_ || conf;
logger = logger_ || logger;
modules = modules_ || modules;

conf.forEach(proc => {
if (procs[proc.name])
return logger.warn("Igonring duplicate process: "+proc.name);

var env = null;
if (proc.env) {
env = {};
for (var i in process.env) {
env[i] = process.env[i];
}
for (var i in proc.env) {
env[i] = proc.env[i];
}
}

var opts = {
cwd: proc.in,
env: env,
restart: !!proc.restart,
};

var p;
if (!proc.as || proc.as === "process") {
p = new Process(proc.name, proc.run, opts);
} else if (proc.as === "group") {
p = new ProcessGroup(proc.name, proc.run, opts);
} else {
return logger.warn(
proc.name+":",
"Invalid 'as' attribute:",
proc.as);
}
p.start();
procs[proc.name] = p;
});
}

function stop(cb) {
var keys = Object.keys(procs);
var next = async(keys.length, cb);
keys.forEach(i => procs[i].stop(next));
}

function event(name, ...params) {
switch (name) {
default:
logger.warn("Unknown event: "+name);
}
}

+ 3
- 2
modules/wallpaper/index.js View File

} }


function event(name, ...params) { function event(name, ...params) {
logger.info("Event", name, params.toString());

switch (name) { switch (name) {
case "reload": case "reload":
runFeh(); runFeh();
break; break;

default:
logger.warn("Unknown event:", name);
} }
} }

+ 1
- 0
package.json View File

"author": "Martin Dørum Nygaard <martid0311@gmail.com> (http://mort.coffee)", "author": "Martin Dørum Nygaard <martid0311@gmail.com> (http://mort.coffee)",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"hconfig": "^0.4.0",
"udev": "^0.4.0" "udev": "^0.4.0"
}, },
"bin": { "bin": {

Loading…
Cancel
Save