gardesk/garchomp / b05b49f

Browse files

add multi-monitor support via RandR

Authored by espadonne
SHA
b05b49f4563c4a3214579716de60e134c6ffa9c2
Parents
da7a230
Tree
4c7e75e

5 changed files

StatusFile+-
M garchomp-ipc/src/lib.rs 13 0
M garchomp/src/compositor/mod.rs 13 3
M garchomp/src/main.rs 13 0
M garchomp/src/x11/connection.rs 117 0
M garchomp/src/x11/mod.rs 1 1
garchomp-ipc/src/lib.rsmodified
@@ -55,6 +55,19 @@ pub struct CompositorStatus {
55
     pub current_workspace: usize,
55
     pub current_workspace: usize,
56
     pub effects_enabled: EffectsStatus,
56
     pub effects_enabled: EffectsStatus,
57
     pub connected_to_gar: bool,
57
     pub connected_to_gar: bool,
58
+    #[serde(default)]
59
+    pub monitors: Vec<MonitorStatus>,
60
+}
61
+
62
+/// Information about a monitor.
63
+#[derive(Debug, Clone, Serialize, Deserialize)]
64
+pub struct MonitorStatus {
65
+    pub name: String,
66
+    pub x: i16,
67
+    pub y: i16,
68
+    pub width: u16,
69
+    pub height: u16,
70
+    pub primary: bool,
58
 }
71
 }
59
 
72
 
60
 /// Status of compositor effects.
73
 /// Status of compositor effects.
garchomp/src/compositor/mod.rsmodified
@@ -14,7 +14,7 @@ pub use workspace::{TransitionDirection, WorkspaceState, WorkspaceTransition};
14
 use crate::config::LuaConfig;
14
 use crate::config::LuaConfig;
15
 use crate::ipc::GarConnection;
15
 use crate::ipc::GarConnection;
16
 use crate::render::{GpuError, HdrConfig, Renderer, WindowRenderInfo};
16
 use crate::render::{GpuError, HdrConfig, Renderer, WindowRenderInfo};
17
-use crate::x11::{CompositeExt, Connection};
17
+use crate::x11::{CompositeExt, Connection, MonitorInfo};
18
 use garchomp_ipc::GarEvent;
18
 use garchomp_ipc::GarEvent;
19
 use std::collections::HashMap;
19
 use std::collections::HashMap;
20
 use thiserror::Error;
20
 use thiserror::Error;
@@ -64,6 +64,8 @@ pub struct Compositor {
64
     lua_config: Option<LuaConfig>,
64
     lua_config: Option<LuaConfig>,
65
     /// Root window background pixmap (wallpaper).
65
     /// Root window background pixmap (wallpaper).
66
     root_pixmap: Option<u32>,
66
     root_pixmap: Option<u32>,
67
+    /// Monitor configuration from RandR.
68
+    pub monitors: Vec<MonitorInfo>,
67
 }
69
 }
68
 
70
 
