Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/codebase-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Daemon mode therefore provides a persistent background service that reacts to us
- Communicate with `capture::CaptureManager` for screenshot actions.
- Exit when `InputState.should_exit` is set (Escape, tray close, etc.).

`WaylandState` coordinates the runtime owners handlers need. `ProtocolGlobals` owns bound globals and toolkit handler state; `PointerRuntime` owns pointer, cursor, pointer-lock, and single-contact touch protocol lifecycles; `InputHudRuntime` owns system-reader lifecycle and reconciliation; `SpotlightRuntime` owns render memory, warning latches, and wheel timing. The root retains cross-owner input and toolbar routing.
`WaylandState` coordinates the runtime owners handlers need. `ProtocolGlobals` owns bound globals and toolkit handler state; `PointerRuntime` owns pointer, cursor, pointer-lock, and single-contact touch protocol lifecycles; `InputHudRuntime` owns system-reader lifecycle and reconciliation; `SpotlightRuntime` owns render memory, warning latches, and wheel timing; `ClipboardRuntime` owns single-flight clipboard workers and queue policy. The root retains cross-owner input and toolbar routing.

Freeze capture waits for the overlay-suppression frame, then selects `wlr-screencopy`, `ext-image-copy-capture`, or the screenshot portal in that order. The two direct protocols capture the active output into shared memory; the portal captures the desktop and the client crops the selected output when needed. Direct capture and portal crop both require compositor-reported output pixels; a missing current mode fails instead of guessing from the overlay buffer.

