Skip to content

Commit 08eece3

Browse files
committed
Fix capture overlay rendering and clipboard handoff
1 parent ab18a64 commit 08eece3

File tree

4 files changed

+120
-84
lines changed

4 files changed

+120
-84
lines changed

src/backend/wayland/state/core.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ impl WaylandState {
99
capture_manager,
1010
session_options,
1111
tokio_handle,
12+
exit_after_capture,
1213
frozen_enabled,
1314
preferred_output_identity,
1415
xdg_fullscreen,
@@ -84,6 +85,7 @@ impl WaylandState {
8485
capture: CaptureState::new(capture_manager),
8586
frozen: FrozenState::new(screencopy_manager),
8687
zoom: ZoomState::new(zoom_manager),
88+
exit_after_capture,
8789
themed_pointer: None,
8890
locked_pointer: None,
8991
relative_pointer: None,
@@ -158,6 +160,10 @@ impl WaylandState {
158160
}
159161
self.data.overlay_suppression = reason;
160162
self.apply_overlay_clickthrough(true);
163+
if let Some(layer) = self.surface.layer_surface_mut() {
164+
layer.set_keyboard_interactivity(KeyboardInteractivity::None);
165+
self.set_current_keyboard_interactivity(Some(KeyboardInteractivity::None));
166+
}
161167
self.input_state.needs_redraw = true;
162168
self.toolbar.mark_dirty();
163169
}
@@ -171,6 +177,7 @@ impl WaylandState {
171177
}
172178
self.data.overlay_suppression = OverlaySuppression::None;
173179
self.apply_overlay_clickthrough(false);
180+
self.refresh_keyboard_interactivity();
174181
self.input_state.needs_redraw = true;
175182
self.toolbar.mark_dirty();
176183
}

src/backend/wayland/state/render.rs

