Files
nix-files/hosts/common/programs/bonsai.nix
2024-12-19 22:28:09 +00:00

74 lines
3.1 KiB
Nix

# bonsai docs: <https://sr.ht/~stacyharper/bonsai/>
{ config, lib, options, pkgs, ... }:
let
cfg = config.sane.programs.bonsai;
in
{
sane.programs.bonsai = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options = {
transitions = mkOption {
type = options.services.bonsaid.settings.type;
default = [];
};
};
};
};
packageUnwrapped = pkgs.bonsai.overrideAttrs (upstream: {
# patch to place the socket in a subdirectory where it can be sandboxed
postPatch = (upstream.postPatch or "") + ''
substituteInPlace cmd/{bonsaictl,bonsaid}/main.ha \
--replace-fail 'path::set(&buf, statedir, "bonsai")' 'path::set(&buf, statedir, "bonsai/bonsai")'
'';
});
fs.".config/bonsai/bonsai_tree.json".symlink.target = config.services.bonsaid.configFile;
sandbox.extraRuntimePaths = [
"bonsai"
];
services.bonsaid = {
description = "bonsai: programmable input dispatcher";
dependencyOf = [ "sway" ]; # to ensure `$XDG_RUNTIME_DIR/bonsai` exists before sway binds it
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 = pkgs.writeShellScript "bonsai-start" ''
# TODO: don't create the sway directory here!
# i do it for now because sway and bonsai call into eachother; circular dependency:
# - sway -> bonsai -> sane-input-handler -> swaymsg
mkdir -p ''${XDG_RUNTIME_DIR}/{bonsai,sway}
exec nice -n -11 bonsaid -t ''${HOME}/.config/bonsai/bonsai_tree.json
'';
cleanupCommand = ''rm -f ''${XDG_RUNTIME_DIR}/bonsai/bonsai'';
readiness.waitExists = [
''''${XDG_RUNTIME_DIR}/bonsai/bonsai''
];
};
};
# plug into the (proposed) nixpkgs bonsaid service.
# it's a user service, and since i don't use the service manager it doesn't actually activate:
# i just steal the config file generation from it :)
services.bonsaid.settings = lib.mkIf cfg.enabled (lib.mkMerge [
cfg.config.transitions
[{
type = "delay";
transitions = [];
# speculative: i've observed a hang inside bonsai (rather, hare-ev) where it
# attempts to read from a timer, assuming it to have expired, and the read *never* returns.
# i think this can happen when an `exec` and a `delay` trigger simultaneously?
# particularly, hare-ev does the exec action callback, during which bonsaid enters a node w/o delay and *disables* the timer, and then reading the timer hangs.
# if true, then adding a delay to the root node alleviates that (so long as all other nodes also have delays).
#
# long term, it may be best to move away from bonsai. aside from the above, it's really easy to get it to segfault.
delay_duration = 30000 * 1000000;
}]
]);
# vvv not actually necessary. TODO: delete this line once the service is upstreamed?
services.bonsaid.enable = lib.mkIf cfg.enabled true;
}