Expand Down
28 changes: 6 additions & 22 deletions src/backend/wayland/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ use smithay_client_toolkit::{
globals::ProvidesBoundGlobal,
shell::wlr_layer::KeyboardInteractivity,
};
use std::{
collections::VecDeque,
time::{Duration, Instant},
};
use std::time::{Duration, Instant};
use wayland_client::{
Proxy, QueueHandle,
protocol::{wl_output, wl_seat},
Expand Down Expand Up @@ -39,7 +36,6 @@ use crate::{
},
config::{Action, Config},
desktop_open::DesktopOpenRequest,
input::state::{ClipboardPasteRequest, TextClipboardRequest, TextPasteTarget},
input::{DrawingState, EraserMode, InputState, Tool, ZoomAction},
session::SessionOptions,
ui::toolbar::{ToolbarBindingHints, ToolbarEvent, ToolbarSnapshot},
Expand All @@ -53,7 +49,6 @@ pub(in crate::backend::wayland) use self::region_capture::WindowSnapDirection;
use super::{
RuntimeOperationController, RuntimeOperationIdSource,
capture::{CapturePreflightRequest, CaptureState, PendingPdfExport},
clipboard::{ClipboardPasteCompletion, ClipboardPublishCompletion},
frozen::{ExtImageCopyManagers, FrozenState},
overlay_passthrough::set_surface_clickthrough,
session::SessionState,
Expand All @@ -75,6 +70,10 @@ mod buffer_damage;
mod canvas_layer;
mod capture;
mod clipboard;
mod clipboard_runtime;
pub(in crate::backend::wayland) use clipboard_runtime::{
HexCopyOutcome, TextCopyOutcome, TextPasteOutcome,
};
mod color_picker;
mod core;
mod data;
Expand Down Expand Up @@ -194,21 +193,10 @@ pub(super) struct WaylandState {
pub(super) font_catalog_prewarm_started: bool,
/// System-reader lifecycle and reconciliation latches for the input HUD.
pub(super) input_hud: input_hud::InputHudRuntime,
pub(super) clipboard_publish: RuntimeOperationController<u64, ClipboardPublishCompletion>,
pub(super) clipboard_paste:
RuntimeOperationController<ClipboardPasteRequest, ClipboardPasteCompletion>,
pub(super) clipboard_hex_copy: RuntimeOperationController<String, Result<(), String>>,
pub(super) clipboard: clipboard_runtime::ClipboardRuntime,
/// Desktop-open work completes off-dispatch; successful completion is what
/// requests overlay exit, so runtime-owned broker teardown cannot race it.
pub(super) desktop_open: RuntimeOperationController<DesktopOpenRequest, Result<(), String>>,
pub(super) pending_hex_copy: Option<String>,
/// Async wl-copy pipeline for text-editor selections (Ctrl+C / Ctrl+X).
pub(super) clipboard_text_copy:
RuntimeOperationController<TextClipboardRequest, Result<(), String>>,
pub(super) pending_text_copy: VecDeque<TextClipboardRequest>,
/// Async wl-paste pipeline for text-editor paste requests (Ctrl+V).
pub(super) clipboard_text_paste:
RuntimeOperationController<TextPasteTarget, Result<Option<String>, String>>,
/// Capacity-one compositor window query for the current native region picker.
/// Its context owns the picker/source correlation, so stale workers cannot
/// mutate a later picker generation.
Expand All @@ -225,10 +213,6 @@ pub(super) struct WaylandState {
region_capture::CutPreviewKey,
region_capture::CutPreviewOutcome,
>,
/// Text paste requests waiting behind an active read. Repeated requests in
/// the current edit generation remain distinct; a new generation replaces
/// stale queued requests from the old edit session.
pub(super) pending_text_paste: VecDeque<TextPasteTarget>,
/// Capacity-one screen text recognition. A busy controller reports
/// busy rather than queuing a region the user has moved on from.
pub(super) ocr: crate::ocr::OcrController,
Expand Down
2 changes: 1 addition & 1 deletion src/backend/wayland/state/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

## Architecture
- This subtree supports live overlay runtime state: buffers, damage, boards, capture routing, clipboard paste, color picker, onboarding, PDF export, render helpers, toolbar plumbing, zoom, and core accessors.
- Protocol and interaction sub-states extracted from `WaylandState` live beside it: `protocol_globals.rs` (bound globals and toolkit handler state), `pointer_runtime.rs` (pointer, cursor, pointer-lock, and touch lifecycles), `input_hud.rs` (system-reader lifecycle and reconciliation), `spotlight_runtime.rs` (render memory, warning latches, and wheel timing), `text_input.rs` (text-input-v3 lifecycle and commit serials), `tablet_runtime.rs` (tablet-input-v2 objects and stylus contact), `key_repeat.rs` (manual key-repeat timing), and `helper_launch.rs` (About/configurator launches requested by input).
- Protocol and interaction sub-states extracted from `WaylandState` live beside it: `protocol_globals.rs` (bound globals and toolkit handler state), `pointer_runtime.rs` (pointer, cursor, pointer-lock, and touch lifecycles), `input_hud.rs` (system-reader lifecycle and reconciliation), `spotlight_runtime.rs` (render memory, warning latches, and wheel timing), `clipboard_runtime.rs` (single-flight workers and queue policy), `text_input.rs` (text-input-v3 lifecycle and commit serials), `tablet_runtime.rs` (tablet-input-v2 objects and stylus contact), `key_repeat.rs` (manual key-repeat timing), and `helper_launch.rs` (About/configurator launches requested by input).
- `render/` owns overlay render phases; `toolbar/` owns runtime toolbar state helpers; `clipboard/` owns session paste helpers.

## Invariants
Expand Down
51 changes: 22 additions & 29 deletions src/backend/wayland/state/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@ use session_paste::{PastePersistenceDecision, SessionPasteWarning};

impl WaylandState {
pub(in crate::backend::wayland) fn drain_clipboard_requests(&mut self) {
if !self.clipboard_publish.is_active()
if !self.clipboard.publish_active()
&& let Some(request) = self.input_state.take_pending_selection_clipboard_publish()
{
self.start_selection_clipboard_publish(request.generation, request.payload_json);
}

if let Some(request) = take_pending_clipboard_paste_if_idle(
&mut self.input_state,
self.clipboard_paste.is_active(),
self.clipboard.paste_active(),
) {
self.start_clipboard_paste(request);
}
}

pub(in crate::backend::wayland) fn poll_clipboard_publish_completion(&mut self) {
match self.clipboard_publish.poll() {
match self.clipboard.poll_publish() {
RuntimeOperationPoll::Idle | RuntimeOperationPoll::Pending { .. } => {}
RuntimeOperationPoll::Ready {
id,
Expand Down Expand Up @@ -76,7 +76,7 @@ impl WaylandState {
}

pub(in crate::backend::wayland) fn poll_clipboard_paste_completion(&mut self) {
match self.clipboard_paste.poll() {
match self.clipboard.poll_paste() {
RuntimeOperationPoll::Idle | RuntimeOperationPoll::Pending { .. } => {}
RuntimeOperationPoll::Ready {
id,
Expand Down Expand Up @@ -125,12 +125,7 @@ impl WaylandState {

fn start_selection_clipboard_publish(&mut self, generation: u64, payload_json: String) {
self.suppress_focus_exit_for(Duration::from_millis(1500));
if let Err(failure) =
self.clipboard_publish
.try_submit(generation, "clipboard-publish", move || {
transfer::resolve_selection_clipboard_publish(generation, payload_json)
})
{
if let Err(failure) = self.clipboard.submit_publish(generation, payload_json) {
let (error, generation) = failure.into_parts();
log::warn!("Could not submit clipboard publish operation: {error}");
self.apply_selection_clipboard_publish_completion(failed_clipboard_publish_completion(
Expand Down Expand Up @@ -374,8 +369,8 @@ impl WaylandState {
);
let context = request.clone();
if let Err(failure) =
self.clipboard_paste
.try_submit(context, "clipboard-fingerprint-probe", move || {
self.clipboard
.submit_paste(context, "clipboard-fingerprint-probe", move || {
let started = Instant::now();
let current = clipboard::clipboard_fingerprint();
log::info!(
Expand All @@ -400,37 +395,35 @@ impl WaylandState {
request.id
);
self.apply_clipboard_paste_completion(failed_clipboard_paste_completion(
request,
&error.to_string(),
request, &error,
));
}
}

fn start_system_clipboard_read(&mut self, request: ClipboardPasteRequest) {
log::info!("Reading system clipboard for paste request {}", request.id);
let context = request.clone();
if let Err(failure) =
self.clipboard_paste
.try_submit(context, "clipboard-paste", move || {
let started = Instant::now();
let result = transfer::resolve_system_clipboard();
log::info!(
"System clipboard read for paste request {} completed in {:?}: {}",
request.id,
started.elapsed(),
result.summary()
);
ClipboardPasteCompletion { request, result }
})
if let Err(failure) = self
.clipboard
.submit_paste(context, "clipboard-paste", move || {
let started = Instant::now();
let result = transfer::resolve_system_clipboard();
log::info!(
"System clipboard read for paste request {} completed in {:?}: {}",
request.id,
started.elapsed(),
result.summary()
);
ClipboardPasteCompletion { request, result }
})
{
let (error, request) = failure.into_parts();
log::warn!(
"Could not submit clipboard paste operation for request {}: {error}",
request.id
);
self.apply_clipboard_paste_completion(failed_clipboard_paste_completion(
request,
&error.to_string(),
request, &error,
));
}
}
Expand Down
Loading
Loading