nix-files/hosts/common/programs/bonsai.nix

122 lines
3.5 KiB
Nix

# bonsai docs: <https://sr.ht/~stacyharper/bonsai/>
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.bonsai;
delayType = with lib; types.submodule {
options = {
type = mkOption {
type = types.enum [ "delay" ];
# default = "delay";
};
delay_duration = mkOption {
type = types.int;
description = ''
used for "delay" types only.
nanoseconds until the event is finalized.
'';
};
transitions = mkOption {
type = types.listOf transitionType;
default = [];
description = ''
list of transitions out of this state (i.e. after completing the delay).
'';
};
};
};
eventType = with lib; types.submodule {
options = {
type = mkOption {
type = types.enum [ "event" ];
# default = "event";
};
event_name = mkOption {
type = types.str;
description = ''
name of event which this transition applies to.
'';
};
transitions = mkOption {
type = types.listOf transitionType;
default = [];
description = ''
list of transitions out of this state.
'';
};
};
};
execType = with lib; types.submodule {
options = {
type = mkOption {
type = types.enum [ "exec" ];
# default = "exec";
};
command = mkOption {
type = types.listOf types.str;
description = ''
command to run when the event is triggered.
'';
};
transitions = mkOption {
type = types.listOf transitionType;
default = [];
description = ''
list of transitions out of this state (i.e. after successfully executing the command)
'';
};
};
};
isDelay = x: delayType.check x && x.type == "delay";
isEvent = x: eventType.check x && x.type == "event";
isExec = x: execType.check x && x.type == "exec";
# unfortunately, `types.oneOf` is naive about submodules, so we need our own type.
# transitionType = lib.types.oneOf [ delayType eventType execType ];
transitionType = with lib.types; mkOptionType {
name = "transition";
check = x: isDelay x || isEvent x || isExec x;
merge = loc: defs: let
defList = builtins.map (d: d.value) defs;
in
if builtins.all isDelay defList then
delayType.merge loc defs
else if builtins.all isEvent defList then
eventType.merge loc defs
else if builtins.all isExec defList then
execType.merge loc defs
else
mergeOneOption loc defs
;
};
in
{
sane.programs.bonsai = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options = {
transitions = mkOption {
type = types.listOf transitionType;
default = [];
};
};
};
};
fs.".config/bonsai/bonsai_tree.json".symlink.text = builtins.toJSON cfg.config.transitions;
sandbox.method = "bwrap";
sandbox.extraRuntimePaths = [
"/" #< just needs "bonsai", but needs to create it first...
];
services.bonsaid = {
description = "bonsai: programmable input dispatcher";
partOf = [ "graphical-session" ];
# nice -n -11 chosen arbitrarily. i hope this will allow for faster response to inputs, but without audio underruns (pipewire is -21, dino -15-ish)
command = "nice -n -11 bonsaid -t $HOME/.config/bonsai/bonsai_tree.json";
cleanupCommand = "rm -f $XDG_RUNTIME_DIR/bonsai";
};
};
}