Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat: PrintScreen captures a screenshot
Action::Screenshot (default binding: bare Print) runs the same capture
as 'ccectl screenshot' — next frame of the enabled output, saved to
~/Pictures/screenshots — so artifacts can be captured from the keyboard
in any compositor state, including zoomed out.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/server/window.rs | 20 ++++++++++++++++++++
src/server/window_manager.rs | 5 +++++
src/server/wlroots_log_wrapper.c | 19 +++++++++++++++++++
wrapper.h | 1 +
4 files changed, 45 insertions(+)
diff --git a/src/server/window.rs b/src/server/window.rs
index 582bcdb..ef894ad 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -1993,6 +1993,11 @@ impl Window {
let dest_y = (py as f64 * (data.scale - 1.0)) as i32;
ffi::river_scene_node_set_position_if_changed(node, dest_x, dest_y);
}
+ // Keep the opaque region in step with the dest scale —
+ // unscaled it covers the shrunken node's translucent CSD
+ // margins and occlusion culling stops repainting behind
+ // the client shadow (stale pixels show through it).
+ ffi::river_scene_buffer_set_scaled_opaque_region(buffer, surface, data.scale);
}
// Non-surface buffers are frozen SAVED copies (see
// save_surface_tree_iter): their natural buffer size is
@@ -2201,6 +2206,11 @@ impl Window {
let dest_y = (py as f64 * (data.scale - 1.0)) as i32;
ffi::river_scene_node_set_position_if_changed(node, dest_x, dest_y);
}
+ // Keep the opaque region in step with the dest scale —
+ // unscaled it covers the shrunken node's translucent CSD
+ // margins and occlusion culling stops repainting behind
+ // the client shadow (stale pixels show through it).
+ ffi::river_scene_buffer_set_scaled_opaque_region(buffer, surface, data.scale);
}
// Non-surface buffers are frozen SAVED copies (see
// save_surface_tree_iter): their natural buffer size is
@@ -3604,6 +3614,11 @@ impl Decoration {
let dest_y = (py as f64 * (data.scale - 1.0)) as i32;
ffi::river_scene_node_set_position_if_changed(node, dest_x, dest_y);
}
+ // Keep the opaque region in step with the dest scale —
+ // unscaled it covers the shrunken node's translucent CSD
+ // margins and occlusion culling stops repainting behind
+ // the client shadow (stale pixels show through it).
+ ffi::river_scene_buffer_set_scaled_opaque_region(buffer, surface, data.scale);
}
// Non-surface buffers are frozen SAVED copies (see
// save_surface_tree_iter): their natural buffer size is
@@ -3674,6 +3689,11 @@ impl Decoration {
let dest_y = (py as f64 * (data.scale - 1.0)) as i32;
ffi::river_scene_node_set_position_if_changed(node, dest_x, dest_y);
}
+ // Keep the opaque region in step with the dest scale —
+ // unscaled it covers the shrunken node's translucent CSD
+ // margins and occlusion culling stops repainting behind
+ // the client shadow (stale pixels show through it).
+ ffi::river_scene_buffer_set_scaled_opaque_region(buffer, surface, data.scale);
}
// Non-surface buffers are frozen SAVED copies (see
// save_surface_tree_iter): their natural buffer size is
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index df2ecce..099203d 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -2129,6 +2129,11 @@ impl WindowManager {
Action::WindowSwitcherPrev => {
self.launch_window_switcher(true);
}
+ Action::Screenshot => {
+ // Same capture as `ccectl screenshot`: the enabled output's
+ // next frame, saved under ~/Pictures/screenshots.
+ let _ = self.process_ipc_command("screenshot");
+ }
Action::Reload => {
log::info!("monolithic execute_action: Reload requested");
match self.reload_config() {
diff --git a/src/server/wlroots_log_wrapper.c b/src/server/wlroots_log_wrapper.c
index e3e3313..ed4068e 100644
--- a/src/server/wlroots_log_wrapper.c
+++ b/src/server/wlroots_log_wrapper.c
@@ -48,6 +48,7 @@ void river_init_wlroots_log(enum wlr_log_importance importance) {
#include <time.h>
#include <scenefx/types/wlr_scene.h>
#include <wlr/types/wlr_output.h>
+#include <wlr/util/region.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h>
@@ -899,6 +900,24 @@ int river_scene_buffer_get_height(struct wlr_scene_buffer *scene_buffer) {
return scene_buffer->dst_height;
}
+/* Scale a surface's opaque region to match a custom buffer dest size and
+ * apply it to the scene buffer. The scene keeps opaque regions in SURFACE
+ * coordinates and scenefx's occlusion culling only intersects them with the
+ * node box — so a zoomed-down window's unscaled opaque rect still covered
+ * (almost) the whole scaled node, INCLUDING its translucent CSD shadow
+ * margins. Culling then skipped repainting behind the shadow ring: stale
+ * pixels showed through the translucent shadow around focused windows when
+ * zoomed (worst at the bottom, where Chromium's ring is tallest). */
+void river_scene_buffer_set_scaled_opaque_region(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_surface *surface, double scale) {
+ pixman_region32_t scaled;
+ pixman_region32_init(&scaled);
+ pixman_region32_copy(&scaled, &surface->opaque_region);
+ wlr_region_scale(&scaled, &scaled, (float)scale);
+ wlr_scene_buffer_set_opaque_region(scene_buffer, &scaled);
+ pixman_region32_fini(&scaled);
+}
+
int river_scene_buffer_get_dest_width(struct wlr_scene_buffer *scene_buffer) {
return scene_buffer->dst_width;
}
diff --git a/wrapper.h b/wrapper.h
index bd996e3..3d7f448 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -269,6 +269,7 @@ void river_scene_node_set_corner_radius(struct wlr_scene_node *node, int radius)
void river_scene_buffer_set_dest_size_if_changed(struct wlr_scene_buffer *scene_buffer, int width, int height);
int river_scene_buffer_get_width(struct wlr_scene_buffer *scene_buffer);
int river_scene_buffer_get_height(struct wlr_scene_buffer *scene_buffer);
+void river_scene_buffer_set_scaled_opaque_region(struct wlr_scene_buffer *scene_buffer, struct wlr_surface *surface, double scale);
int river_scene_buffer_get_dest_width(struct wlr_scene_buffer *scene_buffer);
int river_scene_buffer_get_dest_height(struct wlr_scene_buffer *scene_buffer);
bool river_scene_node_get_enabled(struct wlr_scene_node *node);