diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index 595f86be13c0f5..fac451b7c402e6 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -20,6 +20,8 @@ - [Bazecor](https://github.com/Dygmalab/Bazecor), the graphical configurator for Dygma Products. +- [Bonsai](https://git.sr.ht/~stacyharper/bonsai), a general-purpose event mapper/state machine primarily used to create complex key shortcuts, and as part of the [SXMO](https://sxmo.org/) desktop environment. Available as [services.bonsaid](#opt-services.bonsaid.enable). + - [scanservjs](https://github.com/sbs20/scanservjs/), a web UI for SANE scanners. Available at [services.scanservjs](#opt-services.scanservjs.enable). - [Kimai](https://www.kimai.org/), a web-based multi-user time-tracking application. Available as [services.kimai](options.html#opt-services.kimai). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index d3963d6114cc36..071ccc12b91a4b 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -509,6 +509,7 @@ ./services/desktops/ayatana-indicators.nix ./services/desktops/bamf.nix ./services/desktops/blueman.nix + ./services/desktops/bonsaid.nix ./services/desktops/cpupower-gui.nix ./services/desktops/deepin/deepin-anything.nix ./services/desktops/deepin/dde-api.nix diff --git a/nixos/modules/services/desktops/bonsaid.nix b/nixos/modules/services/desktops/bonsaid.nix new file mode 100644 index 00000000000000..3818e322626a28 --- /dev/null +++ b/nixos/modules/services/desktops/bonsaid.nix @@ -0,0 +1,163 @@ +{ + config, + lib, + pkgs, + ... +}: +let + json = pkgs.formats.json { }; + transitionType = lib.types.submodule { + freeformType = json.type; + options.type = lib.mkOption { + type = lib.types.enum [ + "delay" + "event" + "exec" + ]; + description = '' + Type of transition. Determines how bonsaid interprets the other options in this transition. + ''; + }; + options.command = lib.mkOption { + type = lib.types.nullOr (lib.types.listOf lib.types.str); + default = null; + description = '' + Command to run when this transition is taken. + This is executed inline by `bonsaid` and blocks handling of any other events until completion. + To perform the command asynchronously, specify it like `[ "setsid" "-f" "my-command" ]`. + + Only effects transitions with `type = "exec"`. + ''; + }; + options.delay_duration = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = '' + Nanoseconds to wait after the previous state change before performing this transition. + This can be placed at the same level as a `type = "event"` transition to achieve a + timeout mechanism. + + Only effects transitions with `type = "delay"`. + ''; + }; + options.event_name = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Name of the event which should trigger this transition when received by `bonsaid`. + Events are sent to `bonsaid` by running `bonsaictl -e `. + + Only effects transitions with `type = "event"`. + ''; + }; + options.transitions = lib.mkOption { + type = lib.types.listOf transitionType; + default = [ ]; + description = '' + List of transitions out of this state. + If left empty, then this state is considered a terminal state and entering it will + trigger an immediate transition back to the root state (after processing side effects). + ''; + visible = "shallow"; + }; + }; + cfg = config.services.bonsaid; +in +{ + meta.maintainers = [ lib.maintainers.colinsane ]; + meta.buildDocsInSandbox = false; + + options.services.bonsaid = { + enable = lib.mkEnableOption "bonsaid"; + package = lib.mkPackageOption pkgs "bonsai" { }; + extraFlags = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = '' + Extra flags to pass to `bonsaid`, such as `[ "-v" ]` to enable verbose logging. + ''; + }; + settings = lib.mkOption { + type = lib.types.listOf transitionType; + description = '' + State transition definitions. See the upstream [[README]](https://git.sr.ht/~stacyharper/bonsai) + for extended documentation and a more complete example. + ''; + example = [ + { + type = "event"; + event_name = "power_button_pressed"; + transitions = [ + { + # Hold power button for 600ms to trigger a command + type = "delay"; + delay_duration = 600000000; + transitions = [ + { + type = "exec"; + command = [ + "swaymsg" + "--" + "output" + "*" + "power" + "off" + ]; + # `transitions = []` marks this as a terminal state, + # so bonsai will return to the root state immediately after executing the above command. + transitions = [ ]; + } + ]; + } + { + # If the power button is released before the 600ms elapses, return to the root state. + type = "event"; + event_name = "power_button_released"; + transitions = [ ]; + } + ]; + } + ]; + }; + configFile = lib.mkOption { + type = lib.types.path; + default = + let + filterNulls = + v: + if lib.isAttrs v then + lib.mapAttrs (_: filterNulls) (lib.filterAttrs (_: a: a != null) v) + else if lib.isList v then + lib.map filterNulls (lib.filter (a: a != null) v) + else + v; + in + json.generate "bonsai_tree.json" (filterNulls cfg.settings); + description = '' + Path to a .json file specifying the state transitions. + You don't need to set this unless you prefer to provide the json file + yourself instead of using the `settings` option. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.user.services.bonsaid = { + description = "Bonsai Finite State Machine daemon"; + documentation = [ "https://git.sr.ht/~stacyharper/bonsai" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = lib.escapeShellArgs ( + [ + (lib.getExe' cfg.package "bonsaid") + "-t" + cfg.configFile + ] + ++ cfg.extraFlags + ); + Restart = "on-failure"; + RestartSec = "5s"; + }; + }; + }; +}