69
 impl Compositor {
71
 impl Compositor {
@@ -83,9 +85,9 @@ impl Compositor {
83
         let width = screen.width_in_pixels as u32;
85
         let width = screen.width_in_pixels as u32;
84
         let height = screen.height_in_pixels as u32;
86
         let height = screen.height_in_pixels as u32;
85
 
87
 
86
-        // Initialize GPU renderer
88
+        // Initialize GPU renderer (vsync will be configured after loading Lua config)
87
         tracing::info!("Initializing GPU renderer for {}x{} surface", width, height);
89
         tracing::info!("Initializing GPU renderer for {}x{} surface", width, height);
88
-        let renderer = Renderer::new(overlay, width, height).await?;
90
+        let renderer = Renderer::new(overlay, width, height, crate::render::VSync::default()).await?;
89
         tracing::info!("GPU renderer initialized");
91
         tracing::info!("GPU renderer initialized");
90
 
92
 
91
         // Subscribe to events on root window
93
         // Subscribe to events on root window
@@ -134,6 +136,9 @@ impl Compositor {
134
             tracing::info!("Found root pixmap: {:#x}", pix);
136
             tracing::info!("Found root pixmap: {:#x}", pix);
135
         }
137
         }
136
 
138
 
139
+        // Query monitor configuration
140
+        let monitors = conn.get_monitors().unwrap_or_default();
141
+
137
         let mut compositor = Self {
142
         let mut compositor = Self {
138
             conn,
143
             conn,
139
             overlay,
144
             overlay,
@@ -147,11 +152,16 @@ impl Compositor {
147
             gar,
152
             gar,
148
             lua_config,
153
             lua_config,
149
             root_pixmap,
154
             root_pixmap,
155
+            monitors,
150
         };
156
         };
151
 
157
 
152
         // Get initial active window
158
         // Get initial active window
153
         compositor.active_window = compositor.conn.get_active_window();
159
         compositor.active_window = compositor.conn.get_active_window();
154
 
160
 
161
+        // Apply VSync setting from config
162
+        compositor.renderer.set_vsync(compositor.effects.vsync);
163
+        tracing::info!("VSync mode: {:?}", compositor.effects.vsync);
164
+
155
         // Apply HDR config if enabled
165
         // Apply HDR config if enabled
156
         if let Some(ref lua) = compositor.lua_config {
166
         if let Some(ref lua) = compositor.lua_config {
157
             let hdr_config = lua.get_hdr_config();
167
             let hdr_config = lua.get_hdr_config();
garchomp/src/main.rsmodified
@@ -293,6 +293,18 @@ fn handle_ipc_request(compositor: &mut compositor::Compositor, request: ipc::Cli
293
             }
293
             }
294
         }
294
         }
295
         Request::Status => {
295
         Request::Status => {
296
+            let monitors: Vec<garchomp_ipc::MonitorStatus> = compositor.monitors
297
+                .iter()
298
+                .map(|m| garchomp_ipc::MonitorStatus {
299
+                    name: m.name.clone(),
300
+                    x: m.x,
301
+                    y: m.y,
302
+                    width: m.width,
303
+                    height: m.height,
304
+                    primary: m.primary,
305
+                })
306
+                .collect();
307
+
296
             Response::Status(garchomp_ipc::CompositorStatus {
308
             Response::Status(garchomp_ipc::CompositorStatus {
297
                 version: garchomp_ipc::PROTOCOL_VERSION,
309
                 version: garchomp_ipc::PROTOCOL_VERSION,
298
                 window_count: compositor.windows.len(),
310
                 window_count: compositor.windows.len(),
@@ -304,6 +316,7 @@ fn handle_ipc_request(compositor: &mut compositor::Compositor, request: ipc::Cli
304
                     blur_strength: compositor.effects.blur_strength,
316
                     blur_strength: compositor.effects.blur_strength,
305
                 },
317
                 },
306
                 connected_to_gar: compositor.is_connected_to_gar(),
318
                 connected_to_gar: compositor.is_connected_to_gar(),
319
+                monitors,
307
             })
320
             })
308
         }
321
         }
309
     };
322
     };
garchomp/src/x11/connection.rsmodified
@@ -6,10 +6,28 @@ use thiserror::Error;
6
 use x11rb::connection::{Connection as X11Connection, RequestConnection};
6
 use x11rb::connection::{Connection as X11Connection, RequestConnection};
7
 use x11rb::protocol::composite::ConnectionExt as CompositeConnectionExt;
7
 use x11rb::protocol::composite::ConnectionExt as CompositeConnectionExt;
8
 use x11rb::protocol::damage::ConnectionExt as DamageConnectionExt;
8
 use x11rb::protocol::damage::ConnectionExt as DamageConnectionExt;
9
+use x11rb::protocol::randr::ConnectionExt as RandrConnectionExt;
9
 use x11rb::protocol::xfixes::ConnectionExt as XfixesConnectionExt;
10
 use x11rb::protocol::xfixes::ConnectionExt as XfixesConnectionExt;
10
 use x11rb::protocol::xproto::{ConnectionExt as XprotoConnectionExt, Screen, Window};
11
 use x11rb::protocol::xproto::{ConnectionExt as XprotoConnectionExt, Screen, Window};
11
 use x11rb::rust_connection::RustConnection;
12
 use x11rb::rust_connection::RustConnection;
12
 
13
 
14
+/// Information about a monitor/output.
15
+#[derive(Debug, Clone)]
16
+pub struct MonitorInfo {
17
+    /// Monitor name (e.g., "DP-1", "HDMI-0").
18
+    pub name: String,
19
+    /// X position in pixels.
20
+    pub x: i16,
21
+    /// Y position in pixels.
22
+    pub y: i16,
23
+    /// Width in pixels.
24
+    pub width: u16,
25
+    /// Height in pixels.
26
+    pub height: u16,
27
+    /// Whether this is the primary monitor.
28
+    pub primary: bool,
29
+}
30
+
13
 #[derive(Error, Debug)]
31
 #[derive(Error, Debug)]
