Lua · 12393 bytes Raw Blame History
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)