From 4adb07443a4ba472bfa3ffebdb719949c72fa644 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 13 Jan 2026 16:43:56 +0100 Subject: [PATCH 01/25] shell: Only clear tiling position on move --- src/shell/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 47632dc0..e2809861 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -3134,8 +3134,8 @@ impl Shell { toplevel_enter_workspace(window, to); // we can't restore to a given position - if let WorkspaceRestoreData::Tiling(state) = &mut window_state { - state.take(); + if let WorkspaceRestoreData::Tiling(Some(state)) = &mut window_state { + state.state.take(); } // update fullscreen state to restore to the new workspace if let WorkspaceRestoreData::Fullscreen(Some(FullscreenRestoreData { From a623f18c25471922fdb991c69bf3d7c46c42075b Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 15 Jan 2026 16:34:41 -0700 Subject: [PATCH 02/25] fix: windows are clipped in overview mode causing visual artifact --- src/shell/layout/tiling/mod.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 16f1068e..200d3bc6 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -5563,11 +5563,18 @@ where let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); let scale = geo.size.to_f64() / original_geo.size.to_f64(); + // In overview mode, don't pass max_size to avoid pre-clipping. + // Let constrain_render_elements handle scaling instead. + let max_size = if is_overview { + None + } else { + Some(geo.size.as_logical()) + }; let shadow_element = mapped.shadow_render_element( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, - Some(geo.size.as_logical()), + max_size, Scale::from(output_scale), scale.x.min(scale.y), alpha, @@ -5577,7 +5584,7 @@ where //original_location, geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, - Some(geo.size.as_logical()), + max_size, Scale::from(output_scale), alpha, None, From b5e8c585bf3b520d72dbb0e677482d67af2e055c Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 16 Jan 2026 11:01:50 +0100 Subject: [PATCH 03/25] i18n: translation updates from weblate Co-authored-by: lorduskordus Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/cs/ Translation: Pop OS/COSMIC Comp --- resources/i18n/cs/cosmic_comp.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/i18n/cs/cosmic_comp.ftl b/resources/i18n/cs/cosmic_comp.ftl index 39eee8d1..dbb9c009 100644 --- a/resources/i18n/cs/cosmic_comp.ftl +++ b/resources/i18n/cs/cosmic_comp.ftl @@ -1,5 +1,5 @@ a11y-zoom-move-continuously = Zobrazení se pohybuje plynule s ukazatelem -a11y-zoom-move-onedge = Zobrazení se pohybuje, když ukazatel dosáhne kraje +a11y-zoom-move-onedge = Zobrazení se pohybuje, když ukazatel dosáhne okraje a11y-zoom-move-centered = Zobrazení se pohybuje tak, aby ukazatel zůstal uprostřed a11y-zoom-settings = Nastavení lupy... grow-window = Zvětšit From ef5d1f1c372e8be8865574dc944970b15d47d198 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 21 Jan 2026 00:00:02 +0100 Subject: [PATCH 04/25] i18n: translation updates from weblate Co-authored-by: Aman Alam Co-authored-by: Baurzhan Muftakhidinov Co-authored-by: Hosted Weblate Co-authored-by: jonnysemon Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/ar/ Translation: Pop OS/COSMIC Comp --- resources/i18n/ar/cosmic_comp.ftl | 2 +- resources/i18n/kk/cosmic_comp.ftl | 0 resources/i18n/pa/cosmic_comp.ftl | 0 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 resources/i18n/kk/cosmic_comp.ftl create mode 100644 resources/i18n/pa/cosmic_comp.ftl diff --git a/resources/i18n/ar/cosmic_comp.ftl b/resources/i18n/ar/cosmic_comp.ftl index 9d78b757..3fc78b1c 100644 --- a/resources/i18n/ar/cosmic_comp.ftl +++ b/resources/i18n/ar/cosmic_comp.ftl @@ -10,7 +10,7 @@ unknown-keybinding = <غير محدد> window-menu-minimize = صغِّر window-menu-maximize = كبِّر window-menu-fullscreen = ملء الشاشة -window-menu-tiled = جعل النافذة تطفو +window-menu-tiled = نافذة عائمة window-menu-screenshot = التقط لقطة شاشة window-menu-move = حرِّك window-menu-resize = تغيير الحجم diff --git a/resources/i18n/kk/cosmic_comp.ftl b/resources/i18n/kk/cosmic_comp.ftl new file mode 100644 index 00000000..e69de29b diff --git a/resources/i18n/pa/cosmic_comp.ftl b/resources/i18n/pa/cosmic_comp.ftl new file mode 100644 index 00000000..e69de29b From 996a8a833f598701bbbc4b5caeb65bbc042c0743 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 21 Jan 2026 11:10:11 +0100 Subject: [PATCH 05/25] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aman Alam Co-authored-by: Baurzhan Muftakhidinov Co-authored-by: Hosted Weblate Co-authored-by: Jun Hwi Ku Co-authored-by: gift983 <983649@my.leicestercollege.ac.uk> Co-authored-by: jonnysemon Co-authored-by: 김유빈 Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/ar/ Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/ko/ Translation: Pop OS/COSMIC Comp --- resources/i18n/ko/cosmic_comp.ftl | 28 ++++++++++++++++++++++++++++ resources/i18n/ti/cosmic_comp.ftl | 0 2 files changed, 28 insertions(+) create mode 100644 resources/i18n/ti/cosmic_comp.ftl diff --git a/resources/i18n/ko/cosmic_comp.ftl b/resources/i18n/ko/cosmic_comp.ftl index e69de29b..b4c1650e 100644 --- a/resources/i18n/ko/cosmic_comp.ftl +++ b/resources/i18n/ko/cosmic_comp.ftl @@ -0,0 +1,28 @@ +window-menu-resize-edge-top = 상단 +window-menu-resize-edge-left = 좌측 +window-menu-resize-edge-right = 우측 +window-menu-resize-edge-bottom = 하단 +window-menu-move = 이동 +window-menu-minimize = 최소화 +window-menu-maximize = 최대화 +window-menu-fullscreen = 전체 화면 +window-menu-move-prev-workspace = 이전 워크스페이스로 이동 +window-menu-move-next-workspace = 다음 워크스페이스로 이동 +a11y-zoom-settings = 돋보기 설정... +grow-window = 확대 +shrink-window = 축소 +swap-windows = 창 바꾸기 +stack-windows = 창 스택 +window-menu-tiled = 플로팅 창으로 전환 +window-menu-screenshot = 스크린샷 찍기 +window-menu-resize = 크기 조정 +window-menu-stack = 창 스택 생성 +window-menu-unstack-all = 모든 창 스택 해제 +window-menu-unstack = 창 스택 해제 +a11y-zoom-move-continuously = 포인터를 따라 화면이 계속 이동 +a11y-zoom-move-onedge = 포인터가 가장자리에 도달할 때 화면 이동 +a11y-zoom-move-centered = 포인터를 화면 중앙에 유지하며 이동 +window-menu-close = 닫기 +window-menu-close-all = 모든 창 닫기 +unknown-keybinding = +window-menu-sticky = 고정된 창 diff --git a/resources/i18n/ti/cosmic_comp.ftl b/resources/i18n/ti/cosmic_comp.ftl new file mode 100644 index 00000000..e69de29b From 1dfc948f1ebf2995fcbf9efe570103f9a46cf7e4 Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 15 Jan 2026 17:24:56 -0700 Subject: [PATCH 06/25] fix: only move the grabbed window if a new zone is chosen This should fix super+click on a tiled window poping it out of the tree and tiling it to the right half of the screen (default drop window behavior). --- src/shell/layout/tiling/mod.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 200d3bc6..ffeeb52a 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1384,6 +1384,13 @@ impl TilingLayout { ) -> Option { let node_id = window.tiling_node_id.lock().unwrap().take()?; + // Initialize last_overview_hover to the placeholder position so that + // dropping without mouse movement restores the window to its original position + if matches!(type_, PlaceholderType::GrabbedWindow) { + self.last_overview_hover = + Some((None, TargetZone::InitialPlaceholder(node_id.clone()))); + } + let data = self .queue .trees @@ -2666,7 +2673,7 @@ impl TilingLayout { window.set_bounds(layer_map.non_exclusive_zone().size); } - let mapped = match self.last_overview_hover.as_ref().map(|x| &x.1) { + let mapped = match self.last_overview_hover.as_ref().map(|(_, zone)| zone) { Some(TargetZone::GroupEdge(group_id, direction)) if tree.get(group_id).is_ok() => { let new_id = tree .insert( @@ -4039,7 +4046,7 @@ impl TilingLayout { let is_overview = !matches!(overview.0, OverviewMode::None); let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_)))) - .then(|| self.last_overview_hover.as_ref().map(|x| &x.1)); + .then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone)); let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() { Some(desc.clone()) } else { @@ -4190,7 +4197,7 @@ impl TilingLayout { let mut elements = Vec::default(); let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_)))) - .then(|| self.last_overview_hover.as_ref().map(|x| &x.1)); + .then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone)); let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() { Some(desc.clone()) } else { From 0f7e53b6009d53fac900d13dd07ee57e5c9b84e2 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 13 Jan 2026 16:46:57 -0800 Subject: [PATCH 07/25] Update `smithay` to latest commit Includes an implementation for `VirtualKeyboardHandler`, which is now required. --- Cargo.lock | 41 ++++++++++++------------ Cargo.toml | 2 +- src/wayland/handlers/virtual_keyboard.rs | 31 +++++++++++++++++- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index acc4bf0f..f8d61c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1268,7 +1268,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1578,7 +1578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2875,7 +2875,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d463f34ca3c400fde3a054da0e0b8c6ffa21e4590922f3e18281bb5eeef4cbdc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3499,7 +3499,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4342,9 +4342,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.5" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] @@ -4685,7 +4685,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4992,7 +4992,7 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay" version = "0.7.0" -source = "git+https://github.com/smithay/smithay.git?rev=8148d67#8148d67ea3ee3959a84970e8f80f591e068e65ce" +source = "git+https://github.com/smithay/smithay.git?rev=14a2009#14a2009cda0ef4db81e28941a1dbf4375f07e7f2" dependencies = [ "aliasable", "appendlist", @@ -5028,6 +5028,7 @@ dependencies = [ "tempfile", "thiserror 2.0.17", "tracing", + "tracy-client", "udev", "wayland-client", "wayland-cursor", @@ -5331,7 +5332,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.2", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5638,9 +5639,9 @@ dependencies = [ [[package]] name = "tracy-client" -version = "0.18.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef54005d3d760186fd662dad4b7bb27ecd5531cdef54d1573ebd3f20a9205ed7" +checksum = "a4f6fc3baeac5d86ab90c772e9e30620fc653bf1864295029921a15ef478e6a5" dependencies = [ "loom", "once_cell", @@ -6033,9 +6034,9 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" dependencies = [ "cc", "downcast-rs", @@ -6158,9 +6159,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" dependencies = [ "proc-macro2", "quick-xml", @@ -6169,9 +6170,9 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcbd4f3aba6c9fba70445ad2a484c0ef0356c1a9459b1e8e435bedc1971a6222" +checksum = "9297ab90f8d1f597711d36455c5b1b2290eca59b8134485e377a296b80b118c9" dependencies = [ "bitflags 2.9.4", "downcast-rs", @@ -6182,9 +6183,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" dependencies = [ "dlib", "log", @@ -6352,7 +6353,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e5cca799..a7f6c9c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,4 +147,4 @@ cosmic-protocols = { git = "https://github.com/pop-os//cosmic-protocols", branch cosmic-client-toolkit = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" } [patch.crates-io] -smithay = { git = "https://github.com/smithay/smithay.git", rev = "8148d67" } +smithay = { git = "https://github.com/smithay/smithay.git", rev = "14a2009" } diff --git a/src/wayland/handlers/virtual_keyboard.rs b/src/wayland/handlers/virtual_keyboard.rs index f39ff22a..26d9ec81 100644 --- a/src/wayland/handlers/virtual_keyboard.rs +++ b/src/wayland/handlers/virtual_keyboard.rs @@ -1,6 +1,35 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::state::State; -use smithay::delegate_virtual_keyboard_manager; +use smithay::{ + backend::input::KeyState, + delegate_virtual_keyboard_manager, + input::keyboard::{FilterResult, KeyboardHandle, Keycode, xkb::ModMask}, + utils::SERIAL_COUNTER, + wayland::virtual_keyboard::VirtualKeyboardHandler, +}; + +impl VirtualKeyboardHandler for State { + fn on_keyboard_event( + &mut self, + keycode: Keycode, + state: KeyState, + time: u32, + keyboard: KeyboardHandle, + ) { + let serial = SERIAL_COUNTER.next_serial(); + keyboard.input(self, keycode, state, serial, time, |_, _, _| { + FilterResult::Forward:: + }); + } + fn on_keyboard_modifiers( + &mut self, + _depressed_mods: ModMask, + _latched_mods: ModMask, + _locked_mods: ModMask, + _keyboard: KeyboardHandle, + ) { + } +} delegate_virtual_keyboard_manager!(State); From cac7a5aca67e45a3d06c7b58347b037c4c2507ac Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 13 Jan 2026 18:26:26 -0800 Subject: [PATCH 08/25] image-copy: Use abstraction that's now provided by Smithay This doesn't change much, since the Smithay implementation is based on the `cosmic-comp` version, but made more generic. We provide our own implementation for our workspace capture protocol, but otherwise Smithay handles the boilerplate now. This should not cause any change in behavior. --- src/backend/kms/device.rs | 2 +- src/backend/kms/surface/mod.rs | 16 +- src/backend/render/mod.rs | 2 +- src/input/mod.rs | 6 +- src/shell/focus/target.rs | 2 +- src/shell/workspace.rs | 8 +- src/state.rs | 30 +- src/wayland/handlers/image_capture_source.rs | 61 +- .../{screencopy => image_copy_capture}/mod.rs | 135 +- .../render.rs | 38 +- .../user_data.rs | 68 +- src/wayland/handlers/mod.rs | 2 +- src/wayland/protocols/image_capture_source.rs | 276 +--- src/wayland/protocols/mod.rs | 1 - src/wayland/protocols/screencopy.rs | 1158 ----------------- src/wayland/protocols/toplevel_info.rs | 16 +- 16 files changed, 285 insertions(+), 1536 deletions(-) rename src/wayland/handlers/{screencopy => image_copy_capture}/mod.rs (78%) rename src/wayland/handlers/{screencopy => image_copy_capture}/render.rs (97%) rename src/wayland/handlers/{screencopy => image_copy_capture}/user_data.rs (79%) delete mode 100644 src/wayland/protocols/screencopy.rs diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index 926af374..3d3dfc28 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -8,7 +8,7 @@ use crate::{ config::{CompTransformDef, EdidProduct, ScreenFilter}, shell::Shell, utils::{env::dev_list_var, prelude::*}, - wayland::handlers::screencopy::PendingImageCopyData, + wayland::handlers::image_copy_capture::PendingImageCopyData, }; use anyhow::{Context, Result}; diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 63873b64..e2d2e5b4 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -11,14 +11,9 @@ use crate::{ shell::Shell, state::SurfaceDmabufFeedback, utils::prelude::*, - wayland::{ - handlers::{ - compositor::recursive_frame_time_estimation, - screencopy::{FrameHolder, PendingImageCopyData, SessionData, submit_buffer}, - }, - protocols::screencopy::{ - FailureReason, Frame as ScreencopyFrame, SessionRef as ScreencopySessionRef, - }, + wayland::handlers::{ + compositor::recursive_frame_time_estimation, + image_copy_capture::{FrameHolder, PendingImageCopyData, SessionData, submit_buffer}, }, }; @@ -82,6 +77,9 @@ use smithay::{ utils::{Clock, Monotonic, Physical, Point, Rectangle, Transform}, wayland::{ dmabuf::{DmabufFeedbackBuilder, get_dmabuf}, + image_copy_capture::{ + CaptureFailureReason, Frame as ScreencopyFrame, SessionRef as ScreencopySessionRef, + }, presentation::Refresh, seat::WaylandFocus, shm::{shm_format_to_fourcc, with_buffer_contents}, @@ -1398,7 +1396,7 @@ impl SurfaceThreadState { .lock() .unwrap() .reset(); - frame.fail(FailureReason::Unknown); + frame.fail(CaptureFailureReason::Unknown); } return Err(err).with_context(|| "Failed to submit result for display"); } diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index c7d79967..d439e1ce 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -35,7 +35,7 @@ use crate::{ handlers::{ compositor::FRAME_TIME_FILTER, data_device::get_dnd_icon, - screencopy::{FrameHolder, SessionData, render_session}, + image_copy_capture::{FrameHolder, SessionData, render_session}, }, protocols::workspace::WorkspaceHandle, }, diff --git a/src/input/mod.rs b/src/input/mod.rs index 9b06d0d7..148f7764 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -24,9 +24,8 @@ use crate::{ zoom::ZoomState, }, utils::{prelude::*, quirks::workspace_overview_is_open}, - wayland::{ - handlers::{screencopy::SessionHolder, xwayland_keyboard_grab::XWaylandGrabSeat}, - protocols::screencopy::{BufferConstraints, CursorSessionRef}, + wayland::handlers::{ + image_copy_capture::SessionHolder, xwayland_keyboard_grab::XWaylandGrabSeat, }, }; use calloop::{ @@ -62,6 +61,7 @@ use smithay::{ }, utils::{Point, Rectangle, SERIAL_COUNTER, Serial}, wayland::{ + image_copy_capture::{BufferConstraints, CursorSessionRef}, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, pointer_constraints::{PointerConstraint, with_pointer_constraint}, seat::WaylandFocus, diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index b5d8b8dd..63bf0a56 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -12,7 +12,7 @@ use crate::{ zoom::ZoomFocusTarget, }, utils::prelude::*, - wayland::handlers::{screencopy::SessionHolder, xdg_shell::popup::get_popup_toplevel}, + wayland::handlers::{image_copy_capture::SessionHolder, xdg_shell::popup::get_popup_toplevel}, }; use id_tree::NodeId; use smithay::{ diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 08d32d89..1c9da011 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -13,7 +13,7 @@ use crate::{ state::State, utils::{prelude::*, tween::EaseRectangle}, wayland::{ - handlers::screencopy::ScreencopySessions, + handlers::image_copy_capture::ImageCopySessions, protocols::{ toplevel_info::{toplevel_enter_output, toplevel_leave_output}, workspace::{WorkspaceHandle, WorkspaceUpdateGuard}, @@ -110,7 +110,7 @@ pub struct Workspace { pub handle: WorkspaceHandle, pub focus_stack: FocusStacks, - pub screencopy: ScreencopySessions, + pub image_copy: ImageCopySessions, output_stack: VecDeque, pub(super) backdrop_id: Id, pub dirty: AtomicBool, @@ -377,7 +377,7 @@ impl Workspace { id: None, handle, focus_stack: FocusStacks::default(), - screencopy: ScreencopySessions::default(), + image_copy: ImageCopySessions::default(), output_stack: { let mut queue = VecDeque::new(); queue.push_back(output_match); @@ -410,7 +410,7 @@ impl Workspace { id: pinned.id.clone(), handle, focus_stack: FocusStacks::default(), - screencopy: ScreencopySessions::default(), + image_copy: ImageCopySessions::default(), output_stack: { let mut queue = VecDeque::new(); queue.push_back(pinned.output.clone()); diff --git a/src/state.rs b/src/state.rs index 00ff55c1..6406cc6c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -13,16 +13,15 @@ use crate::{ shell::{CosmicSurface, SeatExt, Shell, grabs::SeatMoveGrabState}, utils::prelude::OutputExt, wayland::{ - handlers::{data_device::get_dnd_icon, screencopy::SessionHolder}, + handlers::{data_device::get_dnd_icon, image_copy_capture::SessionHolder}, protocols::{ a11y::A11yState, corner_radius::CornerRadiusState, drm::WlDrmState, - image_capture_source::ImageCaptureSourceState, + image_capture_source::CosmicImageCaptureSourceState, output_configuration::OutputConfigurationState, output_power::OutputPowerState, overlap_notify::OverlapNotifyState, - screencopy::ScreencopyState, toplevel_info::ToplevelInfoState, toplevel_management::{ManagementCapabilities, ToplevelManagementState}, workspace::{WorkspaceState, WorkspaceUpdateGuard}, @@ -80,6 +79,8 @@ use smithay::{ fractional_scale::{FractionalScaleManagerState, with_fractional_scale}, idle_inhibit::IdleInhibitManagerState, idle_notify::IdleNotifierState, + image_capture_source::{OutputCaptureSourceState, ToplevelCaptureSourceState}, + image_copy_capture::ImageCopyCaptureState, input_method::InputMethodManagerState, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState, @@ -260,8 +261,10 @@ pub struct Common { pub primary_selection_state: PrimarySelectionState, pub ext_data_control_state: ExtDataControlState, pub wlr_data_control_state: WlrDataControlState, - pub image_capture_source_state: ImageCaptureSourceState, - pub screencopy_state: ScreencopyState, + pub cosmic_image_capture_source_state: CosmicImageCaptureSourceState, + pub output_capture_source_state: OutputCaptureSourceState, + pub toplevel_capture_source_state: ToplevelCaptureSourceState, + pub image_copy_capture_state: ImageCopyCaptureState, pub seat_state: SeatState, pub session_lock_manager_state: SessionLockManagerState, pub idle_notifier_state: IdleNotifierState, @@ -645,9 +648,14 @@ impl State { OverlapNotifyState::new::(dh, client_has_no_security_context); let presentation_state = PresentationState::new::(dh, clock.id() as u32); let primary_selection_state = PrimarySelectionState::new::(dh); - let image_capture_source_state = - ImageCaptureSourceState::new::(dh, client_not_sandboxed); - let screencopy_state = ScreencopyState::new::(dh, client_not_sandboxed); + let cosmic_image_capture_source_state = + CosmicImageCaptureSourceState::new::(dh, client_not_sandboxed); + let output_capture_source_state = + OutputCaptureSourceState::new_with_filter::(&dh, client_not_sandboxed); + let toplevel_capture_source_state = + ToplevelCaptureSourceState::new_with_filter::(&dh, client_not_sandboxed); + let image_copy_capture_state = + ImageCopyCaptureState::new_with_filter::(dh, client_not_sandboxed); let shm_state = ShmState::new::(dh, vec![wl_shm::Format::Xbgr8888, wl_shm::Format::Abgr8888]); let cursor_shape_manager_state = CursorShapeManagerState::new::(dh); @@ -754,8 +762,10 @@ impl State { idle_notifier_state, idle_inhibit_manager_state, idle_inhibiting_surfaces, - image_capture_source_state, - screencopy_state, + cosmic_image_capture_source_state, + output_capture_source_state, + toplevel_capture_source_state, + image_copy_capture_state, shm_state, cursor_shape_manager_state, seat_state, diff --git a/src/wayland/handlers/image_capture_source.rs b/src/wayland/handlers/image_capture_source.rs index 686b2aaa..7e2e347a 100644 --- a/src/wayland/handlers/image_capture_source.rs +++ b/src/wayland/handlers/image_capture_source.rs @@ -1,4 +1,59 @@ -use crate::state::State; -use crate::wayland::protocols::image_capture_source::delegate_image_capture_source; +// SPDX-License-Identifier: GPL-3.0-only -delegate_image_capture_source!(State); +use crate::{ + state::State, + wayland::protocols::{ + image_capture_source::{ImageCaptureSourceKind, delegate_cosmic_image_capture_source}, + toplevel_info::window_from_ext, + }, +}; +use smithay::{ + output::Output, + wayland::{ + foreign_toplevel_list::ForeignToplevelHandle, + image_capture_source::{ + ImageCaptureSource, ImageCaptureSourceHandler, OutputCaptureSourceHandler, + OutputCaptureSourceState, ToplevelCaptureSourceHandler, ToplevelCaptureSourceState, + }, + }, +}; + +impl ImageCaptureSourceHandler for State { + fn source_destroyed(&mut self, _source: ImageCaptureSource) {} +} + +impl OutputCaptureSourceHandler for State { + fn output_capture_source_state(&mut self) -> &mut OutputCaptureSourceState { + &mut self.common.output_capture_source_state + } + + fn output_source_created(&mut self, source: ImageCaptureSource, output: &Output) { + source + .user_data() + .insert_if_missing(|| ImageCaptureSourceKind::Output(output.downgrade())); + } +} + +impl ToplevelCaptureSourceHandler for State { + fn toplevel_capture_source_state(&mut self) -> &mut ToplevelCaptureSourceState { + &mut self.common.toplevel_capture_source_state + } + + fn toplevel_source_created( + &mut self, + source: ImageCaptureSource, + toplevel: &ForeignToplevelHandle, + ) { + let data = match window_from_ext(self, toplevel) { + Some(toplevel) => ImageCaptureSourceKind::Toplevel(toplevel.clone()), + None => ImageCaptureSourceKind::Destroyed, + }; + source.user_data().insert_if_missing(|| data); + } +} + +smithay::delegate_image_capture_source!(State); +smithay::delegate_output_capture_source!(State); +smithay::delegate_toplevel_capture_source!(State); + +delegate_cosmic_image_capture_source!(State); diff --git a/src/wayland/handlers/screencopy/mod.rs b/src/wayland/handlers/image_copy_capture/mod.rs similarity index 78% rename from src/wayland/handlers/screencopy/mod.rs rename to src/wayland/handlers/image_copy_capture/mod.rs index 379b04ad..a63365c2 100644 --- a/src/wayland/handlers/screencopy/mod.rs +++ b/src/wayland/handlers/image_copy_capture/mod.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-only + use std::{borrow::Borrow, collections::HashMap, sync::Mutex}; use smithay::{ @@ -15,7 +17,15 @@ use smithay::{ output::Output, reexports::wayland_server::protocol::wl_shm::Format as ShmFormat, utils::{Buffer as BufferCoords, Point, Size, Transform}, - wayland::{dmabuf::get_dmabuf, seat::WaylandFocus}, + wayland::{ + dmabuf::get_dmabuf, + image_capture_source::ImageCaptureSource, + image_copy_capture::{ + BufferConstraints, CursorSession, CursorSessionRef, DmabufConstraints, Frame, FrameRef, + ImageCopyCaptureHandler, ImageCopyCaptureState, Session, SessionRef, + }, + seat::WaylandFocus, + }, }; use crate::{ @@ -24,45 +34,41 @@ use crate::{ utils::prelude::{ OutputExt, PointExt, PointGlobalExt, PointLocalExt, RectExt, RectLocalExt, SeatExt, }, - wayland::protocols::{ - image_capture_source::ImageCaptureSourceData, - screencopy::{ - BufferConstraints, CursorSession, CursorSessionRef, DmabufConstraints, Frame, FrameRef, - ScreencopyHandler, ScreencopyState, Session, SessionRef, delegate_screencopy, - }, - }, + wayland::protocols::image_capture_source::ImageCaptureSourceKind, }; mod render; mod user_data; pub use self::render::*; use self::user_data::*; -pub use self::user_data::{FrameHolder, ScreencopySessions, SessionData, SessionHolder}; +pub use self::user_data::{FrameHolder, ImageCopySessions, SessionData, SessionHolder}; -impl ScreencopyHandler for State { - fn screencopy_state(&mut self) -> &mut ScreencopyState { - &mut self.common.screencopy_state +impl ImageCopyCaptureHandler for State { + fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState { + &mut self.common.image_copy_capture_state } - fn capture_source(&mut self, source: &ImageCaptureSourceData) -> Option { - match source { - ImageCaptureSourceData::Output(weak) => weak + fn capture_constraints(&mut self, source: &ImageCaptureSource) -> Option { + let kind = source.user_data().get::().unwrap(); + match kind { + ImageCaptureSourceKind::Output(weak) => weak .upgrade() .and_then(|output| constraints_for_output(&output, &mut self.backend)), - ImageCaptureSourceData::Workspace(handle) => { + ImageCaptureSourceKind::Workspace(handle) => { let shell = self.common.shell.read(); let output = shell.workspaces.space_for_handle(handle)?.output(); constraints_for_output(output, &mut self.backend) } - ImageCaptureSourceData::Toplevel(window) => { + ImageCaptureSourceKind::Toplevel(window) => { constraints_for_toplevel(window, &mut self.backend) } _ => None, } } - fn capture_cursor_source( + + fn cursor_capture_constraints( &mut self, - _source: &ImageCaptureSourceData, + _source: &ImageCaptureSource, ) -> Option { let size = if let Some((geometry, _)) = self .common @@ -85,8 +91,14 @@ impl ScreencopyHandler for State { } fn new_session(&mut self, session: Session) { - match session.source() { - ImageCaptureSourceData::Output(weak) => { + let kind = session + .source() + .user_data() + .get::() + .unwrap() + .clone(); + match kind { + ImageCaptureSourceKind::Output(weak) => { let Some(mut output) = weak.upgrade() else { session.stop(); return; @@ -100,7 +112,7 @@ impl ScreencopyHandler for State { output.add_session(session); } - ImageCaptureSourceData::Workspace(handle) => { + ImageCaptureSourceKind::Workspace(handle) => { let mut shell = self.common.shell.write(); let Some(workspace) = shell.workspaces.space_for_handle_mut(&handle) else { session.stop(); @@ -114,7 +126,7 @@ impl ScreencopyHandler for State { }); workspace.add_session(session); } - ImageCaptureSourceData::Toplevel(mut toplevel) => { + ImageCaptureSourceKind::Toplevel(mut toplevel) => { let size = toplevel.geometry().size.to_physical(1); session.user_data().insert_if_missing_threadsafe(|| { Mutex::new(SessionUserData::new(OutputDamageTracker::new( @@ -125,9 +137,10 @@ impl ScreencopyHandler for State { }); toplevel.add_session(session); } - ImageCaptureSourceData::Destroyed => unreachable!(), + ImageCaptureSourceKind::Destroyed => unreachable!(), } } + fn new_cursor_session(&mut self, session: CursorSession) { let (pointer_loc, pointer_size, hotspot) = { let seat = self.common.shell.read().seats.last_active().clone(); @@ -154,8 +167,14 @@ impl ScreencopyHandler for State { ))) }); - match session.source() { - ImageCaptureSourceData::Output(weak) => { + let kind = session + .source() + .user_data() + .get::() + .unwrap() + .clone(); + match kind { + ImageCaptureSourceKind::Output(weak) => { let Some(mut output) = weak.upgrade() else { return; }; @@ -184,7 +203,7 @@ impl ScreencopyHandler for State { output.add_cursor_session(session); } - ImageCaptureSourceData::Workspace(handle) => { + ImageCaptureSourceKind::Workspace(handle) => { let mut shell = self.common.shell.write(); let Some(workspace) = shell.workspaces.space_for_handle_mut(&handle) else { return; @@ -215,7 +234,7 @@ impl ScreencopyHandler for State { workspace.add_cursor_session(session); } - ImageCaptureSourceData::Toplevel(mut toplevel) => { + ImageCaptureSourceKind::Toplevel(mut toplevel) => { let shell = self.common.shell.read(); if let Some(element) = shell.element_for_surface(&toplevel) { if element.has_active_window(&toplevel) { @@ -238,38 +257,44 @@ impl ScreencopyHandler for State { toplevel.add_cursor_session(session); } - ImageCaptureSourceData::Destroyed => unreachable!(), + ImageCaptureSourceKind::Destroyed => unreachable!(), } } - fn frame(&mut self, session: SessionRef, frame: Frame) { - match session.source() { - ImageCaptureSourceData::Output(weak) => { + fn frame(&mut self, session: &SessionRef, frame: Frame) { + let kind = session + .source() + .user_data() + .get::() + .unwrap() + .clone(); + match kind { + ImageCaptureSourceKind::Output(weak) => { let Some(mut output) = weak.upgrade() else { return; }; - output.add_frame(session, frame); + output.add_frame(session.clone(), frame); self.backend.schedule_render(&output); } - ImageCaptureSourceData::Workspace(handle) => { + ImageCaptureSourceKind::Workspace(handle) => { render_workspace_to_buffer(self, session, frame, handle) } - ImageCaptureSourceData::Toplevel(toplevel) => { + ImageCaptureSourceKind::Toplevel(toplevel) => { render_window_to_buffer(self, session, frame, &toplevel) } - ImageCaptureSourceData::Destroyed => unreachable!(), + ImageCaptureSourceKind::Destroyed => unreachable!(), } } - fn cursor_frame(&mut self, session: CursorSessionRef, frame: Frame) { + fn cursor_frame(&mut self, session: &CursorSessionRef, frame: Frame) { if !session.has_cursor() { frame.success(Transform::Normal, Vec::new(), self.common.clock.now()); return; } let seat = self.common.shell.read().seats.last_active().clone(); - render_cursor_to_buffer(self, &session, frame, &seat); + render_cursor_to_buffer(self, session, frame, &seat); } fn frame_aborted(&mut self, frame: FrameRef) { @@ -280,13 +305,19 @@ impl ScreencopyHandler for State { } fn session_destroyed(&mut self, session: SessionRef) { - match session.source() { - ImageCaptureSourceData::Output(weak) => { + let kind = session + .source() + .user_data() + .get::() + .unwrap() + .clone(); + match kind { + ImageCaptureSourceKind::Output(weak) => { if let Some(mut output) = weak.upgrade() { output.remove_session(&session); } } - ImageCaptureSourceData::Workspace(handle) => { + ImageCaptureSourceKind::Workspace(handle) => { if let Some(workspace) = self .common .shell @@ -297,19 +328,25 @@ impl ScreencopyHandler for State { workspace.remove_session(&session) } } - ImageCaptureSourceData::Toplevel(mut toplevel) => toplevel.remove_session(&session), - ImageCaptureSourceData::Destroyed => unreachable!(), + ImageCaptureSourceKind::Toplevel(mut toplevel) => toplevel.remove_session(&session), + ImageCaptureSourceKind::Destroyed => unreachable!(), } } fn cursor_session_destroyed(&mut self, session: CursorSessionRef) { - match session.source() { - ImageCaptureSourceData::Output(weak) => { + let kind = session + .source() + .user_data() + .get::() + .unwrap() + .clone(); + match kind { + ImageCaptureSourceKind::Output(weak) => { if let Some(mut output) = weak.upgrade() { output.remove_cursor_session(&session); } } - ImageCaptureSourceData::Workspace(handle) => { + ImageCaptureSourceKind::Workspace(handle) => { if let Some(workspace) = self .common .shell @@ -320,10 +357,10 @@ impl ScreencopyHandler for State { workspace.remove_cursor_session(&session) } } - ImageCaptureSourceData::Toplevel(mut toplevel) => { + ImageCaptureSourceKind::Toplevel(mut toplevel) => { toplevel.remove_cursor_session(&session) } - ImageCaptureSourceData::Destroyed => unreachable!(), + ImageCaptureSourceKind::Destroyed => unreachable!(), } } } @@ -413,4 +450,4 @@ fn constraints_for_renderer( constraints } -delegate_screencopy!(State); +smithay::delegate_image_copy_capture!(State); diff --git a/src/wayland/handlers/screencopy/render.rs b/src/wayland/handlers/image_copy_capture/render.rs similarity index 97% rename from src/wayland/handlers/screencopy/render.rs rename to src/wayland/handlers/image_copy_capture/render.rs index 0013e099..346534ed 100644 --- a/src/wayland/handlers/screencopy/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-only + use calloop::LoopHandle; use smithay::{ backend::{ @@ -26,6 +28,9 @@ use smithay::{ }, wayland::{ dmabuf::get_dmabuf, + image_copy_capture::{ + BufferConstraints, CaptureFailureReason, CursorSessionRef, Frame, SessionRef, + }, seat::WaylandFocus, shm::{shm_format_to_fourcc, with_buffer_contents, with_buffer_contents_mut}, }, @@ -43,13 +48,10 @@ use crate::{ state::{Common, KmsNodes, State}, utils::prelude::{PointExt, PointGlobalExt, RectExt, RectLocalExt, SeatExt}, wayland::{ - handlers::screencopy::{ + handlers::image_copy_capture::{ SessionData, SessionUserData, constraints_for_output, constraints_for_toplevel, }, - protocols::{ - screencopy::{BufferConstraints, CursorSessionRef, FailureReason, Frame, SessionRef}, - workspace::WorkspaceHandle, - }, + protocols::workspace::WorkspaceHandle, }, }; @@ -165,7 +167,7 @@ where .map_err(|err| R::Error::from_gles_error(GlesError::BufferAccessError(err))) .and_then(|x| x) { - frame.fail(FailureReason::Unknown); + frame.fail(CaptureFailureReason::Unknown); return Err(err); } } @@ -249,7 +251,7 @@ where ) .map_err(DTError::Rendering), Err(err) => { - frame.fail(FailureReason::Unknown); + frame.fail(CaptureFailureReason::Unknown); Err(err) } } @@ -257,7 +259,7 @@ where pub fn render_workspace_to_buffer( state: &mut State, - session: SessionRef, + session: &SessionRef, frame: Frame, handle: WorkspaceHandle, ) { @@ -278,14 +280,14 @@ pub fn render_workspace_to_buffer( let buffer_size = buffer_dimensions(&buffer).unwrap(); if mode != Some(buffer_size) { let Some(constraints) = constraints_for_output(&output, &mut state.backend) else { - output.remove_session(&session); + output.remove_session(session); return; }; session.update_constraints(constraints); if let Some(data) = session.user_data().get::() { *data.lock().unwrap() = SessionUserData::new(OutputDamageTracker::from_output(&output)); } - frame.fail(FailureReason::BufferConstraints); + frame.fail(CaptureFailureReason::BufferConstraints); return; } @@ -414,7 +416,7 @@ pub fn render_workspace_to_buffer( Ok(renderer) => renderer, Err(err) => { warn!(?err, "Couldn't use node for screencopy"); - frame.fail(FailureReason::Unknown); + frame.fail(CaptureFailureReason::Unknown); return; } }; @@ -495,12 +497,12 @@ smithay::render_elements! { pub fn render_window_to_buffer( state: &mut State, - session: SessionRef, + session: &SessionRef, frame: Frame, toplevel: &CosmicSurface, ) { if !toplevel.alive() { - toplevel.clone().remove_session(&session); + toplevel.clone().remove_session(session); return; } @@ -509,7 +511,7 @@ pub fn render_window_to_buffer( let buffer_size = buffer_dimensions(&buffer).unwrap(); if buffer_size != geometry.size.to_buffer(1, Transform::Normal) { let Some(constraints) = constraints_for_toplevel(toplevel, &mut state.backend) else { - toplevel.clone().remove_session(&session); + toplevel.clone().remove_session(session); return; }; session.update_constraints(constraints); @@ -518,7 +520,7 @@ pub fn render_window_to_buffer( *data.lock().unwrap() = SessionUserData::new(OutputDamageTracker::new(size, 1.0, Transform::Normal)); } - frame.fail(FailureReason::BufferConstraints); + frame.fail(CaptureFailureReason::BufferConstraints); return; } @@ -666,7 +668,7 @@ pub fn render_window_to_buffer( Ok(renderer) => renderer, Err(err) => { warn!(?err, "Couldn't use node for screencopy"); - frame.fail(FailureReason::Unknown); + frame.fail(CaptureFailureReason::Unknown); return; } }; @@ -760,7 +762,7 @@ pub fn render_cursor_to_buffer( Transform::Normal, )); } - frame.fail(FailureReason::BufferConstraints); + frame.fail(CaptureFailureReason::BufferConstraints); return; } @@ -826,7 +828,7 @@ pub fn render_cursor_to_buffer( Ok(renderer) => renderer, Err(err) => { warn!(?err, "Couldn't use node for screencopy"); - frame.fail(FailureReason::Unknown); + frame.fail(CaptureFailureReason::Unknown); return; } }; diff --git a/src/wayland/handlers/screencopy/user_data.rs b/src/wayland/handlers/image_copy_capture/user_data.rs similarity index 79% rename from src/wayland/handlers/screencopy/user_data.rs rename to src/wayland/handlers/image_copy_capture/user_data.rs index 0a499504..3188cb39 100644 --- a/src/wayland/handlers/screencopy/user_data.rs +++ b/src/wayland/handlers/image_copy_capture/user_data.rs @@ -1,20 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-only + use std::{cell::RefCell, collections::HashMap, sync::Mutex}; use smithay::{ backend::renderer::{damage::OutputDamageTracker, utils::CommitCounter}, output::Output, reexports::wayland_server::{Resource, Weak, protocol::wl_buffer::WlBuffer}, -}; - -use crate::{ - shell::{CosmicSurface, Workspace}, - wayland::protocols::screencopy::{ + wayland::image_copy_capture::{ CursorSession, CursorSessionRef, Frame, FrameRef, Session, SessionRef, }, }; -type ScreencopySessionsData = RefCell; -type PendingScreencopyBuffers = Mutex>; +use crate::shell::{CosmicSurface, Workspace}; + +type ImageCopySessionsData = RefCell; +type PendingImageCopyBuffers = Mutex>; pub type SessionData = Mutex; @@ -54,7 +54,7 @@ impl SessionUserData { } #[derive(Debug, Default)] -pub struct ScreencopySessions { +pub struct ImageCopySessions { sessions: Vec, cursor_sessions: Vec, } @@ -78,9 +78,9 @@ pub trait FrameHolder { impl SessionHolder for Output { fn add_session(&mut self, session: Session) { self.user_data() - .insert_if_missing(ScreencopySessionsData::default); + .insert_if_missing(ImageCopySessionsData::default); self.user_data() - .get::() + .get::() .unwrap() .borrow_mut() .sessions @@ -89,7 +89,7 @@ impl SessionHolder for Output { fn remove_session(&mut self, session: &SessionRef) { self.user_data() - .get::() + .get::() .unwrap() .borrow_mut() .sessions @@ -98,7 +98,7 @@ impl SessionHolder for Output { fn sessions(&self) -> Vec { self.user_data() - .get::() + .get::() .map_or(Vec::new(), |sessions| { sessions .borrow() @@ -111,9 +111,9 @@ impl SessionHolder for Output { fn add_cursor_session(&mut self, session: CursorSession) { self.user_data() - .insert_if_missing(ScreencopySessionsData::default); + .insert_if_missing(ImageCopySessionsData::default); self.user_data() - .get::() + .get::() .unwrap() .borrow_mut() .cursor_sessions @@ -122,7 +122,7 @@ impl SessionHolder for Output { fn remove_cursor_session(&mut self, session: &CursorSessionRef) { self.user_data() - .get::() + .get::() .unwrap() .borrow_mut() .cursor_sessions @@ -131,7 +131,7 @@ impl SessionHolder for Output { fn cursor_sessions(&self) -> Vec { self.user_data() - .get::() + .get::() .map_or(Vec::new(), |sessions| { sessions .borrow() @@ -146,22 +146,22 @@ impl SessionHolder for Output { impl FrameHolder for Output { fn add_frame(&mut self, session: SessionRef, frame: Frame) { self.user_data() - .insert_if_missing_threadsafe(PendingScreencopyBuffers::default); + .insert_if_missing_threadsafe(PendingImageCopyBuffers::default); self.user_data() - .get::() + .get::() .unwrap() .lock() .unwrap() .push((session, frame)); } fn remove_frame(&mut self, frame: &FrameRef) { - if let Some(pending) = self.user_data().get::() { + if let Some(pending) = self.user_data().get::() { pending.lock().unwrap().retain(|(_, f)| f != frame); } } fn take_pending_frames(&self) -> Vec<(SessionRef, Frame)> { self.user_data() - .get::() + .get::() .map(|pending| std::mem::take(&mut *pending.lock().unwrap())) .unwrap_or_default() } @@ -169,14 +169,14 @@ impl FrameHolder for Output { impl SessionHolder for Workspace { fn add_session(&mut self, session: Session) { - self.screencopy.sessions.push(session); + self.image_copy.sessions.push(session); } fn remove_session(&mut self, session: &SessionRef) { - self.screencopy.sessions.retain(|s| s != session); + self.image_copy.sessions.retain(|s| s != session); } fn sessions(&self) -> Vec { - self.screencopy + self.image_copy .sessions .iter() .map(|s| (*s).clone()) @@ -184,14 +184,14 @@ impl SessionHolder for Workspace { } fn add_cursor_session(&mut self, session: CursorSession) { - self.screencopy.cursor_sessions.push(session); + self.image_copy.cursor_sessions.push(session); } fn remove_cursor_session(&mut self, session: &CursorSessionRef) { - self.screencopy.cursor_sessions.retain(|s| s != session); + self.image_copy.cursor_sessions.retain(|s| s != session); } fn cursor_sessions(&self) -> Vec { - self.screencopy + self.image_copy .cursor_sessions .iter() .map(|s| (*s).clone()) @@ -202,9 +202,9 @@ impl SessionHolder for Workspace { impl SessionHolder for CosmicSurface { fn add_session(&mut self, session: Session) { self.user_data() - .insert_if_missing(ScreencopySessionsData::default); + .insert_if_missing(ImageCopySessionsData::default); self.user_data() - .get::() + .get::() .unwrap() .borrow_mut() .sessions @@ -213,7 +213,7 @@ impl SessionHolder for CosmicSurface { fn remove_session(&mut self, session: &SessionRef) { self.user_data() - .get::() + .get::() .unwrap() .borrow_mut() .sessions @@ -221,7 +221,7 @@ impl SessionHolder for CosmicSurface { } fn sessions(&self) -> Vec { self.user_data() - .get::() + .get::() .map_or(Vec::new(), |sessions| { sessions .borrow() @@ -234,9 +234,9 @@ impl SessionHolder for CosmicSurface { fn add_cursor_session(&mut self, session: CursorSession) { self.user_data() - .insert_if_missing(ScreencopySessionsData::default); + .insert_if_missing(ImageCopySessionsData::default); self.user_data() - .get::() + .get::() .unwrap() .borrow_mut() .cursor_sessions @@ -245,7 +245,7 @@ impl SessionHolder for CosmicSurface { fn remove_cursor_session(&mut self, session: &CursorSessionRef) { self.user_data() - .get::() + .get::() .unwrap() .borrow_mut() .cursor_sessions @@ -254,7 +254,7 @@ impl SessionHolder for CosmicSurface { fn cursor_sessions(&self) -> Vec { self.user_data() - .get::() + .get::() .map_or(Vec::new(), |sessions| { sessions .borrow() diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index b057c8c2..06fd816f 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -17,6 +17,7 @@ pub mod fractional_scale; pub mod idle_inhibit; pub mod idle_notify; pub mod image_capture_source; +pub mod image_copy_capture; pub mod input_method; pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; @@ -29,7 +30,6 @@ pub mod pointer_gestures; pub mod presentation; pub mod primary_selection; pub mod relative_pointer; -pub mod screencopy; pub mod seat; pub mod security_context; pub mod selection; diff --git a/src/wayland/protocols/image_capture_source.rs b/src/wayland/protocols/image_capture_source.rs index f71eaca9..1214cbe0 100644 --- a/src/wayland/protocols/image_capture_source.rs +++ b/src/wayland/protocols/image_capture_source.rs @@ -1,10 +1,8 @@ use super::{ - toplevel_info::window_from_ext_handle, workspace::{WorkspaceHandle, WorkspaceHandler}, }; use crate::{ shell::CosmicSurface, - wayland::protocols::toplevel_info::ToplevelInfoHandler, }; use cosmic_protocols::image_capture_source::v1::server::{ zcosmic_workspace_image_capture_source_manager_v1::{ @@ -12,75 +10,54 @@ use cosmic_protocols::image_capture_source::v1::server::{ }, }; use smithay::reexports::wayland_protocols::ext::image_capture_source::v1::server::{ - ext_foreign_toplevel_image_capture_source_manager_v1::{ - Request as ToplevelSourceRequest, ExtForeignToplevelImageCaptureSourceManagerV1, - }, ext_image_capture_source_v1::ExtImageCaptureSourceV1, - ext_output_image_capture_source_manager_v1::{ - Request as OutputSourceRequest, ExtOutputImageCaptureSourceManagerV1, - }, }; use smithay::{ - output::{Output, WeakOutput}, + output::WeakOutput, reexports::wayland_server::{ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, }, + wayland::image_capture_source::{ImageCaptureSource, ImageCaptureSourceData}, }; use wayland_backend::server::GlobalId; #[derive(Debug)] -pub struct ImageCaptureSourceState { - output_source_global: GlobalId, +pub struct CosmicImageCaptureSourceState { workspace_source_global: GlobalId, - toplevel_source_global: GlobalId, } -pub struct OutputImageCaptureSourceManagerGlobalData { - filter: Box Fn(&'a Client) -> bool + Send + Sync>, -} pub struct WorkspaceImageCaptureSourceManagerGlobalData { filter: Box Fn(&'a Client) -> bool + Send + Sync>, } -pub struct ToplevelImageCaptureSourceManagerGlobalData { - filter: Box Fn(&'a Client) -> bool + Send + Sync>, -} #[derive(Debug, Clone, PartialEq)] -pub enum ImageCaptureSourceData { +pub enum ImageCaptureSourceKind { Output(WeakOutput), Workspace(WorkspaceHandle), Toplevel(CosmicSurface), Destroyed, } -impl ImageCaptureSourceState { - pub fn new(display: &DisplayHandle, client_filter: F) -> ImageCaptureSourceState +impl ImageCaptureSourceKind { + pub fn from_resource(resource: &ExtImageCaptureSourceV1) -> Option { + let source = ImageCaptureSource::from_resource(resource)?; + source.user_data().get::().cloned() + } +} + +impl CosmicImageCaptureSourceState { + pub fn new(display: &DisplayHandle, client_filter: F) -> CosmicImageCaptureSourceState where D: GlobalDispatch< - ExtOutputImageCaptureSourceManagerV1, - OutputImageCaptureSourceManagerGlobalData, - > + Dispatch - + GlobalDispatch< ZcosmicWorkspaceImageCaptureSourceManagerV1, WorkspaceImageCaptureSourceManagerGlobalData, > + Dispatch - + GlobalDispatch< - ExtForeignToplevelImageCaptureSourceManagerV1, - ToplevelImageCaptureSourceManagerGlobalData, - > + Dispatch + Dispatch + WorkspaceHandler + 'static, F: for<'a> Fn(&'a Client) -> bool + Send + Sync + Clone + 'static, { - ImageCaptureSourceState { - output_source_global: display - .create_global::( - 1, - OutputImageCaptureSourceManagerGlobalData { - filter: Box::new(client_filter.clone()), - }, - ), + CosmicImageCaptureSourceState { workspace_source_global: display .create_global::( 1, @@ -88,57 +65,12 @@ impl ImageCaptureSourceState { filter: Box::new(client_filter.clone()), }, ), - toplevel_source_global: display - .create_global::( - 1, - ToplevelImageCaptureSourceManagerGlobalData { - filter: Box::new(client_filter), - }, - ), } } - pub fn output_source_id(&self) -> &GlobalId { - &self.output_source_global - } - pub fn workspace_source_id(&self) -> &GlobalId { &self.workspace_source_global } - - pub fn toplevel_source_id(&self) -> &GlobalId { - &self.toplevel_source_global - } -} - -impl - GlobalDispatch< - ExtOutputImageCaptureSourceManagerV1, - OutputImageCaptureSourceManagerGlobalData, - D, - > for ImageCaptureSourceState -where - D: GlobalDispatch< - ExtOutputImageCaptureSourceManagerV1, - OutputImageCaptureSourceManagerGlobalData, - > + Dispatch - + Dispatch - + 'static, -{ - fn bind( - _state: &mut D, - _handle: &DisplayHandle, - _client: &Client, - resource: New, - _global_data: &OutputImageCaptureSourceManagerGlobalData, - data_init: &mut DataInit<'_, D>, - ) { - data_init.init(resource, ()); - } - - fn can_view(client: Client, global_data: &OutputImageCaptureSourceManagerGlobalData) -> bool { - (global_data.filter)(&client) - } } impl @@ -146,7 +78,7 @@ impl ZcosmicWorkspaceImageCaptureSourceManagerV1, WorkspaceImageCaptureSourceManagerGlobalData, D, - > for ImageCaptureSourceState + > for CosmicImageCaptureSourceState where D: GlobalDispatch< ZcosmicWorkspaceImageCaptureSourceManagerV1, @@ -174,70 +106,8 @@ where } } -impl - GlobalDispatch< - ExtForeignToplevelImageCaptureSourceManagerV1, - ToplevelImageCaptureSourceManagerGlobalData, - D, - > for ImageCaptureSourceState -where - D: GlobalDispatch< - ExtForeignToplevelImageCaptureSourceManagerV1, - ToplevelImageCaptureSourceManagerGlobalData, - > + Dispatch - + Dispatch - + 'static, -{ - fn bind( - _state: &mut D, - _handle: &DisplayHandle, - _client: &Client, - resource: New, - _global_data: &ToplevelImageCaptureSourceManagerGlobalData, - data_init: &mut DataInit<'_, D>, - ) { - data_init.init(resource, ()); - } - - fn can_view(client: Client, global_data: &ToplevelImageCaptureSourceManagerGlobalData) -> bool { - (global_data.filter)(&client) - } -} - -impl Dispatch for ImageCaptureSourceState -where - D: Dispatch - + Dispatch - + 'static, -{ - fn request( - _state: &mut D, - _client: &Client, - _resource: &ExtOutputImageCaptureSourceManagerV1, - request: ::Request, - _data: &(), - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, D>, - ) { - if let OutputSourceRequest::CreateSource { source, output } = request { - let data = match Output::from_resource(&output) { - Some(output) => ImageCaptureSourceData::Output(output.downgrade()), - None => ImageCaptureSourceData::Destroyed, - }; - data_init.init(source, data); - } - } - - fn destroyed( - _state: &mut D, - _client: wayland_backend::server::ClientId, - _resource: &ExtOutputImageCaptureSourceManagerV1, - _data: &(), - ) { - } -} - -impl Dispatch for ImageCaptureSourceState +impl Dispatch + for CosmicImageCaptureSourceState where D: Dispatch + Dispatch @@ -253,110 +123,36 @@ where _dhandle: &DisplayHandle, data_init: &mut DataInit<'_, D>, ) { - if let CosmicWorkspaceSourceRequest::CreateSource { source, output } = request { - let data = match state.workspace_state().get_ext_workspace_handle(&output) { - Some(workspace) => ImageCaptureSourceData::Workspace(workspace), - None => ImageCaptureSourceData::Destroyed, - }; - data_init.init(source, data); - } - } - - fn destroyed( - _state: &mut D, - _client: wayland_backend::server::ClientId, - _resource: &ZcosmicWorkspaceImageCaptureSourceManagerV1, - _data: &(), - ) { - } -} - -impl Dispatch for ImageCaptureSourceState -where - D: Dispatch - + Dispatch - + ToplevelInfoHandler - + 'static, -{ - fn request( - state: &mut D, - _client: &Client, - _resource: &ExtForeignToplevelImageCaptureSourceManagerV1, - request: ::Request, - _data: &(), - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, D>, - ) { - if let ToplevelSourceRequest::CreateSource { - source, - toplevel_handle, + if let CosmicWorkspaceSourceRequest::CreateSource { + source: source_handle, + output, } = request { - let data = match window_from_ext_handle(state, &toplevel_handle) { - Some(toplevel) => ImageCaptureSourceData::Toplevel(toplevel.clone()), - None => ImageCaptureSourceData::Destroyed, + let data = match state.workspace_state().get_ext_workspace_handle(&output) { + Some(workspace) => ImageCaptureSourceKind::Workspace(workspace), + None => ImageCaptureSourceKind::Destroyed, }; - data_init.init(source, data); + let source = ImageCaptureSource::new(); + source.user_data().insert_if_missing(|| data); + let instance = data_init.init( + source_handle, + ImageCaptureSourceData { + source: source.clone(), + }, + ); + source.add_instance(&instance); } } - - fn destroyed( - _state: &mut D, - _client: wayland_backend::server::ClientId, - _resource: &ExtForeignToplevelImageCaptureSourceManagerV1, - _data: &(), - ) { - } } -impl Dispatch for ImageCaptureSourceState -where - D: Dispatch + 'static, -{ - fn request( - _state: &mut D, - _client: &Client, - _resource: &ExtImageCaptureSourceV1, - _request: ::Request, - _data: &ImageCaptureSourceData, - _dhandle: &DisplayHandle, - _data_init: &mut DataInit<'_, D>, - ) { - {} - } - - fn destroyed( - _state: &mut D, - _client: wayland_backend::server::ClientId, - _resource: &ExtImageCaptureSourceV1, - _data: &ImageCaptureSourceData, - ) { - } -} - -macro_rules! delegate_image_capture_source { +macro_rules! delegate_cosmic_image_capture_source { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { - smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_capture_source::v1::server::ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1: $crate::wayland::protocols::image_capture_source::OutputImageCaptureSourceManagerGlobalData - ] => $crate::wayland::protocols::image_capture_source::ImageCaptureSourceState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_capture_source::v1::server::ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1: () - ] => $crate::wayland::protocols::image_capture_source::ImageCaptureSourceState); smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::image_capture_source::v1::server::zcosmic_workspace_image_capture_source_manager_v1::ZcosmicWorkspaceImageCaptureSourceManagerV1: $crate::wayland::protocols::image_capture_source::WorkspaceImageCaptureSourceManagerGlobalData - ] => $crate::wayland::protocols::image_capture_source::ImageCaptureSourceState); + ] => $crate::wayland::protocols::image_capture_source::CosmicImageCaptureSourceState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::image_capture_source::v1::server::zcosmic_workspace_image_capture_source_manager_v1::ZcosmicWorkspaceImageCaptureSourceManagerV1: () - ] => $crate::wayland::protocols::image_capture_source::ImageCaptureSourceState); - smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_capture_source::v1::server::ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1: $crate::wayland::protocols::image_capture_source::ToplevelImageCaptureSourceManagerGlobalData - ] => $crate::wayland::protocols::image_capture_source::ImageCaptureSourceState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_capture_source::v1::server::ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1: () - ] => $crate::wayland::protocols::image_capture_source::ImageCaptureSourceState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_capture_source::v1::server::ext_image_capture_source_v1::ExtImageCaptureSourceV1: $crate::wayland::protocols::image_capture_source::ImageCaptureSourceData - ] => $crate::wayland::protocols::image_capture_source::ImageCaptureSourceState); + ] => $crate::wayland::protocols::image_capture_source::CosmicImageCaptureSourceState); }; } -pub(crate) use delegate_image_capture_source; +pub(crate) use delegate_cosmic_image_capture_source; diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 99e39592..57e05b8a 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -7,7 +7,6 @@ pub mod image_capture_source; pub mod output_configuration; pub mod output_power; pub mod overlap_notify; -pub mod screencopy; pub mod toplevel_info; pub mod toplevel_management; pub mod workspace; diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs deleted file mode 100644 index ca756d92..00000000 --- a/src/wayland/protocols/screencopy.rs +++ /dev/null @@ -1,1158 +0,0 @@ -use std::{ - ops, - sync::{Arc, Mutex}, - time::Duration, -}; - -pub use smithay::reexports::wayland_protocols::ext::image_copy_capture::v1::server::{ - ext_image_copy_capture_frame_v1::FailureReason, -}; -use smithay::reexports::wayland_protocols::ext::image_copy_capture::v1::server::{ - ext_image_copy_capture_cursor_session_v1::{self, ExtImageCopyCaptureCursorSessionV1}, - ext_image_copy_capture_frame_v1::{self, ExtImageCopyCaptureFrameV1}, - ext_image_copy_capture_manager_v1::{self, ExtImageCopyCaptureManagerV1}, - ext_image_copy_capture_session_v1::{self, ExtImageCopyCaptureSessionV1}, -}; -use smithay::{ - backend::{ - allocator::{Buffer, Fourcc, Modifier}, - drm::DrmNode, - renderer::{buffer_type, BufferType}, - }, - utils::{user_data::UserDataMap, Buffer as BufferCoords, IsAlive, Size, Transform}, - wayland::{dmabuf::get_dmabuf, shm::with_buffer_contents}, -}; -use smithay::{reexports::wayland_server::protocol::wl_buffer::WlBuffer, utils::Point}; -use smithay::{ - reexports::wayland_server::{ - protocol::wl_shm, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, - Weak, - }, - utils::Rectangle, -}; -use tracing::debug; -use wayland_backend::server::GlobalId; - -use super::image_capture_source::ImageCaptureSourceData; - -#[derive(Debug)] -pub struct ScreencopyState { - global: GlobalId, - known_sessions: Vec, - known_cursor_sessions: Vec, -} - -impl ScreencopyState { - pub fn new(display: &DisplayHandle, client_filter: F) -> ScreencopyState - where - D: GlobalDispatch - + Dispatch - + Dispatch - + Dispatch - + Dispatch - + Dispatch - + ScreencopyHandler - + 'static, - F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, - { - ScreencopyState { - global: display.create_global::( - 1, - ScreencopyGlobalData { - filter: Box::new(client_filter), - }, - ), - known_sessions: Vec::new(), - known_cursor_sessions: Vec::new(), - } - } - - pub fn global_id(&self) -> &GlobalId { - &self.global - } -} - -#[derive(Debug, Clone)] -pub struct BufferConstraints { - pub size: Size, - pub shm: Vec, - pub dma: Option, -} - -#[derive(Debug, Clone)] -pub struct DmabufConstraints { - pub node: DrmNode, - pub formats: Vec<(Fourcc, Vec)>, -} - -#[derive(Debug, Clone)] -pub struct SessionRef { - obj: ExtImageCopyCaptureSessionV1, - inner: Arc>, - user_data: Arc, -} - -impl PartialEq for SessionRef { - fn eq(&self, other: &Self) -> bool { - self.obj == other.obj - } -} - -#[derive(Debug)] -struct SessionInner { - stopped: bool, - constraints: Option, - draw_cursors: bool, - source: ImageCaptureSourceData, - active_frames: Vec, -} - -impl SessionInner { - fn new(source: ImageCaptureSourceData, draw_cursors: bool) -> SessionInner { - SessionInner { - stopped: false, - constraints: None, - draw_cursors, - source, - active_frames: Vec::new(), - } - } -} - -impl IsAlive for SessionRef { - fn alive(&self) -> bool { - self.obj.is_alive() - } -} - -impl SessionRef { - pub fn update_constraints(&self, constraints: BufferConstraints) { - let mut inner = self.inner.lock().unwrap(); - - if !self.obj.is_alive() || inner.stopped { - return; - } - - self.obj - .buffer_size(constraints.size.w as u32, constraints.size.h as u32); - for fmt in &constraints.shm { - self.obj.shm_format(*fmt); - } - if let Some(dma) = constraints.dma.as_ref() { - let node = Vec::from(dma.node.dev_id().to_ne_bytes()); - self.obj.dmabuf_device(node); - for (fmt, modifiers) in &dma.formats { - let modifiers = modifiers - .iter() - .flat_map(|modifier| u64::from(*modifier).to_ne_bytes()) - .collect::>(); - self.obj.dmabuf_format(*fmt as u32, modifiers); - } - } - self.obj.done(); - - inner.constraints = Some(constraints); - } - - pub fn current_constraints(&self) -> Option { - self.inner.lock().unwrap().constraints.clone() - } - - pub fn source(&self) -> ImageCaptureSourceData { - self.inner.lock().unwrap().source.clone() - } - - pub fn draw_cursor(&self) -> bool { - self.inner.lock().unwrap().draw_cursors - } - - pub fn user_data(&self) -> &UserDataMap { - &self.user_data - } -} - -#[derive(Debug)] -pub struct Session(SessionRef); - -impl ops::Deref for Session { - type Target = SessionRef; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl PartialEq for Session { - fn eq(&self, other: &SessionRef) -> bool { - self.0 == *other - } -} - -impl Drop for Session { - fn drop(&mut self) { - let mut inner = self.0.inner.lock().unwrap(); - - if !self.obj.is_alive() || inner.stopped { - return; - } - - for frame in inner.active_frames.drain(..) { - frame - .inner - .lock() - .unwrap() - .fail(&frame.obj, FailureReason::Stopped); - } - - self.obj.stopped(); - inner.constraints.take(); - inner.stopped = true; - } -} - -impl Session { - pub fn stop(self) { - let _ = self; - } -} - -#[derive(Debug, Clone)] -pub struct CursorSessionRef { - obj: ExtImageCopyCaptureCursorSessionV1, - inner: Arc>, - user_data: Arc, -} - -impl PartialEq for CursorSessionRef { - fn eq(&self, other: &Self) -> bool { - self.obj == other.obj - } -} - -#[derive(Debug)] -struct CursorSessionInner { - session: Option, - stopped: bool, - constraints: Option, - source: ImageCaptureSourceData, - position: Option>, - hotspot: Point, - active_frames: Vec, -} - -impl CursorSessionInner { - fn new(source: ImageCaptureSourceData) -> CursorSessionInner { - CursorSessionInner { - session: None, - stopped: false, - constraints: None, - source, - position: None, - hotspot: Point::from((0, 0)), - active_frames: Vec::new(), - } - } -} - -impl IsAlive for CursorSessionRef { - fn alive(&self) -> bool { - self.obj.is_alive() - } -} - -impl CursorSessionRef { - pub fn update_constraints(&self, constrains: BufferConstraints) { - let mut inner = self.inner.lock().unwrap(); - - if !self.obj.is_alive() || inner.stopped { - return; - } - - if let Some(session_obj) = inner.session.as_ref() { - session_obj.buffer_size(constrains.size.w as u32, constrains.size.h as u32); - for fmt in &constrains.shm { - session_obj.shm_format(*fmt); - } - if let Some(dma) = constrains.dma.as_ref() { - let node = Vec::from(dma.node.dev_id().to_ne_bytes()); - session_obj.dmabuf_device(node); - for (fmt, modifiers) in &dma.formats { - let modifiers = modifiers - .iter() - .flat_map(|modifier| u64::from(*modifier).to_ne_bytes()) - .collect::>(); - session_obj.dmabuf_format(*fmt as u32, modifiers); - } - } - session_obj.done(); - } - - inner.constraints = Some(constrains); - } - - pub fn current_constraints(&self) -> Option { - self.inner.lock().unwrap().constraints.clone() - } - - pub fn source(&self) -> ImageCaptureSourceData { - self.inner.lock().unwrap().source.clone() - } - - pub fn has_cursor(&self) -> bool { - self.inner.lock().unwrap().position.is_some() - } - - pub fn set_cursor_pos(&self, position: Option>) { - if !self.obj.is_alive() { - return; - } - - let mut inner = self.inner.lock().unwrap(); - - if inner.position == position { - return; - } - - if inner.position.is_none() { - self.obj.enter(); - self.obj.hotspot(inner.hotspot.x, inner.hotspot.y) - } - - if let Some(new_pos) = position { - self.obj.position(new_pos.x, new_pos.y); - } else { - self.obj.leave() - } - - inner.position = position; - } - - pub fn set_cursor_hotspot(&self, hotspot: impl Into>) { - if !self.obj.is_alive() { - return; - } - - let hotspot = hotspot.into(); - - let mut inner = self.inner.lock().unwrap(); - - if inner.hotspot == hotspot { - return; - } - - inner.hotspot = hotspot; - if inner.position.is_some() { - self.obj.hotspot(hotspot.x, hotspot.y); - } - } - - pub fn user_data(&self) -> &UserDataMap { - &self.user_data - } -} - -#[derive(Debug)] -pub struct CursorSession(CursorSessionRef); - -impl ops::Deref for CursorSession { - type Target = CursorSessionRef; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl PartialEq for CursorSession { - fn eq(&self, other: &CursorSessionRef) -> bool { - self.0 == *other - } -} - -impl Drop for CursorSession { - fn drop(&mut self) { - let mut inner = self.0.inner.lock().unwrap(); - - if !self.0.obj.is_alive() || inner.stopped { - return; - } - - if let Some(session_obj) = inner.session.as_ref() { - session_obj.stopped(); - } - inner.constraints.take(); - - for frame in inner.active_frames.drain(..) { - frame - .inner - .lock() - .unwrap() - .fail(&frame.obj, FailureReason::Stopped); - } - - inner.stopped = true; - } -} - -impl CursorSession { - pub fn stop(self) { - let _ = self; - } -} - -/// Un-owned reference to a frame -#[derive(Clone, Debug)] -pub struct FrameRef { - obj: ExtImageCopyCaptureFrameV1, - inner: Arc>, -} - -impl PartialEq for FrameRef { - fn eq(&self, other: &Self) -> bool { - self.obj == other.obj - } -} - -impl FrameRef { - pub fn buffer(&self) -> WlBuffer { - self.inner.lock().unwrap().buffer.clone().unwrap() - } - - pub fn damage(&self) -> Vec> { - self.inner.lock().unwrap().damage.clone() - } - - pub fn has_failed(&self) -> bool { - self.inner.lock().unwrap().failed.is_some() - } -} - -#[derive(Debug, PartialEq)] -pub struct Frame(FrameRef); - -impl ops::Deref for Frame { - type Target = FrameRef; - - fn deref(&self) -> &FrameRef { - &self.0 - } -} - -impl PartialEq for Frame { - fn eq(&self, other: &FrameRef) -> bool { - self.0 == *other - } -} - -impl Frame { - pub fn success( - self, - transform: impl Into, - damage: impl Into>>>, - presented: impl Into, - ) { - { - let inner = self.0.inner.lock().unwrap(); - if !inner.capture_requested || inner.failed.is_some() { - return; - } - } - - self.obj.transform(transform.into().into()); - for damage in damage.into().into_iter().flatten() { - self.obj - .damage(damage.loc.x, damage.loc.y, damage.size.w, damage.size.h); - } - - let time = presented.into(); - let tv_sec_hi = (time.as_secs() >> 32) as u32; - let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32; - let tv_nsec = time.subsec_nanos(); - self.obj.presentation_time(tv_sec_hi, tv_sec_lo, tv_nsec); - - self.0.inner.lock().unwrap().ready = true; - self.obj.ready() - } - - pub fn fail(self, reason: FailureReason) { - self.0.inner.lock().unwrap().fail(&self.obj, reason); - } -} - -impl Drop for Frame { - fn drop(&mut self) { - // Send `fail` is `sucesss` or `fail` not already called - self.inner - .lock() - .unwrap() - .fail(&self.obj, FailureReason::Unknown); - } -} - -#[derive(Debug)] -struct FrameInner { - constraints: Option, - buffer: Option, - damage: Vec>, - // `SessionInner` contains a `Vec`, so use a weak reference here to - // avoid a cycle. - obj: Weak, - capture_requested: bool, - failed: Option, - ready: bool, -} - -impl FrameInner { - fn new( - obj: ExtImageCopyCaptureSessionV1, - constraints: impl Into>, - ) -> Self { - FrameInner { - constraints: constraints.into(), - buffer: None, - damage: Vec::new(), - obj: obj.downgrade(), - capture_requested: false, - failed: None, - ready: false, - } - } - - fn fail(&mut self, frame: &ExtImageCopyCaptureFrameV1, reason: FailureReason) { - if self.ready || self.failed.is_some() { - return; - } - self.failed = Some(reason); - if self.capture_requested { - frame.failed(reason); - } - } -} - -pub trait ScreencopyHandler { - fn screencopy_state(&mut self) -> &mut ScreencopyState; - - fn capture_source(&mut self, source: &ImageCaptureSourceData) -> Option; - fn capture_cursor_source( - &mut self, - source: &ImageCaptureSourceData, - ) -> Option; - - fn new_session(&mut self, session: Session); - fn new_cursor_session(&mut self, session: CursorSession); - - fn frame(&mut self, session: SessionRef, frame: Frame); - fn cursor_frame(&mut self, session: CursorSessionRef, frame: Frame); - - fn frame_aborted(&mut self, frame_handle: FrameRef); - fn session_destroyed(&mut self, session: SessionRef) { - let _ = session; - } - fn cursor_session_destroyed(&mut self, session: CursorSessionRef) { - let _ = session; - } -} - -pub struct ScreencopyGlobalData { - filter: Box Fn(&'a Client) -> bool + Send + Sync>, -} - -pub struct ScreencopyData; - -pub struct SessionData { - inner: Arc>, -} - -pub struct CursorSessionData { - inner: Arc>, -} -pub struct FrameData { - inner: Arc>, -} - -impl GlobalDispatch for ScreencopyState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + Dispatch - + Dispatch - + Dispatch - + ScreencopyHandler - + 'static, -{ - fn bind( - _state: &mut D, - _handle: &DisplayHandle, - _client: &Client, - resource: New, - _global_data: &ScreencopyGlobalData, - data_init: &mut DataInit<'_, D>, - ) { - data_init.init(resource, ScreencopyData); - } - - fn can_view(client: Client, global_data: &ScreencopyGlobalData) -> bool { - (global_data.filter)(&client) - } -} - -impl Dispatch for ScreencopyState -where - D: Dispatch - + Dispatch - + Dispatch - + Dispatch - + Dispatch - + ScreencopyHandler - + 'static, -{ - fn request( - state: &mut D, - _client: &Client, - _resource: &ExtImageCopyCaptureManagerV1, - request: ::Request, - _data: &ScreencopyData, - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, D>, - ) { - match request { - ext_image_copy_capture_manager_v1::Request::CreateSession { - session, - source, - options, - } => { - if let Some(src) = source.data::() { - if *src != ImageCaptureSourceData::Destroyed { - if let Some(buffer_constraints) = state.capture_source(src) { - let session_data = Arc::new(Mutex::new(SessionInner::new( - src.clone(), - Into::::into(options) == 1, - ))); - let obj = data_init.init( - session, - SessionData { - inner: session_data.clone(), - }, - ); - - let session = SessionRef { - obj, - inner: session_data, - user_data: Arc::new(UserDataMap::new()), - }; - session.update_constraints(buffer_constraints); - state - .screencopy_state() - .known_sessions - .push(session.clone()); - state.new_session(Session(session)); - return; - } - } - } - - let session_data = Arc::new(Mutex::new(SessionInner::new( - ImageCaptureSourceData::Destroyed, - false, - ))); - let obj = data_init.init( - session, - SessionData { - inner: session_data.clone(), - }, - ); - let session = Session(SessionRef { - obj, - inner: session_data, - user_data: Arc::new(UserDataMap::new()), - }); - session.stop(); - } - ext_image_copy_capture_manager_v1::Request::CreatePointerCursorSession { - session, - source, - pointer: _, - } => { - // TODO: use pointer, but we need new smithay api for that. - - if let Some(src) = source.data::() { - if *src != ImageCaptureSourceData::Destroyed { - if let Some(buffer_constraints) = state.capture_cursor_source(src) { - let session_data = - Arc::new(Mutex::new(CursorSessionInner::new(src.clone()))); - let obj = data_init.init( - session, - CursorSessionData { - inner: session_data.clone(), - }, - ); - - let session = CursorSessionRef { - obj, - inner: session_data, - user_data: Arc::new(UserDataMap::new()), - }; - session.update_constraints(buffer_constraints); - state - .screencopy_state() - .known_cursor_sessions - .push(session.clone()); - state.new_cursor_session(CursorSession(session)); - return; - } - } - } - - let session_data = Arc::new(Mutex::new(CursorSessionInner::new( - ImageCaptureSourceData::Destroyed, - ))); - let obj = data_init.init( - session, - CursorSessionData { - inner: session_data.clone(), - }, - ); - let session = CursorSession(CursorSessionRef { - obj, - inner: session_data, - user_data: Arc::new(UserDataMap::new()), - }); - session.stop(); - } - _ => {} - } - } - - fn destroyed( - _state: &mut D, - _client: wayland_backend::server::ClientId, - _resource: &ExtImageCopyCaptureManagerV1, - _data: &ScreencopyData, - ) { - } -} - -impl Dispatch for ScreencopyState -where - D: Dispatch - + Dispatch - + ScreencopyHandler - + 'static, -{ - fn request( - _state: &mut D, - _client: &Client, - resource: &ExtImageCopyCaptureSessionV1, - request: ::Request, - data: &SessionData, - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, D>, - ) { - if let ext_image_copy_capture_session_v1::Request::CreateFrame { frame } = request { - let inner = Arc::new(Mutex::new(FrameInner::new( - resource.clone(), - data.inner.lock().unwrap().constraints.clone(), - ))); - let obj = data_init.init( - frame, - FrameData { - inner: inner.clone(), - }, - ); - data.inner - .lock() - .unwrap() - .active_frames - .push(FrameRef { obj, inner }); - } - } - - fn destroyed( - state: &mut D, - _client: wayland_backend::server::ClientId, - resource: &ExtImageCopyCaptureSessionV1, - _data: &SessionData, - ) { - let scpy = state.screencopy_state(); - if let Some(pos) = scpy - .known_sessions - .iter() - .position(|session| session.obj == *resource) - { - let session = scpy.known_sessions.remove(pos); - state.session_destroyed(session); - } - } -} - -impl Dispatch for ScreencopyState -where - D: Dispatch - + Dispatch - + Dispatch - + ScreencopyHandler - + 'static, -{ - fn request( - _state: &mut D, - _client: &Client, - resource: &ExtImageCopyCaptureCursorSessionV1, - request: ::Request, - data: &CursorSessionData, - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, D>, - ) { - if let ext_image_copy_capture_cursor_session_v1::Request::GetCaptureSession { session } = - request - { - let new_data = CursorSessionData { - inner: data.inner.clone(), - }; - let session = data_init.init(session, new_data); - - let mut inner = data.inner.lock().unwrap(); - if inner.session.is_some() { - resource.post_error( - ext_image_copy_capture_cursor_session_v1::Error::DuplicateSession, - "Duplicate session", - ); - return; - } - - if inner.stopped { - session.stopped(); - } else if let Some(constraints) = inner.constraints.as_ref() { - session.buffer_size(constraints.size.w as u32, constraints.size.h as u32); - for fmt in &constraints.shm { - session.shm_format(*fmt); - } - if let Some(dma) = constraints.dma.as_ref() { - let node = Vec::from(dma.node.dev_id().to_ne_bytes()); - session.dmabuf_device(node); - for (fmt, modifiers) in &dma.formats { - let modifiers = modifiers - .iter() - .flat_map(|modifier| u64::from(*modifier).to_ne_bytes()) - .collect::>(); - session.dmabuf_format(*fmt as u32, modifiers); - } - } - session.done(); - } - inner.session = Some(session); - } - } - - fn destroyed( - state: &mut D, - _client: wayland_backend::server::ClientId, - resource: &ExtImageCopyCaptureCursorSessionV1, - _data: &CursorSessionData, - ) { - let scpy = state.screencopy_state(); - if let Some(pos) = scpy - .known_cursor_sessions - .iter() - .position(|session| session.obj == *resource) - { - let session = scpy.known_cursor_sessions.remove(pos); - state.cursor_session_destroyed(session); - } - } -} - -impl Dispatch for ScreencopyState -where - D: Dispatch - + Dispatch - + ScreencopyHandler - + 'static, -{ - fn request( - _state: &mut D, - _client: &Client, - resource: &ExtImageCopyCaptureSessionV1, - request: ::Request, - data: &CursorSessionData, - _dhandle: &DisplayHandle, - data_init: &mut DataInit<'_, D>, - ) { - if let ext_image_copy_capture_session_v1::Request::CreateFrame { frame } = request { - let inner = Arc::new(Mutex::new(FrameInner::new( - resource.clone(), - data.inner.lock().unwrap().constraints.clone(), - ))); - let obj = data_init.init( - frame, - FrameData { - inner: inner.clone(), - }, - ); - data.inner - .lock() - .unwrap() - .active_frames - .push(FrameRef { obj, inner }); - } - } - - fn destroyed( - _state: &mut D, - _client: wayland_backend::server::ClientId, - _resource: &ExtImageCopyCaptureSessionV1, - _data: &CursorSessionData, - ) { - } -} - -impl Dispatch for ScreencopyState -where - D: Dispatch + ScreencopyHandler + 'static, -{ - fn request( - state: &mut D, - _client: &Client, - resource: &ExtImageCopyCaptureFrameV1, - request: ::Request, - data: &FrameData, - _dhandle: &DisplayHandle, - _data_init: &mut DataInit<'_, D>, - ) { - match request { - ext_image_copy_capture_frame_v1::Request::AttachBuffer { buffer } => { - let mut inner = data.inner.lock().unwrap(); - - if inner.capture_requested { - resource.post_error( - ext_image_copy_capture_frame_v1::Error::AlreadyCaptured, - "Frame was captured previously", - ); - } - - inner.buffer = Some(buffer); - } - ext_image_copy_capture_frame_v1::Request::DamageBuffer { - x, - y, - width, - height, - } => { - let mut inner = data.inner.lock().unwrap(); - - if inner.capture_requested { - resource.post_error( - ext_image_copy_capture_frame_v1::Error::AlreadyCaptured, - "Frame was captured previously", - ); - } - - if x < 0 || y < 0 || width <= 0 || height <= 0 { - resource.post_error( - ext_image_copy_capture_frame_v1::Error::InvalidBufferDamage, - "Coordinates negative or size equal to zero", - ); - return; - } - - inner - .damage - .push(Rectangle::new((x, y).into(), (width, height).into())); - } - ext_image_copy_capture_frame_v1::Request::Capture => { - { - let inner = data.inner.lock().unwrap(); - - if inner.capture_requested { - resource.post_error( - ext_image_copy_capture_frame_v1::Error::AlreadyCaptured, - "Frame was captured previously", - ); - return; - } - - if inner.buffer.is_none() { - resource.post_error( - ext_image_copy_capture_frame_v1::Error::NoBuffer, - "Attempting to capture frame without a buffer", - ); - return; - } - } - - let frame = Frame(FrameRef { - obj: resource.clone(), - inner: data.inner.clone(), - }); - if let Err(reason) = capture_frame(state, frame) { - data.inner.lock().unwrap().fail(resource, reason); - } - } - _ => {} - } - } - - fn destroyed( - state: &mut D, - _client: wayland_backend::server::ClientId, - resource: &ExtImageCopyCaptureFrameV1, - data: &FrameData, - ) { - let frame_ref = FrameRef { - obj: resource.clone(), - inner: data.inner.clone(), - }; - { - let scpy = state.screencopy_state(); - for session in &mut scpy.known_sessions { - session - .inner - .lock() - .unwrap() - .active_frames - .retain(|i| *i != frame_ref); - } - for cursor_session in &mut scpy.known_cursor_sessions { - cursor_session - .inner - .lock() - .unwrap() - .active_frames - .retain(|i| *i != frame_ref); - } - } - state.frame_aborted(frame_ref); - } -} - -fn capture_frame(state: &mut D, frame: Frame) -> Result<(), FailureReason> { - let mut inner = frame.0.inner.lock().unwrap(); - - inner.capture_requested = true; - - if let Some(reason) = inner.failed { - return Err(reason); - } - - if let Some(constraints) = inner.constraints.as_ref() { - let buffer = inner.buffer.as_ref().unwrap(); - match buffer_type(buffer) { - Some(BufferType::Dma) => { - let Some(dma_constraints) = constraints.dma.as_ref() else { - debug!("dma buffer not specified for screencopy"); - return Err(FailureReason::BufferConstraints); - }; - - let dmabuf = match get_dmabuf(buffer) { - Ok(buf) => buf, - Err(err) => { - debug!(?err, "Error accessing dma buffer for screencopy"); - return Err(FailureReason::Stopped); - } - }; - - let buffer_size = dmabuf.size(); - if buffer_size.w < constraints.size.w || buffer_size.h < constraints.size.h { - debug!(?buffer_size, ?constraints.size, "buffer too small for screencopy"); - return Err(FailureReason::BufferConstraints); - } - - let format = dmabuf.format(); - if dma_constraints - .formats - .iter() - .find(|(fourcc, _)| *fourcc == format.code) - .filter(|(_, modifiers)| modifiers.contains(&format.modifier)) - .is_none() - { - debug!( - ?format, - ?dma_constraints, - "unsupported buffer format for screencopy" - ); - return Err(FailureReason::BufferConstraints); - } - } - Some(BufferType::Shm) => { - let buffer_data = match with_buffer_contents(buffer, |_, _, data| data) { - Ok(data) => data, - Err(err) => { - debug!(?err, "Error accessing shm buffer for screencopy"); - return Err(FailureReason::Unknown); - } - }; - - if buffer_data.width < constraints.size.w || buffer_data.height < constraints.size.h - { - debug!(?buffer_data, ?constraints.size, "buffer too small for screencopy"); - return Err(FailureReason::BufferConstraints); - } - - if !constraints.shm.contains(&buffer_data.format) { - debug!(?buffer_data.format, ?constraints.shm, "unsupported buffer format for screencopy"); - return Err(FailureReason::BufferConstraints); - } - } - x => { - debug!(?x, "Attempt to screencopy with unsupported buffer type"); - return Err(FailureReason::BufferConstraints); - } - } - } else { - return Err(FailureReason::Unknown); - } - - let scpy = state.screencopy_state(); - if let Some(session) = scpy - .known_sessions - .iter() - .find(|session| session.obj == inner.obj) - .cloned() - { - if session.inner.lock().unwrap().stopped { - return Err(FailureReason::Stopped); - } - - std::mem::drop(inner); - state.frame(session, frame); - Ok(()) - } else if let Some(session) = scpy - .known_cursor_sessions - .iter() - .find(|session| { - session.inner.lock().unwrap().session.as_ref() == inner.obj.upgrade().ok().as_ref() - }) - .cloned() - { - if session.inner.lock().unwrap().stopped { - return Err(FailureReason::Stopped); - } - - std::mem::drop(inner); - state.cursor_frame(session, frame); - Ok(()) - } else { - Err(FailureReason::Unknown) - } -} - -macro_rules! delegate_screencopy { - ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { - smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_copy_capture::v1::server::ext_image_copy_capture_manager_v1::ExtImageCopyCaptureManagerV1: $crate::wayland::protocols::screencopy::ScreencopyGlobalData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_copy_capture::v1::server::ext_image_copy_capture_manager_v1::ExtImageCopyCaptureManagerV1: $crate::wayland::protocols::screencopy::ScreencopyData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_copy_capture::v1::server::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1: $crate::wayland::protocols::screencopy::SessionData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_copy_capture::v1::server::ext_image_copy_capture_cursor_session_v1::ExtImageCopyCaptureCursorSessionV1: $crate::wayland::protocols::screencopy::CursorSessionData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_copy_capture::v1::server::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1: $crate::wayland::protocols::screencopy::CursorSessionData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols::ext::image_copy_capture::v1::server::ext_image_copy_capture_frame_v1::ExtImageCopyCaptureFrameV1: $crate::wayland::protocols::screencopy::FrameData - ] => $crate::wayland::protocols::screencopy::ScreencopyState); - }; -} -pub(crate) use delegate_screencopy; diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index a64f8e9e..4d30e73f 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -635,14 +635,13 @@ pub fn window_from_handle(handle: ZcosmicToplevelHandleV1) .and_then(|state| state.lock().unwrap().window.clone()) } -pub fn window_from_ext_handle<'a, W: Window + 'static, D>( +pub fn window_from_ext<'a, W: Window + 'static, D>( state: &'a D, - foreign_toplevel: &ExtForeignToplevelHandleV1, + handle: &ForeignToplevelHandle, ) -> Option<&'a W> where D: ToplevelInfoHandler, { - let handle = ForeignToplevelHandle::from_resource(foreign_toplevel)?; state.toplevel_info_state().toplevels.iter().find(|w| { w.user_data().get::().and_then(|inner| { inner @@ -655,6 +654,17 @@ where }) } +pub fn window_from_ext_handle<'a, W: Window + 'static, D>( + state: &'a D, + foreign_toplevel: &ExtForeignToplevelHandleV1, +) -> Option<&'a W> +where + D: ToplevelInfoHandler, +{ + let handle = ForeignToplevelHandle::from_resource(foreign_toplevel)?; + window_from_ext(state, &handle) +} + macro_rules! delegate_toplevel_info { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, $window: ty) => { smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ From 9bc1b6e1ee9b65075af85a21bac81decec554386 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 17 Sep 2025 11:06:02 -0700 Subject: [PATCH 09/25] image-copy: Use `damage_output()` for additional damage The important change here is that we now apply the additional damage first, instead of using `.extend()` to add it after other elements. This is important since `OutputDamageTracker` will ignore our damage elements if there are behind an element with an opaque region. This also makes things a bit simpler, especially `take_screencopy_frames()`, which no longer needs a mutable references to extend then truncate. The implementation of `OutputDamageTracker` isn't entirely clear, but as far as I can tell this is intended to work, and it seems to work in some testing. --- src/backend/kms/surface/mod.rs | 62 ++++++++----------- src/backend/render/element.rs | 26 -------- src/backend/render/mod.rs | 44 +++++++------ .../handlers/image_copy_capture/render.rs | 55 ++++++++-------- 4 files changed, 74 insertions(+), 113 deletions(-) diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index e2d2e5b4..3f8baec1 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -1080,7 +1080,7 @@ impl SurfaceThreadState { let frames = self .mirroring .is_none() - .then(|| take_screencopy_frames(&self.output, &mut elements, &mut has_cursor_mode_none)) + .then(|| take_screencopy_frames(&self.output, &elements, &mut has_cursor_mode_none)) .unwrap_or_default(); // actual rendering @@ -1606,10 +1606,9 @@ fn get_surface_dmabuf_feedback( } } -// TODO: Don't mutate `elements` fn take_screencopy_frames( output: &Output, - elements: &mut Vec>, + elements: &[CosmicElement], has_cursor_mode_none: &mut bool, ) -> Vec<( ScreencopySessionRef, @@ -1624,7 +1623,15 @@ fn take_screencopy_frames( let session_data = session.user_data().get::().unwrap(); let mut damage_tracking = session_data.lock().unwrap(); - let old_len = if !additional_damage.is_empty() { + let buffer = frame.buffer(); + let age = if matches!(buffer_type(&buffer), Some(BufferType::Shm)) { + // TODO re-use offscreen buffer to damage track screencopy to shm + 0 + } else { + damage_tracking.age_for_buffer(&buffer) + }; + + if !additional_damage.is_empty() { let area = output .current_mode() .unwrap() @@ -1634,41 +1641,26 @@ fn take_screencopy_frames( .to_buffer(1, Transform::Normal) .to_f64(); - let old_len = elements.len(); - elements.extend( - additional_damage - .into_iter() - .map(|rect| { - rect.to_f64() - .to_logical( - output.current_scale().fractional_scale(), - output.current_transform(), - &area, - ) - .to_i32_round() - }) - .map(DamageElement::new) - .map(Into::into), - ); - - Some(old_len) - } else { - None + let additional_damage_elements: Vec<_> = additional_damage + .into_iter() + .map(|rect| { + rect.to_f64() + .to_logical( + output.current_scale().fractional_scale(), + output.current_transform(), + &area, + ) + .to_i32_round() + }) + .map(DamageElement::new) + .collect(); + let _ = damage_tracking + .dt + .damage_output(age, &additional_damage_elements); }; - let buffer = frame.buffer(); - let age = if matches!(buffer_type(&frame.buffer()), Some(BufferType::Shm)) { - // TODO re-use offscreen buffer to damage track screencopy to shm - 0 - } else { - damage_tracking.age_for_buffer(&buffer) - }; let res = damage_tracking.dt.damage_output(age, elements); - if let Some(old_len) = old_len { - elements.truncate(old_len); - } - if !session.draw_cursor() { *has_cursor_mode_none = true; } diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index d539a058..7897da65 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -32,7 +32,6 @@ where Cursor(RescaleRenderElement>>), Dnd(WaylandSurfaceRenderElement), MoveGrab(RescaleRenderElement>), - AdditionalDamage(DamageElement), Postprocess( CropRenderElement>>, ), @@ -53,7 +52,6 @@ where CosmicElement::Cursor(elem) => elem.id(), CosmicElement::Dnd(elem) => elem.id(), CosmicElement::MoveGrab(elem) => elem.id(), - CosmicElement::AdditionalDamage(elem) => elem.id(), CosmicElement::Postprocess(elem) => elem.id(), CosmicElement::Zoom(elem) => elem.id(), #[cfg(feature = "debug")] @@ -67,7 +65,6 @@ where CosmicElement::Cursor(elem) => elem.current_commit(), CosmicElement::Dnd(elem) => elem.current_commit(), CosmicElement::MoveGrab(elem) => elem.current_commit(), - CosmicElement::AdditionalDamage(elem) => elem.current_commit(), CosmicElement::Postprocess(elem) => elem.current_commit(), CosmicElement::Zoom(elem) => elem.current_commit(), #[cfg(feature = "debug")] @@ -81,7 +78,6 @@ where CosmicElement::Cursor(elem) => elem.src(), CosmicElement::Dnd(elem) => elem.src(), CosmicElement::MoveGrab(elem) => elem.src(), - CosmicElement::AdditionalDamage(elem) => elem.src(), CosmicElement::Postprocess(elem) => elem.src(), CosmicElement::Zoom(elem) => elem.src(), #[cfg(feature = "debug")] @@ -95,7 +91,6 @@ where CosmicElement::Cursor(elem) => elem.geometry(scale), CosmicElement::Dnd(elem) => elem.geometry(scale), CosmicElement::MoveGrab(elem) => elem.geometry(scale), - CosmicElement::AdditionalDamage(elem) => elem.geometry(scale), CosmicElement::Postprocess(elem) => elem.geometry(scale), CosmicElement::Zoom(elem) => elem.geometry(scale), #[cfg(feature = "debug")] @@ -109,7 +104,6 @@ where CosmicElement::Cursor(elem) => elem.location(scale), CosmicElement::Dnd(elem) => elem.location(scale), CosmicElement::MoveGrab(elem) => elem.location(scale), - CosmicElement::AdditionalDamage(elem) => elem.location(scale), CosmicElement::Postprocess(elem) => elem.location(scale), CosmicElement::Zoom(elem) => elem.location(scale), #[cfg(feature = "debug")] @@ -123,7 +117,6 @@ where CosmicElement::Cursor(elem) => elem.transform(), CosmicElement::Dnd(elem) => elem.transform(), CosmicElement::MoveGrab(elem) => elem.transform(), - CosmicElement::AdditionalDamage(elem) => elem.transform(), CosmicElement::Postprocess(elem) => elem.transform(), CosmicElement::Zoom(elem) => elem.transform(), #[cfg(feature = "debug")] @@ -141,7 +134,6 @@ where CosmicElement::Cursor(elem) => elem.damage_since(scale, commit), CosmicElement::Dnd(elem) => elem.damage_since(scale, commit), CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit), - CosmicElement::AdditionalDamage(elem) => elem.damage_since(scale, commit), CosmicElement::Postprocess(elem) => elem.damage_since(scale, commit), CosmicElement::Zoom(elem) => elem.damage_since(scale, commit), #[cfg(feature = "debug")] @@ -155,7 +147,6 @@ where CosmicElement::Cursor(elem) => elem.opaque_regions(scale), CosmicElement::Dnd(elem) => elem.opaque_regions(scale), CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale), - CosmicElement::AdditionalDamage(elem) => elem.opaque_regions(scale), CosmicElement::Postprocess(elem) => elem.opaque_regions(scale), CosmicElement::Zoom(elem) => elem.opaque_regions(scale), #[cfg(feature = "debug")] @@ -169,7 +160,6 @@ where CosmicElement::Cursor(elem) => elem.alpha(), CosmicElement::Dnd(elem) => elem.alpha(), CosmicElement::MoveGrab(elem) => elem.alpha(), - CosmicElement::AdditionalDamage(elem) => elem.alpha(), CosmicElement::Postprocess(elem) => elem.alpha(), CosmicElement::Zoom(elem) => elem.alpha(), #[cfg(feature = "debug")] @@ -183,7 +173,6 @@ where CosmicElement::Cursor(elem) => elem.kind(), CosmicElement::Dnd(elem) => elem.kind(), CosmicElement::MoveGrab(elem) => elem.kind(), - CosmicElement::AdditionalDamage(elem) => elem.kind(), CosmicElement::Postprocess(elem) => elem.kind(), CosmicElement::Zoom(elem) => elem.kind(), #[cfg(feature = "debug")] @@ -212,9 +201,6 @@ where CosmicElement::Cursor(elem) => elem.draw(frame, src, dst, damage, opaque_regions), CosmicElement::Dnd(elem) => elem.draw(frame, src, dst, damage, opaque_regions), CosmicElement::MoveGrab(elem) => elem.draw(frame, src, dst, damage, opaque_regions), - CosmicElement::AdditionalDamage(elem) => { - RenderElement::::draw(elem, frame, src, dst, damage, opaque_regions) - } CosmicElement::Postprocess(elem) => { let glow_frame = R::glow_frame_mut(frame); RenderElement::::draw( @@ -250,7 +236,6 @@ where CosmicElement::Cursor(elem) => elem.underlying_storage(renderer), CosmicElement::Dnd(elem) => elem.underlying_storage(renderer), CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), - CosmicElement::AdditionalDamage(elem) => elem.underlying_storage(renderer), CosmicElement::Postprocess(elem) => { let glow_renderer = renderer.glow_renderer_mut(); elem.underlying_storage(glow_renderer) @@ -281,17 +266,6 @@ where } } -impl From for CosmicElement -where - R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - R::TextureId: 'static, - CosmicMappedRenderElement: RenderElement, -{ - fn from(elem: DamageElement) -> Self { - Self::AdditionalDamage(elem) - } -} - impl From> for CosmicElement where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index d439e1ce..0e22c8ea 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -1393,21 +1393,20 @@ where )?; let old_len = elements.len(); - elements.extend( - additional_damage - .into_iter() - .map(|rect| { - rect.to_f64() - .to_logical( - output.current_scale().fractional_scale(), - output.current_transform(), - &area, - ) - .to_i32_round() - }) - .map(DamageElement::new) - .map(Into::into), - ); + let additional_damage_elements: Vec<_> = additional_damage + .into_iter() + .map(|rect| { + rect.to_f64() + .to_logical( + output.current_scale().fractional_scale(), + output.current_transform(), + &area, + ) + .to_i32_round() + }) + .map(DamageElement::new) + .collect(); + dt.damage_output(age, &additional_damage_elements)?; Some(old_len) } else { @@ -1513,7 +1512,7 @@ where CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { - let mut elements: Vec> = workspace_elements( + let elements: Vec> = workspace_elements( gpu, renderer, shell, @@ -1528,13 +1527,12 @@ where if let Some(additional_damage) = additional_damage { let output_geo = output.geometry().to_local(output).as_logical(); - elements.extend( - additional_damage - .into_iter() - .filter_map(|rect| rect.intersection(output_geo)) - .map(DamageElement::new) - .map(Into::>::into), - ); + let additional_damage_elements: Vec<_> = additional_damage + .into_iter() + .filter_map(|rect| rect.intersection(output_geo)) + .map(DamageElement::new) + .collect(); + damage_tracker.damage_output(age, &additional_damage_elements)?; } let res = damage_tracker.render_output( diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index 346534ed..e7615222 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -492,7 +492,6 @@ smithay::render_elements! { pub WindowCaptureElement where R: ImportAll + ImportMem; WaylandElement=WaylandSurfaceRenderElement, CursorElement=RelocateRenderElement>, - AdditionalDamage=DamageElement, } pub fn render_window_to_buffer( @@ -543,22 +542,19 @@ pub fn render_window_to_buffer( CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, { - let mut elements = Vec::new(); - - elements.extend( - additional_damage - .into_iter() - .filter_map(|rect| { - let logical_rect = rect.to_logical( - 1, - Transform::Normal, - &geometry.size.to_buffer(1, Transform::Normal), - ); - logical_rect.intersection(Rectangle::from_size(geometry.size)) - }) - .map(DamageElement::new) - .map(Into::>::into), - ); + let additional_damage_elements: Vec<_> = additional_damage + .into_iter() + .filter_map(|rect| { + let logical_rect = rect.to_logical( + 1, + Transform::Normal, + &geometry.size.to_buffer(1, Transform::Normal), + ); + logical_rect.intersection(Rectangle::from_size(geometry.size)) + }) + .map(DamageElement::new) + .collect(); + dt.damage_output(age, &additional_damage_elements)?; let shell = common.shell.read(); let seat = shell.seats.last_active().clone(); @@ -581,6 +577,8 @@ pub fn render_window_to_buffer( }; std::mem::drop(shell); + let mut elements = Vec::new(); + if let Some(location) = location { if draw_cursor { elements.extend( @@ -783,7 +781,17 @@ pub fn render_cursor_to_buffer( CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, { - let mut elements = cursor::draw_cursor( + let additional_damage_elements: Vec<_> = additional_damage + .into_iter() + .filter_map(|rect| { + let logical_rect = rect.to_logical(1, Transform::Normal, &Size::from((64, 64))); + logical_rect.intersection(Rectangle::from_size((64, 64).into())) + }) + .map(DamageElement::new) + .collect(); + dt.damage_output(age, &additional_damage_elements)?; + + let elements = cursor::draw_cursor( renderer, seat, Point::from((0.0, 0.0)), @@ -797,17 +805,6 @@ pub fn render_cursor_to_buffer( .map(WindowCaptureElement::from) .collect::>(); - elements.extend( - additional_damage - .into_iter() - .filter_map(|rect| { - let logical_rect = rect.to_logical(1, Transform::Normal, &Size::from((64, 64))); - logical_rect.intersection(Rectangle::from_size((64, 64).into())) - }) - .map(DamageElement::new) - .map(Into::>::into), - ); - if let Ok(dmabuf) = get_dmabuf(buffer) { let mut dmabuf_clone = dmabuf.clone(); let mut fb = renderer From e1342fb2e36ae115bd61630940c5089a517445d5 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 17 Sep 2025 11:40:51 -0700 Subject: [PATCH 10/25] image-copy: Use "buffer age" of 1 for capture The logic `age_for_buffer` used seems to be a misinterpretation of the protocol. The wording is a little unclear, but it seems tracking buffer age is the responsibility of the client, and the client is required to accumulate damage and pass it in `damage_buffer`. Our clients initially weren't doing that correctly. I updated xdg-desktop-portal-cosmic to use `damage_buffer` after testing on wlroots, and cosmic-workspaces was recently updated as well. --- src/backend/kms/surface/mod.rs | 18 ++--------- .../handlers/image_copy_capture/render.rs | 2 +- .../handlers/image_copy_capture/user_data.rs | 32 ++----------------- 3 files changed, 6 insertions(+), 46 deletions(-) diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 3f8baec1..91715be6 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -1353,13 +1353,6 @@ impl SurfaceThreadState { (&session, frame, res), now.into(), ) { - session - .user_data() - .get::() - .unwrap() - .lock() - .unwrap() - .reset(); tracing::warn!(?err, "Failed to screencopy"); } } @@ -1388,14 +1381,7 @@ impl SurfaceThreadState { } } Err(err) => { - for (session, frame, _) in frames { - session - .user_data() - .get::() - .unwrap() - .lock() - .unwrap() - .reset(); + for (_session, frame, _) in frames { frame.fail(CaptureFailureReason::Unknown); } return Err(err).with_context(|| "Failed to submit result for display"); @@ -1628,7 +1614,7 @@ fn take_screencopy_frames( // TODO re-use offscreen buffer to damage track screencopy to shm 0 } else { - damage_tracking.age_for_buffer(&buffer) + 1 }; if !additional_damage.is_empty() { diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index e7615222..a0e58f5d 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -225,7 +225,7 @@ where // TODO re-use offscreen buffer to damage track screencopy to shm 0 } else { - session_damage_tracking.age_for_buffer(&buffer) + 1 }; let mut fb = offscreen .as_mut() diff --git a/src/wayland/handlers/image_copy_capture/user_data.rs b/src/wayland/handlers/image_copy_capture/user_data.rs index 3188cb39..083f4220 100644 --- a/src/wayland/handlers/image_copy_capture/user_data.rs +++ b/src/wayland/handlers/image_copy_capture/user_data.rs @@ -1,11 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{cell::RefCell, collections::HashMap, sync::Mutex}; +use std::{cell::RefCell, sync::Mutex}; use smithay::{ - backend::renderer::{damage::OutputDamageTracker, utils::CommitCounter}, + backend::renderer::damage::OutputDamageTracker, output::Output, - reexports::wayland_server::{Resource, Weak, protocol::wl_buffer::WlBuffer}, wayland::image_copy_capture::{ CursorSession, CursorSessionRef, Frame, FrameRef, Session, SessionRef, }, @@ -20,36 +19,11 @@ pub type SessionData = Mutex; pub struct SessionUserData { pub dt: OutputDamageTracker, - commit_counter: CommitCounter, - buffer_age: HashMap, CommitCounter>, } impl SessionUserData { pub fn new(tracker: OutputDamageTracker) -> SessionUserData { - SessionUserData { - dt: tracker, - commit_counter: CommitCounter::default(), - buffer_age: HashMap::new(), - } - } - - pub fn age_for_buffer(&mut self, buffer: &WlBuffer) -> usize { - self.buffer_age.retain(|k, _| k.upgrade().is_ok()); - - let weak = buffer.downgrade(); - let age = self - .commit_counter - .distance(self.buffer_age.get(&weak).copied()) - .unwrap_or(0); - self.buffer_age.insert(weak, self.commit_counter); - - self.commit_counter.increment(); - age - } - - pub fn reset(&mut self) { - self.commit_counter = CommitCounter::default(); - self.buffer_age.clear(); + SessionUserData { dt: tracker } } } From 748ecb60a98dd78b5ede28e2eb7f48d4191f579a Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 3 Feb 2026 12:42:16 -0800 Subject: [PATCH 11/25] Add a `profile-with-tracy-gpu` feature --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index a7f6c9c8..e7209087 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,7 @@ debug = ["egui", "egui_plot", "smithay-egui", "anyhow/backtrace"] default = ["systemd"] systemd = ["libsystemd", "logind-zbus"] profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"] +profile-with-tracy-gpu = ["profile-with-tracy", "smithay/tracy_gpu_profiling"] [profile.dev.package.tiny-skia] opt-level = 2 From 8e9f832fad93f3e8b0a07c0c518daeaf325ac9fc Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 13 May 2024 16:28:58 -0700 Subject: [PATCH 12/25] wayland: Support `wl_fixes` protocol using new `smithay` support --- src/state.rs | 2 ++ src/wayland/handlers/fixes.rs | 6 ++++++ src/wayland/handlers/mod.rs | 1 + 3 files changed, 9 insertions(+) create mode 100644 src/wayland/handlers/fixes.rs diff --git a/src/state.rs b/src/state.rs index 6406cc6c..b10c4d8a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -76,6 +76,7 @@ use smithay::{ compositor::{CompositorClientState, CompositorState, SurfaceData}, cursor_shape::CursorShapeManagerState, dmabuf::{DmabufFeedback, DmabufGlobal, DmabufState}, + fixes::FixesState, fractional_scale::{FractionalScaleManagerState, with_fractional_scale}, idle_inhibit::IdleInhibitManagerState, idle_notify::IdleNotifierState, @@ -677,6 +678,7 @@ impl State { VirtualKeyboardManagerState::new::(dh, client_not_sandboxed); AlphaModifierState::new::(dh); SinglePixelBufferState::new::(dh); + FixesState::new::(&dh); let idle_notifier_state = IdleNotifierState::::new(dh, handle.clone()); let idle_inhibit_manager_state = IdleInhibitManagerState::new::(dh); diff --git a/src/wayland/handlers/fixes.rs b/src/wayland/handlers/fixes.rs new file mode 100644 index 00000000..b589aabc --- /dev/null +++ b/src/wayland/handlers/fixes.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use smithay::delegate_fixes; + +delegate_fixes!(State); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 06fd816f..d3d22f00 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -12,6 +12,7 @@ pub mod dmabuf; pub mod drm; pub mod drm_lease; pub mod drm_syncobj; +pub mod fixes; pub mod foreign_toplevel_list; pub mod fractional_scale; pub mod idle_inhibit; From 0e97ddbd009ea2b9e10ab502ceb241715a3518be Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 5 Feb 2026 16:02:28 +0100 Subject: [PATCH 13/25] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Baurzhan Muftakhidinov Co-authored-by: Hafidz Nasruddin Co-authored-by: Hosted Weblate Co-authored-by: Languages add-on Co-authored-by: Quentin PAGÈS Co-authored-by: Zahid Rizky Fakhri Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/id/ Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/kk/ Translation: Pop OS/COSMIC Comp --- resources/i18n/id/cosmic_comp.ftl | 28 ++++++++++++++++++++++++++++ resources/i18n/kk/cosmic_comp.ftl | 28 ++++++++++++++++++++++++++++ resources/i18n/ms/cosmic_comp.ftl | 0 resources/i18n/oc/cosmic_comp.ftl | 0 resources/i18n/uz/cosmic_comp.ftl | 0 5 files changed, 56 insertions(+) create mode 100644 resources/i18n/ms/cosmic_comp.ftl create mode 100644 resources/i18n/oc/cosmic_comp.ftl create mode 100644 resources/i18n/uz/cosmic_comp.ftl diff --git a/resources/i18n/id/cosmic_comp.ftl b/resources/i18n/id/cosmic_comp.ftl index e69de29b..97429864 100644 --- a/resources/i18n/id/cosmic_comp.ftl +++ b/resources/i18n/id/cosmic_comp.ftl @@ -0,0 +1,28 @@ +a11y-zoom-move-continuously = Tampilan bergerak terus menerus dengan penunjuk +a11y-zoom-move-onedge = Tampilan bergerak saat penunjuk mencapai tepi +a11y-zoom-move-centered = Tampilan bergerak untuk menjaga penunjuk tetap di tengah +a11y-zoom-settings = Pengaturan kaca pembesar... +unknown-keybinding = +window-menu-resize-edge-top = Atas +window-menu-resize-edge-left = Kiri +window-menu-resize-edge-right = Kanan +window-menu-resize-edge-bottom = Bawah +window-menu-close = Tutup +window-menu-close-all = Tutup semua jendela +grow-window = Perbesar +shrink-window = Perkecil +swap-windows = Tukarkan Jendela +stack-windows = Tumpukkan Jendela +window-menu-screenshot = Ambil tangkapan layar +window-menu-move = Pindahkan +window-menu-resize = Ukur ulang +window-menu-minimize = Perkecil +window-menu-maximize = Perbesar +window-menu-fullscreen = Layar penuh +window-menu-tiled = Jendela apung +window-menu-move-prev-workspace = Pindahkan ke ruang kerja sebelumnya +window-menu-move-next-workspace = Pindahkan ke ruang kerja selanjutnya +window-menu-stack = Buat tumpukan jendela +window-menu-sticky = Jendela lengket +window-menu-unstack-all = Bongkar tumpukan jendela +window-menu-unstack = Bongkar tumpukan jendela diff --git a/resources/i18n/kk/cosmic_comp.ftl b/resources/i18n/kk/cosmic_comp.ftl index e69de29b..2551c763 100644 --- a/resources/i18n/kk/cosmic_comp.ftl +++ b/resources/i18n/kk/cosmic_comp.ftl @@ -0,0 +1,28 @@ +a11y-zoom-move-continuously = Көрініс курсормен бірге үздіксіз қозғалады +a11y-zoom-move-onedge = Көрініс курсор жиекке жеткенде қозғалады +a11y-zoom-move-centered = Көрініс курсорды ортада сақтау үшін қозғалады +a11y-zoom-settings = Үлкейткіш баптаулары... +grow-window = Үлкейту +shrink-window = Кішірейту +swap-windows = Терезелерді алмастыру +stack-windows = Терезелерді жинақтау +unknown-keybinding = <орнатылмаған> +window-menu-minimize = Қайыру +window-menu-maximize = Жазық қылу +window-menu-fullscreen = Толық экран +window-menu-tiled = Қалқымалы терезе +window-menu-screenshot = Скриншот түсіру +window-menu-move = Жылжыту +window-menu-resize = Өлшемін өзгерту +window-menu-move-prev-workspace = Алдыңғы жұмыс орнына жылжыту +window-menu-move-next-workspace = Келесі жұмыс орнына жылжыту +window-menu-stack = Терезелер жинағын жасау +window-menu-unstack-all = Терезелерді жинақтан шығару +window-menu-unstack = Терезені жинақтан шығару +window-menu-sticky = Жабысқақ терезе +window-menu-close = Жабу +window-menu-close-all = Барлық терезелерді жабу +window-menu-resize-edge-top = Жоғарғы +window-menu-resize-edge-left = Сол жақ +window-menu-resize-edge-right = Оң жақ +window-menu-resize-edge-bottom = Төменгі diff --git a/resources/i18n/ms/cosmic_comp.ftl b/resources/i18n/ms/cosmic_comp.ftl new file mode 100644 index 00000000..e69de29b diff --git a/resources/i18n/oc/cosmic_comp.ftl b/resources/i18n/oc/cosmic_comp.ftl new file mode 100644 index 00000000..e69de29b diff --git a/resources/i18n/uz/cosmic_comp.ftl b/resources/i18n/uz/cosmic_comp.ftl new file mode 100644 index 00000000..e69de29b From 2ea1186723cde954f5b2b697a8ae0a45e836548c Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 5 Feb 2026 18:58:17 -0800 Subject: [PATCH 14/25] kms: Import on device supporting format, if not advertised device Fixes https://github.com/pop-os/cosmic-epoch/issues/2978. This reverses the part of https://github.com/pop-os/cosmic-comp/commit/ca00df0b37 that made it only try import on the advertised GPU. But this version avoids initializing an EGL context simply to re-check the supported texture formats. --- src/backend/kms/device.rs | 4 +++- src/backend/kms/mod.rs | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index 3d3dfc28..3c8665c7 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -95,6 +95,7 @@ pub struct Device { pub drm: GbmDrmOutputManager, supports_atomic: bool, + pub texture_formats: FormatSet, event_token: Option, pub socket: Option, } @@ -285,7 +286,7 @@ impl State { .with_context(|| format!("Failed to add drm device to event loop: {}", dev))?; let socket = match (!is_software) - .then(|| self.create_socket(dh, render_node, texture_formats)) + .then(|| self.create_socket(dh, render_node, texture_formats.clone())) .transpose() { Ok(socket) => socket, @@ -349,6 +350,7 @@ impl State { }, supports_atomic, + texture_formats, event_token: Some(token), socket, }; diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 3737901b..ae887c30 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -14,7 +14,7 @@ use indexmap::IndexMap; use render::gles::GbmGlowBackend; use smithay::{ backend::{ - allocator::{dmabuf::Dmabuf, format::FormatSet}, + allocator::{Buffer, dmabuf::Dmabuf, format::FormatSet}, drm::{DrmDeviceFd, DrmNode, NodeType, VrrSupport, output::DrmOutputRenderElements}, egl::{EGLContext, EGLDevice, EGLDisplay}, input::InputEvent, @@ -491,7 +491,7 @@ impl KmsState { global: &DmabufGlobal, dmabuf: Dmabuf, ) -> Result { - let device = self + let mut device = self .drm_devices .values_mut() .find(|device| { @@ -503,6 +503,21 @@ impl KmsState { }) .context("Couldn't find gpu for dmabuf global")?; + // If device advertised to client doesn't support format/modifier, select + // first device that does. This is needed for image-copy from + // output/toplevel on a different node. + // + // TODO: After + // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/268, + // only try the device specified explicitly by the client, if set. + if !device.texture_formats.contains(&dmabuf.format()) { + device = self + .drm_devices + .values_mut() + .find(|device| device.texture_formats.contains(&dmabuf.format())) + .context("Dmabuf cannot be imported on any gpu")?; + } + let new_client = if let Some(client) = client { let new = device.inner.active_clients.insert(client.id()); device.inner.update_egl( From f865ad7241a772a62b558866ab2dc87e99eb6383 Mon Sep 17 00:00:00 2001 From: Ilia Malanin Date: Sat, 7 Feb 2026 23:38:32 +0100 Subject: [PATCH 15/25] fix: Remove redundant configure in popup reposition_request --- src/wayland/handlers/xdg_shell/mod.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 43e6a46d..4e034850 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -164,12 +164,6 @@ impl XdgShellHandler for State { self.common.shell.read().unconstrain_popup(&surface); surface.send_repositioned(token); - if let Err(err) = surface.send_configure() { - warn!( - ?err, - "Client bug: Unable to re-configure repositioned popup.", - ); - } } fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) { From de7c78accd7b6cfaf751d13ad0efab538b9bbb31 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 9 Feb 2026 17:07:03 -0800 Subject: [PATCH 16/25] layout/tiling: Make `cleanup_drag` push tree only if something changes Since `copy_clone()` preserved IDs, `traverse_pre_order_ids()` can be called on the old tree, without collecting into a `Vec`. Then we can also `copy_clone()` only if there's actually a change, and also only call `push_tree()` in that case. (Once the `LazyCell::get()` stabilization is released, we could use that here, but `Option::get_or_insert_with()` may be more readable anyway.) With this, `cleanup_drag()` should be pretty low-cost, so we shouldn't have to worry about whether or not it's redundant. --- src/shell/layout/tiling/mod.rs | 38 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index ffeeb52a..85efb2ce 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -2637,28 +2637,36 @@ impl TilingLayout { } pub fn cleanup_drag(&mut self) { - let gaps = self.gaps(); + let old_tree = &self.queue.trees.back().unwrap().0; + let mut new_tree = None; - let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); - - if let Some(root) = tree.root_node_id() { - for id in tree - .traverse_pre_order_ids(root) - .unwrap() - .collect::>() - .into_iter() - { - match tree.get_mut(&id).map(|node| node.data_mut()) { - Ok(Data::Placeholder { .. }) => TilingLayout::unmap_internal(&mut tree, &id), + if let Some(root) = old_tree.root_node_id() { + for id in old_tree.traverse_pre_order_ids(root).unwrap() { + match old_tree.get(&id).map(|node| node.data()) { + Ok(Data::Placeholder { .. }) => { + // Copy a tree on write + let new_tree = new_tree.get_or_insert_with(|| old_tree.copy_clone()); + TilingLayout::unmap_internal(new_tree, &id) + } Ok(Data::Group { pill_indicator, .. }) if pill_indicator.is_some() => { - pill_indicator.take(); + let new_tree = new_tree.get_or_insert_with(|| old_tree.copy_clone()); + match new_tree.get_mut(&id).unwrap().data_mut() { + Data::Group { pill_indicator, .. } => { + *pill_indicator = None; + } + _ => unreachable!(), + } } _ => {} } } - let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); - self.queue.push_tree(tree, ANIMATION_DURATION, blocker); + // If anything was changed, push updated tree + if let Some(mut new_tree) = new_tree { + let blocker = + TilingLayout::update_positions(&self.output, &mut new_tree, self.gaps()); + self.queue.push_tree(new_tree, ANIMATION_DURATION, blocker); + } } } From 7e48191253ae04ba95f2d7907cc04b3ae81edce0 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 9 Feb 2026 17:13:07 -0800 Subject: [PATCH 17/25] grabs/moving: Call `cleanup_drag()` unconditionally Previously, drag placeholder would be removed in the call to `tiling_layer.drop_window()` when dropping onto a tiling layer, but would not be removed when dropping to a floating layer. Which would leave a placeholder taking up space, and cause a panic on a future drag operation. Instead, call `cleanup_drag()` regardless, after `drop_window()`, to do any cleanup that is still needed. This moves the call that was previously added in 67d0a825. --- src/shell/grabs/moving.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index f59f93d6..8b01d086 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -906,20 +906,22 @@ impl Drop for MoveGrab { } } } else { - let mut shell = state.common.shell.write(); - shell - .workspaces - .active_mut(&cursor_output) - .unwrap() - .tiling_layer - .cleanup_drag(); - shell.set_overview_mode(None, state.common.event_loop_handle.clone()); None } } else { None }; + let mut shell = state.common.shell.write(); + shell + .workspaces + .active_mut(&cursor_output) + .unwrap() + .tiling_layer + .cleanup_drag(); + shell.set_overview_mode(None, state.common.event_loop_handle.clone()); + drop(shell); + { let cursor_state = seat.user_data().get::().unwrap(); cursor_state.lock().unwrap().unset_shape(); From 38c3840b00982521a4ae3890bc814e3cc8004b75 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 10 Feb 2026 13:41:46 -0800 Subject: [PATCH 18/25] Update `smithay` with fix for image-copy leak Includes fix from https://github.com/Smithay/smithay/pull/1928. --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8d61c80..5aafabe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4992,7 +4992,7 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay" version = "0.7.0" -source = "git+https://github.com/smithay/smithay.git?rev=14a2009#14a2009cda0ef4db81e28941a1dbf4375f07e7f2" +source = "git+https://github.com/smithay/smithay.git?rev=3d3f9e3#3d3f9e359352d95cffd1e53287d57df427fcbd34" dependencies = [ "aliasable", "appendlist", diff --git a/Cargo.toml b/Cargo.toml index e7209087..4218c560 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,4 +148,4 @@ cosmic-protocols = { git = "https://github.com/pop-os//cosmic-protocols", branch cosmic-client-toolkit = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" } [patch.crates-io] -smithay = { git = "https://github.com/smithay/smithay.git", rev = "14a2009" } +smithay = { git = "https://github.com/smithay/smithay.git", rev = "3d3f9e3" } From 3414579256be1aabf6e4caac511e7c4ec838aac2 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 11 Feb 2026 10:09:51 +0100 Subject: [PATCH 19/25] i18n: translation updates from weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fedorov Alexei Co-authored-by: Feike Donia Co-authored-by: Hosted Weblate Co-authored-by: Jun Hwi Ku Co-authored-by: jickson john Co-authored-by: Димко Co-authored-by: 김유빈 Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/ko/ Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/nl/ Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/ru/ Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/uk/ Translation: Pop OS/COSMIC Comp --- resources/i18n/ko/cosmic_comp.ftl | 4 ++-- resources/i18n/ml/cosmic_comp.ftl | 0 resources/i18n/nl/cosmic_comp.ftl | 4 ++-- resources/i18n/ru/cosmic_comp.ftl | 2 +- resources/i18n/uk/cosmic_comp.ftl | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 resources/i18n/ml/cosmic_comp.ftl diff --git a/resources/i18n/ko/cosmic_comp.ftl b/resources/i18n/ko/cosmic_comp.ftl index b4c1650e..1c991c58 100644 --- a/resources/i18n/ko/cosmic_comp.ftl +++ b/resources/i18n/ko/cosmic_comp.ftl @@ -6,8 +6,8 @@ window-menu-move = 이동 window-menu-minimize = 최소화 window-menu-maximize = 최대화 window-menu-fullscreen = 전체 화면 -window-menu-move-prev-workspace = 이전 워크스페이스로 이동 -window-menu-move-next-workspace = 다음 워크스페이스로 이동 +window-menu-move-prev-workspace = 이전 작업 공간으로 이동 +window-menu-move-next-workspace = 다음 작업 공간으로 이동 a11y-zoom-settings = 돋보기 설정... grow-window = 확대 shrink-window = 축소 diff --git a/resources/i18n/ml/cosmic_comp.ftl b/resources/i18n/ml/cosmic_comp.ftl new file mode 100644 index 00000000..e69de29b diff --git a/resources/i18n/nl/cosmic_comp.ftl b/resources/i18n/nl/cosmic_comp.ftl index 162f4f3e..8a7732e8 100644 --- a/resources/i18n/nl/cosmic_comp.ftl +++ b/resources/i18n/nl/cosmic_comp.ftl @@ -19,8 +19,8 @@ window-menu-stack = Begin een vensterstapel window-menu-unstack-all = Beëindig vensterstapel window-menu-unstack = Haal venster uit stapel window-menu-sticky = Vastgezet venster -window-menu-close = Sluit -window-menu-close-all = Sluit alle vensters +window-menu-close = Sluiten +window-menu-close-all = Alle vensters sluiten window-menu-resize-edge-top = Boven window-menu-resize-edge-left = Links window-menu-resize-edge-right = Rechts diff --git a/resources/i18n/ru/cosmic_comp.ftl b/resources/i18n/ru/cosmic_comp.ftl index cdffe933..c4be586d 100644 --- a/resources/i18n/ru/cosmic_comp.ftl +++ b/resources/i18n/ru/cosmic_comp.ftl @@ -19,7 +19,7 @@ window-menu-move-next-workspace = На след. рабочий стол window-menu-stack = Создать стопку окон window-menu-unstack-all = Распустить стопку окон window-menu-unstack = Убрать из стопки -window-menu-sticky = Прилипание окна +window-menu-sticky = Закрепить окно поверх других window-menu-close = Закрыть window-menu-close-all = Закрыть все окна window-menu-resize-edge-top = Наверх diff --git a/resources/i18n/uk/cosmic_comp.ftl b/resources/i18n/uk/cosmic_comp.ftl index cfae1307..1c033aeb 100644 --- a/resources/i18n/uk/cosmic_comp.ftl +++ b/resources/i18n/uk/cosmic_comp.ftl @@ -5,7 +5,7 @@ stack-windows = Групувати вікна unknown-keybinding = window-menu-minimize = Згорнути window-menu-maximize = Розгорнути -window-menu-tiled = Плаваюче вікно +window-menu-tiled = Плавуче вікно window-menu-screenshot = Зробити знімок екрана window-menu-move = Перемістити window-menu-resize = Змінити розмір From c1de9b2f1a19235e0dd543fb5c4843d150c9b079 Mon Sep 17 00:00:00 2001 From: Jakob Date: Fri, 30 Jan 2026 18:22:55 +0100 Subject: [PATCH 20/25] Made it so changing focus to different monitor takes cursor_follows_focus into consideration --- src/wayland/handlers/toplevel_management.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs index 27ff319c..547c6afc 100644 --- a/src/wayland/handlers/toplevel_management.rs +++ b/src/wayland/handlers/toplevel_management.rs @@ -110,7 +110,8 @@ impl ToplevelManagementHandler for State { std::mem::drop(shell); - if seat.active_output() != *output { + // move pointer to window if it’s on a different monitor/output + if seat.active_output() != *output && self.common.config.cosmic_conf.cursor_follows_focus { if let Some(new_pos) = new_pos { seat.set_active_output(output); if let Some(ptr) = seat.get_pointer() { From dca157ba16942cfc4e33be617da14979f528db4c Mon Sep 17 00:00:00 2001 From: Jakob Date: Mon, 9 Feb 2026 20:37:50 +0100 Subject: [PATCH 21/25] Formatting --- src/wayland/handlers/toplevel_management.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs index 547c6afc..46f38bac 100644 --- a/src/wayland/handlers/toplevel_management.rs +++ b/src/wayland/handlers/toplevel_management.rs @@ -111,7 +111,9 @@ impl ToplevelManagementHandler for State { std::mem::drop(shell); // move pointer to window if it’s on a different monitor/output - if seat.active_output() != *output && self.common.config.cosmic_conf.cursor_follows_focus { + if seat.active_output() != *output + && self.common.config.cosmic_conf.cursor_follows_focus + { if let Some(new_pos) = new_pos { seat.set_active_output(output); if let Some(ptr) = seat.get_pointer() { From d7ae6dcb6761af049a443c2c1e42b15970108f52 Mon Sep 17 00:00:00 2001 From: Lysander Treumann Date: Sat, 31 Jan 2026 02:47:03 +0100 Subject: [PATCH 22/25] Added "action on typing" to workspaces configuration. --- cosmic-comp-config/src/workspace.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cosmic-comp-config/src/workspace.rs b/cosmic-comp-config/src/workspace.rs index 31caba7a..7ed73334 100644 --- a/cosmic-comp-config/src/workspace.rs +++ b/cosmic-comp-config/src/workspace.rs @@ -9,6 +9,8 @@ pub struct WorkspaceConfig { pub workspace_mode: WorkspaceMode, #[serde(default)] pub workspace_layout: WorkspaceLayout, + #[serde(default)] + pub action_on_typing: Action, } impl Default for WorkspaceConfig { @@ -16,6 +18,7 @@ impl Default for WorkspaceConfig { Self { workspace_mode: WorkspaceMode::OutputBound, workspace_layout: WorkspaceLayout::Vertical, + action_on_typing: Action::default(), } } } @@ -33,6 +36,14 @@ pub enum WorkspaceLayout { Horizontal, } +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum Action { + #[default] + None, + OpenLauncher, + OpenApplications, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct OutputMatch { pub name: String, From 4a2e76a2cbaeecdfc9b2a88ef5461136ba12b7b7 Mon Sep 17 00:00:00 2001 From: Lysander Treumann Date: Sat, 31 Jan 2026 02:49:35 +0100 Subject: [PATCH 23/25] Moved default of WorkspaceMode from default implementation of WorkspacesConfig into WorkspaceMode declaration. WorkspaceConfig's default can then be derived. --- cosmic-comp-config/src/workspace.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/cosmic-comp-config/src/workspace.rs b/cosmic-comp-config/src/workspace.rs index 7ed73334..5f59063e 100644 --- a/cosmic-comp-config/src/workspace.rs +++ b/cosmic-comp-config/src/workspace.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::EdidProduct; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct WorkspaceConfig { pub workspace_mode: WorkspaceMode, #[serde(default)] @@ -13,18 +13,9 @@ pub struct WorkspaceConfig { pub action_on_typing: Action, } -impl Default for WorkspaceConfig { - fn default() -> Self { - Self { - workspace_mode: WorkspaceMode::OutputBound, - workspace_layout: WorkspaceLayout::Vertical, - action_on_typing: Action::default(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum WorkspaceMode { + #[default] OutputBound, Global, } From 0bd2d67d9486fc939128db496f5c1c9dbd6e0cd7 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 12 Feb 2026 15:09:53 +0100 Subject: [PATCH 24/25] i18n: translation updates from weblate Co-authored-by: Feike Donia Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-comp/nl/ Translation: Pop OS/COSMIC Comp --- resources/i18n/nl/cosmic_comp.ftl | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/resources/i18n/nl/cosmic_comp.ftl b/resources/i18n/nl/cosmic_comp.ftl index 8a7732e8..1bacb6cf 100644 --- a/resources/i18n/nl/cosmic_comp.ftl +++ b/resources/i18n/nl/cosmic_comp.ftl @@ -1,24 +1,24 @@ -a11y-zoom-move-continuously = Vergroting volgt muispijltje -a11y-zoom-move-onedge = Vergroting verplaatst als muispijltje een schermrand raakt -a11y-zoom-move-centered = Vergroting houdt muispijltje steeds in het midden -a11y-zoom-settings = Vergrootglasinstellingen… +a11y-zoom-move-continuously = Aanzicht met de cursor mee laten bewegen +a11y-zoom-move-onedge = Aanzicht verplaatst zich pas als de cursor een schermrand raakt +a11y-zoom-move-centered = Aanzicht houdt de cursor gecentreerd +a11y-zoom-settings = Instellingen van het vergrootglas… grow-window = Vergroten shrink-window = Verkleinen swap-windows = Vensters omwisselen stack-windows = Vensters stapelen -unknown-keybinding = -window-menu-minimize = Minimaliseer -window-menu-maximize = Maximaliseer -window-menu-tiled = Laat venster zweven -window-menu-screenshot = Maak schermafdruk -window-menu-move = Verplaats -window-menu-resize = Pas grootte aan -window-menu-move-prev-workspace = Verplaats naar vorig werkblad -window-menu-move-next-workspace = Verplaats naar volgend werkblad -window-menu-stack = Begin een vensterstapel -window-menu-unstack-all = Beëindig vensterstapel -window-menu-unstack = Haal venster uit stapel -window-menu-sticky = Vastgezet venster +unknown-keybinding = +window-menu-minimize = Minimaliseren +window-menu-maximize = Maximaliseren +window-menu-tiled = Venster laten zweven +window-menu-screenshot = Schermafdruk maken +window-menu-move = Verplaatsen +window-menu-resize = Grootte aanpassen +window-menu-move-prev-workspace = Naar vorig werkblad verplaatsen +window-menu-move-next-workspace = Naar volgend werkblad verplaatsen +window-menu-stack = Vensterstapel beginnen +window-menu-unstack-all = Vensterstapel opheffen +window-menu-unstack = Venster uit stapel halen +window-menu-sticky = Venster vastzetten window-menu-close = Sluiten window-menu-close-all = Alle vensters sluiten window-menu-resize-edge-top = Boven From a7b880ff8a25dbcff16d7070d05c58c4aa91095b Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 13 Feb 2026 12:35:27 -0700 Subject: [PATCH 25/25] Add pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..e6ca28bc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +- [ ] I have disclosed use of any AI generated code in my commit messages. + - If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR. + - In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment. +- [ ] I understand these changes in full and will be able to respond to review comments. +- [ ] My change is accurately described in the commit message. +- [ ] My contribution is tested and working as described. +- [ ] I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions. +