diff --git a/AGENTS.md b/AGENTS.md index 6d81177..579f4e8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,3 +97,5 @@ Signals: `ItemAdded(id s, mime_type s, preview s)`, `ItemDeleted(id s)`, `Histor - `Ordering::Relaxed` is intentional on `Arc` limits (advisory, not critical path). - Never execute clipboard content. Writes go through `wl-clipboard-rs` (`copy_multi`) in the daemon, never via shell subprocess or `eval`. - Excluded apps list is checked before storing any clipboard item. +- Ingest paths are mutually exclusive by environment: on GNOME the monitor cannot bind (Mutter exposes neither data-control protocol) so ingest is GJS `SubmitItem`; on wlroots the built-in monitor is the path. +- List queries (`get_history_page`, `search_history`) return `content_text` truncated to `PREVIEW_CHARS` via `substr`; full content is served only by `GetItemContent` for paste-back. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index be6bd15..7076b56 100755 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -57,10 +57,13 @@ hot ingest path (it fires `SubmitItem` and returns immediately). `bin/strata-daemon` and registers a watchdog: - If the child exits, schedule a respawn with exponential backoff - (1 s, 2 s, 4 s, capped at 30 s). -- After 5 rapid restarts within 60 s, stop and surface a notification. + (1 s, 2 s, 4 s, 8 s, 16 s). A run that lasts at least 5 s resets the + counter, so only a rapid crash loop escalates. +- After 5 rapid restarts, stop retrying and log an error. (A missing + `strata-daemon` binary is reported up front with a desktop notification; + the crash-loop give-up is log-only.) - On disable, send `Shutdown` over D-Bus first, then `SIGTERM` if it - doesn't exit within 2 s. + doesn't exit within 1.5 s. ### Startup @@ -182,15 +185,16 @@ search query, not the SQL parser. ### Pruning -`upsert_item` checks `COUNT(*) > max_history` after each insert and -deletes the oldest excess rows by `created_at ASC`, returning their ids. -The D-Bus layer emits `ItemDeleted` for each so the extension can unlink -the matching `~/.cache/strata/thumbnails/.png`. +After a genuinely new item is inserted, the ingest task calls `prune`, +which deletes every row outside the newest `max_history` (by `created_at`) +and returns their ids. The D-Bus layer emits `ItemDeleted` for each so the +extension can unlink the matching `~/.cache/strata/thumbnails/.png`. ## Concurrency -The Rust daemon runs `tokio::main(flavor = "multi_thread")`. zbus -dispatches each incoming method call on the executor. rusqlite is sync, +The Rust daemon runs on a multi-threaded tokio runtime (`#[tokio::main]`, +which defaults to the multi-thread flavor). zbus dispatches each incoming +method call on the executor. rusqlite is sync, so every DB call is wrapped: ```rust @@ -227,10 +231,13 @@ history size. ### Paginated history `GetHistory(offset, limit)` returns metadata only (id, mime, short text -preview, timestamp). The panel loads `page-size` rows on open, then -another page each time the scroll position passes ~80 % of the viewport. -The Rust side serves these from the `idx_created_at DESC` index with -`LIMIT/OFFSET`, which stays O(log n) for any history size. +preview, timestamp). The preview is truncated in SQL (`substr`, first +~200 chars), so a page of large text items costs a few KB of JSON rather +than megabytes; the full payload is only fetched on paste-back via +`GetItemContent`. The panel loads `page-size` rows on open, then another +page each time the scroll position comes within a fixed threshold (200 px) +of the bottom. The Rust side serves these from the `idx_created_at DESC` +index with `LIMIT/OFFSET`, which stays O(log n) for any history size. Search shortcuts this path: when the search box is non-empty the panel calls `SearchHistory(query, limit)` instead and disables scroll-driven diff --git a/strata-daemon/src/clipboard/monitor.rs b/strata-daemon/src/clipboard/monitor.rs index 4d9f72e..e2cd489 100644 --- a/strata-daemon/src/clipboard/monitor.rs +++ b/strata-daemon/src/clipboard/monitor.rs @@ -1,5 +1,7 @@ -/// Wayland clipboard monitor using the ext-data-control-v1 protocol (GNOME 47+) -/// with a zwlr-data-control-v1 fallback for wlroots-based compositors. +/// Wayland clipboard monitor using the ext-data-control-v1 protocol with a +/// zwlr-data-control-v1 fallback for wlroots-based compositors. GNOME's Mutter +/// exposes neither, so on GNOME this monitor does not bind and clipboard +/// content arrives from the extension via Meta.Selection + SubmitItem. /// /// Runs on a dedicated OS thread (NOT on the tokio runtime) to avoid blocking /// the async executor. Communicates clipboard change events to the tokio world @@ -245,7 +247,9 @@ fn probe_protocol(conn: &Connection) -> Result { // A quick roundtrip to get the global list; no persistent state needed here. let (globals, _) = registry_queue_init::(conn)?; - // ext-data-control-v1 preferred (GNOME 47+, KDE Plasma 6) + // ext-data-control-v1 preferred (KDE Plasma 6 and other compositors that + // expose it). GNOME's Mutter does NOT expose it, so this probe fails on + // GNOME and ingest falls back to the extension's SubmitItem path. if globals.contents().with_list(|list| { list.iter() .any(|g| g.interface == "ext_data_control_manager_v1") @@ -263,7 +267,8 @@ fn probe_protocol(conn: &Connection) -> Result { bail!( "No supported clipboard control protocol found.\n\ - - GNOME: requires GNOME 47+ (Mutter implements ext-data-control-v1)\n\ + - GNOME: Mutter exposes neither protocol; ingest comes from the\n\ + extension via Meta.Selection + SubmitItem (this is expected).\n\ - wlroots: requires zwlr-data-control-v1 (Sway, Hyprland, etc.)" ); } diff --git a/strata-daemon/src/main.rs b/strata-daemon/src/main.rs index c710ba8..f8a70ff 100644 --- a/strata-daemon/src/main.rs +++ b/strata-daemon/src/main.rs @@ -67,8 +67,10 @@ async fn main() -> Result<()> { tracing::info!("D-Bus service registered as org.gnome.Strata"); // ----------------------------------------------------------------------- - // Clipboard monitor (Wayland protocols - optional, soft-fail on GNOME) - // GJS provides clipboard content via SubmitItem when this is unavailable. + // Clipboard monitor (Wayland protocols - optional, soft-fail on GNOME). + // GNOME's Mutter does not expose ext-data-control-v1 or zwlr-data-control-v1, + // so this fails to bind there and the extension feeds content via SubmitItem + // instead. On wlroots compositors (Sway, Hyprland) the monitor is the path. // ----------------------------------------------------------------------- match clipboard::monitor::spawn(clip_tx) { Ok(()) => tracing::info!("Wayland clipboard monitor started"),