14
 pub enum ConnectionError {
32
 pub enum ConnectionError {
15
     #[error("failed to connect to X11 display")]
33
     #[error("failed to connect to X11 display")]
@@ -224,4 +242,103 @@ impl Connection {
224
 
242
 
225
         reply.value32().and_then(|mut v| v.next()).filter(|&p| p != 0)
243
         reply.value32().and_then(|mut v| v.next()).filter(|&p| p != 0)
226
     }
244
     }
245
+
246
+    /// Query all monitors using RandR.
247
+    /// Returns a list of active monitors with their geometry.
248
+    pub fn get_monitors(&self) -> Result<Vec<MonitorInfo>> {
249
+        // Query RandR version first
250
+        let randr_version = self.conn.randr_query_version(1, 5)?.reply()?;
251
+        tracing::debug!(
252
+            "RandR version: {}.{}",
253
+            randr_version.major_version,
254
+            randr_version.minor_version
255
+        );
256
+
257
+        // Get screen resources
258
+        let resources = self.conn.randr_get_screen_resources(self.root())?.reply()?;
259
+
260
+        // Get primary output
261
+        let primary = self.conn.randr_get_output_primary(self.root())?.reply()?;
262
+        let primary_output = primary.output;
263
+
264
+        let mut monitors = Vec::new();
265
+
266
+        // Iterate through all CRTCs to find active outputs
267
+        for crtc in &resources.crtcs {
268
+            let crtc_info = match self.conn.randr_get_crtc_info(*crtc, 0)?.reply() {
269
+                Ok(info) => info,
270
+                Err(_) => continue,
271
+            };
272
+
273
+            // Skip disabled CRTCs
274
+            if crtc_info.width == 0 || crtc_info.height == 0 {
275
+                continue;
276
+            }
277
+
278
+            // Get output name from first connected output
279
+            for output in &crtc_info.outputs {
280
+                let output_info = match self.conn.randr_get_output_info(*output, 0)?.reply() {
281
+                    Ok(info) => info,
282
+                    Err(_) => continue,
283
+                };
284
+
285
+                // Convert name bytes to string
286
+                let name = String::from_utf8_lossy(&output_info.name).to_string();
287
+
288
+                monitors.push(MonitorInfo {
289
+                    name,
290
+                    x: crtc_info.x,
291
+                    y: crtc_info.y,
292
+                    width: crtc_info.width,
293
+                    height: crtc_info.height,
294
+                    primary: *output == primary_output,
295
+                });
296
+
297
+                // Only take first output per CRTC
298
+                break;
299
+            }
300
+        }
301
+
302
+        // Sort by position (left to right, top to bottom)
303
+        monitors.sort_by(|a, b| {
304
+            if a.y != b.y {
305
+                a.y.cmp(&b.y)
306
+            } else {
307
+                a.x.cmp(&b.x)
308
+            }
309
+        });
310
+
311
+        tracing::info!("Found {} monitors", monitors.len());
312
+        for (i, m) in monitors.iter().enumerate() {
313
+            tracing::info!(
314
+                "  Monitor {}: {} {}x{}+{}+{} {}",
315
+                i, m.name, m.width, m.height, m.x, m.y,
316
+                if m.primary { "(primary)" } else { "" }
317
+            );
318
+        }
319
+
320
+        Ok(monitors)
321
+    }
322
+
323
+    /// Find which monitor a point is on.
324
+    pub fn point_to_monitor(&self, x: i32, y: i32, monitors: &[MonitorInfo]) -> Option<usize> {
325
+        for (i, m) in monitors.iter().enumerate() {
326
+            let mx = m.x as i32;
327
+            let my = m.y as i32;
328
+            let mw = m.width as i32;
329
+            let mh = m.height as i32;
330
+
331
+            if x >= mx && x < mx + mw && y >= my && y < my + mh {
332
+                return Some(i);
333
+            }
334
+        }
335
+        None
336
+    }
337
+
338
+    /// Find which monitor a window is primarily on (by center point).
339
+    pub fn window_to_monitor(&self, x: i16, y: i16, width: u16, height: u16, monitors: &[MonitorInfo]) -> Option<usize> {
340
+        let center_x = x as i32 + (width as i32 / 2);
341
+        let center_y = y as i32 + (height as i32 / 2);
342
+        self.point_to_monitor(center_x, center_y, monitors)
343
+    }
227
 }
344
 }
garchomp/src/x11/mod.rsmodified
@@ -7,5 +7,5 @@ mod visual;
7
 
7
 
8
 pub use atoms::Atoms;
8
 pub use atoms::Atoms;
9
 pub use composite::CompositeExt;
9
 pub use composite::CompositeExt;
10
-pub use connection::{Connection, ConnectionError};
10
+pub use connection::{Connection, ConnectionError, MonitorInfo};
11
 pub use visual::{VisualConfig, create_colormap, find_best_visual, get_visual_depth, has_deepcolor_support};
11
 pub use visual::{VisualConfig, create_colormap, find_best_visual, get_visual_depth, has_deepcolor_support};