diff options
Diffstat (limited to '.config/niri')
| -rwxr-xr-x | .config/niri/apply-colors.sh | 84 | ||||
| -rw-r--r-- | .config/niri/config.kdl | 321 | ||||
| -rwxr-xr-x | .config/niri/set-backdrop.sh | 29 | ||||
| -rwxr-xr-x | .config/niri/theme-switcher.sh | 135 |
4 files changed, 569 insertions, 0 deletions
diff --git a/.config/niri/apply-colors.sh b/.config/niri/apply-colors.sh new file mode 100755 index 0000000..505578c --- /dev/null +++ b/.config/niri/apply-colors.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Apply a wallpaper and generate colors with wallust (16-color palette) + matugen (Material You / M3). +# Usage: apply-colors.sh <image> [light|dark] +# Called by theme-switcher.sh and the waybar random-wallpaper click. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$(realpath "$0")")" && pwd)" +IMG="${1:-}" +MODE="${2:-auto}" +LOG="${THEME_LOG:-/tmp/theme-switcher.log}" + +log() { + printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$LOG" +} + +# Record the exact failing command + line on any error. +trap 'log "apply-colors FAILED (exit $?) in ${BASH_SOURCE[0]}:$LINENO: $BASH_COMMAND"' ERR + +if [ -z "$IMG" ] || [ ! -f "$IMG" ]; then + echo "usage: $0 <image> [light|dark]" >&2 + exit 1 +fi + +# wait for the awww daemon to be ready (it is spawned next to this script at login) +log "awww img $IMG" +for _ in $(seq 1 10); do + if awww query >/dev/null 2>&1; then + break + fi + sleep 1 +done + +# set the wallpaper FIRST and unconditionally — this is the critical, must-work +# step. Color generation below is best-effort: a failure there must never block +# the wallpaper from changing (that coupling is what froze the wallpaper before). +awww img "$IMG" || { log "awww img FAILED ($IMG)"; exit 1; } + +# blurred copy for the Overview backdrop (sibling or on-the-fly generation) +"$SCRIPT_DIR/set-backdrop.sh" "$IMG" || log "set-backdrop FAILED ($IMG)" + +# wallust: classic 16-color palette (foot, btop, ...) +# fastresize (the config default) is flaky on some images — always use resized. +# Best-effort: a wallust failure must not prevent the wallpaper from sticking. +log "wallust palette=$MODE $IMG" +case "$MODE" in +light|dark) WALLUST_PALETTE=("--palette" "$MODE") ;; +*) WALLUST_PALETTE=() ;; +esac +# wallust's `resized` backend is intermittently flaky on some images; retry a +# few times, but ultimately tolerate failure. +ok=0 +for attempt in 1 2 3; do + if wallust run -b resized "${WALLUST_PALETTE[@]}" "$IMG"; then + ok=1 + break + fi + log "wallust attempt $attempt FAILED ($IMG, mode=$MODE)" + [ "$attempt" -lt 3 ] && sleep 2 +done +if [ "$ok" -ne 1 ]; then + log "wallust FAILED after 3 attempts ($IMG, mode=$MODE) — continuing without wallust colors" +else + # foot needs bare RRGGBB, not #RRGGBB — strip the leading '#' + sed -i 's/#\([0-9a-fA-F]\)/\1/g' ~/.cache/wallust/foot-theme.ini 2>/dev/null || true +fi + +# matugen: Material You / M3 colors (niri focus ring, ...) +# --prefer is required for non-interactive runs (multiple source colors). +# Best-effort: a matugen failure (e.g. bad config after an upgrade) must not +# block the wallpaper. +log "matugen mode=$MODE $IMG" +case "$MODE" in +light) matugen image "$IMG" -m "light" --prefer saturation ;; +dark) matugen image "$IMG" -m "dark" --prefer saturation ;; +*) matugen image "$IMG" --prefer saturation ;; +esac || log "matugen FAILED ($IMG, mode=$MODE) — continuing without matugen colors" + +# reload consumers +killall -SIGUSR2 waybar 2>/dev/null || true +killall -USR1 foot 2>/dev/null || true +niri msg action load-config-file 2>/dev/null || true + +log "apply-colors OK ($IMG, $MODE)" diff --git a/.config/niri/config.kdl b/.config/niri/config.kdl new file mode 100644 index 0000000..9d7db14 --- /dev/null +++ b/.config/niri/config.kdl @@ -0,0 +1,321 @@ +include optional=true "/home/seraphim/.cache/matugen/theme.kdl" +spawn-at-startup "mako" +spawn-at-startup "xrdb" "-merge" "/home/seraphim/.Xresources" +spawn-at-startup "vicinae" "server" +spawn-at-startup "gentoo-pipewire-launcher" +spawn-at-startup "telegram-desktop" "-autostart" "--minimized" +spawn-at-startup "keepassxc" "--minimized" +spawn-at-startup "awww-daemon" +spawn-at-startup "awww-daemon" "--namespace" "backdrop" +spawn-at-startup "/usr/libexec/hyprpolkitagent" +spawn-at-startup "dbus-update-activation-environment" "WAYLAND_DISPLAY" "DISPLAY" "XDG_CURRENT_DESKTOP" +spawn-at-startup "darkman" "run" +spawn-at-startup "/home/seraphim/dotfiles-gentoo/niri/.config/niri/theme-switcher.sh" "night" +spawn-at-startup "sh" "-c" "NO_AT_BRIDGE=1 waybar" +spawn-at-startup "/usr/bin/Throne" +spawn-at-startup "mpd" +prefer-no-csd +screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" + +environment { + OZONE_PLATFORM "wayland" + ELECTRON_OZONE_PLATFORM_HINT "auto" + XDG_CURRENT_DESKTOP "niri" +} +blur { + passes 4 + offset 5.0 + noise 0.02 + saturation 1.1 +} + +// awww-daemon --namespace backdrop: pre-blurred wallpaper shown in the +// Overview backdrop. niri cannot blur a backdrop surface itself. +layer-rule { + match namespace="^awww-daemonbackdrop$" + place-within-backdrop true +} +input { + focus-follows-mouse max-scroll-amount="100%" + + keyboard { + xkb { + layout "us,ru" + options "grp:lalt_lshift_toggle,compose:ralt,ctrl:nocaps" + } + repeat-delay 300 + repeat-rate 60 + numlock + } + + mouse { + accel-speed -0.8 + accel-profile "flat" + } +} + +output "DP-2" { + mode "2560x1440" + scale 1.5 + position x=0 y=0 +} + +output "HDMI-A-1" { + mode "1920x1080@71.910" + scale 1 +} + +layout { + gaps 0.5 + center-focused-column "never" + + preset-column-widths { + proportion 0.33333 + proportion 0.5 + proportion 0.66667 + } + + default-column-width { proportion 0.5; } + + focus-ring { + width 2 + } + + border { + off //width 0.5 + //active-color "#ffc87f" + //inactive-color "#505050" + //urgent-color "#9b0000" + } + + shadow { + off + //softness 30 + //spread 5 + //offset x=0 y=5 + //color "#0007" + } +} + +animations { + workspace-switch { + duration-ms 275 + curve "ease-out-cubic" + } + horizontal-view-movement { + spring damping-ratio=1.0 stiffness=800 epsilon=0.0001 + } + window-movement { + spring damping-ratio=1.0 stiffness=800 epsilon=0.0001 + } +// window-open { +// duration-ms 150 +// curve "ease-out-expo" +// } +// window-close { +// duration-ms 150 +// curve "ease-out-quad" +// } + window-resize { + spring damping-ratio=1.0 stiffness=800 epsilon=0.0001 + } + overview-open-close { + duration-ms 150 + curve "ease-out-quad" + } +} + +window-rule { + match app-id=r#"^org\.wezfurlong\.wezterm$"# + geometry-corner-radius 12 + clip-to-geometry true + background-effect { + xray false + } +} + +// Helium PiP fix +window-rule { + match app-id=r#"helium$"# title="^Picture-in-Picture$" + open-floating true + geometry-corner-radius 9000 + clip-to-geometry true + shadow { + off + } +} + +// Steam notifs fix +window-rule { + match app-id="steam" title=r#"^notificationtoasts_\d+_desktop$"# + open-floating true + open-focused false + default-floating-position x=16 y=16 relative-to="bottom-right" +} + +gestures { + hot-corners { + off + } +} + +cursor { + xcursor-theme "Bibata-Modern-Classic" + xcursor-size 15 +} + +overview { + zoom 0.5 + workspace-shadow { + off + //softness 15 + //spread 3 + //offset x=0 y=6 + //color "#00000080" + } +} +debug { + honor-xdg-activation-with-invalid-serial +} +binds { + // Application Launchers + Mod+Shift+T { spawn "telegram-desktop"; } + Mod+Shift+B { spawn "/opt/helium/helium" "--enable-features=UseOzonePlatform" "--ozone-platform=wayland"; } + Mod+D { spawn "vicinae" "toggle"; } + Mod+Ctrl+V { spawn "vicinae" "deeplink" "vicinae://launch/clipboard/history"; } + Mod+T hotkey-overlay-title="File Manager" { spawn "foot" "-e" "yazi"; } + Mod+Alt+R { spawn "foot" "-e" "rmpc"; } + Mod+S hotkey-overlay-title="Terminal" { spawn "foot"; } + Super+Alt+L hotkey-overlay-title="Lock Screen" { spawn "swaylock"; } + + // Audio & Media Controls + XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1+ -l 1.0"; } + XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.1-"; } + XF86AudioMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; } + XF86AudioMicMute allow-when-locked=true { spawn-sh "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; } + + XF86AudioPlay allow-when-locked=true { spawn-sh "playerctl play-pause"; } + XF86AudioStop allow-when-locked=true { spawn-sh "playerctl stop"; } + XF86AudioPrev allow-when-locked=true { spawn-sh "playerctl previous"; } + XF86AudioNext allow-when-locked=true { spawn-sh "playerctl next"; } + + // universal clipboard hotkeys (super + c / v / x) + mod+c { spawn-sh "sleep 0.001 && wtype -M ctrl -k insert -m ctrl"; } + mod+v { spawn-sh "sleep 0.001 && wtype -M shift -k insert -m shift"; } + mod+x { spawn-sh "sleep 0.001 && wtype -M shift -k delete -m shift"; } + + // Window Management & Views + Mod+Tab repeat=false { toggle-overview; } + Mod+Grave repeat=false { toggle-overview; } + Mod+W repeat=false { close-window; } + Mod+F { maximize-column; } + Mod+Shift+F { fullscreen-window; } + Mod+Ctrl+F { maximize-window-to-edges; } + //Mod+Ctrl+F { expand-column-to-available-width; } + Mod+Ctrl+C { center-visible-columns; } + Mod+Shift+V { toggle-window-floating; } + Mod+Q { toggle-column-tabbed-display; } + + // Focus Navigation + Mod+Left { focus-column-left; } + Mod+Down { focus-window-down; } + Mod+Up { focus-window-up; } + Mod+Right { focus-column-right; } + Mod+H { focus-column-left; } + Mod+J { focus-window-down; } + Mod+K { focus-window-up; } + Mod+L { focus-column-right; } + Mod+Shift+WheelScrollUp { focus-monitor-left; } + Mod+Shift+WheelScrollDown { focus-monitor-right; } + + // Window Movement + Mod+Ctrl+Left { move-column-left; } + Mod+Ctrl+Down { move-window-down; } + Mod+Ctrl+Up { move-window-up; } + Mod+Ctrl+Right { move-column-right; } + Mod+Ctrl+H { move-column-left; } + Mod+Ctrl+J { move-window-down; } + Mod+Ctrl+K { move-window-up; } + Mod+Ctrl+L { move-column-right; } + + Mod+Home { focus-column-first; } + Mod+End { focus-column-last; } + Mod+Ctrl+Home { move-column-to-first; } + Mod+Ctrl+End { move-column-to-last; } + + // Monitor Focus & Movement + Mod+Shift+1 { focus-monitor-left; } + Mod+Shift+2 { focus-monitor-right; } + Mod+Shift+Left { focus-monitor-left; } + Mod+Shift+Down { focus-monitor-down; } + Mod+Shift+Up { focus-monitor-up; } + Mod+Shift+Right { focus-monitor-right; } + Mod+Shift+H { focus-monitor-left; } + Mod+Shift+J { focus-monitor-down; } + Mod+Shift+K { focus-monitor-up; } + Mod+Shift+L { focus-monitor-right; } + + Mod+Shift+Ctrl+1 { move-column-to-monitor-left; } + Mod+Shift+Ctrl+2 { move-column-to-monitor-right; } + Mod+Shift+Ctrl+Left { move-column-to-monitor-left; } + Mod+Shift+Ctrl+Down { move-column-to-monitor-down; } + Mod+Shift+Ctrl+Up { move-column-to-monitor-up; } + Mod+Shift+Ctrl+Right { move-column-to-monitor-right; } + Mod+Shift+Ctrl+H { move-column-to-monitor-left; } + Mod+Shift+Ctrl+J { move-column-to-monitor-down; } + Mod+Shift+Ctrl+K { move-column-to-monitor-up; } + Mod+Shift+Ctrl+L { move-column-to-monitor-right; } + + // Workspace Navigation + Mod+Shift+Page_Down { move-workspace-down; } + Mod+Shift+Page_Up { move-workspace-up; } + Mod+Shift+U { move-workspace-down; } + Mod+Shift+I { move-workspace-up; } + + // Direct Workspace Switching (1-9) + Mod+1 { focus-workspace 1; } + Mod+2 { focus-workspace 2; } + Mod+3 { focus-workspace 3; } + Mod+4 { focus-workspace 4; } + Mod+5 { focus-workspace 5; } + Mod+6 { focus-workspace 6; } + Mod+7 { focus-workspace 7; } + Mod+8 { focus-workspace 8; } + Mod+9 { focus-workspace 9; } + + Mod+Ctrl+1 { move-column-to-workspace 1; } + Mod+Ctrl+2 { move-column-to-workspace 2; } + Mod+Ctrl+3 { move-column-to-workspace 3; } + Mod+Ctrl+4 { move-column-to-workspace 4; } + Mod+Ctrl+5 { move-column-to-workspace 5; } + Mod+Ctrl+6 { move-column-to-workspace 6; } + Mod+Ctrl+7 { move-column-to-workspace 7; } + Mod+Ctrl+8 { move-column-to-workspace 8; } + Mod+Ctrl+9 { move-column-to-workspace 9; } + + // Column & Window Sizing + Mod+BracketLeft { consume-or-expel-window-left; } + Mod+BracketRight { consume-or-expel-window-right; } + + Mod+R { switch-preset-column-width; } + Mod+Shift+R { switch-preset-column-width-back; } + Mod+Ctrl+Shift+R { switch-preset-window-height; } + Mod+Ctrl+R { reset-window-height; } + + Mod+Minus { set-column-width "-10%"; } + Mod+Equal { set-column-width "+10%"; } + Mod+Shift+Minus { set-window-height "-10%"; } + Mod+Shift+Equal { set-window-height "+10%"; } + + // Mouse Wheel Bindings + Mod+WheelScrollUp { focus-column-left; } + Mod+WheelScrollDown { focus-column-right; } + + // Screenshots + Print { screenshot; } + Ctrl+Print { screenshot-screen; } + Alt+Print { screenshot-window; } + + // System Control + Mod+Shift+E { quit; } +} diff --git a/.config/niri/set-backdrop.sh b/.config/niri/set-backdrop.sh new file mode 100755 index 0000000..efd02b3 --- /dev/null +++ b/.config/niri/set-backdrop.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Push a blurred copy of a wallpaper to the awww-daemon backdrop namespace. +# Prefers a pre-made <name>-blur.<ext> sibling; otherwise generates one into the cache. +# Used by theme-switcher.sh and apply-colors.sh for the niri Overview backdrop. + +set -euo pipefail + +IMG="${1:-}" +if [ -z "$IMG" ] || [ ! -f "$IMG" ]; then + echo "usage: $0 <image>" >&2 + exit 1 +fi + +EXT="${IMG##*.}" +SIBLING="${IMG%.*}-blur.${EXT}" +if [ -f "$SIBLING" ]; then + BLURRED="$SIBLING" +else + CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/niri-backdrop" + mkdir -p "$CACHE_DIR" + BASE=$(basename "${IMG%.*}") + BLURRED="$CACHE_DIR/${BASE}-blur.jpg" + if [ ! -f "$BLURRED" ] || [ "$IMG" -nt "$BLURRED" ]; then + magick "$IMG" -blur 0x16 -quality 90 "$BLURRED" + fi +fi + +awww img "$BLURRED" -n backdrop +echo "$BLURRED" >/tmp/current_backdrop_state
\ No newline at end of file diff --git a/.config/niri/theme-switcher.sh b/.config/niri/theme-switcher.sh new file mode 100755 index 0000000..6d1f548 --- /dev/null +++ b/.config/niri/theme-switcher.sh @@ -0,0 +1,135 @@ +#!/bin/bash + +# Day/night theme switcher + wallpaper/color applier. +# Two wallpapers: day.jpg (9:00-18:00) and night.jpg (18:00-9:00). +# +# Usage: +# ./theme-switcher.sh one-shot: apply based on current time, then exit +# ./theme-switcher.sh day|light force light theme, then exit +# ./theme-switcher.sh night|dark force dark theme, then exit +# ./theme-switcher.sh --daemon continuous time-based loop (used at login) + +WALLPAPERS="$HOME/Pictures/wallpapers" +STATE_FILE="/tmp/current_theme_state" +WALLPAPER_STATE_FILE="/tmp/current_wallpaper_state" +BACKDROP_STATE_FILE="/tmp/current_backdrop_state" +PID_FILE="/tmp/theme-switcher.daemon.pid" +LOG="/tmp/theme-switcher.log" +SCRIPT_DIR="$(cd "$(dirname "$(realpath "$0")")" && pwd)" +SET_BACKDROP="$SCRIPT_DIR/set-backdrop.sh" + +log() { + printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$LOG" +} + +# Resolve TARGET_STATE / MODE / WALLPAPER. Optional $1 forces day|night. +resolve_target() { + local force="${1:-}" + if [ "$force" = "day" ] || [ "$force" = "light" ]; then + TARGET_STATE="light" + MODE="light" + WALLPAPER="$WALLPAPERS/day.jpg" + elif [ "$force" = "night" ] || [ "$force" = "dark" ]; then + TARGET_STATE="dark" + MODE="dark" + WALLPAPER="$WALLPAPERS/night.jpg" + else + local HOUR + HOUR=$(date +%H) + HOUR=$((10#$HOUR)) + if [ "$HOUR" -ge 9 ] && [ "$HOUR" -lt 18 ]; then + TARGET_STATE="light" + MODE="light" + WALLPAPER="$WALLPAPERS/day.jpg" + else + TARGET_STATE="dark" + MODE="dark" + WALLPAPER="$WALLPAPERS/night.jpg" + fi + fi +} + +# Apply the resolved target once. Returns 0 on success. +apply_target() { + if [ ! -f "$WALLPAPER" ]; then + log "wallpaper missing: $WALLPAPER" + return 1 + fi + + local CURRENT_THEME CURRENT_WALLPAPER EXPECTED_BACKDROP ACTUAL_BACKDROP + + CURRENT_THEME=$(cat "$STATE_FILE" 2>/dev/null || true) + # Already in the correct theme: don't re-run wallust/matugen every loop tick. + # Still self-heal a drifted backdrop below, then bail out. + if [ "$CURRENT_THEME" = "$TARGET_STATE" ]; then + EXPECTED_BACKDROP=$(cat "$BACKDROP_STATE_FILE" 2>/dev/null || true) + ACTUAL_BACKDROP=$(awww query -n backdrop -j 2>/dev/null | jq -r '.["backdrop"][0].displaying.image // empty') + if [ -n "$EXPECTED_BACKDROP" ] && [ "$ACTUAL_BACKDROP" != "$EXPECTED_BACKDROP" ]; then + "$SET_BACKDROP" "$WALLPAPER" + fi + return 0 + fi + CURRENT_WALLPAPER=$(cat "$WALLPAPER_STATE_FILE" 2>/dev/null || true) + + # Self-heal the Overview backdrop if it drifted from the expected image. + EXPECTED_BACKDROP=$(cat "$BACKDROP_STATE_FILE" 2>/dev/null || true) + ACTUAL_BACKDROP=$(awww query -n backdrop -j 2>/dev/null | jq -r '.["backdrop"][0].displaying.image // empty') + if [ -n "$EXPECTED_BACKDROP" ] && [ "$ACTUAL_BACKDROP" != "$EXPECTED_BACKDROP" ]; then + "$SET_BACKDROP" "$WALLPAPER" + fi + + log "switching to $TARGET_STATE ($WALLPAPER)" + if sh "$SCRIPT_DIR/apply-colors.sh" "$WALLPAPER" "$MODE"; then + if [ "$CURRENT_THEME" != "$TARGET_STATE" ]; then + darkman set "$TARGET_STATE" + fi + + ln -sf \ + "theme-${TARGET_STATE}.css" \ + "$HOME/dotfiles-gentoo/waybar/.config/waybar/theme.css" + + ln -sf \ + "settings-${TARGET_STATE}.ini" \ + "$HOME/dotfiles-gentoo/gtk/.config/gtk-3.0/settings.ini" + + echo "$TARGET_STATE" >"$STATE_FILE" + echo "$WALLPAPER" >"$WALLPAPER_STATE_FILE" + log "switched to $TARGET_STATE ($WALLPAPER)" + return 0 + else + log "FAILED to apply $TARGET_STATE ($WALLPAPER)" + return 1 + fi +} + +daemon_loop() { + # Refuse to run a second daemon instance (e.g. manual run while one is alive). + if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null; then + log "daemon already running (pid $(cat "$PID_FILE")), exiting" + exit 0 + fi + echo $$ >"$PID_FILE" + + while true; do + resolve_target + apply_target || true # failure is retried on the next loop, state is left untouched + sleep 60 + done +} + +case "${1:-}" in +--daemon | -d) daemon_loop ;; +"" | day | light | night | dark) + resolve_target "${1:-}" + apply_target + exit $? + ;; +-h | --help) + echo "usage: $0 [day|night|--daemon]" >&2 + exit 0 + ;; +*) + echo "usage: $0 [day|night|--daemon]" >&2 + exit 1 + ;; +esac |
