gardesk/garshot / dbfb5b1

Browse files

x11: add XComposite support for compositor-aware screen capture

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
dbfb5b156d5414e26c98ffa8a34070d59a99e159
Parents
827a0ab
Tree
d5502a7

3 changed files

StatusFile+-
M Cargo.toml 1 1
M garshot/src/x11/connection.rs 73 0
M garshot/src/x11/shm.rs 7 3
Cargo.tomlmodified
@@ -11,7 +11,7 @@ authors = ["mfwolffe"]
1111
 
1212
 [workspace.dependencies]
1313
 # X11
14
-x11rb = { version = "0.13", features = ["randr", "shm", "xfixes", "shape"] }
14
+x11rb = { version = "0.13", features = ["randr", "shm", "xfixes", "shape", "composite"] }
1515
 
1616
 # Async runtime
1717
 tokio = { version = "1", features = ["full", "signal"] }
garshot/src/x11/connection.rsmodified
@@ -1,6 +1,7 @@
11
 //! X11 connection management for garshot.
22
 
33
 use x11rb::connection::Connection as X11Connection;
4
+use x11rb::protocol::composite::ConnectionExt as CompositeExt;
45
 use x11rb::protocol::xproto::*;
56
 use x11rb::rust_connection::RustConnection;
67
 
@@ -22,6 +23,10 @@ pub struct Connection {
2223
     pub depth: u8,
2324
     /// Root visual ID.
2425
     pub visual: Visualid,
26
+    /// Whether a compositor is active.
27
+    pub compositor_active: bool,
28
+    /// Composite overlay window (if compositor is active).
29
+    pub overlay_window: Option<Window>,
2530
 }
2631
 
2732
 impl Connection {
@@ -44,6 +49,42 @@ impl Connection {
4449
             screen.root_depth
4550
         );
4651
 
52
+        // Check if a compositor is running by looking for _NET_WM_CM_S{screen} selection owner
53
+        let compositor_active = Self::detect_compositor(&conn, screen_num, screen.root)?;
54
+
55
+        // Initialize composite extension and get overlay window if compositor is active
56
+        let overlay_window = if compositor_active {
57
+            // Query composite extension version
58
+            if let Ok(reply) = conn.composite_query_version(0, 4)?.reply() {
59
+                tracing::info!(
60
+                    "Composite extension version {}.{}",
61
+                    reply.major_version,
62
+                    reply.minor_version
63
+                );
64
+
65
+                // Get the overlay window - this is where composited content is rendered
66
+                match conn.composite_get_overlay_window(screen.root)?.reply() {
67
+                    Ok(overlay) => {
68
+                        tracing::info!("Got composite overlay window: 0x{:x}", overlay.overlay_win);
69
+                        Some(overlay.overlay_win)
70
+                    }
71
+                    Err(e) => {
72
+                        tracing::warn!("Failed to get overlay window: {}", e);
73
+                        None
74
+                    }
75
+                }
76
+            } else {
77
+                tracing::warn!("Composite extension not available");
78
+                None
79
+            }
80
+        } else {
81
+            None
82
+        };
83
+
84
+        if compositor_active {
85
+            tracing::info!("Compositor detected - will use overlay window for capture");
86
+        }
87
+
4788
         Ok(Self {
4889
             root: screen.root,
4990
             width: screen.width_in_pixels,
@@ -52,9 +93,29 @@ impl Connection {
5293
             visual: screen.root_visual,
5394
             conn,
5495
             screen_num,
96
+            compositor_active,
97
+            overlay_window,
5598
         })
5699
     }
57100
 
101
+    /// Detect if a compositor is running.
102
+    fn detect_compositor(conn: &RustConnection, screen_num: usize, _root: Window) -> Result<bool> {
103
+        // The compositor manager selection is _NET_WM_CM_S{screen}
104
+        let atom_name = format!("_NET_WM_CM_S{}", screen_num);
105
+        let atom = conn.intern_atom(false, atom_name.as_bytes())?.reply()?.atom;
106
+
107
+        // Check if the selection has an owner
108
+        let owner = conn.get_selection_owner(atom)?.reply()?.owner;
109
+
110
+        if owner != x11rb::NONE {
111
+            tracing::debug!("Compositor detected: _NET_WM_CM_S{} owner is 0x{:x}", screen_num, owner);
112
+            Ok(true)
113
+        } else {
114
+            tracing::debug!("No compositor detected");
115
+            Ok(false)
116
+        }
117
+    }
118
+
58119
     /// Get the screen structure.
59120
     pub fn screen(&self) -> &Screen {
60121
         &self.conn.setup().roots[self.screen_num]
@@ -86,6 +147,18 @@ impl std::fmt::Debug for Connection {
86147
             .field("root", &format_args!("0x{:x}", self.root))
87148
             .field("size", &format_args!("{}x{}", self.width, self.height))
88149
             .field("depth", &self.depth)
150
+            .field("compositor_active", &self.compositor_active)
89151
             .finish()
90152
     }
91153
 }
154
+
155
+impl Drop for Connection {
156
+    fn drop(&mut self) {
157
+        // Release the overlay window if we acquired it
158
+        if let Some(overlay) = self.overlay_window {
159
+            tracing::debug!("Releasing composite overlay window 0x{:x}", overlay);
160
+            let _ = self.conn.composite_release_overlay_window(self.root);
161
+            let _ = self.conn.flush();
162
+        }
163
+    }
164
+}
garshot/src/x11/shm.rsmodified
@@ -149,16 +149,20 @@ impl ShmCapture {
149149
             )));
150150
         }
151151
 
152
+        // Use overlay window if compositor is active, otherwise use root
153
+        let drawable = conn.overlay_window.unwrap_or(conn.root);
154
+
152155
         tracing::debug!(
153
-            "Capturing region {}x{}+{}+{} ({} bytes)",
154
-            width, height, x, y, byte_count
156
+            "Capturing region {}x{}+{}+{} ({} bytes) from drawable 0x{:x}{}",
157
+            width, height, x, y, byte_count, drawable,
158
+            if conn.compositor_active { " (compositor active)" } else { "" }
155159
         );
156160
 
157161
         // Use shm_get_image to capture directly to shared memory
158162
         let reply = conn
159163
             .conn
160164
             .shm_get_image(
161
-                conn.root,
165
+                drawable,
162166
                 x,
163167
                 y,
164168
                 width,