From 0fc804a42d3f39821f41144edad066954d3a4814 Mon Sep 17 00:00:00 2001 From: Yorick Barbanneau Date: Mon, 1 Sep 2025 00:58:03 +0200 Subject: [PATCH 1/3] refactor(sway): rework the whole screen capture part --- modules/home-manager/desktop/sway/default.nix | 3 + .../desktop/sway/includes/files/screencapt.sh | 160 ++++++++++++++++++ .../desktop/sway/includes/sway.nix | 91 +++------- 3 files changed, 184 insertions(+), 70 deletions(-) create mode 100755 modules/home-manager/desktop/sway/includes/files/screencapt.sh diff --git a/modules/home-manager/desktop/sway/default.nix b/modules/home-manager/desktop/sway/default.nix index 2b2dc67..024cb23 100644 --- a/modules/home-manager/desktop/sway/default.nix +++ b/modules/home-manager/desktop/sway/default.nix @@ -107,6 +107,8 @@ in # emojione font-awesome grim + hicolor-icon-theme + jq lato liberation_ttf libertine @@ -117,6 +119,7 @@ in noto-fonts-cjk-sans slurp wl-clipboard + wl-screenrec xdg-utils ]; diff --git a/modules/home-manager/desktop/sway/includes/files/screencapt.sh b/modules/home-manager/desktop/sway/includes/files/screencapt.sh new file mode 100755 index 0000000..16b8375 --- /dev/null +++ b/modules/home-manager/desktop/sway/includes/files/screencapt.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +set -eu -o pipefail + +APP_NAME="ScreenCapt" + +declare -A COMMAND +COMMAND=( + [screenshot]="grim" + [video]="wl-screenrec" +) +declare -A ICONS +ICONS=( + ["screenshot"]="accessories-screenshot" + ["video"]="record-desktop" +) + +declare -A OUTPUT_DIR +OUTPUT_DIR=( + ["screenshot"]="${XDG_PICTURES_DIR:-"~/pictures"}/screenshots" + ["video"]="${XDG_VIDEOS_DIR:="~/videos"}/screenrecords" +) + +declare -A OUTPUT_FILE_EXT +OUTPUT_FILE_EXT=( + ["screenshot"]="png" + ["video"]="mp4" +) + +TO_FILE=0 +RECORD_AUDIO=0 +ACTION="screenshot" +REGION_TYPE="window" + +notify() { + local summary message command + filename="${1:-""}" + summary="New ${ACTION}!" + if [[ $TO_FILE -eq 1 ]]; then + message+="Available in ${filename##*/}" + else + message+="Available in the clipboard" + fi + command=(notify-send "${summary}" --app-name="$APP_NAME") + if [[ -n "$filename" && "$ACTION" == "screenshots" ]]; then + command+=(--icon="${filename}") + else + command+=("--icon=${ICONS[$ACTION]}") + fi + command+=("$message") + "${command[@]}" +} + +process_args() { + while :; do + case ''${1:-""} in + screenshot) + ACTION="$1" + ;; + + video) + # If a video record is in progress Stop it + if pgrep "wl-screenrec"; then + kill -s SIGINT $(pgrep "wl-screenrec") + exit 0 + fi + TO_FILE=1 + ACTION="$1" + ;; + -r | --region) + if [[ "${2:-""}" =~ ^(r|region|s|screen|w|window)$ ]]; then + REGION_TYPE="$2" + else + exit 1 + fi + ;; + -f | --file) + TO_FILE=1 + ;; + -a | --with-audio) + RECORD_AUDIO=1 + ;; + *) + break + ;; + esac + shift + done +} + +get_app_name() { + local appname + case "$REGION_TYPE" in + r | region) + appname="region" + ;; + s | screen) + appname="screen" + ;; + w | window) + appname=$(swaymsg -t get_tree | jq -r '.. | ((.nodes? // empty) + (.floating_nodes? // empty))[] | select(.visible and .pid and .focused) | "\(.app_id)"') + ;; + esac + printf "%s" "${appname:-}" +} + +get_region() { + local region switch + case "$REGION_TYPE" in + r | region) + region=$(slurp) + switch="-g" + ;; + s | screen) + region=$(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | "\(.name)"') + switch="-o" + ;; + w | window) + region=$(swaymsg -t get_tree | jq -r '.. | ((.nodes? // empty) + (.floating_nodes? // empty))[] | select(.visible and .pid and .focused) | .rect | "\(.x),\(.y) \(.width)x\(.height)"') + switch="-g" + ;; + esac + printf "%s%s" "${switch:-}" "${region:-}" +} + +get_output_filename() { + local date + printf -v date '%(%F_%H.%M.%S)T' + printf "%s/%s-%s.%s" "${OUTPUT_DIR[$ACTION]}" "$date" "$(get_app_name)" "${OUTPUT_FILE_EXT[$ACTION]}" +} + +ensure_directories_exist() { + for dir in "${!OUTPUT_DIR[@]}"; do + mkdir -p "${OUTPUT_DIR[$dir]}" + done +} + +main() { + process_args "$@" + ensure_directories_exist + local command + command=("${COMMAND[$ACTION]}" "$(get_region)") + if [[ "$TO_FILE" -eq 1 ]]; then + local filename + filename=$(get_output_filename) + if [[ "$ACTION" = "video" ]]; then + if [[ "$RECORD_AUDIO" -eq 1 ]]; then + command+=(--audio) + fi + command+=(-f) + fi + command+=("$filename") + "${command[@]}" + else + "${command[@]}" - | wl-copy + fi + notify "${filename:-""}" + exit 0 +} + +main "$@" diff --git a/modules/home-manager/desktop/sway/includes/sway.nix b/modules/home-manager/desktop/sway/includes/sway.nix index ef2ebbc..4f0874c 100644 --- a/modules/home-manager/desktop/sway/includes/sway.nix +++ b/modules/home-manager/desktop/sway/includes/sway.nix @@ -89,9 +89,10 @@ in "${mod}+a" = "focus parent"; "${mod}+Shift+minus" = "move scratchpad"; "${mod}+minus" = "scratchpad show"; - "${mod}+p" = "exec screenshot window"; - "${mod}+Shift+p" = "exec screenshot screen"; + "${mod}+p" = "exec screencapt --region window"; + "${mod}+Shift+p" = "exec screencapt --region screen"; "${mod}+Alt+p" = "mode screenshot"; + "${mod}+Alt+r" = "mode screenrecord"; # Media stuff "${mod}+F1" = "exec ${pkgs.brightnessctl}/bin/brightnessctl s 1%-"; "${mod}+F2" = "exec ${pkgs.brightnessctl}/bin/brightnessctl s +1%"; @@ -122,12 +123,22 @@ in "Escape" = "mode default"; }; "screenshot" = { - "s" = "exec screenshot screen; mode default"; - "Shift+s" = "exec screenshot screen -f; mode default"; - "r" = "exec screenshot region; mode default"; - "Shift+r" = "exec screenshot region -f; mode default"; - "w" = "exec screenshot window; mode default"; - "Shift+w" = "exec screenshot window -f; mode default"; + "s" = "exec screencapt --region screen; mode default"; + "Shift+s" = "exec screencapt --region screen -f; mode default"; + "r" = "exec screencapt --region region; mode default"; + "Shift+r" = "exec screencapt --region region -f; mode default"; + "w" = "exec screencapt --region window; mode default"; + "Shift+w" = "exec screencapt --region window -f; mode default"; + "Return" = "mode default"; + "Escape" = "mode default"; + }; + "screenrecord" = { + "s" = "exec screencapt video --region screen; mode default"; + "Shift+s" = "exec screencapt --region video screen -a; mde default"; + "r" = "exec screencapt video --region region; mode default"; + "Shift+r" = "exec screencapt video --region region -a; mode default"; + "w" = "exec screencapt video --region window -f ; mode default"; + "Shift+w" = "exec screencapt --region video window -a; mode default"; "Return" = "mode default"; "Escape" = "mode default"; }; @@ -228,69 +239,9 @@ in title_align right ''; }; - home.file.".local/bin/screenshot" = { + home.file.".local/bin/screencapt" = { executable = true; - text = '' - #!/usr/bin/env bash - set -eu -o pipefail - - SWAYMSG="${pkgs.sway}/bin/swaymsg" - JQ="${pkgs.jq}/bin/jq" - WLCOPY="${pkgs.wl-clipboard}/bin/wl-copy" - GRIM="${pkgs.grim}/bin/grim" - SLURP="${pkgs.slurp}/bin/slurp" - - # What we want to screenshot - case ''${1:-} in - region) - RECT=$(''$SLURP) - APPNAME="-region" - OUTPUT=$(''${SWAYMSG} -t get_outputs | ''${JQ} -r '.[] | select(.focused) | "\(.name)"') - COMMAND=( - "''${GRIM}" - -g - "''$RECT" - ) - ;; - screen) - OUTPUT=$(''${SWAYMSG} -t get_outputs | ''${JQ} -r '.[] | select(.focused) | "\(.name)"') - COMMAND=( - "''${GRIM}" - -o - "''${OUTPUT}" - ) - ;; - window) - RECT=$(''${SWAYMSG} -t get_tree | ''${JQ} -r '.. | ((.nodes? // empty) + (.floating_nodes? // empty))[] | select(.visible and .pid and .focused) | .rect | "\(.x),\(.y) \(.width)x\(.height)"' ) - APPNAME=$(''${SWAYMSG} -t get_tree | ''${JQ} -r '.. | ((.nodes? // empty) + (.floating_nodes? // empty))[] | select(.visible and .pid and .focused) | "\(.app_id)"') - COMMAND=( - "''${GRIM}" - -g - "''${RECT}" - ) - ;; - *) - >&2 printf "Can't understand what you need\n" - exit 1 - esac - - # Where we want to put it - case ''${2:-"-c"} in - -c|--clipboard) - COMMAND+=(-) - "''${COMMAND[@]}" | ''${WLCOPY} - ;; - -f|--file) - PICTURES_DIR="''${XDG_PICTURES_DIR}/screenshots" - mkdir -p "''${PICTURES_DIR}" - printf -v DATE '%(%F_%H.%M.%S)T' - printf -v FILE "%s/%s-%s%s.png" "''${PICTURES_DIR}" "''${DATE}" "''${OUTPUT:-""}" "''${APPNAME:-""}" - "''${COMMAND[@]}" "''${FILE//\"/}" - ;; - esac - exit 0 - ''; + source = ./files/screencapt.sh; }; - }; } From ed79106342732a9fd6f9eb5cd8da2c774a043899 Mon Sep 17 00:00:00 2001 From: Yorick Barbanneau Date: Mon, 1 Sep 2025 01:18:27 +0200 Subject: [PATCH 2/3] feat(desktop): make icon theme configurable --- modules/home-manager/desktop/sway/default.nix | 22 +++++++++++++++++-- .../desktop/sway/includes/mako.nix | 8 ++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/modules/home-manager/desktop/sway/default.nix b/modules/home-manager/desktop/sway/default.nix index 024cb23..b3950e7 100644 --- a/modules/home-manager/desktop/sway/default.nix +++ b/modules/home-manager/desktop/sway/default.nix @@ -46,6 +46,24 @@ in }; }; + iconTheme = mkOption { + type = types.package; + default = pkgs.papirus-icon-theme; + description = ''Icon theme package to use''; + }; + + iconThemeName = mkOption { + type = types.str; + default = "Papirus Dark"; + description = ''Icon theme variant to use''; + }; + + iconThemePathname = mkOption { + type = types.str; + default = "Papirus-Dark"; + description = ''Icon theme variant to use''; + }; + waybar = { laptop = mkOption { type = types.bool; @@ -136,8 +154,8 @@ in package = pkgs.arc-theme; }; iconTheme = { - name = "Papirus Dark"; - package = pkgs.papirus-icon-theme; + name = cfg.iconThemeName; + package = cfg.iconTheme; }; font = { name = "Deja Vu Sans"; diff --git a/modules/home-manager/desktop/sway/includes/mako.nix b/modules/home-manager/desktop/sway/includes/mako.nix index 3b8d3df..b098010 100644 --- a/modules/home-manager/desktop/sway/includes/mako.nix +++ b/modules/home-manager/desktop/sway/includes/mako.nix @@ -1,5 +1,8 @@ { config, pkgs, lib, ... }: with lib; +let + cfg = config.modules.desktop.sway; +in { config = mkIf config.modules.desktop.sway.enable { systemd.user.services.mako = { @@ -24,12 +27,11 @@ with lib; border-radius = 0; border-size = 2; icons = true; + icon-path = "${cfg.iconTheme}/share/icons/${cfg.iconThemePathname}"; max-icon-size = 64; layer = "overlay"; anchor = "top-right"; - format = '' - %a\n%s\n%b - ''; + format = ''%a\n%s\n%b''; "urgency=high" = { border-color = "#F268b3"; }; From 79d55b304c7e44e0de5adfe8989edcd9a0b4f126 Mon Sep 17 00:00:00 2001 From: Yorick Barbanneau Date: Mon, 1 Sep 2025 01:20:43 +0200 Subject: [PATCH 3/3] feat(waybar): add a record module to track screen recording --- .../sway/includes/files/waybar-style.css | 14 +++++++ .../desktop/sway/includes/waybar.nix | 38 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/modules/home-manager/desktop/sway/includes/files/waybar-style.css b/modules/home-manager/desktop/sway/includes/files/waybar-style.css index ffc4be3..f7cc59f 100644 --- a/modules/home-manager/desktop/sway/includes/files/waybar-style.css +++ b/modules/home-manager/desktop/sway/includes/files/waybar-style.css @@ -117,3 +117,17 @@ button:hover{ font-family: "DejaVu sans"; font-size: 12px; } +#custom-screenrecord { + color: @color-critical; + border-bottom: 1px solid @color-critical; + animation-name: critical-animation; + animation-duration: 2s; + animation-timing-function: ease-in-out; + animation-direction: alternate; + animation-iteration-count: infinite; +} + +@keyframes critical-animation { + from {color: @color-critical;} + to {color: white;} +} diff --git a/modules/home-manager/desktop/sway/includes/waybar.nix b/modules/home-manager/desktop/sway/includes/waybar.nix index f9c2e6a..85641fa 100644 --- a/modules/home-manager/desktop/sway/includes/waybar.nix +++ b/modules/home-manager/desktop/sway/includes/waybar.nix @@ -1,4 +1,4 @@ -{lib, config, ...}: +{lib, config, pkgs, ...}: with lib; let cfg = config.modules.desktop.sway; @@ -16,7 +16,9 @@ in layer = "top"; spacing = 6; disable-toolptips = true; - modules-center = []; + modules-center = [ + "custom/screenrecord" + ]; modules-left = [ "sway/workspaces" "sway/mode" @@ -53,6 +55,7 @@ in "custom/sep" "clock" "custom/sep" + "privacy" "tray" ]; "clock" = { @@ -73,6 +76,14 @@ in "format" = "|"; "tooltip" = false; }; + "custom/screenrecord" = { + "format" = "  [rec.] "; + "interval" = 1; + "exec" = "echo '{\"class\": \"recording\"}'"; + "exec-if" = "${pkgs.procps}/bin/pgrep wl-screenrec"; + "on-click" = "exec ${pkgs.coreutils}/bin/kill -s SIGINT $(${pkgs.procps}/bin/pgrep wl-screenrec)"; + "tooltype" = false; + }; "idle_inhibitor" = { "format" = "{icon}"; "format-icons" = { @@ -96,6 +107,29 @@ in "format-wifi" = "{essid} ({signalStrength}%) "; "tooltip" = false; }; + "privacy"= { + "icon-spacing" = 4; + "icon-size" = 10; + "transition-duration" = 250; + "modules"= [ + { + "type" = "screenshare"; + "tooltip" = true; + "tooltip-icon-size" = 16; + } + { + "type" = "audio-out"; + "tooltip" = true; + "tooltip-icon-size" = 24; + } + { + "type" = "audio-in"; + "tooltip" = true; + "tooltip-icon-size" = 24; + } + ]; + "ignore-monitor" = true; + }; "pulseaudio#output" = { "format" = "{volume}% {icon} "; "format-bluetooth" = "{volume}% {icon}";