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"]
11
 
11
 
12
 [workspace.dependencies]
12
 [workspace.dependencies]
13
 # X11
13
 # X11
14
-x11rb = { version = "0.13", features = ["randr", "shm", "xfixes", "shape"] }
14
+x11rb = { version = "0.13", features = ["randr", "shm", "xfixes", "shape", "composite"] }
15
 
15
 
16
 # Async runtime
16
 # Async runtime
17
 tokio = { version = "1", features = ["full", "signal"] }
17
 tokio = { version = "1", features = ["full", "signal"] }
garshot/src/x11/connection.rsmodified
@@ -1,6 +1,7 @@
1
 //! X11 connection management for garshot.
1
 //! X11 connection management for garshot.
2
 
2
 
3
 use x11rb::connection::Connection as X11Connection;
3
 use x11rb::connection::Connection as X11Connection;
4
+use x11rb::protocol::composite::ConnectionExt as CompositeExt;
4
 use x11rb::protocol::xproto::*;
5
 use x11rb::protocol::xproto::*;
5
 use x11rb::rust_connection::RustConnection;
6
 use x11rb::rust_connection::RustConnection;
6
 
7
 
@@ -22,6 +23,10 @@ pub struct Connection {
22
     pub depth: u8,
23
     pub depth: u8,
23
     /// Root visual ID.
24
     /// Root visual ID.
24
     pub visual: Visualid,
25
     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>,
25
 }
30
 }
26
 
31
 
27
 impl Connection {
32
 impl Connection {
@@ -44,6 +49,42 @@ impl Connection {
44
             screen.root_depth
49
             screen.root_depth
45
         );
50
         );
46
 
51
 
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
+
47
         Ok(Self {
88
         Ok(Self {
48
             root: screen.root,
89
             root: screen.root,
49
             width: screen.width_in_pixels,
90
             width: screen.width_in_pixels,
@@ -52,9 +93,29 @@ impl Connection {
52
             visual: screen.root_visual,
93
             visual: screen.root_visual,
53
             conn,
94
             conn,
54
             screen_num,
95
             screen_num,
96
+            compositor_active,
97
+            overlay_window,
55
         })
98
         })
56
     }
99
     }
57
 
100
 
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
+
58
     /// Get the screen structure.
119
     /// Get the screen structure.
59
     pub fn screen(&self) -> &Screen {
120
     pub fn screen(&self) -> &Screen {
60
         &self.conn.setup().roots[self.screen_num]
121
         &self.conn.setup().roots[self.screen_num]
@@ -86,6 +147,18 @@ impl std::fmt::Debug for Connection {
86
             .field("root", &format_args!("0x{:x}", self.root))
147
             .field("root", &format_args!("0x{:x}", self.root))
87
             .field("size", &format_args!("{}x{}", self.width, self.height))
148
             .field("size", &format_args!("{}x{}", self.width, self.height))
88
             .field("depth", &self.depth)
149
             .field("depth", &self.depth)
150
+            .field("compositor_active", &self.compositor_active)
89
             .finish()
151
             .finish()
90
     }
152
     }
91
 }
153
 }
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 {
149
             )));
149
             )));
150
         }
150
         }
151
 
151
 
152
+        // Use overlay window if compositor is active, otherwise use root
153
+        let drawable = conn.overlay_window.unwrap_or(conn.root);
154
+
152
         tracing::debug!(
155
         tracing::debug!(
153
-            "Capturing region {}x{}+{}+{} ({} bytes)",
156
+            "Capturing region {}x{}+{}+{} ({} bytes) from drawable 0x{:x}{}",
154
-            width, height, x, y, byte_count
157
+            width, height, x, y, byte_count, drawable,
158
+            if conn.compositor_active { " (compositor active)" } else { "" }
155
         );
159
         );
156
 
160
 
157
         // Use shm_get_image to capture directly to shared memory
161
         // Use shm_get_image to capture directly to shared memory
158
         let reply = conn
162
         let reply = conn
159
             .conn
163
             .conn
160
             .shm_get_image(
164
             .shm_get_image(
161
-                conn.root,
165
+                drawable,
162
                 x,
166
                 x,
163
                 y,
167
                 y,
164
                 width,
168
                 width,