diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9134829 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,93 @@ +# AGENTS Guide: Dotfiles Repository + +Chezmoi-managed dotfiles with device-adaptive templates. Source files in `~/dotfiles/`, deployed to `~/` via `chezmoi apply`. + +## Device Profiles + +| Profile | Hostname | Primary Monitor | Has Touchpad | Has Battery | +|---------|----------|-----------------|--------------|-------------| +| desktop | box | DP-2 | No | No | +| laptop | bluefin | eDP-1 | Yes | Yes | + +## Template Variables + +```go +{{ .deviceProfile }} // "desktop" or "laptop" +{{ .hostname }} // "box" or "bluefin" +{{ .primaryMonitor }} // "DP-2" or "eDP-1" +{{ .hasTouchpad }} // boolean +{{ .hasBattery }} // boolean +{{ .idleTimeout }} // 300 (desktop) or 180 (laptop) +{{ .secretsPath }} // "~/secrets" +``` + +## File Naming Conventions + +| Pattern | Effect | +|---------|--------| +| `private_dot_*` | Hidden file with 700 permissions | +| `dot_*` | Hidden file with 755 permissions | +| `executable_*` | Executable (755) | +| `*.tmpl` | Processed as Go template | + +## Directory Structure + +``` +home/ +├── private_dot_config/ # ~/.config/ +│ ├── hypr/ # Hyprland configs +│ ├── fish/ # Shell +│ ├── waybar/ # Status bar +│ └── ghostty/ # Terminal +├── dot_local/bin/ # ~/.local/bin/ scripts +└── .chezmoiscripts/ # One-time setup scripts +``` + +## Hyprland Config Files + +All in `home/private_dot_config/hypr/`: + +| File | Purpose | +|------|---------| +| `hyprland.conf.tmpl` | Main config, keybindings, window rules | +| `monitors.conf.tmpl` | Monitor setup | +| `autostart.conf.tmpl` | Startup applications | +| `colors.conf` | Color palette | +| `workspaces.conf` | Workspace definitions | +| `hyprpaper.conf.tmpl` | Wallpaper | +| `hypridle.conf.tmpl` | Idle/power management | +| `hyprlock.conf` | Lock screen | + +## Template Conditionals + +```go +{{- if eq .deviceProfile "desktop" }} +# Desktop only +{{- else if eq .deviceProfile "laptop" }} +# Laptop only +{{- end }} + +{{- if .hasBattery }} +# Battery-dependent (laptop brightness keys, etc.) +{{- end }} + +{{- if .hasTouchpad }} +# Touchpad settings +{{- end }} +``` + +## Key Commands + +```bash +chezmoi apply # Deploy changes +chezmoi diff # Preview changes +chezmoi edit # Edit managed file +chezmoi add # Add new file to management +``` + +## Standards + +- Scripts: `#!/usr/bin/env bash` with `set -euo pipefail` +- Secrets: Store in `~/secrets/`, reference via `{{ .secretsPath }}` +- Window rules: Use `windowrulev2` (not deprecated `windowrule`) +- Whitespace: Use `{{-` to trim in templates diff --git a/home/dot_local/bin/executable_hypr-debounce b/home/dot_local/bin/executable_hypr-debounce new file mode 100644 index 0000000..73a8714 --- /dev/null +++ b/home/dot_local/bin/executable_hypr-debounce @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +ms=${1:-200} +shift || true + +if [ "$#" -eq 0 ]; then + echo "usage: hypr-debounce " >&2 + exit 2 +fi + +key=$(printf '%s\0' "$@" | sha1sum | awk '{print $1}') +state="/tmp/hypr-debounce-$key" +now=$(date +%s%3N) + +if [ -f "$state" ]; then + last=$(cat "$state" 2>/dev/null || echo 0) + if [ $((now - last)) -lt "$ms" ]; then + exit 0 + fi +fi + +printf '%s\n' "$now" > "$state" +exec "$@" diff --git a/home/dot_local/bin/executable_launch-on-workspace b/home/dot_local/bin/executable_launch-on-workspace new file mode 100644 index 0000000..7e84c6f --- /dev/null +++ b/home/dot_local/bin/executable_launch-on-workspace @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Usage: launch-on-workspace + +target_ws="$1" +target_class="$2" +shift 2 +# Use array to preserve argument quoting +cmd=("$@") + +echo "$(date) [launch-on-workspace] Launching class='$target_class' to workspace='$target_ws'" >> /tmp/hypr-launch.log + +# Start the application with preserved arguments +nohup "${cmd[@]}" >/dev/null 2>&1 & + +# Wait for the window to appear (up to 15 seconds) +for i in {1..60}; do + # Get address of matching window + # We check both class and initialClass for robustness + addr=$(hyprctl clients -j | jq -r ".[] | select(.class == \"$target_class\" or .initialClass == \"$target_class\") | .address" | head -1) + + if [ ! -z "$addr" ] && [ "$addr" != "null" ]; then + echo "$(date) [launch-on-workspace] Found $target_class at $addr, moving to $target_ws" >> /tmp/hypr-launch.log + hyprctl dispatch movetoworkspacesilent "$target_ws,address:$addr" + exit 0 + fi + sleep 0.25 +done + +echo "$(date) [launch-on-workspace] Timeout waiting for $target_class" >> /tmp/hypr-launch.log +exit 1 diff --git a/home/private_dot_config/alacritty/alacritty.toml b/home/private_dot_config/alacritty/alacritty.toml new file mode 100644 index 0000000..708c349 --- /dev/null +++ b/home/private_dot_config/alacritty/alacritty.toml @@ -0,0 +1,38 @@ +# Alacritty configuration +# Generated from ~/.config/ghostty/config for feature parity where possible. + +[window] +opacity = 0.9 +decorations = "Full" +dynamic_padding = true + +[window.padding] +x = 12 +y = 12 + +[scrolling] +# Alacritty has a finite history buffer; Ghostty unlimited scrollback is not available. +history = 100000 + +[cursor] +style = { shape = "Block", blinking = "Never" } + +[selection] +# Approximate Ghostty's copy-on-select behavior. +save_to_clipboard = true + +[mouse] +hide_when_typing = true + +[font] +size = 12.0 + +[window.class] +# Keep class/app_id compatible with existing Hyprland rules targeting Ghostty. +instance = "ghostty" +general = "ghostty" + +# Note: +# - Background blur is compositor-specific and not a native Alacritty setting. +# - Ghostty shell-integration options do not have Alacritty equivalents. +# - Quake/quick-terminal toggle is not built into Alacritty. diff --git a/home/private_dot_config/atuin/config.toml b/home/private_dot_config/atuin/config.toml new file mode 100644 index 0000000..ec544d2 --- /dev/null +++ b/home/private_dot_config/atuin/config.toml @@ -0,0 +1,6 @@ +# Atuin config (managed by chezmoi) +# +# Keep normal "Up" history scoped to this shell session, while Ctrl-R +# search stays global so older/other-session commands are still discoverable. +filter_mode = "global" +filter_mode_shell_up_key_binding = "session" diff --git a/home/private_dot_config/fish/config.fish b/home/private_dot_config/fish/config.fish index a7e2b53..927cd2e 100644 --- a/home/private_dot_config/fish/config.fish +++ b/home/private_dot_config/fish/config.fish @@ -1,4 +1,5 @@ # Man pages with bat +set -g fish_greeting set -x MANROFFOPT "-c" set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" @@ -8,6 +9,17 @@ fish_add_path -m $HOME/.local/bin # LM Studio fish_add_path $HOME/.lmstudio/bin +# Bun +fish_add_path ~/.bun/bin + +# Backblaze B2 +alias backblaze-b2=bbb2 + +# SSHS (optional) +if command -q sshs + alias ss=sshs +end + # Scaleway (lazy-load) function scw --wraps=scw functions -e scw @@ -26,6 +38,23 @@ function history builtin history --show-time='%F %T ' end +# Atuin history (fuzzy search + sync) +if status is-interactive; and command -q atuin + atuin init fish | source + if functions -q _atuin_search + bind \cr _atuin_search + end +end + +# Ctrl+S launches sshs when available +if status is-interactive; and command -q sshs + function __sshs_launch --description "Launch sshs via keybind" + commandline -f cancel + sshs + end + bind \cs __sshs_launch +end + # Bash-style !! and !$ function __history_previous_command switch (commandline -t) @@ -48,3 +77,218 @@ end bind ! __history_previous_command bind '$' __history_previous_command_arguments + +# Terminal SSH background color derived from server IP (Ghostty/Foot) +if not set -q TERM_SSH_BG_RESET + # Match configured terminal background as the reset target. + set -gx TERM_SSH_BG_RESET "#141414" +end + +function __term_supports_osc11 --description "Check if terminal supports OSC 11 bg color" + if not status is-interactive + return 1 + end + + # Ghostty + if test "$TERM_PROGRAM" = "ghostty" + return 0 + else if string match -q "*ghostty*" "$TERM" + return 0 + else if set -q GHOSTTY + return 0 + end + + # Foot + if string match -q "foot*" "$TERM" + return 0 + end + + return 1 +end + +function __term_bg_set_from_ip --description "Set terminal background derived from IP" + if not __term_supports_osc11 + return + end + + if test (count $argv) -lt 1 + return + end + + set -l server_ip $argv[1] + if test -z "$server_ip" + return + end + + set -l hash (printf '%s' "$server_ip" | cksum | awk '{print $1}') + set -l base_r 20 + set -l base_g 20 + set -l base_b 20 + set -l r_delta (math --scale 0 "($hash % 49) - 24") + set -l g_delta (math --scale 0 "(($hash / 49) % 49) - 24") + set -l b_delta (math --scale 0 "(($hash / 2401) % 49) - 24") + set -l r (math --scale 0 "$base_r + $r_delta") + set -l g (math --scale 0 "$base_g + $g_delta") + set -l b (math --scale 0 "$base_b + $b_delta") + + if test $r -lt 0; set r 0; end + if test $g -lt 0; set g 0; end + if test $b -lt 0; set b 0; end + if test $r -gt 255; set r 255; end + if test $g -gt 255; set g 255; end + if test $b -gt 255; set b 255; end + + printf '\e]11;#%02x%02x%02x\a' $r $g $b + set -g __term_ssh_bg_active 1 +end + +function __term_ssh_bg --description "Set terminal background for SSH" + if not __term_supports_osc11 + return + end + + if not set -q SSH_CONNECTION + return + end + + set -l server_ip (string split " " -- $SSH_CONNECTION)[3] + __term_bg_set_from_ip $server_ip +end + +set -g __term_ssh_in_progress 0 +function __term_ssh_postexec_reset --on-event fish_postexec --description "Reset terminal background after SSH command" + if test $__term_ssh_in_progress -eq 1 + __term_ssh_bg_reset --force + set -g __term_ssh_in_progress 0 + end +end + +function __term_ssh_prompt_reset --on-event fish_prompt --description "Reset terminal background on prompt redraw after SSH" + if test $__term_ssh_in_progress -eq 1 + __term_ssh_bg_reset --force + set -g __term_ssh_in_progress 0 + end + if set -q __term_ssh_bg_active; and test $__term_ssh_bg_active -eq 1 + __term_ssh_bg_reset --force + end +end + +function __term_ssh_ctrl_c --description "Reset terminal background before Ctrl+C" + if set -q __term_ssh_bg_active; and test $__term_ssh_bg_active -eq 1 + __term_ssh_bg_reset --force + end + commandline -f cancel-commandline +end + +bind \cc __term_ssh_ctrl_c + +function __term_ssh_bg_reset --on-event fish_exit --description "Reset terminal background after SSH" + set -l force 0 + if test (count $argv) -gt 0; and test "$argv[1]" = "--force" + set force 1 + end + + if not __term_supports_osc11 + return + end + + if test $force -ne 1; and not set -q SSH_CONNECTION + return + end + + if set -q TERM_SSH_BG_RESET + printf '\e]11;%s\a' "$TERM_SSH_BG_RESET" + else + printf '\e]111\a' + end + set -g __term_ssh_bg_active 0 +end + +__term_ssh_bg + +function __term_wrap_ssh --on-event fish_prompt --description "Wrap ssh for terminal bg color" + if functions -q __term_ssh_wrapped + return + end + + if not command -q ssh + return + end + + set -g __term_use_ssh_function 0 + if functions -q ssh + functions -c ssh __term_ssh_orig + functions -e ssh + set -g __term_use_ssh_function 1 + end + + function ssh --wraps=__term_ssh_orig --description "SSH with terminal background color per target" + set -l host "" + set -l skip_next 0 + set -l after_ddash 0 + + for arg in $argv + if test $skip_next -eq 1 + set skip_next 0 + continue + end + + if test "$arg" = "--" + set after_ddash 1 + continue + end + + if test $after_ddash -eq 0; and string match -q -- "-*" $arg + switch $arg + case "-b" "-c" "-D" "-E" "-e" "-F" "-I" "-i" "-J" "-L" "-l" "-m" "-O" "-o" "-p" "-Q" "-R" "-S" "-W" "-w" + set skip_next 1 + continue + case "-b*" "-c*" "-D*" "-E*" "-e*" "-F*" "-I*" "-i*" "-J*" "-L*" "-l*" "-m*" "-O*" "-o*" "-p*" "-Q*" "-R*" "-S*" "-W*" "-w*" + continue + case "-B" "-G" "-K" "-M" "-N" "-n" "-T" "-t" "-v" "-V" "-x" "-X" "-Y" "-y" "-q" "-f" "-g" "-k" + continue + end + end + + if test -z "$host" + set host $arg + break + end + end + + if __term_supports_osc11; and test -n "$host" + set -l host_only (string split "@" -- $host)[-1] + set -l resolved "" + + if command -q getent + set resolved (getent ahosts $host_only 2>/dev/null | awk 'NR==1 {print $1}') + end + + if test -z "$resolved"; and command -q host + set resolved (host $host_only 2>/dev/null | awk '/has address/ {print $4; exit}') + end + + if test -n "$resolved" + __term_bg_set_from_ip $resolved + end + end + + set -g __term_ssh_in_progress 1 + if test $__term_use_ssh_function -eq 1 + __term_ssh_orig $argv + else + command ssh $argv + end + set -l ssh_status $status + + if __term_supports_osc11 + __term_ssh_bg_reset --force + end + set -g __term_ssh_in_progress 0 + + return $ssh_status + end + + function __term_ssh_wrapped --description "Marker for wrapped ssh" + end +end diff --git a/home/private_dot_config/fish/functions/gha-cancel-run.fish b/home/private_dot_config/fish/functions/gha-cancel-run.fish index 8400501..e6c0407 100644 --- a/home/private_dot_config/fish/functions/gha-cancel-run.fish +++ b/home/private_dot_config/fish/functions/gha-cancel-run.fish @@ -9,15 +9,16 @@ function gha-cancel-run --description "Cancel a GitHub Actions run using its URL # Extract owner, repo, and run_id from the URL using regular expressions # This regex is specifically designed to parse the run URL format - if not string match --regex 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' "$run_url" + set -l matches (string match --regex 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' -- "$run_url") + if test (count $matches) -eq 0 echo "Error: Invalid GitHub Actions run URL format." echo "Expected format: https://github.com/OWNER/REPO/actions/runs/RUN_ID" return 1 end - set -l owner (string match --regex --groups=1 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' "$run_url")[2] - set -l repo (string match --regex --groups=2 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' "$run_url")[2] - set -l run_id (string match --regex --groups=3 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' "$run_url")[2] + set -l owner $matches[2] + set -l repo $matches[3] + set -l run_id $matches[4] echo "Attempting to cancel run ID: $run_id in $owner/$repo..." diff --git a/home/private_dot_config/fish/functions/gha-force-cancel-run.fish b/home/private_dot_config/fish/functions/gha-force-cancel-run.fish new file mode 100644 index 0000000..e5f1c5e --- /dev/null +++ b/home/private_dot_config/fish/functions/gha-force-cancel-run.fish @@ -0,0 +1,34 @@ +function gha-force-cancel-run --description "Force-cancel a GitHub Actions run using its URL" + if test -z "$argv[1]" + echo "Usage: gha-force-cancel-run " + echo "Example: gha-force-cancel-run https://github.com/OWNER/REPO/actions/runs/123456789" + return 1 + end + + set -l run_url $argv[1] + set -l matches (string match --regex 'https://github.com/([^/]+)/([^/]+)/actions/runs/([0-9]+)' -- "$run_url") + if test (count $matches) -eq 0 + echo "Error: Invalid GitHub Actions run URL format." + echo "Expected format: https://github.com/OWNER/REPO/actions/runs/RUN_ID" + return 1 + end + + set -l owner $matches[2] + set -l repo $matches[3] + set -l run_id $matches[4] + + echo "Attempting to force-cancel run ID: $run_id in $owner/$repo..." + + gh api \ + --method POST \ + -H "Accept: application/vnd.github.v3+json" \ + "/repos/$owner/$repo/actions/runs/$run_id/force-cancel" + + if test $status -eq 0 + echo "Force-cancel request sent successfully for run ID: $run_id" + else + echo "Failed to send force-cancel request for run ID: $run_id" + echo "Check the error message above for details." + return 1 + end +end diff --git a/home/private_dot_config/foot/foot.ini b/home/private_dot_config/foot/foot.ini new file mode 100644 index 0000000..32672aa --- /dev/null +++ b/home/private_dot_config/foot/foot.ini @@ -0,0 +1,29 @@ +[main] +font=monospace:size=12 +pad=12x12 center +term=foot +selection-target=both + +[scrollback] +lines=100000 + +[cursor] +style=block +blink=no +unfocused-style=hollow + +[mouse] +hide-when-typing=yes + +[colors] +background=141414 +alpha=0.9 +foreground=e0e0e0 + +[csd] +preferred=server + +[key-bindings] +clipboard-copy=Control+Shift+c +clipboard-paste=Control+Shift+v +spawn-terminal=Control+Shift+n diff --git a/home/private_dot_config/hypr/autostart.conf.tmpl b/home/private_dot_config/hypr/autostart.conf.tmpl index b3cff6d..071b612 100644 --- a/home/private_dot_config/hypr/autostart.conf.tmpl +++ b/home/private_dot_config/hypr/autostart.conf.tmpl @@ -9,7 +9,7 @@ exec-once = xwayland-satellite # System tray & bar exec-once = nm-applet & -exec-once = waybar +exec-once = qs # Notifications exec-once = swaync @@ -23,14 +23,14 @@ exec-once = copyq --start-server # User apps exec-once = zen-browser -exec-once = chromium --profile-directory=work --class=chromium-work -exec-once = chromium --profile-directory=llm --class=chromium-llm -exec-once = heynote -exec-once = telegram-desktop -exec-once = slack -exec-once = thunderbird -exec-once = spotify -exec-once = ticktick +exec-once = ~/.local/bin/launch-on-workspace 2 chromium-work chromium --profile-directory=Default --class=chromium-work +exec-once = ~/.local/bin/launch-on-workspace "special:llm" chromium-llm chromium --user-data-dir=$HOME/.config/chromium-llm --class=chromium-llm +exec-once = ~/.local/bin/launch-on-workspace "special:llm" Heynote heynote +exec-once = ~/.local/bin/launch-on-workspace "name:tg" org.telegram.desktop Telegram +exec-once = ~/.local/bin/launch-on-workspace "name:tg" Slack slack +exec-once = ~/.local/bin/launch-on-workspace "name:tg" org.mozilla.Thunderbird thunderbird +exec-once = ~/.local/bin/launch-on-workspace "name:media" spotify spotify +exec-once = ~/.local/bin/launch-on-workspace "special:org" ticktick ticktick {{- if eq .deviceProfile "desktop" }} # Desktop: Hyprland plugins diff --git a/home/private_dot_config/hypr/hyprland.conf.tmpl b/home/private_dot_config/hypr/hyprland.conf.tmpl index dae2192..eabd99f 100644 --- a/home/private_dot_config/hypr/hyprland.conf.tmpl +++ b/home/private_dot_config/hypr/hyprland.conf.tmpl @@ -3,7 +3,6 @@ # Managed by chezmoi - edit source at ~/dotfiles source = ~/.config/hypr/colors.conf -source = ~/.config/hypr/autostart.conf source = ~/.config/hypr/workspaces.conf source = ~/.config/hypr/monitors.conf @@ -17,7 +16,7 @@ source = ~/.config/hypr/monitors.conf ### MY PROGRAMS ### ################### -$terminal = ghostty +$terminal = foot $fileManager = thunar $menu = wofi --show drun $run_menu = wofi --show run @@ -51,14 +50,14 @@ decoration { inactive_opacity = 1.0 shadow { - enabled = true + enabled = false range = 4 render_power = 3 color = rgba(1a1a1aee) } blur { - enabled = true + enabled = false size = 3 passes = 1 vibrancy = 0.1696 @@ -66,7 +65,7 @@ decoration { } animations { - enabled = yes + enabled = no bezier = easeOutQuint,0.23,1,0.32,1 bezier = easeInOutCubic,0.65,0.05,0.36,1 @@ -97,15 +96,15 @@ animations { } # Smart gaps window rules -windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1] -windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1] -windowrulev2 = bordersize 0, floating:0, onworkspace:f[1] -windowrulev2 = rounding 0, floating:0, onworkspace:f[1] +windowrule = match:float false, match:workspace w[tv1], border_size 0 +windowrule = match:float false, match:workspace w[tv1], rounding 0 +windowrule = match:float false, match:workspace f[1], border_size 0 +windowrule = match:float false, match:workspace f[1], rounding 0 # Quick Memo popup (yad) -windowrulev2 = float, class:^(yad)$ -windowrulev2 = center, class:^(yad)$ -windowrulev2 = pin, class:^(yad)$ +windowrule = match:class ^(yad)$, float on +windowrule = match:class ^(yad)$, center on +windowrule = match:class ^(yad)$, pin on binds { hide_special_on_workspace_change = true @@ -251,6 +250,11 @@ bind = , Print, exec, ~/.local/bin/screenshot bind = $mainMod, mouse_down, workspace, e+1 bind = $mainMod, mouse_up, workspace, e-1 +# Toggle llm workspace +bind = , mouse:279, exec, ~/.local/bin/hypr-debounce 125 hyprctl dispatch togglespecialworkspace llm +bind = , mouse:278, hyprexpo:expo, toggle +bind = , mouse:277, exec, ~/.local/bin/hypr-debounce 125 hyprctl dispatch workspace previous + # Move/resize windows with mainMod + LMB/RMB bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:273, resizewindow @@ -274,18 +278,22 @@ bindl = , XF86AudioPrev, exec, playerctl previous ############################## # Window rules for apps -windowrulev2 = workspace 1, class:^zen$ -windowrulev2 = workspace 2, class:^(chromium-work)$ -windowrulev2 = workspace special:llm, class:^(chromium-llm)$ -windowrulev2 = workspace special:llm, class:^(heynote|Heynote)$ -windowrulev2 = workspace name:tg, class:^(org.telegram.desktop)$ -windowrulev2 = workspace name:tg, class:^(discord)$ -windowrulev2 = workspace name:tg, class:^Slack$ -windowrulev2 = workspace name:tg, class:^org.mozilla.Thunderbird$ -windowrulev2 = workspace name:media, class:^spotify$ -windowrulev2 = workspace special:org, class:^ticktick$ -windowrulev2 = suppressevent maximize, class:.* -windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 +windowrule = match:class ^zen$, workspace 1 +windowrule = match:class ^chromium-work$, workspace 2 +windowrule = match:initial_class ^chromium-work$, workspace 2 +windowrule = match:class ^chromium-llm$, workspace special:llm +windowrule = match:initial_class ^chromium-llm$, workspace special:llm +windowrule = match:class ^(heynote|Heynote)$, workspace special:llm +windowrule = match:class ^(org.telegram.desktop)$, workspace name:tg +windowrule = match:class ^(discord)$, workspace name:tg +windowrule = match:class ^Slack$, workspace name:tg +windowrule = match:class ^org.mozilla.Thunderbird$, workspace name:tg +windowrule = match:class ^spotify$, workspace name:media +windowrule = match:initial_class ^spotify$, workspace name:media +windowrule = match:class ^ticktick$, workspace special:org +windowrule = match:initial_class ^ticktick$, workspace special:org +windowrule = match:class .*, suppress_event maximize +windowrule = match:class ^$, match:title ^$, match:xwayland true, match:float true, match:fullscreen false, match:pin false, no_focus on ############# ### PLUGINS ### @@ -307,3 +315,6 @@ plugin { {{- end }} } } + +# Autostart (must be at end so windowrule rules are loaded first) +source = ~/.config/hypr/autostart.conf diff --git a/home/private_dot_config/hypr/hyprpaper.conf.tmpl b/home/private_dot_config/hypr/hyprpaper.conf.tmpl index f0a4a74..e0e9f98 100644 --- a/home/private_dot_config/hypr/hyprpaper.conf.tmpl +++ b/home/private_dot_config/hypr/hyprpaper.conf.tmpl @@ -1,13 +1,9 @@ # Hyprpaper Configuration # Device: {{ .deviceProfile }} ({{ .hostname }}) -preload = ~/Pictures/wlpp/wallhaven-9dkeqd.png +splash = false -{{- if eq .deviceProfile "desktop" }} -wallpaper = {{ .primaryMonitor }}, ~/Pictures/wlpp/wallhaven-9dkeqd.png -{{- if .secondaryMonitor }} -wallpaper = {{ .secondaryMonitor }}, ~/Pictures/wlpp/wallhaven-9dkeqd.png -{{- end }} -{{- else }} -wallpaper = {{ .primaryMonitor }}, ~/Pictures/wlpp/wallhaven-9dkeqd.png -{{- end }} +wallpaper { + monitor = + path = ~/Pictures/wlpp/wallhaven-9dkeqd.png +} diff --git a/home/private_dot_config/niri/config.kdl.tmpl b/home/private_dot_config/niri/config.kdl.tmpl new file mode 100644 index 0000000..fb2d44a --- /dev/null +++ b/home/private_dot_config/niri/config.kdl.tmpl @@ -0,0 +1,172 @@ +// Niri configuration +// Device: {{ .deviceProfile }} ({{ .hostname }}) +// Managed by chezmoi - edit source at ~/dotfiles + +input { + keyboard { + xkb { + layout "us,ru" + options "caps:escape" + } + numlock + } + +{{- if .hasTouchpad }} + touchpad { + tap + dwt + natural-scroll + } +{{- end }} + + focus-follows-mouse +} + +{{- if eq .deviceProfile "desktop" }} +output "{{ .primaryMonitor }}" { + mode "{{ .primaryResolution }}" + position x=0 y=0 +} + +{{- if .secondaryMonitor }} +output "{{ .secondaryMonitor }}" { + mode "{{ .secondaryResolution }}" +} +{{- end }} +{{- else }} +output "{{ .primaryMonitor }}" { + mode "{{ .primaryResolution }}" +} +{{- end }} + +layout { + gaps 5 + center-focused-column "never" + + focus-ring { + off + } + + border { + on + width 2 + // Subtle orange gradient for a muted "Hackers (1995)" look. + active-gradient from="#ff9a33" to="#ffad55" angle=90 + inactive-color "#3a2a12cc" + urgent-color "#ff0000" + } + + shadow { + off + } +} + +animations { + off +} + +// Avoid border/background fill showing through transparent terminals. +prefer-no-csd + +// Startup apps (portable subset from Hyprland autostart) +spawn-at-startup "nm-applet" +spawn-at-startup "qs" +spawn-at-startup "swaync" +spawn-at-startup "hyprpaper" +spawn-at-startup "hypridle" +spawn-at-startup "copyq" "--start-server" +spawn-at-startup "zen-browser" + +// Keep quick memo popup floating. +window-rule { + match app-id=r#"^(yad)$"# + open-floating true +} + +binds { + Mod+Shift+Slash { show-hotkey-overlay; } + + // Terminal / launcher + Mod+Q { spawn "alacritty"; } + Mod+R { spawn-sh "wofi --show drun"; } + + // Core controls + Mod+K repeat=false { close-window; } + Mod+M { quit; } + Mod+V { toggle-window-floating; } + Mod+E { toggle-overview; } + Mod+F { maximize-column; } + Mod+Shift+F { fullscreen-window; } + + // Layout toggle (us/ru) + Mod+Space { switch-layout "next"; } + + // Lock + Super+Alt+L { spawn-sh "pactl set-sink-mute @DEFAULT_SINK@ 1 && hyprlock"; } + + // VPN and screenshot helpers + F6 { spawn-sh "~/.local/bin/vpn-switcher"; } + Print { spawn-sh "~/.local/bin/screenshot"; } + + // Snap-like half-screen placement for focused window. + // Keep navigation on Mod+H / Mod+L. + Mod+Left { + spawn-sh "niri msg action move-window-to-floating && niri msg action set-window-width '50%' && niri msg action move-floating-window -x -100000"; + } + Mod+Right { + spawn-sh "niri msg action move-window-to-floating && niri msg action set-window-width '50%' && niri msg action move-floating-window -x +100000"; + } + Mod+Up { focus-window-up; } + Mod+Down { focus-window-down; } + Mod+H { focus-column-left; } + Mod+L { focus-column-right; } + Mod+J { focus-window-down; } + Mod+I { focus-window-up; } + + // Move windows/columns + Mod+Ctrl+Left { move-column-left; } + Mod+Ctrl+Right { move-column-right; } + Mod+Ctrl+Up { move-window-up; } + Mod+Ctrl+Down { move-window-down; } + + // Workspace navigation + 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; } + + // Scroll workspaces + Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; } + Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; } + + // Media keys + XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.05+ -l 1.0"; } + XF86AudioLowerVolume allow-when-locked=true { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.05-"; } + 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"; } + +{{- if .hasBattery }} + XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "set" "+10%"; } + XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "set" "10%-"; } +{{- end }} + + XF86AudioNext allow-when-locked=true { spawn-sh "playerctl next"; } + XF86AudioPause allow-when-locked=true { spawn-sh "playerctl play-pause"; } + XF86AudioPlay allow-when-locked=true { spawn-sh "playerctl play-pause"; } + XF86AudioPrev allow-when-locked=true { spawn-sh "playerctl previous"; } +} diff --git a/home/private_dot_config/quickshell/shell.qml.tmpl b/home/private_dot_config/quickshell/shell.qml.tmpl new file mode 100644 index 0000000..685e224 --- /dev/null +++ b/home/private_dot_config/quickshell/shell.qml.tmpl @@ -0,0 +1,568 @@ +import QtQuick +import Quickshell +import Quickshell.Hyprland +import Quickshell.Io +import Quickshell.Services.SystemTray +import Quickshell.Widgets + +ShellRoot { + id: root + + property color bgColor: "#141414" + property color fgColor: "#cccccc" + property color dimColor: "#666666" + property color okColor: "#00ff00" + property color warnColor: "#ffaa00" + property color critColor: "#ff0000" + + Process { + id: shellExec + running: false + } + + function runShell(cmd) { + shellExec.command = ["/usr/bin/env", "bash", "-lc", cmd] + shellExec.running = true + } + + function trim(s) { + return (s || "").toString().trim() + } + + function wsLabel(name) { + if (name === "1") return "WEB" + if (name === "2") return "CODE" + if (name === "3") return "TERM" + if (name === "4") return "IDE" + if (name === "5") return "VM" + if (name === "6") return "GFX" + if (name === "7") return "DOC" + if (name === "8") return "GAME" + if (name === "9") return "MISC" + if (name === "10") return "TMP" + if (name === "special:termius") return "s:term" + if (name === "special:org") return "s:org" + if (name === "special:llm") return "s:llm" + return "WS" + } + + function wsIgnored(name) { + return name === "chrome-sharing-indicator" || name === "special:chrome-sharing-indicator" + } + + component ModuleBox: Rectangle { + color: "transparent" + border.width: 1 + border.color: root.fgColor + radius: 0 + implicitHeight: 22 + } + + Variants { + model: Quickshell.screens + + delegate: PanelWindow { + id: panel + required property var modelData + + screen: modelData + color: "transparent" + exclusionMode: ExclusionMode.Normal + + anchors { + bottom: true + left: true + right: true + } + + implicitHeight: 34 + + property var hyprMonitor: Hyprland.monitorFor(modelData) + visible: {{- if eq .deviceProfile "desktop" }}hyprMonitor && hyprMonitor.name === "{{ .primaryMonitor }}"{{- else }}true{{- end }} + + Rectangle { + anchors.fill: parent + anchors.margins: 4 + color: root.bgColor + + Row { + id: mainRow + anchors.fill: parent + anchors.leftMargin: 6 + anchors.rightMargin: 6 + spacing: 0 + + Row { + id: leftGroup + anchors.verticalCenter: parent.verticalCenter + spacing: 6 + + ModuleBox { + id: workspacesBox + implicitWidth: workspacesRow.implicitWidth + 12 + + Row { + id: workspacesRow + anchors.centerIn: parent + spacing: 6 + + Repeater { + model: Hyprland.workspaces + + delegate: Rectangle { + required property var modelData + property bool ignored: root.wsIgnored(modelData.name) + + visible: !ignored + color: modelData.urgent + ? root.critColor + : (modelData.active ? root.fgColor : "transparent") + border.width: 1 + border.color: modelData.urgent ? root.critColor : root.fgColor + radius: 0 + implicitHeight: 18 + implicitWidth: wsText.implicitWidth + 10 + + Text { + id: wsText + anchors.centerIn: parent + text: root.wsLabel(modelData.name) + color: modelData.active || modelData.urgent ? "#000000" : root.fgColor + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: modelData.activate() + } + } + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: function(wheel) { + if (wheel.angleDelta.y > 0) { + Hyprland.dispatch("workspace e+1") + } else if (wheel.angleDelta.y < 0) { + Hyprland.dispatch("workspace e-1") + } + } + } + } + } + + Item { + width: Math.max(0, panel.width - leftGroup.implicitWidth - rightGroup.implicitWidth - 12) + height: 1 + } + + Row { + id: rightGroup + anchors.verticalCenter: parent.verticalCenter + spacing: 6 + + ModuleBox { + id: vpnBox + implicitWidth: vpnText.implicitWidth + 12 + property string vpnClass: "disconnected" + + Text { + id: vpnText + anchors.centerIn: parent + text: "VPN off" + color: vpnBox.vpnClass === "connected" + ? root.okColor + : (vpnBox.vpnClass === "disconnected" ? root.dimColor : root.fgColor) + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + Process { + id: vpnProc + command: ["/usr/bin/env", "bash", "-lc", "~/.local/bin/vpn-status"] + running: true + stdout: StdioCollector { + onStreamFinished: { + const raw = root.trim(this.text) + try { + const parsed = JSON.parse(raw) + vpnText.text = parsed.text || "VPN off" + vpnBox.vpnClass = parsed.class || "disconnected" + } catch (_) { + vpnText.text = raw || "VPN off" + vpnBox.vpnClass = "disconnected" + } + } + } + } + + Timer { + interval: 5000 + running: true + repeat: true + onTriggered: vpnProc.running = true + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: root.runShell("~/.local/bin/vpn-switcher") + } + } + + ModuleBox { + id: spkBox + implicitWidth: spkText.implicitWidth + 12 + + Text { + id: spkText + anchors.centerIn: parent + text: "VOL --" + color: root.fgColor + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + Process { + id: spkProc + command: ["/usr/bin/env", "bash", "-lc", "mute=$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | awk '{print $2}'); vol=$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | awk 'NR==1{print $5}'); if [ \"${mute}\" = \"yes\" ]; then echo 'VOL muted'; elif [ -n \"${vol}\" ]; then echo \"VOL ${vol}\"; else echo 'VOL --'; fi"] + running: true + stdout: StdioCollector { onStreamFinished: spkText.text = root.trim(this.text) || "VOL --" } + } + + Timer { + interval: 3000 + running: true + repeat: true + onTriggered: spkProc.running = true + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: function(mouse) { + if (mouse.button === Qt.RightButton) { + root.runShell("pactl set-sink-mute @DEFAULT_SINK@ toggle") + } else { + root.runShell("pavucontrol -t 3") + } + spkProc.running = true + } + onWheel: function(wheel) { + if (wheel.angleDelta.y > 0) { + root.runShell("~/.local/bin/audio-sink-cycle up") + } else if (wheel.angleDelta.y < 0) { + root.runShell("~/.local/bin/audio-sink-cycle down") + } + spkProc.running = true + } + } + } + + ModuleBox { + id: micBox + implicitWidth: micText.implicitWidth + 12 + + Text { + id: micText + anchors.centerIn: parent + text: "MIC --" + color: root.fgColor + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + Process { + id: micProc + command: ["/usr/bin/env", "bash", "-lc", "mute=$(pactl get-source-mute @DEFAULT_SOURCE@ 2>/dev/null | awk '{print $2}'); vol=$(pactl get-source-volume @DEFAULT_SOURCE@ 2>/dev/null | awk 'NR==1{print $5}'); if [ \"${mute}\" = \"yes\" ]; then echo 'MIC muted'; elif [ -n \"${vol}\" ]; then echo \"MIC ${vol}\"; else echo 'MIC --'; fi"] + running: true + stdout: StdioCollector { onStreamFinished: micText.text = root.trim(this.text) || "MIC --" } + } + + Timer { + interval: 3000 + running: true + repeat: true + onTriggered: micProc.running = true + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: function(mouse) { + if (mouse.button === Qt.RightButton) { + root.runShell("pactl set-source-mute @DEFAULT_SOURCE@ toggle") + } else { + root.runShell("pavucontrol -t 4") + } + micProc.running = true + } + onWheel: function(wheel) { + if (wheel.angleDelta.y > 0) { + root.runShell("pactl set-source-volume @DEFAULT_SOURCE@ +2%") + } else if (wheel.angleDelta.y < 0) { + root.runShell("pactl set-source-volume @DEFAULT_SOURCE@ -2%") + } + micProc.running = true + } + } + } + + ModuleBox { + id: netBox + implicitWidth: netText.implicitWidth + 12 + + Text { + id: netText + anchors.centerIn: parent + text: "NET down" + color: root.fgColor + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + Process { + id: netProc + command: ["/usr/bin/env", "bash", "-lc", "wifi=$(iwgetid -r 2>/dev/null || true); if [ -n \"$wifi\" ]; then sig=$(awk 'NR==3{gsub(/\./,\"\",$3); q=$3+0; printf \"%d\", int((q/70)*100)}' /proc/net/wireless 2>/dev/null); [ -z \"$sig\" ] && sig=0; echo \"NET wifi $wifi ${sig}%\"; exit; fi; iface=$(ip route | awk '/^default/{print $5; exit}'); if [ -n \"$iface\" ]; then ip4=$(ip -4 addr show dev \"$iface\" | awk '/inet /{print $2; exit}' | cut -d/ -f1); if [ -n \"$ip4\" ]; then echo \"NET eth $ip4\"; else echo 'NET link'; fi; else echo 'NET down'; fi"] + running: true + stdout: StdioCollector { onStreamFinished: netText.text = root.trim(this.text) || "NET down" } + } + + Timer { + interval: 5000 + running: true + repeat: true + onTriggered: netProc.running = true + } + } + +{{- if .hasBattery }} + ModuleBox { + id: batteryBox + implicitWidth: batteryText.implicitWidth + 12 + + Text { + id: batteryText + anchors.centerIn: parent + text: "BAT --" + color: root.fgColor + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + Process { + id: batteryProc + command: ["/usr/bin/env", "bash", "-lc", "cap=$(cat /sys/class/power_supply/BAT*/capacity 2>/dev/null | head -n1); stat=$(cat /sys/class/power_supply/BAT*/status 2>/dev/null | head -n1); if [ -z \"$cap\" ]; then echo 'BAT --'; exit; fi; if [ \"$stat\" = 'Charging' ]; then echo \"BAT+ ${cap}%\"; elif [ \"$stat\" = 'Full' ]; then echo 'BAT full'; elif [ \"$stat\" = 'Not charging' ] || [ \"$stat\" = 'Unknown' ]; then echo \"BAT= ${cap}%\"; else echo \"BAT ${cap}%\"; fi"] + running: true + stdout: StdioCollector { + onStreamFinished: { + const v = root.trim(this.text) + batteryText.text = v || "BAT --" + const m = v.match(/(\d+)%/) + if (v.startsWith("BAT+")) { + batteryText.color = root.okColor + } else if (m && Number(m[1]) <= 15) { + batteryText.color = root.critColor + } else if (m && Number(m[1]) <= 30) { + batteryText.color = root.warnColor + } else { + batteryText.color = root.fgColor + } + } + } + } + + Timer { + interval: 10000 + running: true + repeat: true + onTriggered: batteryProc.running = true + } + } + + ModuleBox { + id: brtBox + implicitWidth: brtText.implicitWidth + 12 + + Text { + id: brtText + anchors.centerIn: parent + text: "BRT --" + color: root.fgColor + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + Process { + id: brtProc + command: ["/usr/bin/env", "bash", "-lc", "pct=$(brightnessctl -m 2>/dev/null | awk -F, '{print $4}' | tr -d '%'); if [ -n \"$pct\" ]; then echo \"BRT ${pct}%\"; else echo 'BRT --'; fi"] + running: true + stdout: StdioCollector { onStreamFinished: brtText.text = root.trim(this.text) || "BRT --" } + } + + Timer { + interval: 5000 + running: true + repeat: true + onTriggered: brtProc.running = true + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: function(wheel) { + if (wheel.angleDelta.y > 0) { + root.runShell("brightnessctl s 5%+") + } else if (wheel.angleDelta.y < 0) { + root.runShell("brightnessctl s 5%-") + } + brtProc.running = true + } + } + } +{{- end }} + + ModuleBox { + id: cpuBox + implicitWidth: cpuText.implicitWidth + 12 + + Text { + id: cpuText + anchors.centerIn: parent + text: "CPU --" + color: root.fgColor + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + Process { + id: cpuProc + command: ["/usr/bin/env", "bash", "-lc", "read -r _ u n s i _ < /proc/stat; used=$((u+n+s)); total=$((u+n+s+i)); if [ \"$total\" -gt 0 ]; then printf 'CPU %d%%\\n' $((used*100/total)); else echo 'CPU --'; fi"] + running: true + stdout: StdioCollector { onStreamFinished: cpuText.text = root.trim(this.text) || "CPU --" } + } + + Timer { + interval: 5000 + running: true + repeat: true + onTriggered: cpuProc.running = true + } + } + + ModuleBox { + id: memBox + implicitWidth: memText.implicitWidth + 12 + + Text { + id: memText + anchors.centerIn: parent + text: "MEM --" + color: root.fgColor + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + Process { + id: memProc + command: ["/usr/bin/env", "bash", "-lc", "free | awk '/Mem:/ { if ($2 > 0) printf \"MEM %d%%\\n\", int($3*100/$2); else print \"MEM --\"; }'"] + running: true + stdout: StdioCollector { onStreamFinished: memText.text = root.trim(this.text) || "MEM --" } + } + + Timer { + interval: 5000 + running: true + repeat: true + onTriggered: memProc.running = true + } + } + + ModuleBox { + id: clockBox + implicitWidth: clockText.implicitWidth + 12 + + Text { + id: clockText + anchors.centerIn: parent + text: "TIME --" + color: root.fgColor + font.family: "Terminus, IBM Plex Mono, JetBrainsMono Nerd Font, monospace" + font.pixelSize: 12 + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: { + const now = new Date() + clockText.text = Qt.formatDateTime(now, "'TIME' yyyy-MM-dd HH:mm") + } + } + + Component.onCompleted: { + const now = new Date() + clockText.text = Qt.formatDateTime(now, "'TIME' yyyy-MM-dd HH:mm") + } + } + + ModuleBox { + id: trayBox + implicitWidth: trayRow.implicitWidth + 12 + + Row { + id: trayRow + anchors.centerIn: parent + spacing: 4 + + Repeater { + model: SystemTray.items + + delegate: Rectangle { + required property var modelData + color: "transparent" + border.width: 0 + implicitWidth: 18 + implicitHeight: 18 + + IconImage { + anchors.fill: parent + source: modelData.icon + asynchronous: true + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: function(mouse) { + if (mouse.button === Qt.RightButton && modelData.hasMenu) { + modelData.display(panel, mouse.x, mouse.y) + } else if (mouse.button === Qt.MiddleButton) { + modelData.secondaryActivate() + } else { + modelData.activate() + } + } + onWheel: function(wheel) { + if (wheel.angleDelta.y > 0) { + modelData.scroll(1, false) + } else if (wheel.angleDelta.y < 0) { + modelData.scroll(-1, false) + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..29f318f --- /dev/null +++ b/justfile @@ -0,0 +1,40 @@ +set shell := ["bash", "-euo", "pipefail", "-c"] + +# Show available recipes +default: + @just --list + +# Chezmoi workflows +apply: + chezmoi apply + +diff: + chezmoi diff + +status: + chezmoi status + +edit file: + chezmoi edit {{file}} + +# Niri workflows +niri-reload: + niri msg action load-config-file + +niri-validate: + chezmoi execute-template < home/private_dot_config/niri/config.kdl.tmpl > /tmp/niri-config.kdl + niri validate -c /tmp/niri-config.kdl + +# Quickshell / Hypr helpers +qs-reload: + pkill -USR2 qs || true + +hypr-reload: + hyprctl reload + +# Combined helpers +apply-niri: apply niri-validate niri-reload + +apply-qs: apply qs-reload + +check: diff niri-validate