diff --git a/hosts/common/programs/swaynotificationcenter/default.nix b/hosts/common/programs/swaynotificationcenter/default.nix index 9cdbc95c7..93b4218a8 100644 --- a/hosts/common/programs/swaynotificationcenter/default.nix +++ b/hosts/common/programs/swaynotificationcenter/default.nix @@ -18,71 +18,6 @@ let cfg = config.sane.programs.swaynotificationcenter; - fbcli-wrapper = pkgs.writeShellApplication { - name = "swaync-fbcli"; - runtimeInputs = [ - config.sane.programs.feedbackd.package - pkgs.procps # for pkill - cfg.package - ]; - text = '' - # if in Do Not Disturb, don't do any feedback - # TODO: better solution is to actually make use of feedbackd profiles. - # i.e. set profile to `quiet` when in DnD mode - if [ "$SWAYNC_URGENCY" != "Critical" ] && [ "$(swaync-client --get-dnd)" = "true" ]; then - exit - fi - - # kill children if killed, to allow that killing this parent process will end the real fbcli call - cleanup() { - echo "aborting fbcli notification (PID $child)" - pkill -P "$child" - exit 0 # exit cleanly to avoid swaync alerting a script failure - } - trap cleanup SIGINT SIGQUIT SIGTERM - - # feedbackd stops playback when the caller exits - # and fbcli will exit immediately if it has no stdin. - # so spoof a stdin: - /bin/sh -c "true | fbcli $*" & - child=$! - wait - ''; - }; - fbcli = "${fbcli-wrapper}/bin/swaync-fbcli"; - - # we do this because swaync's exec naively splits the command on space to produce its argv, rather than parsing the shell. - # [ "pkill" "-f" "fbcli" "--event" ... ] -> breaks pkill - # [ "pkill" "-f" "fbcli --event ..." ] -> is what we want - fbcli-stop-wrapper = pkgs.writeShellApplication { - name = "fbcli-stop"; - runtimeInputs = [ - pkgs.procps # for pkill - ]; - text = '' - pkill -e -f "${fbcli} $*" - ''; - }; - fbcli-stop = "${fbcli-stop-wrapper}/bin/fbcli-stop"; - - kill-singleton_ = pkgs.writeShellApplication { - name = "kill-singleton"; - runtimeInputs = [ - pkgs.procps # for pgrep - pkgs.gnugrep - ]; - text = '' - pids=$(pgrep --full "$*" | tr '\n' ' ') || true - # only act if there's exactly one pid - if echo "$pids" | grep -Eq '^[0-9]+ ?$'; then - kill "$pids" - else - echo "kill-singleton: skipping because multiple pids match: $pids" - fi - ''; - }; - kill-singleton = "${kill-singleton_}/bin/kill-singleton"; - systemctl-toggle = pkgs.writeShellApplication { name = "systemctl-toggle"; runtimeInputs = [ @@ -207,93 +142,8 @@ in hide-on-clear = true; #< hide control center when clicking "clear all" hide-on-action = true; script-fail-notify = true; - scripts = { - # a script can match regex on these fields. only fired if all listed fields match: - # - app-name - # - desktop-entry - # - summary - # - body - # - urgency (Low/Normal/Critical) - # - category - # additionally, the script can be run either on receipt or action: - # - run-on = "receive" or "action" - # when script is run, these env vars are available: - # - SWAYNC_BODY - # - SWAYNC_DESKTOP_ENTRY - # - SWAYNC_URGENCY - # - SWAYNC_TIME - # - SWAYNC_APP_NAME - # - SWAYNC_CATEGORY - # - SWAYNC_REPLACES_ID - # - SWAYNC_ID - # - SWAYNC_SUMMARY - - # rules to use for testing. trigger with: - # - `notify-send test test:message` (etc) - # should also be possible to trigger via any messaging app - fbcli-test-im = { - body = "test:message"; - exec = "${fbcli} --event proxied-message-new-instant"; - }; - fbcli-test-call = { - body = "test:call"; - exec = "${fbcli} --event phone-incoming-call -t 20"; - }; - fbcli-test-call-stop = { - body = "test:call-stop"; - exec = "${fbcli-stop} --event phone-incoming-call -t 20"; - }; - fbcli-test-timer = { - body = "test:timer"; - exec = "${fbcli} --event timeout-completed"; - }; - - incoming-im-known-app-name = { - # trigger notification sound on behalf of these IM clients. - app-name = "(Chats|Dino|discord|dissent|Element|Fractal)"; - body = "^(?!Incoming call).*$"; #< don't match Dino Incoming calls - exec = "${fbcli} --event proxied-message-new-instant"; - }; - incoming-im-known-desktop-entry = { - # trigger notification sound on behalf of these IM clients. - # these clients don't have an app-name (listed as ""), but do have a desktop-entry - desktop-entry = "com.github.uowuo.abaddon"; - exec = "${fbcli} --event proxied-message-new-instant"; - }; - incoming-call = { - app-name = "Dino"; - body = "^Incoming call$"; - exec = "${fbcli} --event phone-incoming-call -t 20"; - }; - incoming-call-acted-on = { - # when the notification is clicked, stop sounding the ringer - app-name = "Dino"; - body = "^Incoming call$"; - run-on = "action"; - exec = "${fbcli-stop} --event phone-incoming-call -t 20"; - }; - timer-done = { - # sxmo_timer.sh fires off notifications like "Done with 10m" when a 10minute timer completes. - # it sends such a notification every second until dismissed - app-name = "notify-send"; - summary = "^Done with .*$"; - # XXX: could use alarm-clock-elapsed, but that's got a duration > 1s - # which isn't great for sxmo's 1s repeat. - # TODO: maybe better to have sxmo only notify once, and handle this like with Dino's incoming call - exec = "${fbcli} --event timeout-completed"; - }; - timer-done-acted-on = { - # when the notification is clicked, kill whichever sxmo process is sending it - app-name = "notify-send"; - summary = "^Done with .*$"; - run-on = "action"; - # process tree looks like: - # - foot -T <...> /nix/store/.../sh /nix/store/.../.sxmo_timer.sh-wrapped timerrun - # - /nix/store/.../sh /nix/store/.../.sxmo_timer.sh-wrapped timerrun duration - # we want to match exactly one of those, reliably. - # foot might not be foot, but alacritty, kitty, or any other terminal. - exec = "${kill-singleton} ^[^ ]* ?[^ ]*sxmo_timer.sh(-wrapped)? timerrun"; - }; + scripts = import ./scripts.nix { + inherit pkgs; }; notification-visibility = { # match incoming notifications and decide if they should be visible. diff --git a/hosts/common/programs/swaynotificationcenter/scripts.nix b/hosts/common/programs/swaynotificationcenter/scripts.nix new file mode 100644 index 000000000..77d4c0975 --- /dev/null +++ b/hosts/common/programs/swaynotificationcenter/scripts.nix @@ -0,0 +1,114 @@ +# this file defines the `scripts` entry within ~/.config/swaync/config.json. +# it describes special things to do in response to specific notifications, +# e.g. sound a ringer when we get a call, ... +{ pkgs }: +let + fbcli-wrapper = pkgs.writeShellApplication { + name = "swaync-fbcli"; + runtimeInputs = [ + pkgs.feedbackd + pkgs.procps # for pkill + pkgs.swaynotificationcenter + ]; + text = '' + # if in Do Not Disturb, don't do any feedback + # TODO: better solution is to actually make use of feedbackd profiles. + # i.e. set profile to `quiet` when in DnD mode + if [ "$SWAYNC_URGENCY" != "Critical" ] && [ "$(swaync-client --get-dnd)" = "true" ]; then + exit + fi + + # kill children if killed, to allow that killing this parent process will end the real fbcli call + cleanup() { + echo "aborting fbcli notification (PID $child)" + pkill -P "$child" + exit 0 # exit cleanly to avoid swaync alerting a script failure + } + trap cleanup SIGINT SIGQUIT SIGTERM + + # feedbackd stops playback when the caller exits + # and fbcli will exit immediately if it has no stdin. + # so spoof a stdin: + /bin/sh -c "true | fbcli $*" & + child=$! + wait + ''; + }; + fbcli = "${fbcli-wrapper}/bin/swaync-fbcli"; + + # we do this because swaync's exec naively splits the command on space to produce its argv, rather than parsing the shell. + # [ "pkill" "-f" "fbcli" "--event" ... ] -> breaks pkill + # [ "pkill" "-f" "fbcli --event ..." ] -> is what we want + fbcli-stop-wrapper = pkgs.writeShellApplication { + name = "fbcli-stop"; + runtimeInputs = [ + pkgs.procps # for pkill + ]; + text = '' + pkill -e -f "${fbcli} $*" + ''; + }; + fbcli-stop = "${fbcli-stop-wrapper}/bin/fbcli-stop"; +in +{ + # a script can match regex on these fields. only fired if all listed fields match: + # - app-name + # - desktop-entry + # - summary + # - body + # - urgency (Low/Normal/Critical) + # - category + # additionally, the script can be run either on receipt or action: + # - run-on = "receive" or "action" + # when script is run, these env vars are available: + # - SWAYNC_BODY + # - SWAYNC_DESKTOP_ENTRY + # - SWAYNC_URGENCY + # - SWAYNC_TIME + # - SWAYNC_APP_NAME + # - SWAYNC_CATEGORY + # - SWAYNC_REPLACES_ID + # - SWAYNC_ID + # - SWAYNC_SUMMARY + + # rules to use for testing. trigger with: + # - `notify-send test test:message` (etc) + # should also be possible to trigger via any messaging app + fbcli-test-im = { + body = "test:message"; + exec = "${fbcli} --event proxied-message-new-instant"; + }; + fbcli-test-call = { + body = "test:call"; + exec = "${fbcli} --event phone-incoming-call -t 20"; + }; + fbcli-test-call-stop = { + body = "test:call-stop"; + exec = "${fbcli-stop} --event phone-incoming-call -t 20"; + }; + + incoming-im-known-app-name = { + # trigger notification sound on behalf of these IM clients. + app-name = "(Chats|Dino|discord|dissent|Element|Fractal)"; + body = "^(?!Incoming call).*$"; #< don't match Dino Incoming calls + exec = "${fbcli} --event proxied-message-new-instant"; + }; + incoming-im-known-desktop-entry = { + # trigger notification sound on behalf of these IM clients. + # these clients don't have an app-name (listed as ""), but do have a desktop-entry + desktop-entry = "com.github.uowuo.abaddon"; + exec = "${fbcli} --event proxied-message-new-instant"; + }; + incoming-call = { + app-name = "Dino"; + body = "^Incoming call$"; + exec = "${fbcli} --event phone-incoming-call -t 20"; + }; + incoming-call-acted-on = { + # when the notification is clicked, stop sounding the ringer + app-name = "Dino"; + body = "^Incoming call$"; + run-on = "action"; + exec = "${fbcli-stop} --event phone-incoming-call -t 20"; + }; +}