Lines changed: 73 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@ impl WaylandState {
44
pub(in crate::backend::wayland) fn render(&mut self, qh: &QueueHandle<Self>) -> Result<bool> {
55
debug!("=== RENDER START ===");
66
let board_mode = self.input_state.board_mode();
7-
let suppressed = self.overlay_suppressed()
8-
&& !(self.data.overlay_suppression == OverlaySuppression::Zoom
9-
&& board_mode != BoardMode::Transparent);
7+
let suppression = if self.data.overlay_suppression == OverlaySuppression::Zoom
8+
&& board_mode != BoardMode::Transparent
9+
{
10+
OverlaySuppression::None
11+
} else {
12+
self.data.overlay_suppression
13+
};
14+
let render_canvas = !matches!(
15+
suppression,
16+
OverlaySuppression::Frozen | OverlaySuppression::Zoom
17+
);
18+
let render_ui = suppression == OverlaySuppression::None;
1019

1120
// Create pool if needed
1221
let buffer_count = self.config.performance.buffer_count as usize;
@@ -65,7 +74,7 @@ impl WaylandState {
6574
ctx.paint().context("Failed to clear background")?;
6675
ctx.set_operator(cairo::Operator::Over);
6776

68-
if !suppressed {
77+
if render_canvas {
6978
let allow_background_image =
7079
!(self.zoom.is_engaged() && board_mode != BoardMode::Transparent);
7180
let zoom_render_image = if self.zoom.active && allow_background_image {
@@ -307,74 +316,78 @@ impl WaylandState {
307316
let _ = ctx.restore();
308317
}
309318

310-
// Render frozen badge even if status bar is hidden
311-
if self.input_state.frozen_active()
312-
&& !self.zoom.active
313-
&& self.config.ui.show_frozen_badge
314-
{
315-
crate::ui::render_frozen_badge(&ctx, width, height);
316-
}
317-
// Render a zoom badge when the status bar is hidden or zoom is locked.
318-
if self.input_state.zoom_active()
319-
&& (!self.input_state.show_status_bar || self.input_state.zoom_locked())
320-
{
321-
crate::ui::render_zoom_badge(
322-
&ctx,
323-
width,
324-
height,
325-
self.input_state.zoom_scale(),
326-
self.input_state.zoom_locked(),
327-
);
328-
}
319+
if render_ui {
320+
// Render frozen badge even if status bar is hidden
321+
if self.input_state.frozen_active()
322+
&& !self.zoom.active
323+
&& self.config.ui.show_frozen_badge
324+
{
325+
crate::ui::render_frozen_badge(&ctx, width, height);
326+
}
327+
// Render a zoom badge when the status bar is hidden or zoom is locked.
328+
if self.input_state.zoom_active()
329+
&& (!self.input_state.show_status_bar || self.input_state.zoom_locked())
330+
{
331+
crate::ui::render_zoom_badge(
332+
&ctx,
333+
width,
334+
height,
335+
self.input_state.zoom_scale(),
336+
self.input_state.zoom_locked(),
337+
);
338+
}
329339

330-
// Render status bar if enabled
331-
if self.input_state.show_status_bar {
332-
crate::ui::render_status_bar(
333-
&ctx,
334-
&self.input_state,
335-
self.config.ui.status_bar_position,
336-
&self.config.ui.status_bar_style,
337-
width,
338-
height,
339-
);
340-
}
340+
// Render status bar if enabled
341+
if self.input_state.show_status_bar {
342+
crate::ui::render_status_bar(
343+
&ctx,
344+
&self.input_state,
345+
self.config.ui.status_bar_position,
346+
&self.config.ui.status_bar_style,
347+
width,
348+
height,
349+
);
350+
}
341351

342-
// Render help overlay if toggled
343-
if self.input_state.show_help {
344-
crate::ui::render_help_overlay(
345-
&ctx,
346-
&self.config.ui.help_overlay_style,
347-
width,
348-
height,
349-
self.frozen_enabled(),
350-
);
351-
}
352+
// Render help overlay if toggled
353+
if self.input_state.show_help {
354+
crate::ui::render_help_overlay(
355+
&ctx,
356+
&self.config.ui.help_overlay_style,
357+
width,
358+
height,
359+
self.frozen_enabled(),
360+
);
361+
}
352362

353-
if !self.zoom.active {
354-
crate::ui::render_properties_panel(&ctx, &self.input_state, width, height);
363+
if !self.zoom.active {
364+
crate::ui::render_properties_panel(&ctx, &self.input_state, width, height);
355365

356-
if self.input_state.is_context_menu_open() {
357-
self.input_state
358-
.update_context_menu_layout(&ctx, width, height);
366+
if self.input_state.is_context_menu_open() {
367+
self.input_state
368+
.update_context_menu_layout(&ctx, width, height);
369+
} else {
370+
self.input_state.clear_context_menu_layout();
371+
}
372+
373+
// Render context menu if open
374+
crate::ui::render_context_menu(&ctx, &self.input_state, width, height);
359375
} else {
360376
self.input_state.clear_context_menu_layout();
361377
}
362378

363-
// Render context menu if open
364-
crate::ui::render_context_menu(&ctx, &self.input_state, width, height);
379+
// Inline toolbars (xdg fallback) render directly into main surface when layer-shell is unavailable.
380+
if self.toolbar.is_visible() && self.inline_toolbars_active() {
381+
let snapshot = self.toolbar_snapshot();
382+
if self.toolbar.update_snapshot(&snapshot) {
383+
self.toolbar.mark_dirty();
384+
}
385+
self.render_inline_toolbars(&ctx, &snapshot);
386+
}
365387
} else {
366388
self.input_state.clear_context_menu_layout();
367389
}
368390

369-
// Inline toolbars (xdg fallback) render directly into main surface when layer-shell is unavailable.
370-
if self.toolbar.is_visible() && self.inline_toolbars_active() {
371-
let snapshot = self.toolbar_snapshot();
372-
if self.toolbar.update_snapshot(&snapshot) {
373-
self.toolbar.mark_dirty();
374-
}
375-
self.render_inline_toolbars(&ctx, &snapshot);
376-
}
377-
378391
let _ = ctx.restore();
379392
}
380393

src/backend/wayland/state/toolbar/visibility.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ impl WaylandState {
129129
pub(in crate::backend::wayland) fn desired_keyboard_interactivity(
130130
&self,
131131
) -> KeyboardInteractivity {
132+
if self.overlay_suppressed() {
133+
return KeyboardInteractivity::None;
134+
}
132135
desired_keyboard_interactivity_for(self.layer_shell.is_some(), self.toolbar.is_visible())
133136
}
134137

src/capture/clipboard.rs

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
33
use super::types::CaptureError;
44
use std::process::{Command, Stdio};
5-
use wl_clipboard_rs::copy::{MimeType, Options, Source};
5+
use wl_clipboard_rs::copy::{MimeType, Options, ServeRequests, Source};
66

77
/// Copy image data to the Wayland clipboard.
88
///
9-
/// Attempts to use wl-clipboard-rs library first, falls back to
10-
/// wl-copy command if the library fails.
9+
/// Attempts to use wl-copy first, falls back to wl-clipboard-rs
10+
/// if the command path fails.
1111
///
1212
/// # Arguments
1313
/// * `image_data` - Raw PNG image bytes
@@ -20,7 +20,7 @@ pub fn copy_to_clipboard(image_data: &[u8]) -> Result<(), CaptureError> {
2020
image_data.len()
2121
);
2222

23-
// Prefer wl-copy CLI (provided by wl-clipboard package); fall back to library if unavailable.
23+
// Prefer wl-copy CLI; fall back to wl-clipboard-rs if needed.
2424
match copy_via_command(image_data) {
2525
Ok(()) => {
2626
log::info!("Successfully copied to clipboard via wl-copy command");
@@ -50,13 +50,9 @@ pub fn copy_to_clipboard(image_data: &[u8]) -> Result<(), CaptureError> {
5050

5151
/// Copy to clipboard using wl-clipboard-rs library.
5252
fn copy_via_library(image_data: &[u8]) -> Result<(), CaptureError> {
53-
use wl_clipboard_rs::copy::ServeRequests;
54-
5553
let mut opts = Options::new();
56-
57-
// Serve clipboard requests until paste or replacement
58-
// This keeps the clipboard data available after our process exits
59-
opts.serve_requests(ServeRequests::Only(1)); // Serve one paste then exit
54+
// Serve requests until clipboard ownership changes.
55+
opts.serve_requests(ServeRequests::Unlimited);
6056

6157
opts.copy(
6258
Source::Bytes(image_data.into()),
@@ -92,21 +88,38 @@ fn copy_via_command(image_data: &[u8]) -> Result<(), CaptureError> {
9288
})?;
9389
}
9490

95-
// Wait for completion
96-
let output = child
97-
.wait_with_output()
98-
.map_err(|e| CaptureError::ClipboardError(format!("Failed to wait for wl-copy: {}", e)))?;
99-
100-
if !output.status.success() {
101-
let stderr = String::from_utf8_lossy(&output.stderr);
102-
return Err(CaptureError::ClipboardError(format!(
103-
"wl-copy failed: {}",
104-
stderr
105-
)));
91+
match child.try_wait() {
92+
Ok(Some(status)) => {
93+
if !status.success() {
94+
return Err(CaptureError::ClipboardError(
95+
"wl-copy exited unsuccessfully".to_string(),
96+
));
97+
}
98+
log::debug!("wl-copy command completed successfully");
99+
Ok(())
100+
}
101+
Ok(None) => {
102+
// Wait in the background so we don't block the capture pipeline.
103+
std::thread::spawn(move || match child.wait_with_output() {
104+
Ok(output) => {
105+
if !output.status.success() {
106+
let stderr = String::from_utf8_lossy(&output.stderr);
107+
log::warn!("wl-copy failed: {}", stderr.trim());
108+
} else {
109+
log::debug!("wl-copy command completed successfully");
110+
}
111+
}
112+
Err(err) => {
113+
log::warn!("Failed to wait for wl-copy: {}", err);
114+
}
115+
});
116+
Ok(())
117+
}
118+
Err(err) => Err(CaptureError::ClipboardError(format!(
119+
"Failed to poll wl-copy status: {}",
120+
err
121+
))),
106122
}
107-
108-
log::debug!("wl-copy command completed successfully");
109-
Ok(())
110123
}
111124

112125
/// Check if clipboard functionality is available.

0 commit comments

Comments
 (0)