1
0
Fork 0

Add sway/wofi/foot Wayland desktop stack with per-host toggle

New opt-in feature for x86_64 desktops: sway + wofi + foot + swaybg +
swaylock + swayidle + grim + slurp + waybar + wl-clipboard. mako on
arch, dunst on debian (mako isn't packaged for debian).

Files:
- .chezmoi.yaml.tmpl: added data.sway flag (true on miche/byte/kaiser,
  false on Pis). Override per host with ~/.config/chezmoi/features/sway
  or ~/.config/chezmoi/features/no-sway marker files.
- run_once_40-install-sway.sh.tmpl: installs packages if .sway=true,
  exits 0 otherwise. Sets up the marker file.
- dot_config/{sway,foot,wofi,waybar,mako}/: existing configs from miche.

Per-host toggle workflow:
  # On any box, enable sway:
  touch ~/.config/chezmoi/features/sway
  chezmoi apply

  # On a sway-enabled box, disable it:
  touch ~/.config/chezmoi/features/no-sway
  rm ~/.config/chezmoi/features/sway
  chezmoi apply

Currently sway packages are already installed on miche (existed before
this commit). Byte will get them via the new run_once_40 script.
Pis (rye, crouton) are unaffected — install script early-returns.
This commit is contained in:
Rain 2026-06-22 14:23:56 -04:00
parent fe73bbecba
commit 6bbaa8f2f5
21 changed files with 965 additions and 1 deletions

View file

@ -61,3 +61,21 @@ age:
data:
os_family: {{ $osFamily | quote }}
os_id: {{ .chezmoi.osRelease.id | quote }}
# Sway/Wayland desktop stack — enabled per host. Pis don't need it
# (headless); x86_64 desktops do. Override at runtime by creating
# `~/.config/chezmoi/features/sway` (force ON) or
# `~/.config/chezmoi/features/no-sway` (force OFF).
#
# To toggle for a new box: `touch ~/.config/chezmoi/features/sway`
# then `chezmoi apply`.
{{- $swayOverrideOn := stat (joinPath .chezmoi.homeDir ".config/chezmoi/features/sway") }}
{{- $swayOverrideOff := stat (joinPath .chezmoi.homeDir ".config/chezmoi/features/no-sway") }}
{{- if $swayOverrideOn }}
sway: true
{{- else if $swayOverrideOff }}
sway: false
{{- else }}
{{- $swayHosts := list "miche" "byte" "kaiser" }}
sway: {{ has .chezmoi.hostname $swayHosts }}
{{- end }}

88
dot_config/foot/foot.ini Normal file
View file

@ -0,0 +1,88 @@
# foot terminal — gruvbox dark, Maple Mono 14pt with Terminus fallback
# Reference: man foot.ini
[main]
font=Maple Mono CN:size=14,Terminus:size=14
font-bold=Maple Mono CN:weight=bold:size=14
font-italic=Maple Mono CN:slant=italic:size=14
font-bold-italic=Maple Mono CN:weight=bold:slant=italic:size=14
font-size-adjustment=0.5
pad=8x8
term=foot
# (Previously set to "foot-extra", but that terminfo is not installed in the
# CachyOS foot package; only foot, foot+base, foot-direct are. Setting
# TERM=foot-extra breaks every ncurses program — htop, nvtop, vim, etc. —
# with "cannot initialize terminal type". "foot" has 256 colors and
# everything modern tools need.)
# kaiser runs zsh as the login shell (SHELL=/bin/fish was misleading — that
# comes from sway's inherited env, not the user's actual shell). Hardcode
# /usr/bin/zsh here. foot does NOT expand $SHELL in the shell= value (it
# passes the literal string to its child), so don't try the sh,-c trick —
# it just makes foot spawn a binary literally named "${SHELL:-/bin/bash}"
# and exit with "No such file or directory".
shell=/usr/bin/zsh
# PTY
# utmphelper is not packaged with foot on this CachyOS build. Leave it unset
# to disable utmp logging for foot sessions; harmless and not required.
# Initial geometry
initial-window-size-chars=120x32
[scrollback]
lines=10000
indicator-position=relative
[cursor]
style=block
blink=yes
blink-rate=500
beam-thickness=2px
[mouse]
hide-when-typing=yes
alternate-scroll-mode=true
[colors-dark]
# Gruvbox dark palette (gruvbox-community) — bg0_h / fg / 16 ANSI
# https://github.com/gruvbox-community/gruvbox
background=1d2021
foreground=ebdbb2
# Slight transparency — 0.90 alpha. Foot 1.27+ uses a separate alpha key
# (in [colors-dark], not 8-digit hex — that was foot 1.24 syntax).
alpha=0.90
# Regular ANSI (0-7)
regular0=282828
regular1=cc241d
regular2=98971a
regular3=d79921
regular4=458588
regular5=b16286
regular6=689d6a
regular7=a89984
# Bright ANSI (8-15)
bright0=928374
bright1=fb4934
bright2=b8bb26
bright3=fabd2f
bright4=83a598
bright5=d3869b
bright6=8ec07c
bright7=ebdbb2
# Selection (FG BG pair)
selection-foreground=1d2021
selection-background=ebdbb2
# Search box (FG BG pairs)
search-box-match=ebdbb2 458588
search-box-no-match=ebdbb2 cc241d
[bell]
urgent=no
notify=no
visual=no
[url]
launch=xdg-open {url}

25
dot_config/foot/patch.py Normal file
View file

@ -0,0 +1,25 @@
from pathlib import Path
p = Path("/home/rain/.config/foot/foot.ini")
text = p.read_text()
old_lines = [
"# foot does not expand env variables in the `shell=` value, so we have to",
"# go through `sh -c` to make the parent's $SHELL take effect. When foot",
"# is launched from a fish session, $SHELL is /bin/fish, and foot runs fish.",
"# When launched from bash, $SHELL is /bin/bash, and foot runs bash. The",
"# default fallback is /bin/bash if $SHELL is unset for any reason.",
'shell=sh,-c,"exec ${SHELL:-/bin/bash} -i"',
]
new_lines = [
"# kaiser runs zsh as the login shell. Hardcode /usr/bin/zsh here.",
"# foot does NOT expand $SHELL in shell= (it passes the literal string",
"# to its child process), so the sh,-c trick makes foot try to exec a",
'# binary literally named "${SHELL:-/bin/bash}" and die with',
'# "No such file or directory". Point shell= at the binary directly.',
"shell=/usr/bin/zsh",
]
old = "\n".join(old_lines)
new = "\n".join(new_lines)
assert old in text, "old block not found in foot.ini"
text = text.replace(old, new, 1)
p.write_text(text)
print("OK, new size:", len(text))

22
dot_config/mako/config Normal file
View file

@ -0,0 +1,22 @@
# mako gruvbox-material-dark-hard
font=Maple Mono NF 11
background-color=#1d2021
text-color=#ebdbb2
border-size=2
border-color=#d79921
border-radius=0
default-timeout=5000
padding=10
margin=8
width=400
[urgency=high]
border-color=#fb4934
default-timeout=0
[urgency=normal]
border-color=#d79921
[urgency=low]
border-color=#504945
default-timeout=3000

24
dot_config/sway/caffeine.sh Executable file
View file

@ -0,0 +1,24 @@
#!/bin/bash
# caffeine.sh — toggle caffeine mode by killing/restarting swayidle
# When ON: kill swayidle (no idle lock/screen-off)
# When OFF: restart swayidle with normal timeouts
WALLPAPER="$HOME/.local/share/wallpapers/teach-invaders-gruvbox.png"
FLAG="/tmp/caffeine-inhibit"
if [ -f "$FLAG" ]; then
# Turn OFF caffeine — restart swayidle
rm -f "$FLAG"
killall swayidle 2>/dev/null
swayidle -w \
timeout 300 "swaylock -f -i $WALLPAPER" \
timeout 600 'swaymsg "output * power off"' \
resume 'swaymsg "output * power on"' \
before-sleep "swaylock -f -i $WALLPAPER" &
notify-send -t 2000 "☕ Caffeine OFF" "Idle sleep enabled" 2>/dev/null
else
# Turn ON caffeine — kill swayidle
touch "$FLAG"
killall swayidle 2>/dev/null
notify-send -t 2000 "☕ Caffeine ON" "Idle sleep disabled" 2>/dev/null
fi

View file

@ -0,0 +1,48 @@
# === sway keybinds cheatsheet ===
# generated from ~/.config/sway/config — `Super` = the Windows key (Mod4)
# reload this view with: Super + /
# LAUNCHERS
Super+Enter open foot terminal
Super+D open wofi app launcher
Super+E open thunar (file manager)
Super+B open firefox
Super+/ show this cheatsheet (in foot, via less)
Super+Shift+E exit sway (with prompt)
# WINDOW MANAGEMENT
Super+Q kill focused window
Super+F fullscreen toggle
Super+V split toggle (vertical <-> horizontal)
Super+S stacking layout
Super+W tabbed layout
Super+Space floating toggle
# FOCUS / MOVE
Super+arrows focus window in direction
Super+Shift+arrows move window in direction
# WORKSPACES
Super+1 .. Super+0 switch to workspace N (0 = 10)
Super+Tab toggle back to previous workspace
Super+Shift+1..0 move focused window to workspace N
# SCREENSHOTS
Print region screenshot to file
Super+Print full-screen screenshot to file
Super+Shift+S region screenshot to clipboard
# SCRATCHPAD
Super+` toggle dropdown foot (sway scratchpad)
# RESIZE MODE (Super+R enters, Esc/Enter exits)
resize mode:
arrows shrink/grow 30px in direction
Esc / Enter exit resize mode
# LOCK / IDLE
Ctrl+Alt+L lock now
5 min idle auto-lock
10 min idle auto screen-off
# TIP: Super+Shift+C reloads this config without logging out.

4
dot_config/sway/cheatsheet.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
# cheatsheet.sh — opens the sway keybind cheatsheet in a new foot terminal
# running less. Press q to quit.
exec foot sh -c 'exec less -SRFX "$HOME/.config/sway/cheatsheet.md"'

148
dot_config/sway/config Normal file
View file

@ -0,0 +1,148 @@
# === ~/.config/sway/config ===
# MINIMAL — single file, no includes. Tier-1+2+3 in one go.
# We can split into includes later once this is proven to boot.
# === variables ===
set $mod Mod4
set $term foot
set $menu wofi --show drun --insensitive -p "launch"
set $browser zen
set $fileManager thunar
# === output / input ===
# Gruvbox wallpaper — sway's native bg command manages swaybg lifecycle internally
output * bg $HOME/.local/share/wallpapers/teach-invaders-gruvbox.png fill
# Touchpad: tap-to-click, natural scroll, drag
input type:touchpad {
tap enabled
tap_button_map lrm
natural_scroll enabled
dwt enabled
accel_profile adaptive
}
# === window borders (gruvbox-hard) ===
default_border pixel 2
default_floating_border pixel 2
gaps inner 8
gaps outer 4
smart_borders on
smart_gaps on
# gruvbox-orange focus border (replaces the default "generic blue")
client.focused #d79921 #1d2021 #ebdbb2 #d79921 #d79921
client.unfocused #3c3836 #1d2021 #a89984 #3c3836 #3c3836
client.focused_inactive #504945 #1d2021 #a89984 #504945 #504945
client.urgent #fb4934 #1d2021 #ebdbb2 #fb4934 #fb4934
# === swaybar (replaced by waybar — keeping as fallback) ===
# If waybar isn't installed yet, this provides a basic bar.
bar {
swaybar_command waybar
}
# === autostart (kept tiny, all stderr silenced) ===
# Make user-local themes/icons visible to GTK and Qt
exec sh -c 'export XDG_DATA_DIRS="$HOME/.local/share:$HOME/.local/share/flatpak/exports/share:/usr/local/share:/usr/share"; export XDG_DATA_HOME="$HOME/.local/share"; export XDG_CONFIG_HOME="$HOME/.config"; systemctl --user import-environment XDG_DATA_DIRS XDG_DATA_HOME XDG_CONFIG_HOME 2>/dev/null; dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=sway'
exec /usr/libexec/polkit-gnome-authentication-agent-1
exec clipmenud
exec sh -c 'systemctl --user start xdg-desktop-portal xdg-desktop-portal-wlr 2>/dev/null || true'
exec sh -c 'gsettings set org.gnome.desktop.interface gtk-theme Gruvbox-Material-Dark-Hard 2>/dev/null; gsettings set org.gnome.desktop.interface icon-theme Gruvbox-Material-Dark 2>/dev/null; gsettings set org.gnome.desktop.interface color-scheme prefer-dark 2>/dev/null; true'
exec gnome-keyring-daemon --start --components=secrets
exec mako
exec wlsunset -l 39.96 -L -82.99
exec sworkstyle
# === idle / lock ===
exec swayidle -w \
timeout 300 '$HOME/.config/sway/lock-fancy.sh' \
timeout 600 'swaymsg "output * power off"' \
resume 'swaymsg "output * power on"' \
before-sleep '$HOME/.config/sway/lock-fancy.sh'
# === keybinds ===
bindsym $mod+Return exec $term
bindsym $mod+d exec $menu
bindsym $mod+e exec $fileManager
bindsym $mod+b exec $browser
bindsym $mod+q kill
bindsym $mod+Shift+c reload
bindsym Ctrl+Alt+l exec $HOME/.config/sway/lock-fancy.sh
bindsym $mod+slash exec $HOME/.config/sway/cheatsheet.sh
bindsym $mod+question exec $HOME/.config/sway/cheatsheet.sh
bindsym $mod+Shift+e exec swaynag -t warning -m 'Exit Sway?' -B 'Yes, exit Sway' 'swaymsg exit'
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right
bindsym $mod+1 workspace number 1
bindsym $mod+2 workspace number 2
bindsym $mod+3 workspace number 3
bindsym $mod+4 workspace number 4
bindsym $mod+5 workspace number 5
bindsym $mod+6 workspace number 6
bindsym $mod+7 workspace number 7
bindsym $mod+8 workspace number 8
bindsym $mod+9 workspace number 9
bindsym $mod+0 workspace number 10
bindsym $mod+Tab workspace back_and_forth
bindsym $mod+Shift+1 move container to workspace number 1
bindsym $mod+Shift+2 move container to workspace number 2
bindsym $mod+Shift+3 move container to workspace number 3
bindsym $mod+Shift+4 move container to workspace number 4
bindsym $mod+Shift+5 move container to workspace number 5
bindsym $mod+Shift+6 move container to workspace number 6
bindsym $mod+Shift+7 move container to workspace number 7
bindsym $mod+Shift+8 move container to workspace number 8
bindsym $mod+Shift+9 move container to workspace number 9
bindsym $mod+Shift+0 move container to workspace number 10
bindsym $mod+f fullscreen
bindsym $mod+v split toggle splitv
bindsym $mod+s layout stacking
bindsym $mod+w layout tabbed
bindsym $mod+space exec $menu
bindsym $mod+Shift+space floating toggle
bindsym Print exec grim -g "$(slurp)" ~/Pictures/screenshot-$(date +%Y%m%d-%H%M%S).png
bindsym $mod+Print exec grim ~/Pictures/screenshot-$(date +%Y%m%d-%H%M%S).png
bindsym $mod+Shift+s exec grim -g "$(slurp)" - | wl-copy
mode "resize" {
bindsym Left resize shrink width 30px
bindsym Down resize grow height 30px
bindsym Up resize shrink height 30px
bindsym Right resize grow width 30px
bindsym Escape mode "default"
bindsym Return mode "default"
}
bindsym $mod+r mode "resize"
# === F12 dropdown terminal ===
# F12 opens a floating foot. Uses -a dropdown so for_window only floats
# the dropdown, not regular foot windows from Super+Enter.
# Close it with Super+Q or Ctrl+D.
bindsym F12 exec foot -a dropdown
bindsym $mod+grave exec foot -a dropdown
for_window [app_id="dropdown"] floating enable, sticky enable, resize set width 1100 height 600, move position center
# === media keys ===
bindsym --locked XF86AudioRaiseVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
bindsym --locked XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindsym --locked XF86AudioMute exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindsym --locked XF86AudioMicMute exec wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
bindsym --locked XF86AudioPlay exec playerctl play-pause 2>/dev/null || true
bindsym --locked XF86AudioNext exec playerctl next 2>/dev/null || true
bindsym --locked XF86AudioPrev exec playerctl previous 2>/dev/null || true
bindsym --locked XF86MonBrightnessUp exec sh -c 'echo $(( $(cat /sys/class/backlight/acpi_video0/brightness) + 1 > $(cat /sys/class/backlight/acpi_video0/max_brightness) ? $(cat /sys/class/backlight/acpi_video0/max_brightness) : $(cat /sys/class/backlight/acpi_video0/brightness) + 1 )) | sudo tee /sys/class/backlight/acpi_video0/brightness'
bindsym --locked XF86MonBrightnessDown exec sh -c 'echo $(( $(cat /sys/class/backlight/acpi_video0/brightness) - 1 < 0 ? 0 : $(cat /sys/class/backlight/acpi_video0/brightness) - 1 )) | sudo tee /sys/class/backlight/acpi_video0/brightness'
# pointer / mouse — the gruvbox-material-gtk repo doesn't ship an xcursor theme,
# so leave the default alone. (Cursor shows fine on the default theme.)

View file

@ -0,0 +1,18 @@
# === ~/.config/sway/config.d/00-gruvbox.conf ===
# gruvbox material dark hard — wired through gsettings so Thunar, Firefox chrome,
# and any GTK3/4 app pick it up the moment sway starts.
# Run gsettings in a sh -c wrapper so we can pipe stderr to /dev/null.
# This is necessary because the very first exec block runs before dbus is
# always reachable, and gsettings prints a long bar-warning to stderr when
# it can't find the bus. We don't want the red bar to surface that.
exec_always {
gsettings set org.gnome.desktop.interface gtk-theme 'Gruvbox-Material-Dark-Hard' 2>/dev/null
gsettings set org.gnome.desktop.interface icon-theme 'Gruvbox-Material-Dark' 2>/dev/null
gsettings set org.gnome.desktop.interface cursor-theme 'Gruvbox-Material-Dark' 2>/dev/null
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' 2>/dev/null
gsettings set org.cinnamon.desktop.interface gtk-theme 'Gruvbox-Material-Dark-Hard' 2>/dev/null
}
# gnome-keyring (in case any GTK app needs secret service for ssh-agent bridge)
exec gnome-keyring-daemon --start --components=secrets

View file

@ -0,0 +1,76 @@
# === ~/.config/sway/config.d/10-keybinds.conf ===
# sway keybindings. Mint+Cinnamon muscle memory → Super (Mod4).
# === essentials ===
bindsym $mod+Return exec $term
bindsym $mod+d exec $menu
bindsym $mod+e exec $fileManager
bindsym $mod+b exec $browser
bindsym $mod+q kill
bindsym $mod+Shift+c reload
bindsym $mod+Shift+e exec swaynag -t warning \
-m 'Exit Sway?' \
-B 'Yes, exit Sway' 'swaymsg exit'
# === move focus ===
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right
# === workspace navigation ===
bindsym $mod+1 workspace number 1
bindsym $mod+2 workspace number 2
bindsym $mod+3 workspace number 3
bindsym $mod+4 workspace number 4
bindsym $mod+5 workspace number 5
bindsym $mod+6 workspace number 6
bindsym $mod+7 workspace number 7
bindsym $mod+8 workspace number 8
bindsym $mod+9 workspace number 9
bindsym $mod+0 workspace number 10
bindsym $mod+Tab workspace back_and_forth
bindsym $mod+Shift+1 move container to workspace number 1
bindsym $mod+Shift+2 move container to workspace number 2
bindsym $mod+Shift+3 move container to workspace number 3
bindsym $mod+Shift+4 move container to workspace number 4
bindsym $mod+Shift+5 move container to workspace number 5
bindsym $mod+Shift+6 move container to workspace number 6
bindsym $mod+Shift+7 move container to workspace number 7
bindsym $mod+Shift+8 move container to workspace number 8
bindsym $mod+Shift+9 move container to workspace number 9
bindsym $mod+Shift+0 move container to workspace number 10
# === layout ===
bindsym $mod+f fullscreen
bindsym $mod+v split toggle splitv
bindsym $mod+s layout stacking
bindsym $mod+w layout tabbed
bindsym $mod+space floating toggle
bindsym $mod+Shift+space focus mode_toggle
# === screenshot (grim + slurp) ===
bindsym Print exec grim -g "$(slurp)" ~/Pictures/screenshot-$(date +%Y%m%d-%H%M%S).png
bindsym $mod+Print exec grim ~/Pictures/screenshot-$(date +%Y%m%d-%H%M%S).png
bindsym $mod+Shift+s exec grim -g "$(slurp)" - | wl-copy
# === resize ===
mode "resize" {
bindsym Left resize shrink width 30px
bindsym Down resize grow height 30px
bindsym Up resize shrink height 30px
bindsym Right resize grow width 30px
bindsym Escape mode "default"
bindsym Return mode "default"
}
bindsym $mod+r mode "resize"
# === scratchpads (quick terminal / browser) ===
# Start a headless foot server, then a dropdown client window. Toggle with $mod+grave.
bindsym $mod+grave exec foot --server
bindsym $mod+apostrophe exec foot -a dropdown
for_window [app_id="dropdown"] floating enable, sticky enable, resize set width 1100 height 600, move position center

View file

@ -0,0 +1,15 @@
# === ~/.config/sway/config.d/20-autostart.conf ===
# background services started when sway comes up.
# polkit agent — needed for gparted, mount UIs in thunar, etc.
exec /usr/libexec/polkit-gnome-authentication-agent-1
# clipboard — start clipmenud directly (it watches the clipboard internally)
# Drop the wl-paste --watch wrapper to avoid bar noise if the socket isn't ready.
exec clipmenud
# xdg-desktop-portal — required for native file/screen sharing in Firefox/Chromium.
# Skip if services aren't enabled; the user can enable with:
# systemctl --user enable --now xdg-desktop-portal xdg-desktop-portal-wlr
# Until then, those systemctl lines are quiet no-ops.
exec sh -c 'systemctl --user start xdg-desktop-portal xdg-desktop-portal-wlr 2>/dev/null || true'

View file

@ -0,0 +1,17 @@
# ~/.config/sway/config.d/50-launchers.conf
# Launcher keybinds. The base $menu var in config is the drun launcher.
# These add run mode, window switcher, and the powermenu.
# $mod+grave already used by foot dropdown — don't clobber.
# $mod+Tab already used by workspace back_and_forth — don't clobber.
# $mod+slash is the cheatsheet — keep it.
# Exec ad-hoc command (not in $PATH discovery)
bindsym $mod+F12 exec wofi --show run --prompt "exec"
# Sway window switcher (replaces `swayr`-style overview)
bindsym $mod+F11 exec wofi --show window
# Power menu — dmenu mode driven by wofi-powermenu script.
# wofi has no custom-mode feature; the script does the dispatch.
bindsym $mod+Escape exec $HOME/.local/bin/wofi-powermenu

34
dot_config/sway/lock-fancy.sh Executable file
View file

@ -0,0 +1,34 @@
#!/bin/bash
# lock-fancy.sh — fancy swaylock: snapshot the screen, dim+blur it, and use as background.
# grim + ffmpeg (both already installed). No swaylock-effects overlay needed.
set -e
TMP=/tmp/lock-bg-483833.png
DIM=/tmp/lock-bg-dim-483833.png
# Snapshot the current screen
grim -t png "$TMP" 2>/dev/null
# Dim + blur via ffmpeg (much faster than ImageMagick)
if [ -f "$TMP" ] && command -v ffmpeg >/dev/null; then
ffmpeg -loglevel error -y -i "$TMP" -vf "boxblur=12:1,eq=brightness=-0.30:saturation=0.6" -frames:v 1 "$DIM"
IMG="$DIM"
else
# Fallback to the regular wallpaper
IMG="$HOME/.local/share/wallpapers/teach-invaders-gruvbox.png"
fi
# Launch swaylock
swaylock -f -i "$IMG" \
--indicator-radius 100 \
--indicator-thickness 6 \
--ring-color ebdbb2 \
--inside-color 1d202180 \
--key-hl-color fe8019 \
--bs-hl-color fb4934 \
--text-color ebdbb2 \
--separator-color 00000000 \
--fade-in 0.3
rm -f "$TMP" "$DIM" 2>/dev/null

View file

@ -0,0 +1,58 @@
#!/bin/bash
# toggle-dropdown.sh — toggle the dropdown foot terminal
# Writes debug info to /tmp/sway-f12.log so we can diagnose from SSH
LOG=/tmp/sway-f12.log
echo "=== $(date) F12/Super+grave pressed ===" >> "$LOG"
echo "WAYLAND_DISPLAY=$WAYLAND_DISPLAY" >> "$LOG"
echo "USER=$USER XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" >> "$LOG"
# Find the sway socket
SOCK=$(ls -t /run/user/$(id -u)/sway-ipc.*.sock 2>/dev/null | head -1)
echo "SOCK=$SOCK" >> "$LOG"
if [ -z "$SOCK" ]; then
echo "ERROR: no sway socket found" >> "$LOG"
notify-send -t 2000 "sway: no IPC socket" 2>>"$LOG" || true
exit 1
fi
# Check if a foot window is in the scratchpad (hidden)
HIDDEN=$(SWAYSOCK=$SOCK swaymsg -t get_tree 2>>"$LOG" | python3 -c "
import sys, json
try:
tree = json.load(sys.stdin)
def find_app(node, app_id):
if node.get('app_id') == app_id and node.get('name', '').startswith('__i3'):
return node
for c in node.get('nodes', []) + node.get('floating_nodes', []):
r = find_app(c, app_id)
if r is not None:
return r
return None
dd = find_app(tree, 'foot')
print('hidden' if dd is not None else 'none')
except Exception as e:
print(f'err:{e}')
" 2>>"$LOG")
echo "HIDDEN=$HIDDEN" >> "$LOG"
if [ "$HIDDEN" = "hidden" ]; then
SWAYSOCK=$SOCK swaymsg '[app_id="foot"] scratchpad show' >>"$LOG" 2>&1
echo "toggled: scratchpad show" >> "$LOG"
else
# Spawn a new foot. Pass through WAYLAND_DISPLAY explicitly.
if [ -n "$WAYLAND_DISPLAY" ]; then
WAYLAND_DISPLAY=$WAYLAND_DISPLAY foot >>"$LOG" 2>&1 &
else
# try to find wayland-1 socket
if [ -S "/run/user/$(id -u)/wayland-1" ]; then
WAYLAND_DISPLAY=wayland-1 foot >>"$LOG" 2>&1 &
else
foot >>"$LOG" 2>&1 &
fi
fi
echo "spawned foot" >> "$LOG"
fi
echo "=== done ===" >> "$LOG"

29
dot_config/sway/wifi-menu.sh Executable file
View file

@ -0,0 +1,29 @@
#!/bin/bash
# wifi-menu.sh — wofi-based WiFi network picker using nmcli
# Lists available networks, connects on selection.
# Get list of networks: SSID, signal, security
SELECTED=$(nmcli -t -f SSID,SIGNAL,SECURITY device wifi list --rescan yes 2>/dev/null | \
grep -v '^$' | \
sort -t: -k2 -nr | \
awk -F: '{
sig = $2 + 0
if (sig > 75) bars = "████"
else if (sig > 50) bars = "███░"
else if (sig > 25) bars = "██░░"
else bars = "█░░░"
sec = ($3 != "" && $3 != "--") ? " 󰌾" : ""
printf "%s %s%s\n", bars, $1, sec
}' | \
wofi --dmenu --insensitive --prompt "WiFi" -W 400 -H 500 2>/dev/null)
# Exit if nothing selected
[ -z "$SELECTED" ] && exit 0
# Extract SSID (everything after the signal bars + spaces)
SSID=$(echo "$SELECTED" | sed 's/^[█░ ]* //; s/ 🔒$//')
# Connect
nmcli device wifi connect "$SSID" 2>&1 | grep -q "successfully" && \
notify-send -t 3000 "WiFi" "Connected to $SSID" || \
notify-send -t 5000 "WiFi" "Failed to connect to $SSID"

102
dot_config/waybar/config Normal file
View file

@ -0,0 +1,102 @@
{
"layer": "top",
"position": "top",
"height": 32,
"spacing": 0,
"modules-left": [
"sway/workspaces",
"sway/mode"
],
"modules-center": [
"clock",
"custom/caffeine"
],
"modules-right": [
"pulseaudio",
"battery",
"cpu",
"memory",
"tray",
"network"
],
"sway/workspaces": {
"disable-scroll": true,
"format": "{name}",
"format-icons": {
"focused": "\u25cf",
"default": "\u25cb"
}
},
"clock": {
"format": " {:%a %b %d %H:%M}",
"tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>",
"format-alt": " {:%Y-%m-%d}"
},
"pulseaudio": {
"format": "{icon} {volume}%",
"format-muted": "\udb81\udd81 muted",
"format-icons": {
"default": [
"\udb81\udd7f",
"\udb81\udd80",
"\udb81\udd7e"
]
},
"scroll-step": 5,
"on-click": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
"on-click-right": "pavucontrol"
},
"battery": {
"states": {
"good": 80,
"warning": 30,
"critical": 15
},
"format": "{icon} {capacity}%",
"format-charging": "\udb80\udc84 {capacity}%",
"format-plugged": "\udb80\udc84 {capacity}%",
"format-icons": [
"\udb80\udc8e",
"\udb80\udc7a",
"\udb80\udc7b",
"\udb80\udc7c",
"\udb80\udc7d",
"\udb80\udc7e",
"\udb80\udc7f",
"\udb80\udc80",
"\udb80\udc81",
"\udb80\udc82",
"\udb80\udc79"
]
},
"cpu": {
"format": "\udb83\udee0 {usage}%",
"tooltip": true
},
"memory": {
"format": "\udb80\udf5b {}%",
"tooltip-format": "{used:0.1f}G / {total:0.1f}G"
},
"network": {
"format-wifi": "\udb82\udd28 {essid}",
"format-ethernet": "\udb80\ude00 {ipaddr}",
"format-disconnected": "\udb82\udd2d disconnected",
"format-linked": "\udb82\udd2a {ifname}",
"tooltip-format": "{ifname} \u2014 {ipaddr}",
"on-click": "$HOME/.config/sway/wifi-menu.sh"
},
"custom/caffeine": {
"format": "{icon}",
"format-icons": {
"default": "\udb80\udd76",
"active": "\u2615"
},
"exec": "if [ -f /tmp/caffeine-inhibit ]; then echo '{\"alt\":\"active\",\"text\":\"caffeine\"}'; else echo '{\"alt\":\"default\",\"text\":\"caffeine\"}'; fi",
"exec-interval": 2,
"return-type": "json",
"on-click": "$HOME/.config/sway/caffeine.sh"
},
"tray": {
"spacing": 8
}
}

View file

@ -0,0 +1,92 @@
/* === waybar — gruvbox-material-dark-hard, polished === */
* {
font-family: "Maple Mono NF";
font-size: 14px;
min-height: 0;
padding: 0;
margin: 0;
border: none;
}
/* transparent bar so the desktop bleeds through at the edges */
window#waybar {
background-color: rgba(29, 32, 33, 0.92);
color: #ebdbb2;
border-bottom: 1px solid #3c3836;
}
window#waybar.hidden { opacity: 0.2; }
/* workspaces */
#workspaces button {
padding: 0 12px;
background: transparent;
color: #a89984;
box-shadow: inset 0 -2px transparent;
transition: all 200ms ease;
}
#workspaces button:hover {
background: #3c3836;
color: #ebdbb2;
}
#workspaces button.focused {
color: #fabd2f;
box-shadow: inset 0 -2px #fabd2f;
}
#workspaces button.urgent {
color: #fb4934;
animation: blink 1s linear infinite alternate;
}
#mode {
color: #fb4934;
background: #282828;
padding: 0 12px;
font-weight: bold;
}
#clock {
color: #ebdbb2;
font-weight: bold;
padding: 0 16px;
border-left: 1px solid #3c3836;
border-right: 1px solid #3c3836;
}
#clock:hover { color: #fabd2f; }
/* right-side modules — shared layout, individual color */
#pulseaudio, #battery, #cpu, #memory, #network, #tray, #custom-caffeine {
padding: 0 12px;
border-left: 1px solid #3c3836;
transition: all 200ms ease;
}
#pulseaudio:hover, #battery:hover, #cpu:hover, #memory:hover, #network:hover, #tray:hover, #custom-caffeine:hover {
background: #3c3836;
}
#pulseaudio { color: #b8bb26; }
#pulseaudio.muted { color: #928374; }
#battery { color: #d79921; }
#battery.charging { color: #b8bb26; }
#battery.good { color: #b8bb26; }
#battery.warning { color: #fe8019; }
#battery.critical {
color: #fb4934;
animation: blink 1s linear infinite alternate;
}
#cpu { color: #83a598; }
#memory { color: #d3869b; }
#network { color: #fabd2f; }
#network.disconnected { color: #fb4934; }
#tray { padding: 0 10px; }
#custom-caffeine { color: #a89984; }
#custom-caffeine.active { color: #fabd2f; }
#custom-caffeine.active:hover { background: #3c3836; }
#sway-workspaces { margin: 0 4px; }
@keyframes blink {
to { opacity: 0.3; }
}

19
dot_config/wofi/config Normal file
View file

@ -0,0 +1,19 @@
# ~/.config/wofi/config — gruvbox launcher defaults
# Loaded by wofi before style.css. CLI flags still win.
[window]
location=top
width=620
height=420
[prompt]
prompt=go
insensitive=true
[search]
matching=fuzzy
sort_order=default
[keybindings]
return=activate
escape=close

View file

@ -0,0 +1,12 @@
# ~/.config/wofi/powermenu.config — plain INI, not rasi
# wofi's config format is INI. There is no rasi-style `configuration { }`
# block. The icon theme comes from gsettings / dconf, not from here.
location=top
yoffset=32
width=420
height=280
prompt=power
insensitive=true
hide_scroll=true
matching=exact

57
dot_config/wofi/style.css Normal file
View file

@ -0,0 +1,57 @@
/* wofi gruvbox-material-dark-hard — extended rice */
window {
margin: 0px;
border: 2px solid #d79921;
border-radius: 10px;
background-color: #1d2021;
background-image: linear-gradient(to bottom, #1d2021, #282828);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
font-family: "Maple Mono NF";
font-size: 14px;
}
#input {
margin: 0px;
padding: 10px;
border: none;
border-radius: 10px 10px 0 0;
color: #ebdbb2;
background-color: #282828;
}
#inner-box {
margin: 0px;
border: none;
background-color: transparent;
}
#outer-box {
margin: 0px;
border: none;
background-color: transparent;
}
#scroll {
margin: 0px;
border: none;
}
#text {
margin: 0px;
padding: 6px 10px;
border: none;
color: #ebdbb2;
background-color: transparent;
}
#entry {
transition: background-color 120ms ease-out;
}
#entry:selected {
background-color: #3c3836;
}
#entry:selected #text {
color: #fabd2f;
}

View file

@ -0,0 +1,58 @@
#!/usr/bin/env bash
# =============================================================================
# run_once_40-install-sway.sh.tmpl
# Install the sway + wofi + foot + supporting tooling desktop stack.
#
# Gated on .sway (set in .chezmoi.yaml.tmpl). When false (Pis, headless
# boxes, etc.), this script exits 0 without doing anything.
#
# To opt a new box in: `touch ~/.config/chezmoi/features/sway` then
# `chezmoi apply`. To opt out: `touch ~/.config/chezmoi/features/no-sway`.
#
# Idempotent: skips if sway is already installed.
# =============================================================================
set -euo pipefail
log() { printf '\033[1;34m[sway]\033[0m %s\n' "$*"; }
# --- 0. Gate on the .sway flag ---
{{ if not .sway -}}
log "sway not enabled for this host (.sway=false). Skipping."
log "To enable: touch ~/.config/chezmoi/features/sway && chezmoi apply"
exit 0
{{ end -}}
# --- 1. Install packages ---
SWAY_PKGS=(sway wofi foot swaybg swaylock swayidle grim slurp waybar wl-clipboard)
{{ if eq .os_family "arch" -}}
# mako is in arch [extra]
SWAY_PKGS+=(mako)
{{ else if eq .os_family "debian" -}}
# mako isn't packaged on debian. Use dunst (lightweight notification daemon,
# widely compatible with sway/waybar setups).
SWAY_PKGS+=(dunst)
{{ else -}}
log "WARNING: sway packages not configured for os_family={{ .os_family }}"
{{ end -}}
log "installing sway stack: ${SWAY_PKGS[*]}"
{{ if eq .os_family "arch" -}}
sudo pacman -S --needed --noconfirm "${SWAY_PKGS[@]}"
{{ else if eq .os_family "debian" -}}
export DEBIAN_FRONTEND=noninteractive
sudo apt-get install -y --no-install-recommends "${SWAY_PKGS[@]}"
{{ end -}}
log "sway stack installed"
sway --version 2>&1 | head -1
foot --version 2>&1 | head -1
wofi --version 2>&1 | head -1
# --- 2. Mark the box so subsequent chezmoi applies know sway is enabled ---
mkdir -p ~/.config/chezmoi/features
touch ~/.config/chezmoi/features/sway
log "marked box: ~/.config/chezmoi/features/sway"
log "sway stack install complete"