@@ -0,0 +1,373 @@ |
| 1 | +-- gar default configuration |
| 2 | +-- Copy to ~/.config/gar/init.lua to customize |
| 3 | + |
| 4 | +-- Autostart applications (only run once per session) |
| 5 | +-- gar.exec_once("random-bg cold") -- Pick random wallpaper from a themed directory |
| 6 | +-- garbg daemon is managed by systemd (garbg.service) with internal X11 retry |
| 7 | +gar.exec_once("garbg set ~/Pictures/background/cold/ --random") |
| 8 | + |
| 9 | +-- Screen locker daemon |
| 10 | +gar.exec_once("garlock daemon") |
| 11 | + |
| 12 | +-- Clipboard manager daemon |
| 13 | +gar.exec_once("garclip daemon --foreground") |
| 14 | + |
| 15 | +-- Uncomment the ones you want: |
| 16 | +-- gar.exec_once("picom") -- Compositor (for transparency/shadows) |
| 17 | +-- gar.exec_once("dunst") -- Notification daemon |
| 18 | +-- gar.exec_once("nm-applet") -- NetworkManager tray icon |
| 19 | +-- gar.exec_once("blueman-applet") -- Bluetooth tray icon |
| 20 | +-- gar.exec_once("xss-lock -- i3lock -c 000000") -- Auto-lock on suspend |
| 21 | + |
| 22 | +-- Appearance |
| 23 | +gar.set("border_width", 2) |
| 24 | +gar.set("border_color_focused", "#5294e2") |
| 25 | +gar.set("border_color_unfocused", "#2d2d2d") |
| 26 | +gar.set("gap_inner", 12) |
| 27 | +gar.set("gap_outer", 16) |
| 28 | + |
| 29 | +-- Visual Effects (picom compositor) |
| 30 | +-- gar auto-generates ~/.config/gar/picom.conf from these settings |
| 31 | +-- Changes take effect on reload (Mod+Shift+R) - picom is signaled automatically |
| 32 | +gar.set("corner_radius", 18) -- 0 = square corners |
| 33 | +gar.set("blur_enabled", true) |
| 34 | +gar.set("blur_method", "dual_kawase") -- "gaussian", "dual_kawase", "box" |
| 35 | +gar.set("blur_strength", 5) -- 1-20 for dual_kawase |
| 36 | +gar.set("shadow_enabled", true) |
| 37 | +gar.set("shadow_radius", 12) |
| 38 | +gar.set("shadow_opacity", 0.75) |
| 39 | +gar.set("shadow_offset_x", -7) |
| 40 | +gar.set("shadow_offset_y", -7) |
| 41 | +gar.set("opacity_focused", 0.94) |
| 42 | +gar.set("opacity_unfocused", 0.6) -- Set to 0.9 to dim unfocused windows |
| 43 | +gar.set("fade_enabled", true) |
| 44 | +gar.set("fade_delta", 3) |
| 45 | + |
| 46 | +-- Title bars (disabled by default) |
| 47 | +-- gar.set("titlebar_enabled", true) |
| 48 | +-- gar.set("titlebar_height", 20) |
| 49 | +-- gar.set("titlebar_color_focused", "#3d3d3d") |
| 50 | +-- gar.set("titlebar_color_unfocused", "#2d2d2d") |
| 51 | +-- gar.set("titlebar_text_color", "#ffffff") |
| 52 | + |
| 53 | +-- Border gradients (purple to blue theme) |
| 54 | +-- direction options: "vertical", "horizontal", "diagonal" |
| 55 | +gar.set("border_gradient_enabled", true) |
| 56 | +gar.set("border_gradient_start_focused", "#9b59b6") -- Purple |
| 57 | +gar.set("border_gradient_end_focused", "#3498db") -- Blue |
| 58 | +gar.set("border_gradient_start_unfocused", "#4a235a") -- Dark purple |
| 59 | +gar.set("border_gradient_end_unfocused", "#1a5276") -- Dark blue |
| 60 | +gar.set("border_gradient_direction", "diagonal") |
| 61 | + |
| 62 | +-- Animations (picom v12+) |
| 63 | +-- open options: "slide-in", "fly-in", "appear", "none" |
| 64 | +-- close options: "slide-out", "fly-out", "disappear", "none" |
| 65 | +gar.set("animation_open", "appear") |
| 66 | +gar.set("animation_close", "disappear") |
| 67 | +gar.set("animation_duration", 0.15) -- seconds |
| 68 | + |
| 69 | +-- Custom GLSL shader (picom, requires GLX backend) |
| 70 | +-- Bundled shaders: ~/.config/gar/shaders/focused-glow.glsl, dim-unfocused.glsl |
| 71 | +-- gar.set("picom_shader", "~/.config/gar/shaders/focused-glow.glsl") |
| 72 | + |
| 73 | +-- Per-window picom rules |
| 74 | +-- NOTE: When using rules, picom ignores old-style options (inactive-opacity, shadow-exclude, etc.) |
| 75 | +-- Only enable rules if you want full control via rules-based config |
| 76 | +-- Available rule options: corner_radius, opacity, shadow, blur_background, shader |
| 77 | +-- Match syntax: class_g = 'ClassName', class_i = 'instance', name = 'title', window_type = 'type' |
| 78 | +-- gar.picom_rule({ |
| 79 | +-- match = "class_g = 'Alacritty'", |
| 80 | +-- blur_background = true, |
| 81 | +-- opacity = 0.92, |
| 82 | +-- }) |
| 83 | +-- gar.picom_rule({ |
| 84 | +-- match = "class_g = 'firefox'", |
| 85 | +-- corner_radius = 8, |
| 86 | +-- }) |
| 87 | + |
| 88 | +-- garbar status bar (native gar integration) |
| 89 | +-- When this table is present, gar automatically spawns and manages garbar |
| 90 | +gar.bar = { |
| 91 | + height = 32, |
| 92 | + position = "top", |
| 93 | + background = "#1a1a1a", |
| 94 | + foreground = "#ffffff", |
| 95 | + opacity = 1.0, |
| 96 | + fonts = { |
| 97 | + "JetBrainsMono Nerd Font:size=10", |
| 98 | + "Symbols Nerd Font:size=10", |
| 99 | + }, |
| 100 | + padding = { left = 8, right = 16, top = 0, bottom = 0 }, |
| 101 | + |
| 102 | + -- Module layout |
| 103 | + modules_left = { "workspaces", "window_title" }, |
| 104 | + modules_center = {}, |
| 105 | + modules_right = { "almanta", "filesystem", "memory", "cpu", "battery", "wlan", "volume", "datetime" }, |
| 106 | + |
| 107 | + -- Module configurations |
| 108 | + modules = { |
| 109 | + workspaces = { |
| 110 | + font_size = 11, |
| 111 | + focused = { |
| 112 | + foreground = "#ffffff", |
| 113 | + background = "transparent", |
| 114 | + underline = { width = 2, color = "#33ccff" }, |
| 115 | + }, |
| 116 | + unfocused = { |
| 117 | + foreground = "#666666", |
| 118 | + background = "transparent", |
| 119 | + }, |
| 120 | + urgent = { |
| 121 | + foreground = "#ffffff", |
| 122 | + background = "#ff5555", |
| 123 | + }, |
| 124 | + }, |
| 125 | + window_title = { |
| 126 | + max_length = 50, |
| 127 | + empty_text = "Desktop", |
| 128 | + }, |
| 129 | + datetime = { |
| 130 | + format = " %a %b %d %H:%M", |
| 131 | + }, |
| 132 | + cpu = { |
| 133 | + format = " {usage}%", |
| 134 | + }, |
| 135 | + memory = { |
| 136 | + format = " {percent}%", |
| 137 | + }, |
| 138 | + battery = { |
| 139 | + device = "auto", |
| 140 | + format_charging = " {percent}%", |
| 141 | + format_discharging = " {percent}%", |
| 142 | + format_full = " Full", |
| 143 | + }, |
| 144 | + -- Script modules (custom commands) |
| 145 | + script = { |
| 146 | + almanta = { |
| 147 | + exec = [[ |
| 148 | +HOST="espadon@almanta" |
| 149 | +SSH_OPTS="-T -o BatchMode=yes -o ConnectTimeout=2 -o ConnectionAttempts=1" |
| 150 | +if ! ssh $SSH_OPTS "$HOST" "echo ok" >/dev/null 2>&1; then |
| 151 | + echo "almanta: down" |
| 152 | + exit 0 |
| 153 | +fi |
| 154 | +load=$(ssh $SSH_OPTS "$HOST" "cut -d' ' -f1 /proc/loadavg" 2>/dev/null || echo "?") |
| 155 | +mem=$(ssh $SSH_OPTS "$HOST" "free | grep '^Mem:' | awk '{printf \"%.0f\", (\$3/\$2)*100}'" 2>/dev/null || echo "?") |
| 156 | +disk=$(ssh $SSH_OPTS "$HOST" "df / | tail -1 | awk '{print \$5}' | tr -d '%'" 2>/dev/null || echo "?") |
| 157 | +echo "almanta L:$load M:$mem% D:$disk%" |
| 158 | +]], |
| 159 | + interval = 30, |
| 160 | + }, |
| 161 | + filesystem = { |
| 162 | + exec = [[df -h / | awk 'NR==2 {print " " $4}']], |
| 163 | + interval = 60, |
| 164 | + }, |
| 165 | + wlan = { |
| 166 | + exec = [[ |
| 167 | +IFACE="wlp1s0f0" |
| 168 | +if [ -d "/sys/class/net/$IFACE" ]; then |
| 169 | + STATE=$(cat /sys/class/net/$IFACE/operstate 2>/dev/null) |
| 170 | + if [ "$STATE" = "up" ]; then |
| 171 | + ESSID=$(iwgetid -r 2>/dev/null || echo "") |
| 172 | + IP=$(ip -4 addr show $IFACE 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1) |
| 173 | + if [ -n "$ESSID" ]; then |
| 174 | + echo " $ESSID $IP" |
| 175 | + else |
| 176 | + echo " connected" |
| 177 | + fi |
| 178 | + else |
| 179 | + echo " offline" |
| 180 | + fi |
| 181 | +else |
| 182 | + echo " N/A" |
| 183 | +fi |
| 184 | +]], |
| 185 | + interval = 5, |
| 186 | + }, |
| 187 | + volume = { |
| 188 | + exec = [[ |
| 189 | +if command -v pamixer >/dev/null 2>&1; then |
| 190 | + if pamixer --get-mute | grep -q true; then |
| 191 | + echo " muted" |
| 192 | + else |
| 193 | + VOL=$(pamixer --get-volume) |
| 194 | + echo " $VOL%" |
| 195 | + fi |
| 196 | +elif command -v pactl >/dev/null 2>&1; then |
| 197 | + VOL=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -oP '\d+%' | head -1) |
| 198 | + MUTE=$(pactl get-sink-mute @DEFAULT_SINK@ | grep -oP 'yes|no') |
| 199 | + if [ "$MUTE" = "yes" ]; then |
| 200 | + echo " muted" |
| 201 | + else |
| 202 | + echo " $VOL" |
| 203 | + fi |
| 204 | +else |
| 205 | + echo " N/A" |
| 206 | +fi |
| 207 | +]], |
| 208 | + interval = 1, |
| 209 | + }, |
| 210 | + }, |
| 211 | + }, |
| 212 | +} |
| 213 | + |
| 214 | +-- Behavior |
| 215 | +gar.set("follow_window_on_move", true) -- Follow window when using Mod+Shift+number |
| 216 | + |
| 217 | +-- Mod key: "mod" = Super/Win, "alt" = Alt |
| 218 | +-- Use "mod" for real X session, "alt" for nested testing (Xephyr) |
| 219 | +local mod = "mod" |
| 220 | + |
| 221 | +-- Terminal |
| 222 | +gar.bind(mod .. "+Return", function() |
| 223 | + gar.exec("alacritty || kitty || foot || xterm") |
| 224 | +end) |
| 225 | + |
| 226 | +-- Close window |
| 227 | +gar.bind(mod .. "+q", gar.close_window) |
| 228 | + |
| 229 | +-- Reload config |
| 230 | +gar.bind(mod .. "+shift+r", gar.reload) |
| 231 | + |
| 232 | +-- Exit gar (return to display manager) |
| 233 | +gar.bind(mod .. "+shift+e", gar.exit) |
| 234 | + |
| 235 | +-- Focus navigation (arrow keys) |
| 236 | +gar.bind(mod .. "+Left", gar.focus("left")) |
| 237 | +gar.bind(mod .. "+Right", gar.focus("right")) |
| 238 | +gar.bind(mod .. "+Up", gar.focus("up")) |
| 239 | +gar.bind(mod .. "+Down", gar.focus("down")) |
| 240 | + |
| 241 | +-- Focus navigation (vim keys) |
| 242 | +gar.bind(mod .. "+h", gar.focus("left")) |
| 243 | +gar.bind(mod .. "+l", gar.focus("right")) |
| 244 | +gar.bind(mod .. "+k", gar.focus("up")) |
| 245 | +gar.bind(mod .. "+j", gar.focus("down")) |
| 246 | + |
| 247 | +-- Swap windows |
| 248 | +gar.bind(mod .. "+shift+Left", gar.swap("left")) |
| 249 | +gar.bind(mod .. "+shift+Right", gar.swap("right")) |
| 250 | +gar.bind(mod .. "+shift+Up", gar.swap("up")) |
| 251 | +gar.bind(mod .. "+shift+Down", gar.swap("down")) |
| 252 | + |
| 253 | +gar.bind(mod .. "+shift+h", gar.swap("left")) |
| 254 | +gar.bind(mod .. "+shift+l", gar.swap("right")) |
| 255 | +gar.bind(mod .. "+shift+k", gar.swap("up")) |
| 256 | +gar.bind(mod .. "+shift+j", gar.swap("down")) |
| 257 | + |
| 258 | +-- Resize |
| 259 | +gar.bind(mod .. "+ctrl+Left", gar.resize("left", 0.05)) |
| 260 | +gar.bind(mod .. "+ctrl+Right", gar.resize("right", 0.05)) |
| 261 | +gar.bind(mod .. "+ctrl+Up", gar.resize("up", 0.05)) |
| 262 | +gar.bind(mod .. "+ctrl+Down", gar.resize("down", 0.05)) |
| 263 | + |
| 264 | +gar.bind(mod .. "+ctrl+h", gar.resize("left", 0.05)) |
| 265 | +gar.bind(mod .. "+ctrl+l", gar.resize("right", 0.05)) |
| 266 | +gar.bind(mod .. "+ctrl+k", gar.resize("up", 0.05)) |
| 267 | +gar.bind(mod .. "+ctrl+j", gar.resize("down", 0.05)) |
| 268 | + |
| 269 | +-- Equalize splits |
| 270 | +gar.bind(mod .. "+e", gar.equalize) |
| 271 | + |
| 272 | +-- Toggle floating |
| 273 | +gar.bind(mod .. "+f", gar.toggle_floating) |
| 274 | + |
| 275 | +-- Toggle fullscreen |
| 276 | +gar.bind(mod .. "+shift+f", gar.toggle_fullscreen) |
| 277 | + |
| 278 | +-- Cycle through floating windows |
| 279 | +gar.bind(mod .. "+grave", gar.cycle_floating) -- Mod+` (backtick) |
| 280 | + |
| 281 | +-- Workspaces |
| 282 | +for i = 1, 9 do |
| 283 | + gar.bind(mod .. "+" .. i, gar.workspace(i)) |
| 284 | + gar.bind(mod .. "+shift+" .. i, gar.move_to_workspace(i)) |
| 285 | +end |
| 286 | +gar.bind(mod .. "+0", gar.workspace(10)) |
| 287 | +gar.bind(mod .. "+shift+0", gar.move_to_workspace(10)) |
| 288 | + |
| 289 | +-- Multi-monitor (comma/period = prev/next) |
| 290 | +gar.bind(mod .. "+comma", gar.focus_monitor("prev")) |
| 291 | +gar.bind(mod .. "+period", gar.focus_monitor("next")) |
| 292 | +gar.bind(mod .. "+shift+comma", gar.move_to_monitor("prev")) |
| 293 | +gar.bind(mod .. "+shift+period", gar.move_to_monitor("next")) |
| 294 | + |
| 295 | +-- Workspace cycling |
| 296 | +gar.bind(mod .. "+Tab", gar.workspace_next()) |
| 297 | +gar.bind(mod .. "+shift+Tab", gar.workspace_prev()) |
| 298 | + |
| 299 | +-- Launchers (garlaunch) |
| 300 | +local garlaunch = os.getenv("HOME") .. "/.cargo/bin/garlaunch" |
| 301 | +gar.bind(mod .. "+space", function() |
| 302 | + gar.exec(garlaunch .. " --mode drun 2>/tmp/garlaunch-debug.log") |
| 303 | +end) |
| 304 | +gar.bind(mod .. "+shift+space", function() |
| 305 | + gar.exec(garlaunch .. " --mode window 2>/tmp/garlaunch-debug.log") |
| 306 | +end) |
| 307 | +gar.bind(mod .. "+r", function() |
| 308 | + gar.exec(garlaunch .. " --mode run 2>/tmp/garlaunch-debug.log") |
| 309 | +end) |
| 310 | + |
| 311 | +-- dmenu alternative (if rofi not available) |
| 312 | +gar.bind(mod .. "+p", function() |
| 313 | + gar.exec("dmenu_run") |
| 314 | +end) |
| 315 | + |
| 316 | +-- Screenshot (requires scrot or maim) |
| 317 | +gar.bind("Print", function() |
| 318 | + gar.exec("scrot -e 'mv $f ~/Pictures/' || maim ~/Pictures/screenshot-$(date +%s).png") |
| 319 | +end) |
| 320 | +gar.bind(mod .. "+Print", function() |
| 321 | + gar.exec("scrot -s -e 'mv $f ~/Pictures/' || maim -s ~/Pictures/screenshot-$(date +%s).png") |
| 322 | +end) |
| 323 | + |
| 324 | +-- Screenshot (garshot) |
| 325 | +gar.bind("ctrl+shift+3", function() |
| 326 | + gar.exec("garshot select --annotate") -- Region selection with annotation editor |
| 327 | +end) |
| 328 | +gar.bind("ctrl+shift+4", function() |
| 329 | + gar.exec("garshot select") -- Interactive region selection with blur overlay |
| 330 | +end) |
| 331 | +gar.bind("ctrl+shift+5", function() |
| 332 | + gar.exec("garshot screen") -- Full screen capture |
| 333 | +end) |
| 334 | +gar.bind("ctrl+shift+6", function() |
| 335 | + gar.exec("garshot window") -- Active window capture |
| 336 | +end) |
| 337 | + |
| 338 | +-- Slideshow mode (Mod+Shift+P to avoid conflict with dmenu on Mod+P) |
| 339 | +gar.bind(mod .. "+shift+p", function() gar.exec("garbg set ~/Pictures/background/cold/ --random --interval 2m") end) |
| 340 | + |
| 341 | +-- Lock screen (requires i3lock, swaylock, or slock) |
| 342 | +gar.bind(mod .. "+Escape", function() |
| 343 | + gar.exec("i3lock -c 000000 || swaylock -c 000000 || slock") |
| 344 | +end) |
| 345 | + |
| 346 | +-- Lock screen with garlock (via daemon) |
| 347 | +gar.bind(mod .. "+shift+q", function() |
| 348 | + gar.exec("garlock lock") |
| 349 | +end) |
| 350 | + |
| 351 | +-- Volume controls (requires pactl/pamixer) |
| 352 | +gar.bind("XF86AudioRaiseVolume", function() |
| 353 | + gar.exec("pactl set-sink-volume @DEFAULT_SINK@ +5% || pamixer -i 5") |
| 354 | +end) |
| 355 | +gar.bind("XF86AudioLowerVolume", function() |
| 356 | + gar.exec("pactl set-sink-volume @DEFAULT_SINK@ -5% || pamixer -d 5") |
| 357 | +end) |
| 358 | +gar.bind("XF86AudioMute", function() |
| 359 | + gar.exec("pactl set-sink-mute @DEFAULT_SINK@ toggle || pamixer -t") |
| 360 | +end) |
| 361 | + |
| 362 | +-- Brightness controls (requires brightnessctl or light) |
| 363 | +gar.bind("XF86MonBrightnessUp", function() |
| 364 | + gar.exec("brightnessctl set +10% || light -A 10") |
| 365 | +end) |
| 366 | +gar.bind("XF86MonBrightnessDown", function() |
| 367 | + gar.exec("brightnessctl set 10%- || light -U 10") |
| 368 | +end) |
| 369 | + |
| 370 | +-- Clipboard history (garclip) |
| 371 | +gar.bind(mod .. "+shift+v", function() |
| 372 | + gar.exec("garclip-picker") |
| 373 | +end) |