Rust · 8438 bytes Raw Blame History
1 mod lua;
2
3 pub use lua::{Action, Keybind, LuaConfig, LuaState, RuleActions, WindowMatch, WindowRule};
4
5 #[derive(Debug, Clone)]
6 pub struct Config {
7 pub border_width: u32,
8 pub border_color_focused: u32,
9 pub border_color_unfocused: u32,
10 pub border_color_urgent: u32,
11 pub gap_inner: u32,
12 pub gap_outer: u32,
13 // Title bar settings
14 pub titlebar_enabled: bool,
15 pub titlebar_height: u32,
16 pub titlebar_color_focused: u32,
17 pub titlebar_color_unfocused: u32,
18 pub titlebar_text_color: u32,
19 // Behavior settings
20 pub follow_window_on_move: bool,
21 pub mouse_follows_focus: bool,
22 // Manual bar/panel reserved space (overrides struts)
23 pub bar_height: u32,
24 // Compositor visual settings (picom)
25 // These are stored for reference and potential dynamic picom config generation
26 pub corner_radius: u32,
27 pub blur_enabled: bool,
28 pub blur_method: String,
29 pub blur_strength: u32,
30 pub shadow_enabled: bool,
31 pub shadow_radius: u32,
32 pub shadow_opacity: f64,
33 pub shadow_offset_x: i32,
34 pub shadow_offset_y: i32,
35 pub opacity_focused: f64,
36 pub opacity_unfocused: f64,
37 pub fade_enabled: bool,
38 pub fade_delta: u32,
39 }
40
41 impl Config {
42 /// Generate picom.conf content from current config settings.
43 pub fn generate_picom_config(&self) -> String {
44 let blur_section = if self.blur_enabled {
45 format!(
46 r#"# Blur
47 blur-method = "{}";
48 blur-strength = {};
49 blur-background = true;
50 blur-background-frame = false;
51 blur-kern = "3x3box";
52
53 blur-background-exclude = [
54 "window_type = 'dock'",
55 "window_type = 'desktop'",
56 "window_type = 'menu'",
57 "window_type = 'dropdown_menu'",
58 "window_type = 'popup_menu'",
59 "_NET_WM_BYPASS_COMPOSITOR@:32c = 1"
60 ];"#,
61 self.blur_method, self.blur_strength
62 )
63 } else {
64 "# Blur disabled".to_string()
65 };
66
67 let shadow_section = if self.shadow_enabled {
68 format!(
69 r#"# Shadows
70 shadow = true;
71 shadow-radius = {};
72 shadow-opacity = {:.2};
73 shadow-offset-x = {};
74 shadow-offset-y = {};
75
76 shadow-exclude = [
77 "window_type = 'dock'",
78 "window_type = 'desktop'",
79 "window_type = 'menu'",
80 "window_type = 'dropdown_menu'",
81 "window_type = 'popup_menu'",
82 "window_type = 'tooltip'",
83 "_NET_WM_STATE@:32a *= '_NET_WM_STATE_FULLSCREEN'",
84 "_NET_WM_BYPASS_COMPOSITOR@:32c = 1"
85 ];"#,
86 self.shadow_radius,
87 self.shadow_opacity,
88 self.shadow_offset_x,
89 self.shadow_offset_y
90 )
91 } else {
92 "# Shadows disabled\nshadow = false;".to_string()
93 };
94
95 let fade_section = if self.fade_enabled {
96 format!(
97 r#"# Fading / Animations
98 fading = true;
99 fade-in-step = 0.028;
100 fade-out-step = 0.03;
101 fade-delta = {};
102
103 no-fading-destroyed-argb = true;
104
105 fade-exclude = [
106 "window_type = 'menu'",
107 "window_type = 'dropdown_menu'",
108 "window_type = 'popup_menu'"
109 ];"#,
110 self.fade_delta
111 )
112 } else {
113 "# Fading disabled\nfading = false;".to_string()
114 };
115
116 let opacity_section = if self.opacity_unfocused < 1.0 {
117 format!(
118 r#"# Focus Opacity
119 active-opacity = {:.2};
120 inactive-opacity = {:.2};
121 frame-opacity = 1.0;"#,
122 self.opacity_focused, self.opacity_unfocused
123 )
124 } else {
125 "# Focus opacity: all windows fully opaque".to_string()
126 };
127
128 format!(
129 r#"# picom.conf - Auto-generated by gar window manager
130 # DO NOT EDIT MANUALLY - changes will be overwritten on reload
131 # Edit ~/.config/gar/init.lua instead and reload with Mod+Shift+R
132
133 # Backend Configuration
134 backend = "glx";
135 vsync = true;
136 use-ewmh-active-win = true;
137 glx-no-stencil = true;
138 glx-no-rebind-pixmap = true;
139
140 # Rounded Corners
141 corner-radius = {};
142
143 rounded-corners-exclude = [
144 "window_type = 'dock'",
145 "window_type = 'desktop'",
146 "window_type = 'tooltip'",
147 "window_type = 'menu'",
148 "window_type = 'dropdown_menu'",
149 "window_type = 'popup_menu'",
150 "_NET_WM_STATE@:32a *= '_NET_WM_STATE_FULLSCREEN'"
151 ];
152
153 {}
154
155 {}
156
157 {}
158
159 {}
160
161 # Window Type Settings
162 wintypes:
163 {{
164 tooltip = {{
165 fade = true;
166 shadow = false;
167 opacity = 0.95;
168 focus = true;
169 blur-background = false;
170 }};
171 dock = {{
172 shadow = false;
173 clip-shadow-above = true;
174 }};
175 dnd = {{
176 shadow = false;
177 }};
178 popup_menu = {{
179 opacity = 0.95;
180 shadow = false;
181 }};
182 dropdown_menu = {{
183 opacity = 0.95;
184 shadow = false;
185 }};
186 }};
187 "#,
188 self.corner_radius,
189 blur_section,
190 shadow_section,
191 fade_section,
192 opacity_section
193 )
194 }
195
196 /// Write picom config to ~/.config/gar/picom.conf and signal picom to reload.
197 pub fn write_picom_config(&self) -> std::io::Result<()> {
198 let config_dir = dirs::config_dir()
199 .ok_or_else(|| std::io::Error::new(
200 std::io::ErrorKind::NotFound,
201 "Could not determine config directory"
202 ))?
203 .join("gar");
204
205 // Ensure directory exists
206 std::fs::create_dir_all(&config_dir)?;
207
208 let config_path = config_dir.join("picom.conf");
209 let content = self.generate_picom_config();
210
211 std::fs::write(&config_path, &content)?;
212 tracing::info!("Generated picom config at {:?}", config_path);
213
214 // Signal picom to reload
215 Self::reload_picom();
216
217 Ok(())
218 }
219
220 /// Restart picom to apply new configuration.
221 /// Picom doesn't support config reload via signal, so we kill and restart it.
222 fn reload_picom() {
223 use std::process::Command;
224 use std::thread;
225 use std::time::Duration;
226
227 // Kill existing picom
228 match Command::new("pkill").arg("picom").status() {
229 Ok(status) if status.success() => {
230 tracing::info!("Killed picom for restart");
231 }
232 Ok(_) => {
233 tracing::debug!("picom was not running");
234 }
235 Err(e) => {
236 tracing::warn!("Failed to kill picom: {}", e);
237 return;
238 }
239 }
240
241 // Brief pause to let picom fully exit
242 thread::sleep(Duration::from_millis(100));
243
244 // Restart picom with the new config
245 let config_path = dirs::config_dir()
246 .map(|d| d.join("gar").join("picom.conf"))
247 .unwrap_or_default();
248
249 match Command::new("picom")
250 .args(["-b", "--config"])
251 .arg(&config_path)
252 .spawn()
253 {
254 Ok(_) => {
255 tracing::info!("Restarted picom with config {:?}", config_path);
256 }
257 Err(e) => {
258 tracing::warn!("Failed to restart picom: {}", e);
259 }
260 }
261 }
262 }
263
264 impl Default for Config {
265 fn default() -> Self {
266 Self {
267 border_width: 2,
268 border_color_focused: 0x5294e2,
269 border_color_unfocused: 0x2d2d2d,
270 border_color_urgent: 0xff5555, // Red for urgent windows
271 gap_inner: 0,
272 gap_outer: 0,
273 // Title bars disabled by default
274 titlebar_enabled: false,
275 titlebar_height: 20,
276 titlebar_color_focused: 0x3d3d3d,
277 titlebar_color_unfocused: 0x2d2d2d,
278 titlebar_text_color: 0xffffff,
279 // Behavior: follow window when moving to another workspace
280 follow_window_on_move: false,
281 // Behavior: warp mouse pointer to center of focused window
282 mouse_follows_focus: false,
283 // Manual bar height (0 = use struts from dock windows)
284 bar_height: 0,
285 // Compositor settings (picom) - matching picom.conf defaults
286 corner_radius: 12,
287 blur_enabled: true,
288 blur_method: "dual_kawase".to_string(),
289 blur_strength: 5,
290 shadow_enabled: true,
291 shadow_radius: 12,
292 shadow_opacity: 0.75,
293 shadow_offset_x: -7,
294 shadow_offset_y: -7,
295 opacity_focused: 1.0,
296 opacity_unfocused: 1.0, // No unfocused dimming by default
297 fade_enabled: true,
298 fade_delta: 10,
299 }
300 }
301 }
302