From 650768211fbfa3bcde7f7b52c152cc630dbe6404 Mon Sep 17 00:00:00 2001 From: Frederic Laing Date: Fri, 26 Jun 2026 16:08:59 +0200 Subject: [PATCH 01/56] fix: keep windows visible until overview commits a buffer --- src/utils/quirks.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/utils/quirks.rs b/src/utils/quirks.rs index 2ecda9fd..e0954fd4 100644 --- a/src/utils/quirks.rs +++ b/src/utils/quirks.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -use smithay::{desktop::layer_map_for_output, output::Output}; +use smithay::{ + backend::renderer::utils::with_renderer_surface_state, desktop::layer_map_for_output, + output::Output, +}; /// Layer shell namespace used by `cosmic-workspaces` // TODO: Avoid special case, or add protocol to expose required behavior @@ -10,5 +13,14 @@ pub const WORKSPACE_OVERVIEW_NAMESPACE: &str = "cosmic-workspace-overview"; pub fn workspace_overview_is_open(output: &Output) -> bool { layer_map_for_output(output) .layers() - .any(|s| s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE) + .filter(|s| s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE) + // Only consider the overview open once it has committed a buffer. The + // surface is inserted into the layer map on its initial (bufferless) + // commit, so checking for the namespace alone hides all toplevels for a + // frame before the overview has anything to draw, briefly flashing the + // bare wallpaper. + .any(|s| { + with_renderer_surface_state(s.wl_surface(), |state| state.buffer().is_some()) + .unwrap_or(false) + }) } From ccc4f36065086b4e5b044003baf3669366bb9da3 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 29 Jun 2026 17:20:02 +0200 Subject: [PATCH 02/56] chore: update smithay --- Cargo.lock | 548 +++++++++++----------- Cargo.toml | 2 +- src/backend/winit.rs | 2 +- src/shell/element/stack.rs | 55 ++- src/shell/element/window.rs | 41 +- src/shell/focus/target.rs | 43 +- src/shell/grabs/delay.rs | 30 +- src/shell/grabs/menu/mod.rs | 34 +- src/shell/grabs/mod.rs | 88 ++-- src/shell/grabs/moving.rs | 23 +- src/shell/layout/floating/grabs/resize.rs | 30 +- src/shell/layout/mod.rs | 2 +- src/shell/layout/tiling/grabs/resize.rs | 64 +-- src/shell/zoom.rs | 59 +-- src/utils/iced/mod.rs | 43 +- src/xwayland.rs | 1 + 16 files changed, 513 insertions(+), 552 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30f751b1..1bc58958 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,9 +429,9 @@ dependencies = [ [[package]] name = "block2" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ "objc2", ] @@ -522,20 +522,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.11.0", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - [[package]] name = "calloop" version = "0.14.4" @@ -551,25 +537,13 @@ dependencies = [ "tracing", ] -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop 0.13.0", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - [[package]] name = "calloop-wayland-source" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ - "calloop 0.14.4", + "calloop", "rustix 1.1.4", "wayland-backend", "wayland-client", @@ -819,7 +793,7 @@ dependencies = [ "bitflags 2.11.0", "cosmic-protocols", "libc", - "smithay-client-toolkit 0.20.0", + "smithay-client-toolkit", "wayland-client", "wayland-protocols", ] @@ -830,7 +804,7 @@ version = "1.0.0" dependencies = [ "anyhow", "bitflags 2.11.0", - "calloop 0.14.4", + "calloop", "cgmath", "clap_lex", "cosmic-comp-config", @@ -903,7 +877,7 @@ version = "1.0.0" source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" dependencies = [ "atomicwrites", - "calloop 0.14.4", + "calloop", "cosmic-config-derive", "cosmic-settings-daemon", "dirs", @@ -1010,7 +984,7 @@ dependencies = [ "rustc-hash 2.1.1", "self_cell", "skrifa 0.40.0", - "smol_str 0.3.5", + "smol_str", "swash", "sys-locale", "unicode-bidi", @@ -1234,10 +1208,14 @@ dependencies = [ ] [[package]] -name = "dispatch" -version = "0.2.0" +name = "dispatch2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "objc2", +] [[package]] name = "displaydoc" @@ -1267,7 +1245,7 @@ dependencies = [ "bitflags 2.11.0", "mime 0.1.0", "raw-window-handle", - "smithay-client-toolkit 0.20.0", + "smithay-client-toolkit", "smithay-clipboard", ] @@ -2291,7 +2269,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 2.1.1", "serde", - "smol_str 0.3.5", + "smol_str", "thiserror 2.0.18", "web-time", "window_clipboard", @@ -2801,6 +2779,16 @@ dependencies = [ "winnow 0.6.24", ] +[[package]] +name = "keyboard-types" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" +dependencies = [ + "bitflags 2.11.0", + "serde", +] + [[package]] name = "keyframe" version = "1.1.1" @@ -3558,96 +3546,59 @@ dependencies = [ "objc_id", ] -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - [[package]] name = "objc2" -version = "0.5.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ - "objc-sys", "objc2-encode", ] [[package]] name = "objc2-app-kit" -version = "0.2.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.11.0", "block2", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.0", "libc", - "objc2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", + "objc2-core-foundation", ] [[package]] -name = "objc2-cloud-kit" -version = "0.2.2" +name = "objc2-core-video" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" dependencies = [ "bitflags 2.11.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.11.0", - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2", - "objc2", - "objc2-contacts", - "objc2-foundation", + "objc2-core-foundation", + "objc2-core-graphics", ] [[package]] @@ -3658,106 +3609,25 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" -version = "0.2.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.11.0", - "block2", - "dispatch", - "libc", - "objc2", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.11.0", "block2", "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.11.0", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2", - "objc2-foundation", + "objc2-core-foundation", ] [[package]] name = "objc2-ui-kit" -version = "0.2.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.11.0", - "block2", "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.11.0", - "block2", - "objc2", - "objc2-core-location", + "objc2-core-foundation", "objc2-foundation", ] @@ -4382,15 +4252,6 @@ dependencies = [ "font-types 0.11.0", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -4469,6 +4330,18 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "reis" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3fedd2777cde52c1be5e572efbec485eac7b801c47820eda388d4f13b9c4b" +dependencies = [ + "calloop", + "enumflags2", + "log", + "rustix 1.1.4", +] + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -4924,14 +4797,14 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay" version = "0.7.0" -source = "git+https://github.com/smithay/smithay.git?rev=85f83ab#85f83ab61711e725aa1f3481df4f4dc5370e925b" +source = "git+https://github.com/smithay/smithay.git?rev=8eb4076#8eb4076cad8705c56f5e3bac8577b83576b6ce76" dependencies = [ "aliasable", "appendlist", "ash", "atomic_float", "bitflags 2.11.0", - "calloop 0.14.4", + "calloop", "cc", "cgmath", "cursor-icon", @@ -4953,6 +4826,7 @@ dependencies = [ "pkg-config", "profiling", "rand 0.9.2", + "reis", "rustix 1.1.4", "scopeguard", "sha2", @@ -4974,31 +4848,6 @@ dependencies = [ "xkbcommon 0.9.0", ] -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.11.0", - "calloop 0.13.0", - "calloop-wayland-source 0.3.0", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - [[package]] name = "smithay-client-toolkit" version = "0.20.0" @@ -5007,8 +4856,8 @@ checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ "bitflags 2.11.0", "bytemuck", - "calloop 0.14.4", - "calloop-wayland-source 0.4.1", + "calloop", + "calloop-wayland-source", "cursor-icon", "libc", "log", @@ -5036,7 +4885,7 @@ source = "git+https://github.com/pop-os/smithay-clipboard?tag=sctk-0.20#859b02c8 dependencies = [ "libc", "raw-window-handle", - "smithay-client-toolkit 0.20.0", + "smithay-client-toolkit", "wayland-backend", ] @@ -5057,15 +4906,6 @@ dependencies = [ "xkbcommon 0.8.0", ] -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - [[package]] name = "smol_str" version = "0.3.5" @@ -6655,50 +6495,223 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" -version = "0.30.12" +version = "0.31.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +checksum = "2879d2854d1a43e48f67322d4bd097afcb6eb8f8f775c8de0260a71aea1df1aa" dependencies = [ - "ahash", - "android-activity", - "atomic-waker", "bitflags 2.11.0", - "block2", - "bytemuck", - "calloop 0.13.0", "cfg_aliases", - "concurrent-queue", - "core-foundation", - "core-graphics", "cursor-icon", "dpi", - "js-sys", "libc", - "memmap2", + "raw-window-handle", + "rustix 1.1.4", + "smol_str", + "tracing", + "winit-android", + "winit-appkit", + "winit-common", + "winit-core", + "winit-orbital", + "winit-uikit", + "winit-wayland", + "winit-web", + "winit-win32", + "winit-x11", +] + +[[package]] +name = "winit-android" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d9c0d2cd93efec3a9f9ad819cfaf0834782403af7c0d248c784ec0c61761df" +dependencies = [ + "android-activity", + "bitflags 2.11.0", + "dpi", "ndk", + "raw-window-handle", + "smol_str", + "tracing", + "winit-core", +] + +[[package]] +name = "winit-appkit" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21310ca07851a49c348e0c2cc768e36b52ca65afda2c2354d78ed4b90074d8aa" +dependencies = [ + "bitflags 2.11.0", + "block2", + "dispatch2", + "dpi", "objc2", "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-video", + "objc2-foundation", + "raw-window-handle", + "smol_str", + "tracing", + "winit-common", + "winit-core", +] + +[[package]] +name = "winit-common" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45375fbac4cbb77260d83a30b1f9d8105880dbac99a9ae97f56656694680ff69" +dependencies = [ + "memmap2", + "objc2", + "objc2-core-foundation", + "smol_str", + "tracing", + "winit-core", + "x11-dl", + "xkbcommon-dl", +] + +[[package]] +name = "winit-core" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f0ccd7abb43740e2c6124ac7cae7d865ecec74eec63783e8922577ac232583" +dependencies = [ + "bitflags 2.11.0", + "cursor-icon", + "dpi", + "keyboard-types", + "raw-window-handle", + "smol_str", + "web-time", +] + +[[package]] +name = "winit-orbital" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ea1fb262e7209f265f12bd0cc792c399b14355675e65531e9c8a87db287d46" +dependencies = [ + "bitflags 2.11.0", + "dpi", + "orbclient", + "raw-window-handle", + "redox_syscall 0.5.18", + "smol_str", + "tracing", + "winit-core", +] + +[[package]] +name = "winit-uikit" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680a356e798837d8eb274d4556e83bceaf81698194e31aafc5cfb8a9f2fab643" +dependencies = [ + "bitflags 2.11.0", + "block2", + "dispatch2", + "dpi", + "objc2", + "objc2-core-foundation", "objc2-foundation", "objc2-ui-kit", - "orbclient", - "percent-encoding", - "pin-project", "raw-window-handle", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "smithay-client-toolkit 0.19.2", - "smol_str 0.2.2", + "smol_str", + "tracing", + "winit-common", + "winit-core", +] + +[[package]] +name = "winit-wayland" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce5afb2ba07da603f84b722c95f9f9396d2cedae3944fb6c0cda4a6f88de545" +dependencies = [ + "ahash", + "bitflags 2.11.0", + "calloop", + "cursor-icon", + "dpi", + "libc", + "memmap2", + "raw-window-handle", + "rustix 1.1.4", + "smithay-client-toolkit", + "smol_str", "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-protocols-plasma", + "winit-common", + "winit-core", +] + +[[package]] +name = "winit-web" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2490a953fb776fbbd5e295d54f1c3847f4f15b6c3929ec53c09acda6487a92" +dependencies = [ + "atomic-waker", + "bitflags 2.11.0", + "concurrent-queue", + "cursor-icon", + "dpi", + "js-sys", + "pin-project", + "raw-window-handle", + "smol_str", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", "web-time", - "windows-sys 0.52.0", + "winit-core", +] + +[[package]] +name = "winit-win32" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644ea78af0e858aa3b092e5d1c67c41995a98220c81813f1353b28bc8bb91eaa" +dependencies = [ + "bitflags 2.11.0", + "cursor-icon", + "dpi", + "raw-window-handle", + "smol_str", + "tracing", + "unicode-segmentation", + "windows-sys 0.59.0", + "winit-core", +] + +[[package]] +name = "winit-x11" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa5b600756534c7041aa93cd0d244d44b09fca1b89e202bd1cd80dd9f3636c46" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "calloop", + "cursor-icon", + "dpi", + "libc", + "percent-encoding", + "raw-window-handle", + "rustix 1.1.4", + "smol_str", + "tracing", + "winit-common", + "winit-core", "x11-dl", "x11rb", "xkbcommon-dl", @@ -6840,6 +6853,7 @@ dependencies = [ "once_cell", "rustix 1.1.4", "x11rb-protocol", + "xcursor", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 56b9e2c9..f1a04f02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,4 +143,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 = "85f83ab" } +smithay = { git = "https://github.com/smithay/smithay.git", rev = "8eb4076" } diff --git a/src/backend/winit.rs b/src/backend/winit.rs index a35b0c98..0ff249a7 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -26,7 +26,7 @@ use smithay::{ calloop::{EventLoop, ping}, wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::DisplayHandle, - winit::platform::pump_events::PumpStatus, + winit::event_loop::pump_events::PumpStatus, }, utils::Transform, wayland::{dmabuf::DmabufFeedbackBuilder, presentation::Refresh}, diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 4d198cfa..9f8584f5 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -61,8 +61,8 @@ use smithay::{ PointerTarget, RelativeMotionEvent, }, touch::{ - DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget, - UpEvent, + DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, + TouchTarget, UpEvent, }, }, output::Output, @@ -79,7 +79,7 @@ use std::{ hash::Hash, sync::{ Arc, LazyLock, Mutex, - atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}, + atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicUsize, Ordering}, }, }; @@ -114,6 +114,7 @@ pub struct CosmicStackInternal { scroll_to_focus: AtomicBool, previous_keyboard: AtomicUsize, pointer_entered: AtomicU8, + touch_serial: AtomicU32, reenter: AtomicBool, potential_drag: Mutex>, override_alive: AtomicBool, @@ -171,6 +172,7 @@ impl CosmicStack { scroll_to_focus: AtomicBool::new(false), previous_keyboard: AtomicUsize::new(0), pointer_entered: AtomicU8::new(0), + touch_serial: AtomicU32::new(0), reenter: AtomicBool::new(false), potential_drag: Mutex::new(None), override_alive: AtomicBool::new(true), @@ -1815,55 +1817,62 @@ impl PointerTarget for CosmicStack { } impl TouchTarget for CosmicStack { - fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent, seq: Serial) { + fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent) { let mut event = event.clone(); let active_window_geo = self.0.with_program(|p| { p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry() }); event.location -= active_window_geo.loc.to_f64(); - TouchTarget::down(&self.0, seat, data, &event, seq) + self.0 + .with_program(|p| p.touch_serial.store(event.serial.into(), Ordering::Release)); + TouchTarget::down(&self.0, seat, data, &event) } - fn up(&self, seat: &Seat, data: &mut State, event: &UpEvent, seq: Serial) { - TouchTarget::up(&self.0, seat, data, event, seq) + fn up(&self, seat: &Seat, data: &mut State, event: &UpEvent) { + TouchTarget::up(&self.0, seat, data, event) } - fn motion(&self, seat: &Seat, data: &mut State, event: &TouchMotionEvent, seq: Serial) { + fn motion(&self, seat: &Seat, data: &mut State, event: &TouchMotionEvent) { let mut event = event.clone(); let active_window_geo = self.0.with_program(|p| { p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry() }); event.location -= active_window_geo.loc.to_f64(); - TouchTarget::motion(&self.0, seat, data, &event, seq); + TouchTarget::motion(&self.0, seat, data, &event); if event.location.y < 0.0 || event.location.y > TAB_HEIGHT as f64 || event.location.x < 64.0 || event.location.x > (active_window_geo.size.w as f64 - 64.0) { - self.start_drag(data, seat, seq); + self.start_drag( + data, + seat, + self.0 + .with_program(|p| p.touch_serial.load(Ordering::Acquire)) + .into(), + ); } } - fn frame(&self, seat: &Seat, data: &mut State, seq: Serial) { - TouchTarget::frame(&self.0, seat, data, seq) + fn frame(&self, seat: &Seat, data: &mut State, frame: FrameMarker) { + TouchTarget::frame(&self.0, seat, data, frame) } - fn cancel(&self, seat: &Seat, data: &mut State, seq: Serial) { - TouchTarget::cancel(&self.0, seat, data, seq) + fn cancel(&self, seat: &Seat, data: &mut State, frame: FrameMarker) { + TouchTarget::cancel(&self.0, seat, data, frame) } - fn shape(&self, seat: &Seat, data: &mut State, event: &ShapeEvent, seq: Serial) { - TouchTarget::shape(&self.0, seat, data, event, seq) + fn shape(&self, seat: &Seat, data: &mut State, event: &ShapeEvent) { + TouchTarget::shape(&self.0, seat, data, event) } - fn orientation( - &self, - _seat: &Seat, - _data: &mut State, - _event: &OrientationEvent, - _seq: Serial, - ) { + fn orientation(&self, seat: &Seat, data: &mut State, event: &OrientationEvent) { + TouchTarget::orientation(&self.0, seat, data, event) + } + + fn last_frame(&self, seat: &Seat, data: &mut State) -> Option { + TouchTarget::last_frame(&self.0, seat, data) } } diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index dd45de4b..0adbfab7 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -47,8 +47,8 @@ use smithay::{ GestureSwipeUpdateEvent, MotionEvent, PointerTarget, RelativeMotionEvent, }, touch::{ - DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget, - UpEvent, + DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, + TouchTarget, UpEvent, }, }, output::Output, @@ -1190,43 +1190,42 @@ impl PointerTarget for CosmicWindow { } impl TouchTarget for CosmicWindow { - fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent, seq: Serial) { + fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent) { let mut event = event.clone(); self.0.with_program(|p| { event.location -= p.window.geometry().loc.to_f64(); }); - TouchTarget::down(&self.0, seat, data, &event, seq) + TouchTarget::down(&self.0, seat, data, &event) } - fn up(&self, seat: &Seat, data: &mut State, event: &UpEvent, seq: Serial) { - TouchTarget::up(&self.0, seat, data, event, seq) + fn up(&self, seat: &Seat, data: &mut State, event: &UpEvent) { + TouchTarget::up(&self.0, seat, data, event) } - fn motion(&self, seat: &Seat, data: &mut State, event: &TouchMotionEvent, seq: Serial) { + fn motion(&self, seat: &Seat, data: &mut State, event: &TouchMotionEvent) { let mut event = event.clone(); event.location -= self.0.with_program(|p| p.window.geometry().loc.to_f64()); - TouchTarget::motion(&self.0, seat, data, &event, seq) + TouchTarget::motion(&self.0, seat, data, &event) } - fn frame(&self, seat: &Seat, data: &mut State, seq: Serial) { - TouchTarget::frame(&self.0, seat, data, seq) + fn frame(&self, seat: &Seat, data: &mut State, frame: FrameMarker) { + TouchTarget::frame(&self.0, seat, data, frame) } - fn cancel(&self, seat: &Seat, data: &mut State, seq: Serial) { - TouchTarget::cancel(&self.0, seat, data, seq) + fn cancel(&self, seat: &Seat, data: &mut State, frame: FrameMarker) { + TouchTarget::cancel(&self.0, seat, data, frame) } - fn shape(&self, seat: &Seat, data: &mut State, event: &ShapeEvent, seq: Serial) { - TouchTarget::shape(&self.0, seat, data, event, seq) + fn shape(&self, seat: &Seat, data: &mut State, event: &ShapeEvent) { + TouchTarget::shape(&self.0, seat, data, event) } - fn orientation( - &self, - _seat: &Seat, - _data: &mut State, - _event: &OrientationEvent, - _seq: Serial, - ) { + fn orientation(&self, seat: &Seat, data: &mut State, event: &OrientationEvent) { + TouchTarget::orientation(&self.0, seat, data, event) + } + + fn last_frame(&self, seat: &Seat, data: &mut State) -> Option { + TouchTarget::last_frame(&self.0, seat, data) } } diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index b0366f45..fff77282 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -29,8 +29,8 @@ use smithay::{ MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent, }, touch::{ - DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget, - UpEvent, + DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, + TouchTarget, UpEvent, }, }, reexports::wayland_server::{ @@ -498,39 +498,36 @@ impl PointerTarget for PointerFocusTarget { } impl TouchTarget for PointerFocusTarget { - fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent, seq: Serial) { - self.inner_touch_target().down(seat, data, event, seq); + fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent) { + self.inner_touch_target().down(seat, data, event); } - fn up(&self, seat: &Seat, data: &mut State, event: &UpEvent, seq: Serial) { - self.inner_touch_target().up(seat, data, event, seq); + fn up(&self, seat: &Seat, data: &mut State, event: &UpEvent) { + self.inner_touch_target().up(seat, data, event); } - fn motion(&self, seat: &Seat, data: &mut State, event: &TouchMotionEvent, seq: Serial) { - self.inner_touch_target().motion(seat, data, event, seq); + fn motion(&self, seat: &Seat, data: &mut State, event: &TouchMotionEvent) { + self.inner_touch_target().motion(seat, data, event); } - fn frame(&self, seat: &Seat, data: &mut State, seq: Serial) { - self.inner_touch_target().frame(seat, data, seq); + fn frame(&self, seat: &Seat, data: &mut State, frame: FrameMarker) { + self.inner_touch_target().frame(seat, data, frame); } - fn cancel(&self, seat: &Seat, data: &mut State, seq: Serial) { - self.inner_touch_target().cancel(seat, data, seq); + fn cancel(&self, seat: &Seat, data: &mut State, frame: FrameMarker) { + self.inner_touch_target().cancel(seat, data, frame); } - fn shape(&self, seat: &Seat, data: &mut State, event: &ShapeEvent, seq: Serial) { - self.inner_touch_target().shape(seat, data, event, seq); + fn shape(&self, seat: &Seat, data: &mut State, event: &ShapeEvent) { + self.inner_touch_target().shape(seat, data, event); } - fn orientation( - &self, - seat: &Seat, - data: &mut State, - event: &OrientationEvent, - seq: Serial, - ) { - self.inner_touch_target() - .orientation(seat, data, event, seq); + fn orientation(&self, seat: &Seat, data: &mut State, event: &OrientationEvent) { + self.inner_touch_target().orientation(seat, data, event); + } + + fn last_frame(&self, seat: &Seat, data: &mut State) -> Option { + self.inner_touch_target().last_frame(seat, data) } } diff --git a/src/shell/grabs/delay.rs b/src/shell/grabs/delay.rs index dbf98d91..96d33dc6 100644 --- a/src/shell/grabs/delay.rs +++ b/src/shell/grabs/delay.rs @@ -202,19 +202,12 @@ impl> TouchGrab for DelayGrab { handle: &mut TouchInnerHandle<'_, State>, focus: Option<(::TouchFocus, Point)>, event: &DownEvent, - seq: Serial, ) { - handle.down(data, focus, event, seq); + handle.down(data, focus, event); } - fn up( - &mut self, - data: &mut State, - handle: &mut TouchInnerHandle<'_, State>, - event: &UpEvent, - seq: Serial, - ) { - handle.up(data, event, seq); + fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) { + handle.up(data, event); if event.slot == TouchGrab::start_data(self).slot { handle.unset_grab(self, data); @@ -227,9 +220,8 @@ impl> TouchGrab for DelayGrab { handle: &mut TouchInnerHandle<'_, State>, focus: Option<(::TouchFocus, Point)>, event: &TouchMotionEvent, - seq: Serial, ) { - handle.motion(data, focus, event, seq); + handle.motion(data, focus, event); let distance = self.start_data.distance(event.location); if distance >= 1. @@ -245,12 +237,12 @@ impl> TouchGrab for DelayGrab { } } - fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { - handle.frame(data, seq) + fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { + handle.frame(data) } - fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { - handle.cancel(data, seq); + fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { + handle.cancel(data); handle.unset_grab(self, data); } @@ -259,9 +251,8 @@ impl> TouchGrab for DelayGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &ShapeEvent, - seq: Serial, ) { - handle.shape(data, event, seq) + handle.shape(data, event) } fn orientation( @@ -269,9 +260,8 @@ impl> TouchGrab for DelayGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &OrientationEvent, - seq: Serial, ) { - handle.orientation(data, event, seq); + handle.orientation(data, event); } fn start_data(&self) -> &TouchGrabStartData { diff --git a/src/shell/grabs/menu/mod.rs b/src/shell/grabs/menu/mod.rs index 094f941c..14a9f917 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -718,7 +718,6 @@ impl TouchGrab for MenuGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &DownEvent, - seq: Serial, ) { { let mut guard = self.elements.lock().unwrap(); @@ -753,21 +752,15 @@ impl TouchGrab for MenuGrab { time: event.time, }; if element.touch_entered.is_none() { - TouchTarget::down(&element.iced, &self.seat, data, &new_event, seq); + TouchTarget::down(&element.iced, &self.seat, data, &new_event); element.touch_entered = Some(event.slot); } } } - handle.down(data, None, event, seq); + handle.down(data, None, event); } - fn up( - &mut self, - data: &mut State, - handle: &mut TouchInnerHandle<'_, State>, - event: &UpEvent, - seq: Serial, - ) { + fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) { { let elements = self.elements.lock().unwrap(); for element in elements.iter().filter(|elem| { @@ -775,7 +768,7 @@ impl TouchGrab for MenuGrab { .as_ref() .is_some_and(|slot| *slot == event.slot) }) { - TouchTarget::up(&element.iced, &self.seat, data, event, seq); + TouchTarget::up(&element.iced, &self.seat, data, event); } } handle.unset_grab(self, data); @@ -787,7 +780,6 @@ impl TouchGrab for MenuGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &TouchMotionEvent, - seq: Serial, ) { { let elements = self.elements.lock().unwrap(); @@ -796,24 +788,24 @@ impl TouchGrab for MenuGrab { .as_ref() .is_some_and(|slot| *slot == event.slot) }) { - TouchTarget::motion(&element.iced, &self.seat, data, event, seq); + TouchTarget::motion(&element.iced, &self.seat, data, event); } } - handle.motion(data, None, event, seq); + handle.motion(data, None, event); } - fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { - handle.frame(data, seq); + fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { + handle.frame(data); } - fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { + fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { { let mut elements = self.elements.lock().unwrap(); for element in elements.iter_mut() { let _ = element.touch_entered.take(); } } - handle.cancel(data, seq); + handle.cancel(data); } fn shape( @@ -821,9 +813,8 @@ impl TouchGrab for MenuGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &smithay::input::touch::ShapeEvent, - seq: Serial, ) { - handle.shape(data, event, seq); + handle.shape(data, event); } fn orientation( @@ -831,9 +822,8 @@ impl TouchGrab for MenuGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &smithay::input::touch::OrientationEvent, - seq: Serial, ) { - handle.orientation(data, event, seq); + handle.orientation(data, event); } fn start_data(&self) -> &TouchGrabStartData { diff --git a/src/shell/grabs/mod.rs b/src/shell/grabs/mod.rs index 0c5448c1..515916fd 100644 --- a/src/shell/grabs/mod.rs +++ b/src/shell/grabs/mod.rs @@ -387,24 +387,17 @@ impl TouchGrab for ResizeGrab { handle: &mut TouchInnerHandle<'_, State>, focus: Option<(PointerFocusTarget, Point)>, event: &DownEvent, - seq: Serial, ) { match self { - ResizeGrab::Floating(grab) => TouchGrab::down(grab, data, handle, focus, event, seq), - ResizeGrab::Tiling(grab) => TouchGrab::down(grab, data, handle, focus, event, seq), + ResizeGrab::Floating(grab) => TouchGrab::down(grab, data, handle, focus, event), + ResizeGrab::Tiling(grab) => TouchGrab::down(grab, data, handle, focus, event), } } - fn up( - &mut self, - data: &mut State, - handle: &mut TouchInnerHandle<'_, State>, - event: &UpEvent, - seq: Serial, - ) { + fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) { match self { - ResizeGrab::Floating(grab) => TouchGrab::up(grab, data, handle, event, seq), - ResizeGrab::Tiling(grab) => TouchGrab::up(grab, data, handle, event, seq), + ResizeGrab::Floating(grab) => TouchGrab::up(grab, data, handle, event), + ResizeGrab::Tiling(grab) => TouchGrab::up(grab, data, handle, event), } } @@ -414,25 +407,24 @@ impl TouchGrab for ResizeGrab { handle: &mut TouchInnerHandle<'_, State>, focus: Option<(PointerFocusTarget, Point)>, event: &TouchMotionEvent, - seq: Serial, ) { match self { - ResizeGrab::Floating(grab) => TouchGrab::motion(grab, data, handle, focus, event, seq), - ResizeGrab::Tiling(grab) => TouchGrab::motion(grab, data, handle, focus, event, seq), + ResizeGrab::Floating(grab) => TouchGrab::motion(grab, data, handle, focus, event), + ResizeGrab::Tiling(grab) => TouchGrab::motion(grab, data, handle, focus, event), } } - fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { + fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { match self { - ResizeGrab::Floating(grab) => TouchGrab::frame(grab, data, handle, seq), - ResizeGrab::Tiling(grab) => TouchGrab::frame(grab, data, handle, seq), + ResizeGrab::Floating(grab) => TouchGrab::frame(grab, data, handle), + ResizeGrab::Tiling(grab) => TouchGrab::frame(grab, data, handle), } } - fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { + fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { match self { - ResizeGrab::Floating(grab) => TouchGrab::cancel(grab, data, handle, seq), - ResizeGrab::Tiling(grab) => TouchGrab::cancel(grab, data, handle, seq), + ResizeGrab::Floating(grab) => TouchGrab::cancel(grab, data, handle), + ResizeGrab::Tiling(grab) => TouchGrab::cancel(grab, data, handle), } } @@ -441,11 +433,10 @@ impl TouchGrab for ResizeGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &ShapeEvent, - seq: Serial, ) { match self { - ResizeGrab::Floating(grab) => TouchGrab::shape(grab, data, handle, event, seq), - ResizeGrab::Tiling(grab) => TouchGrab::shape(grab, data, handle, event, seq), + ResizeGrab::Floating(grab) => TouchGrab::shape(grab, data, handle, event), + ResizeGrab::Tiling(grab) => TouchGrab::shape(grab, data, handle, event), } } @@ -454,11 +445,10 @@ impl TouchGrab for ResizeGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &OrientationEvent, - seq: Serial, ) { match self { - ResizeGrab::Floating(grab) => TouchGrab::orientation(grab, data, handle, event, seq), - ResizeGrab::Tiling(grab) => TouchGrab::orientation(grab, data, handle, event, seq), + ResizeGrab::Floating(grab) => TouchGrab::orientation(grab, data, handle, event), + ResizeGrab::Tiling(grab) => TouchGrab::orientation(grab, data, handle, event), } } @@ -729,24 +719,17 @@ impl TouchGrab for MoveGrab { handle: &mut TouchInnerHandle<'_, State>, focus: Option<(PointerFocusTarget, Point)>, event: &DownEvent, - seq: Serial, ) { match self { - MoveGrab::Move(grab) => TouchGrab::down(grab, data, handle, focus, event, seq), - MoveGrab::Delayed(grab) => TouchGrab::down(grab, data, handle, focus, event, seq), + MoveGrab::Move(grab) => TouchGrab::down(grab, data, handle, focus, event), + MoveGrab::Delayed(grab) => TouchGrab::down(grab, data, handle, focus, event), } } - fn up( - &mut self, - data: &mut State, - handle: &mut TouchInnerHandle<'_, State>, - event: &UpEvent, - seq: Serial, - ) { + fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) { match self { - MoveGrab::Move(grab) => TouchGrab::up(grab, data, handle, event, seq), - MoveGrab::Delayed(grab) => TouchGrab::up(grab, data, handle, event, seq), + MoveGrab::Move(grab) => TouchGrab::up(grab, data, handle, event), + MoveGrab::Delayed(grab) => TouchGrab::up(grab, data, handle, event), } } @@ -756,25 +739,24 @@ impl TouchGrab for MoveGrab { handle: &mut TouchInnerHandle<'_, State>, focus: Option<(PointerFocusTarget, Point)>, event: &TouchMotionEvent, - seq: Serial, ) { match self { - MoveGrab::Move(grab) => TouchGrab::motion(grab, data, handle, focus, event, seq), - MoveGrab::Delayed(grab) => TouchGrab::motion(grab, data, handle, focus, event, seq), + MoveGrab::Move(grab) => TouchGrab::motion(grab, data, handle, focus, event), + MoveGrab::Delayed(grab) => TouchGrab::motion(grab, data, handle, focus, event), } } - fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { + fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { match self { - MoveGrab::Move(grab) => TouchGrab::frame(grab, data, handle, seq), - MoveGrab::Delayed(grab) => TouchGrab::frame(grab, data, handle, seq), + MoveGrab::Move(grab) => TouchGrab::frame(grab, data, handle), + MoveGrab::Delayed(grab) => TouchGrab::frame(grab, data, handle), } } - fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { + fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { match self { - MoveGrab::Move(grab) => TouchGrab::cancel(grab, data, handle, seq), - MoveGrab::Delayed(grab) => TouchGrab::cancel(grab, data, handle, seq), + MoveGrab::Move(grab) => TouchGrab::cancel(grab, data, handle), + MoveGrab::Delayed(grab) => TouchGrab::cancel(grab, data, handle), } } @@ -783,11 +765,10 @@ impl TouchGrab for MoveGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &ShapeEvent, - seq: Serial, ) { match self { - MoveGrab::Move(grab) => TouchGrab::shape(grab, data, handle, event, seq), - MoveGrab::Delayed(grab) => TouchGrab::shape(grab, data, handle, event, seq), + MoveGrab::Move(grab) => TouchGrab::shape(grab, data, handle, event), + MoveGrab::Delayed(grab) => TouchGrab::shape(grab, data, handle, event), } } @@ -796,11 +777,10 @@ impl TouchGrab for MoveGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &OrientationEvent, - seq: Serial, ) { match self { - MoveGrab::Move(grab) => TouchGrab::orientation(grab, data, handle, event, seq), - MoveGrab::Delayed(grab) => TouchGrab::orientation(grab, data, handle, event, seq), + MoveGrab::Move(grab) => TouchGrab::orientation(grab, data, handle, event), + MoveGrab::Delayed(grab) => TouchGrab::orientation(grab, data, handle, event), } } diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index c92c5bf4..e73b108d 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -40,7 +40,7 @@ use smithay::{ touch::{self, GrabStartData as TouchGrabStartData, TouchGrab, TouchInnerHandle}, }, output::Output, - utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER, Scale, Serial}, + utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER, Scale}, }; use std::{ collections::HashSet, @@ -652,9 +652,8 @@ impl TouchGrab for MoveGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &touch::DownEvent, - seq: Serial, ) { - handle.down(data, None, event, seq) + handle.down(data, None, event) } fn up( @@ -662,13 +661,12 @@ impl TouchGrab for MoveGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &touch::UpEvent, - seq: Serial, ) { if event.slot == >::start_data(self).slot { handle.unset_grab(self, data); } - handle.up(data, event, seq); + handle.up(data, event); } fn motion( @@ -677,20 +675,19 @@ impl TouchGrab for MoveGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &touch::MotionEvent, - seq: Serial, ) { if event.slot == >::start_data(self).slot { self.update_location(data, event.location); } - handle.motion(data, None, event, seq); + handle.motion(data, None, event); } - fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { - handle.frame(data, seq) + fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { + handle.frame(data) } - fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, _seq: Serial) { + fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { handle.unset_grab(self, data); } @@ -699,9 +696,8 @@ impl TouchGrab for MoveGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &touch::ShapeEvent, - seq: Serial, ) { - handle.shape(data, event, seq) + handle.shape(data, event) } fn orientation( @@ -709,9 +705,8 @@ impl TouchGrab for MoveGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &touch::OrientationEvent, - seq: Serial, ) { - handle.orientation(data, event, seq) + handle.orientation(data, event) } fn start_data(&self) -> &TouchGrabStartData { diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs index a1e63eac..f9e5cbfd 100644 --- a/src/shell/layout/floating/grabs/resize.rs +++ b/src/shell/layout/floating/grabs/resize.rs @@ -29,7 +29,7 @@ use smithay::{ }, }, output::Output, - utils::{IsAlive, Logical, Point, Rectangle, Serial, Size}, + utils::{IsAlive, Logical, Point, Rectangle, Size}, }; use tracing::debug; @@ -330,23 +330,16 @@ impl TouchGrab for ResizeSurfaceGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &DownEvent, - seq: Serial, ) { - handle.down(data, None, event, seq) + handle.down(data, None, event) } - fn up( - &mut self, - data: &mut State, - handle: &mut TouchInnerHandle<'_, State>, - event: &UpEvent, - seq: Serial, - ) { + fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) { if event.slot == >::start_data(self).slot { handle.unset_grab(self, data); } - handle.up(data, event, seq); + handle.up(data, event); } fn motion( @@ -355,7 +348,6 @@ impl TouchGrab for ResizeSurfaceGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &TouchMotionEvent, - seq: Serial, ) { if event.slot == >::start_data(self).slot && self.update_location(event.location.as_global()) @@ -363,14 +355,14 @@ impl TouchGrab for ResizeSurfaceGrab { handle.unset_grab(self, data); } - handle.motion(data, None, event, seq); + handle.motion(data, None, event); } - fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { - handle.frame(data, seq) + fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { + handle.frame(data) } - fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, _seq: Serial) { + fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { handle.unset_grab(self, data); } @@ -379,9 +371,8 @@ impl TouchGrab for ResizeSurfaceGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &ShapeEvent, - seq: Serial, ) { - handle.shape(data, event, seq) + handle.shape(data, event) } fn orientation( @@ -389,9 +380,8 @@ impl TouchGrab for ResizeSurfaceGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &OrientationEvent, - seq: Serial, ) { - handle.orientation(data, event, seq) + handle.orientation(data, event) } fn start_data(&self) -> &TouchGrabStartData { diff --git a/src/shell/layout/mod.rs b/src/shell/layout/mod.rs index a2475411..987f2f90 100644 --- a/src/shell/layout/mod.rs +++ b/src/shell/layout/mod.rs @@ -32,7 +32,7 @@ pub fn is_dialog(window: &CosmicSurface) -> bool { } WindowSurface::X11(surface) => { if surface.is_override_redirect() - || surface.is_popup() + || surface.is_modal() || !matches!( surface.window_type(), None | Some(WmWindowType::Normal) | Some(WmWindowType::Utility) diff --git a/src/shell/layout/tiling/grabs/resize.rs b/src/shell/layout/tiling/grabs/resize.rs index 51d5c9a2..9b135119 100644 --- a/src/shell/layout/tiling/grabs/resize.rs +++ b/src/shell/layout/tiling/grabs/resize.rs @@ -22,12 +22,13 @@ use smithay::{ PointerTarget, RelativeMotionEvent, }, touch::{ - DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent, - OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, TouchTarget, UpEvent, + DownEvent, FrameMarker, GrabStartData as TouchGrabStartData, + MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab, + TouchInnerHandle, TouchTarget, UpEvent, }, }, output::WeakOutput, - utils::{IsAlive, Logical, Point, Serial}, + utils::{IsAlive, Logical, Point}, }; use super::super::{Data, TilingLayout}; @@ -126,7 +127,7 @@ impl PointerTarget for ResizeForkTarget { } impl TouchTarget for ResizeForkTarget { - fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent, _seq: Serial) { + fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent) { let seat = seat.clone(); let node = self.node.clone(); let output = self.output.clone(); @@ -157,25 +158,14 @@ impl TouchTarget for ResizeForkTarget { }); } - fn up(&self, _seat: &Seat, _data: &mut State, _event: &UpEvent, _seq: Serial) {} - fn motion( - &self, - _seat: &Seat, - _data: &mut State, - _event: &TouchMotionEvent, - _seq: Serial, - ) { - } - fn frame(&self, _seat: &Seat, _data: &mut State, _seq: Serial) {} - fn cancel(&self, _seat: &Seat, _data: &mut State, _seq: Serial) {} - fn shape(&self, _seat: &Seat, _data: &mut State, _event: &ShapeEvent, _seq: Serial) {} - fn orientation( - &self, - _seat: &Seat, - _data: &mut State, - _event: &OrientationEvent, - _seq: Serial, - ) { + fn up(&self, _seat: &Seat, _data: &mut State, _event: &UpEvent) {} + fn motion(&self, _seat: &Seat, _data: &mut State, _event: &TouchMotionEvent) {} + fn frame(&self, _seat: &Seat, _data: &mut State, _frame: FrameMarker) {} + fn cancel(&self, _seat: &Seat, _data: &mut State, _frame: FrameMarker) {} + fn shape(&self, _seat: &Seat, _data: &mut State, _event: &ShapeEvent) {} + fn orientation(&self, _seat: &Seat, _data: &mut State, _event: &OrientationEvent) {} + fn last_frame(&self, _seat: &Seat, _data: &mut State) -> Option { + None } } @@ -508,23 +498,16 @@ impl TouchGrab for ResizeForkGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &DownEvent, - seq: Serial, ) { - handle.down(data, None, event, seq) + handle.down(data, None, event) } - fn up( - &mut self, - data: &mut State, - handle: &mut TouchInnerHandle<'_, State>, - event: &UpEvent, - seq: Serial, - ) { + fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) { if event.slot == >::start_data(self).slot { handle.unset_grab(self, data); } - handle.up(data, event, seq); + handle.up(data, event); } fn motion( @@ -533,7 +516,6 @@ impl TouchGrab for ResizeForkGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &TouchMotionEvent, - seq: Serial, ) { if event.slot == >::start_data(self).slot && self.update_location(data, event.location, false) @@ -541,14 +523,14 @@ impl TouchGrab for ResizeForkGrab { handle.unset_grab(self, data); } - handle.motion(data, None, event, seq); + handle.motion(data, None, event); } - fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { - handle.frame(data, seq) + fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { + handle.frame(data) } - fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, _seq: Serial) { + fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>) { handle.unset_grab(self, data); } @@ -557,9 +539,8 @@ impl TouchGrab for ResizeForkGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &ShapeEvent, - seq: Serial, ) { - handle.shape(data, event, seq) + handle.shape(data, event) } fn start_data(&self) -> &TouchGrabStartData { @@ -574,9 +555,8 @@ impl TouchGrab for ResizeForkGrab { data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &OrientationEvent, - seq: Serial, ) { - handle.orientation(data, event, seq) + handle.orientation(data, event) } fn unset(&mut self, data: &mut State) { diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs index b4ba339f..40f8da8e 100644 --- a/src/shell/zoom.rs +++ b/src/shell/zoom.rs @@ -22,8 +22,8 @@ use smithay::{ MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent, }, touch::{ - DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget, - UpEvent, + DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, + TouchTarget, UpEvent, }, }, output::Output, @@ -1036,58 +1036,59 @@ impl PointerTarget for ZoomFocusTarget { } impl TouchTarget for ZoomFocusTarget { - fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent, seq: Serial) { + fn down(&self, seat: &Seat, data: &mut State, event: &DownEvent) { match self { - ZoomFocusTarget::Main(elem) => TouchTarget::down(elem, seat, data, event, seq), - ZoomFocusTarget::Menu(elem) => TouchTarget::down(elem, seat, data, event, seq), + ZoomFocusTarget::Main(elem) => TouchTarget::down(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => TouchTarget::down(elem, seat, data, event), } } - fn up(&self, seat: &Seat, data: &mut State, event: &UpEvent, seq: Serial) { + fn up(&self, seat: &Seat, data: &mut State, event: &UpEvent) { match self { - ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event, seq), - ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event, seq), + ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event), } } - fn motion(&self, seat: &Seat, data: &mut State, event: &TouchMotionEvent, seq: Serial) { + fn motion(&self, seat: &Seat, data: &mut State, event: &TouchMotionEvent) { match self { - ZoomFocusTarget::Main(elem) => TouchTarget::motion(elem, seat, data, event, seq), - ZoomFocusTarget::Menu(elem) => TouchTarget::motion(elem, seat, data, event, seq), + ZoomFocusTarget::Main(elem) => TouchTarget::motion(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => TouchTarget::motion(elem, seat, data, event), } } - fn frame(&self, seat: &Seat, data: &mut State, seq: Serial) { + fn frame(&self, seat: &Seat, data: &mut State, frame: FrameMarker) { match self { - ZoomFocusTarget::Main(elem) => TouchTarget::frame(elem, seat, data, seq), - ZoomFocusTarget::Menu(elem) => TouchTarget::frame(elem, seat, data, seq), + ZoomFocusTarget::Main(elem) => TouchTarget::frame(elem, seat, data, frame), + ZoomFocusTarget::Menu(elem) => TouchTarget::frame(elem, seat, data, frame), } } - fn cancel(&self, seat: &Seat, data: &mut State, seq: Serial) { + fn cancel(&self, seat: &Seat, data: &mut State, frame: FrameMarker) { match self { - ZoomFocusTarget::Main(elem) => TouchTarget::cancel(elem, seat, data, seq), - ZoomFocusTarget::Menu(elem) => TouchTarget::cancel(elem, seat, data, seq), + ZoomFocusTarget::Main(elem) => TouchTarget::cancel(elem, seat, data, frame), + ZoomFocusTarget::Menu(elem) => TouchTarget::cancel(elem, seat, data, frame), } } - fn shape(&self, seat: &Seat, data: &mut State, event: &ShapeEvent, seq: Serial) { + fn shape(&self, seat: &Seat, data: &mut State, event: &ShapeEvent) { match self { - ZoomFocusTarget::Main(elem) => TouchTarget::shape(elem, seat, data, event, seq), - ZoomFocusTarget::Menu(elem) => TouchTarget::shape(elem, seat, data, event, seq), + ZoomFocusTarget::Main(elem) => TouchTarget::shape(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => TouchTarget::shape(elem, seat, data, event), } } - fn orientation( - &self, - seat: &Seat, - data: &mut State, - event: &OrientationEvent, - seq: Serial, - ) { + fn orientation(&self, seat: &Seat, data: &mut State, event: &OrientationEvent) { match self { - ZoomFocusTarget::Main(elem) => TouchTarget::orientation(elem, seat, data, event, seq), - ZoomFocusTarget::Menu(elem) => TouchTarget::orientation(elem, seat, data, event, seq), + ZoomFocusTarget::Main(elem) => TouchTarget::orientation(elem, seat, data, event), + ZoomFocusTarget::Menu(elem) => TouchTarget::orientation(elem, seat, data, event), + } + } + + fn last_frame(&self, seat: &Seat, data: &mut State) -> Option { + match self { + ZoomFocusTarget::Main(elem) => TouchTarget::last_frame(elem, seat, data), + ZoomFocusTarget::Menu(elem) => TouchTarget::last_frame(elem, seat, data), } } } diff --git a/src/utils/iced/mod.rs b/src/utils/iced/mod.rs index 186abf04..44a722db 100644 --- a/src/utils/iced/mod.rs +++ b/src/utils/iced/mod.rs @@ -51,13 +51,12 @@ use smithay::{ PointerTarget, RelativeMotionEvent, }, touch::{ - DownEvent, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchTarget, - UpEvent, + DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, + TouchTarget, UpEvent, }, }, output::Output, - reexports::calloop::RegistrationToken, - reexports::calloop::{self, LoopHandle, futures::Scheduler}, + reexports::calloop::{self, LoopHandle, RegistrationToken, futures::Scheduler}, utils::{ Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, Transform, @@ -180,6 +179,8 @@ pub(crate) struct IcedElementInternal { last_seat: Arc, Serial)>>>, cursor_pos: Option>, touch_map: HashMap, + last_touch_frame: Option, + last_touch_serial: Option, // iced theme: Theme, @@ -228,6 +229,8 @@ impl Clone for IcedElementInternal

{ last_seat: self.last_seat.clone(), cursor_pos: self.cursor_pos, touch_map: self.touch_map.clone(), + last_touch_frame: None, + last_touch_serial: None, theme: self.theme.clone(), renderer, state, @@ -253,6 +256,8 @@ impl fmt::Debug for IcedElementInternal

{ .field("last_seat", &self.last_seat) .field("cursor_pos", &self.cursor_pos) .field("touch_map", &self.touch_map) + .field("last_touch_frame", &self.last_touch_frame) + .field("last_touch_serial", &self.last_touch_serial) .field("theme", &"...") .field("renderer", &"...") .field("state", &"...") @@ -309,6 +314,8 @@ impl IcedElement

{ cursor_pos: None, last_seat, touch_map: HashMap::new(), + last_touch_frame: None, + last_touch_serial: None, theme, renderer, state, @@ -633,7 +640,6 @@ impl TouchTarget for IcedEleme seat: &Seat, _data: &mut crate::state::State, event: &DownEvent, - seq: Serial, ) { let mut internal = self.0.lock().unwrap(); let id = Finger(i32::from(event.slot) as u64); @@ -644,7 +650,8 @@ impl TouchTarget for IcedEleme .queue_event(Event::Touch(TouchEvent::FingerPressed { id, position })); internal.touch_map.insert(id, position); internal.cursor_pos = Some(event_location); - *internal.last_seat.lock().unwrap() = Some((seat.clone(), seq)); + internal.last_touch_serial = Some(event.serial); + *internal.last_seat.lock().unwrap() = Some((seat.clone(), event.serial)); internal.update(false); } @@ -653,12 +660,12 @@ impl TouchTarget for IcedEleme seat: &Seat, _data: &mut crate::state::State, event: &UpEvent, - seq: Serial, ) { let mut internal = self.0.lock().unwrap(); let id = Finger(i32::from(event.slot) as u64); if let Some(position) = internal.touch_map.remove(&id) { - *internal.last_seat.lock().unwrap() = Some((seat.clone(), seq)); + *internal.last_seat.lock().unwrap() = + Some((seat.clone(), internal.last_touch_serial.unwrap())); internal .state .queue_event(Event::Touch(TouchEvent::FingerLifted { id, position })); @@ -671,13 +678,13 @@ impl TouchTarget for IcedEleme seat: &Seat, _data: &mut crate::state::State, event: &TouchMotionEvent, - seq: Serial, ) { let mut internal = self.0.lock().unwrap(); let id = Finger(i32::from(event.slot) as u64); let event_location = event.location.downscale(internal.additional_scale); let position = IcedPoint::new(event_location.x as f32, event_location.y as f32); - *internal.last_seat.lock().unwrap() = Some((seat.clone(), seq)); + *internal.last_seat.lock().unwrap() = + Some((seat.clone(), internal.last_touch_serial.unwrap())); internal .state .queue_event(Event::Touch(TouchEvent::FingerMoved { id, position })); @@ -690,17 +697,19 @@ impl TouchTarget for IcedEleme &self, _seat: &Seat, _data: &mut crate::state::State, - _seq: Serial, + frame: FrameMarker, ) { + self.0.lock().unwrap().last_touch_frame = Some(frame); } fn cancel( &self, _seat: &Seat, _data: &mut crate::state::State, - _seq: Serial, + frame: FrameMarker, ) { let mut internal = self.0.lock().unwrap(); + internal.last_touch_frame = Some(frame); for (id, position) in std::mem::take(&mut internal.touch_map) { internal .state @@ -714,7 +723,6 @@ impl TouchTarget for IcedEleme _seat: &Seat, _data: &mut crate::state::State, _event: &ShapeEvent, - _seq: Serial, ) { } @@ -723,9 +731,16 @@ impl TouchTarget for IcedEleme _seat: &Seat, _data: &mut crate::state::State, _event: &OrientationEvent, - _seq: Serial, ) { } + + fn last_frame( + &self, + _seat: &Seat, + _data: &mut crate::state::State, + ) -> Option { + self.0.lock().unwrap().last_touch_frame + } } impl KeyboardTarget for IcedElement

{ diff --git a/src/xwayland.rs b/src/xwayland.rs index eb3e1570..aba65ef5 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -115,6 +115,7 @@ impl State { &self.common.display_handle, None, std::iter::empty::<(OsString, OsString)>(), + std::iter::empty::(), true, Stdio::null(), Stdio::null(), From bb584aab7f8edc428b3328fb4e1be3869ecdc8e8 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 29 Jun 2026 17:31:44 +0200 Subject: [PATCH 03/56] backend: Don't attempt dma-copies from newer intel chips --- src/backend/kms/render/gles.rs | 64 +++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/src/backend/kms/render/gles.rs b/src/backend/kms/render/gles.rs index 5d79b1ee..3eb1e599 100644 --- a/src/backend/kms/render/gles.rs +++ b/src/backend/kms/render/gles.rs @@ -1,21 +1,30 @@ // SPDX-License-Identifier: GPL-3.0-only -use smithay::backend::{ - SwapBuffersError, - allocator::{ - Allocator, - dmabuf::{AnyError, Dmabuf, DmabufAllocator}, - gbm::GbmAllocator, - }, - drm::{CreateDrmNodeError, DrmNode}, - renderer::{ - RendererSuper, - gles::{GlesError, GlesRenderer}, - glow::GlowRenderer, - multigpu::{ApiDevice, Error as MultiError, GraphicsApi}, +use clap_lex::OsStrExt; +use rustix::path::Arg; +use smithay::{ + backend::{ + SwapBuffersError, + allocator::{ + Allocator, + dmabuf::{AnyError, Dmabuf, DmabufAllocator}, + gbm::GbmAllocator, + }, + drm::{CreateDrmNodeError, DrmNode}, + renderer::{ + RendererSuper, + gles::{GlesError, GlesRenderer, ffi}, + glow::GlowRenderer, + multigpu::{ApiDevice, Error as MultiError, GraphicsApi}, + }, }, + reexports::drm::control::Device, +}; +use std::{ + borrow::{Borrow, BorrowMut}, + cell::Cell, + ffi::{CStr, c_char}, }; -use std::{borrow::Borrow, cell::Cell}; use std::{ collections::HashMap, fmt, @@ -98,7 +107,7 @@ impl GbmGlowBackend { } } -impl GraphicsApi for GbmGlowBackend { +impl GraphicsApi for GbmGlowBackend { type Device = GbmGlowDevice; type Error = Error; @@ -123,11 +132,26 @@ impl GraphicsApi for GbmGlowBackend { .any(|renderer| renderer.node.dev_id() == node.dev_id()) }) .flat_map(|(node, (allocator, renderer))| { - let renderer = renderer.replace(None)?; + let mut renderer = renderer.replace(None)?; + let is_intel = allocator + .as_ref() + .get_driver() + .is_ok_and(|drv| drv.name().contains("i915")); + let intel_export_quirk = is_intel + && BorrowMut::::borrow_mut(&mut renderer) + .with_context(|gl| unsafe { + CStr::from_ptr(gl.GetString(ffi::RENDERER) as *const c_char) + }) + .is_ok_and(|name| { + name.as_str().is_ok_and(|name| { + name.contains("MTL") || name.contains("ARL") || name.contains("LNL") + }) + }); Some(GbmGlowDevice { node: *node, renderer, + intel_export_quirk, allocator: Box::new(DmabufAllocator(allocator.clone())), }) }) @@ -150,6 +174,7 @@ impl GraphicsApi for GbmGlowBackend { pub struct GbmGlowDevice { node: DrmNode, renderer: GlowRenderer, + intel_export_quirk: bool, allocator: Box>, } @@ -180,9 +205,14 @@ impl ApiDevice for GbmGlowDevice { fn can_do_cross_device_imports(&self) -> bool { !Borrow::::borrow(&self.renderer).is_software() } + + fn should_do_cross_device_exports(&self) -> bool { + !self.intel_export_quirk + } } -impl FromGlesError for MultiError, T> +impl FromGlesError + for MultiError, T> where T::Error: 'static, <::Renderer as RendererSuper>::Error: 'static, From c5775d20109ce5b7785b271d9283636da87c2e52 Mon Sep 17 00:00:00 2001 From: Tom Grushka Date: Tue, 30 Jun 2026 14:21:02 -0600 Subject: [PATCH 04/56] change update_focal_point to use f64 to avoid rounding errors --- src/shell/zoom.rs | 49 ++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs index 40f8da8e..ffeed63b 100644 --- a/src/shell/zoom.rs +++ b/src/shell/zoom.rs @@ -259,10 +259,8 @@ impl ZoomState { original_position: Point, movement: ZoomMovement, ) { - let cursor_position = cursor_position.to_i32_round(); - let original_position = original_position.to_i32_round(); - let output_geometry = output.geometry(); - let mut zoomed_output_geometry = output.zoomed_geometry().unwrap(); + let output_geometry = output.geometry().to_f64(); + let mut zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64(); let output_state = output.user_data().get::>().unwrap(); let mut output_state_ref = output_state.lock().unwrap(); @@ -275,44 +273,40 @@ impl ZoomState { let cursor_position = cursor_position.to_local(output); match movement { - ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position.to_f64(), + ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position, ZoomMovement::OnEdge => { if !zoomed_output_geometry - .overlaps_or_touches(Rectangle::new(original_position, Size::from((16, 16)))) + .overlaps_or_touches(Rectangle::new(original_position, Size::from((16., 16.)))) { zoomed_output_geometry.loc = cursor_position.to_global(output) - - zoomed_output_geometry.size.downscale(2).to_point(); + - zoomed_output_geometry.size.downscale(2.).to_point(); let mut focal_point = zoomed_output_geometry .loc .to_local(output) - .upscale( - output_geometry.size.w - / (output_geometry.size.w - zoomed_output_geometry.size.w), - ) + .upscale(output_state_ref.level) .to_global(output); focal_point.x = focal_point.x.clamp( output_geometry.loc.x, - output_geometry.loc.x + output_geometry.size.w - 1, + output_geometry.loc.x + output_geometry.size.w - 1., ); focal_point.y = focal_point.y.clamp( output_geometry.loc.y, - output_geometry.loc.y + output_geometry.size.h - 1, + output_geometry.loc.y + output_geometry.size.h - 1., ); output_state_ref.previous_point = Some((output_state_ref.focal_point, Instant::now())); - output_state_ref.focal_point = focal_point.to_local(output).to_f64(); + output_state_ref.focal_point = focal_point.to_local(output); } else if !zoomed_output_geometry.contains(cursor_position.to_global(output)) { let mut diff = output_state_ref.focal_point.to_global(output) + (cursor_position.to_global(output) - original_position) - .to_f64() .upscale(output_state_ref.level); diff.x = diff.x.clamp( - output_geometry.loc.x as f64, - ((output_geometry.loc.x + output_geometry.size.w) as f64).next_down(), + output_geometry.loc.x, + (output_geometry.loc.x + output_geometry.size.w).next_down(), ); diff.y = diff.y.clamp( - output_geometry.loc.y as f64, - ((output_geometry.loc.y + output_geometry.size.h) as f64).next_down(), + output_geometry.loc.y, + (output_geometry.loc.y + output_geometry.size.h).next_down(), ); diff -= output_state_ref.focal_point.to_global(output); @@ -321,28 +315,27 @@ impl ZoomState { } ZoomMovement::Centered => { zoomed_output_geometry.loc = cursor_position.to_global(output) - - zoomed_output_geometry.size.downscale(2).to_point(); + - zoomed_output_geometry.size.downscale(2.).to_point(); let mut focal_point = zoomed_output_geometry .loc .to_local(output) .upscale( - output_geometry - .size - .w - .checked_div(output_geometry.size.w - zoomed_output_geometry.size.w) - .unwrap_or(1), + (output_geometry.size.w + / (output_geometry.size.w - zoomed_output_geometry.size.w) + .max(f64::EPSILON)) + .max(1.), ) .to_global(output); focal_point.x = focal_point.x.clamp( output_geometry.loc.x, - output_geometry.loc.x + output_geometry.size.w - 1, + output_geometry.loc.x + output_geometry.size.w - 1., ); focal_point.y = focal_point.y.clamp( output_geometry.loc.y, - output_geometry.loc.y + output_geometry.size.h - 1, + output_geometry.loc.y + output_geometry.size.h - 1., ); - output_state_ref.focal_point = focal_point.to_local(output).to_f64(); + output_state_ref.focal_point = focal_point.to_local(output); } } } From dacdc3db39bd2b0fb1b0b5104686a270f9cac7e4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 1 Jul 2026 12:06:30 -0400 Subject: [PATCH 05/56] chore: workaround for Tiger Lake --- src/backend/kms/render/gles.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/kms/render/gles.rs b/src/backend/kms/render/gles.rs index 3eb1e599..4a75c0ab 100644 --- a/src/backend/kms/render/gles.rs +++ b/src/backend/kms/render/gles.rs @@ -144,7 +144,10 @@ impl GraphicsApi for GbmGlowBackend { }) .is_ok_and(|name| { name.as_str().is_ok_and(|name| { - name.contains("MTL") || name.contains("ARL") || name.contains("LNL") + name.contains("TGL") + || name.contains("MTL") + || name.contains("ARL") + || name.contains("LNL") }) }); From a9f2427c00dddbd78ddc3990c84751d6993c3f8d Mon Sep 17 00:00:00 2001 From: Alexander Daichendt Date: Sun, 28 Jun 2026 16:30:53 +0200 Subject: [PATCH 06/56] fix: internal laptop screen stuck on vendor logo with external monitor --- src/backend/kms/drm_helpers.rs | 95 +++++++++++++++++++++++----------- src/backend/kms/mod.rs | 25 +++++++-- 2 files changed, 85 insertions(+), 35 deletions(-) diff --git a/src/backend/kms/drm_helpers.rs b/src/backend/kms/drm_helpers.rs index 6144e4f0..bc80b2b8 100644 --- a/src/backend/kms/drm_helpers.rs +++ b/src/backend/kms/drm_helpers.rs @@ -77,24 +77,6 @@ pub fn display_configuration( let mut req = AtomicModeReq::new(); let plane_handles = device.plane_handles()?; - for conn in connectors - .iter() - .flat_map(|conn| device.get_connector(*conn, false).ok()) - .filter(|conn| { - if let Some(enc) = conn.current_encoder() - && let Ok(enc) = device.get_encoder(enc) - && let Some(crtc) = enc.crtc() - { - return cleanup.contains(&crtc); - } - false - }) - .map(|info| info.handle()) - { - let crtc_id = get_prop(device, conn, "CRTC_ID")?; - req.add_property(conn, crtc_id, property::Value::CRTC(None)); - } - // We cannot just shortcut and use the legacy api for all cleanups because of this. // (Technically a device does not need to be atomic for planes to be used, but nobody does this otherwise.) for plane in plane_handles { @@ -108,7 +90,7 @@ pub fn display_configuration( _ => false, }, )?; - if cleanup.contains(&crtc) || !is_primary { + if !is_primary && !cleanup.contains(&crtc) { let crtc_id = get_prop(device, plane, "CRTC_ID")?; let fb_id = get_prop(device, plane, "FB_ID")?; req.add_property(plane, crtc_id, property::Value::CRTC(None)); @@ -116,27 +98,80 @@ pub fn display_configuration( } } } - - for crtc in cleanup { - let mode_id = get_prop(device, crtc, "MODE_ID")?; - let active = get_prop(device, crtc, "ACTIVE")?; - req.add_property(crtc, active, property::Value::Boolean(false)); - req.add_property(crtc, mode_id, property::Value::Unknown(0)); - } - device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?; } else { for crtc in res_handles.crtcs() { #[allow(deprecated)] let _ = device.set_cursor(*crtc, Option::<&DumbBuffer>::None); } - for crtc in cleanup { + } + disable_crtcs(device, supports_atomic, &cleanup)?; + + Ok(map) +} + +/// Disables the given CRTCs and detaches their connectors/planes. +pub fn disable_crtcs( + device: &mut impl ControlDevice, + supports_atomic: bool, + crtcs: &[crtc::Handle], +) -> Result<()> { + if crtcs.is_empty() { + return Ok(()); + } + + let res_handles = device.resource_handles()?; + + if supports_atomic { + let mut req = AtomicModeReq::new(); + + for conn in res_handles + .connectors() + .iter() + .flat_map(|conn| device.get_connector(*conn, false).ok()) + .filter(|conn| { + if let Some(enc) = conn.current_encoder() + && let Ok(enc) = device.get_encoder(enc) + && let Some(crtc) = enc.crtc() + { + return crtcs.contains(&crtc); + } + false + }) + .map(|info| info.handle()) + { + let crtc_id = get_prop(device, conn, "CRTC_ID")?; + req.add_property(conn, crtc_id, property::Value::CRTC(None)); + } + + for plane in device.plane_handles()? { + let info = device.get_plane(plane)?; + if let Some(crtc) = info.crtc() + && crtcs.contains(&crtc) + { + let crtc_id = get_prop(device, plane, "CRTC_ID")?; + let fb_id = get_prop(device, plane, "FB_ID")?; + req.add_property(plane, crtc_id, property::Value::CRTC(None)); + req.add_property(plane, fb_id, property::Value::Framebuffer(None)); + } + } + + for crtc in crtcs { + let mode_id = get_prop(device, *crtc, "MODE_ID")?; + let active = get_prop(device, *crtc, "ACTIVE")?; + req.add_property(*crtc, active, property::Value::Boolean(false)); + req.add_property(*crtc, mode_id, property::Value::Unknown(0)); + } + + device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?; + } else { + for crtc in crtcs { // null commit (necessary to trigger removal on the kernel side with the legacy api.) - let _ = device.set_crtc(crtc, None, (0, 0), &[], None); + let _ = device.set_crtc(*crtc, None, (0, 0), &[], None); } } - Ok(map) + Ok(()) } pub fn interface_name(device: &impl ControlDevice, connector: connector::Handle) -> Result { diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 694d6ce7..07612790 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -852,11 +852,26 @@ impl KmsGuard<'_> { // first drop old surfaces if !test_only { - for output in outputs.iter().filter(|o| !o.is_enabled()) { - device - .inner - .surfaces - .retain(|_, surface| surface.output != *output); + let mut disabled_crtcs = Vec::new(); + device.inner.surfaces.retain(|crtc, surface| { + if outputs + .iter() + .any(|o| !o.is_enabled() && surface.output == *o) + { + disabled_crtcs.push(*crtc); + false + } else { + true + } + }); + + let supports_atomic = device.drm.device().is_atomic(); + if let Err(err) = drm_helpers::disable_crtcs( + device.drm.device_mut(), + supports_atomic, + &disabled_crtcs, + ) { + warn!("Failed to disable crtcs for disabled outputs: {err}"); } } From f5e0ba1594e428954f2a1647b03564be1afaec4a Mon Sep 17 00:00:00 2001 From: Alexander Daichendt Date: Mon, 29 Jun 2026 20:42:23 +0200 Subject: [PATCH 07/56] make device `DrmDevice` in disable_crts --- src/backend/kms/device.rs | 6 +----- src/backend/kms/drm_helpers.rs | 16 ++++++---------- src/backend/kms/mod.rs | 9 +++------ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index c7a0a160..2b0e8f9d 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -96,7 +96,6 @@ pub struct Device { pub inner: InnerDevice, pub drm: GbmDrmOutputManager, - supports_atomic: bool, pub texture_formats: FormatSet, event_token: Option, pub socket: Option, @@ -711,7 +710,6 @@ impl Device { let (drm, notifier) = DrmDevice::new(fd.clone(), false) .with_context(|| format!("Failed to initialize drm device for: {}", path.display()))?; let dev_node = DrmNode::from_dev_id(dev)?; - let supports_atomic = drm.is_atomic(); let gbm = GbmDevice::new(fd) .with_context(|| format!("Failed to initialize GBM device for {}", path.display()))?; @@ -831,7 +829,6 @@ impl Device { active_clients, }, - supports_atomic, texture_formats, event_token: Some(token), socket, @@ -840,8 +837,7 @@ impl Device { pub fn enumerate_surfaces(&mut self) -> Result { // enumerate our outputs - let config = - drm_helpers::display_configuration(self.drm.device_mut(), self.supports_atomic)?; + let config = drm_helpers::display_configuration(self.drm.device_mut())?; let surfaces = self .inner diff --git a/src/backend/kms/drm_helpers.rs b/src/backend/kms/drm_helpers.rs index bc80b2b8..8629c9d7 100644 --- a/src/backend/kms/drm_helpers.rs +++ b/src/backend/kms/drm_helpers.rs @@ -3,6 +3,7 @@ use anyhow::{Context, Result, anyhow}; use libdisplay_info::{edid::DisplayDescriptorTag, info::Info}; use smithay::{ + backend::drm::DrmDevice, reexports::drm::control::{ AtomicCommitFlags, Device as ControlDevice, Mode, ModeFlags, PlaneType, ResourceHandle, atomic::AtomicModeReq, @@ -16,8 +17,7 @@ use smithay::{ use std::{collections::HashMap, ops::Range}; pub fn display_configuration( - device: &mut impl ControlDevice, - supports_atomic: bool, + device: &mut DrmDevice, ) -> Result>> { let res_handles = device.resource_handles()?; let connectors = res_handles.connectors(); @@ -73,7 +73,7 @@ pub fn display_configuration( } // And then cleanup - if supports_atomic { + if device.is_atomic() { let mut req = AtomicModeReq::new(); let plane_handles = device.plane_handles()?; @@ -105,24 +105,20 @@ pub fn display_configuration( let _ = device.set_cursor(*crtc, Option::<&DumbBuffer>::None); } } - disable_crtcs(device, supports_atomic, &cleanup)?; + disable_crtcs(device, &cleanup)?; Ok(map) } /// Disables the given CRTCs and detaches their connectors/planes. -pub fn disable_crtcs( - device: &mut impl ControlDevice, - supports_atomic: bool, - crtcs: &[crtc::Handle], -) -> Result<()> { +pub fn disable_crtcs(device: &mut DrmDevice, crtcs: &[crtc::Handle]) -> Result<()> { if crtcs.is_empty() { return Ok(()); } let res_handles = device.resource_handles()?; - if supports_atomic { + if device.is_atomic() { let mut req = AtomicModeReq::new(); for conn in res_handles diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 07612790..13832da8 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -865,12 +865,9 @@ impl KmsGuard<'_> { } }); - let supports_atomic = device.drm.device().is_atomic(); - if let Err(err) = drm_helpers::disable_crtcs( - device.drm.device_mut(), - supports_atomic, - &disabled_crtcs, - ) { + if let Err(err) = + drm_helpers::disable_crtcs(device.drm.device_mut(), &disabled_crtcs) + { warn!("Failed to disable crtcs for disabled outputs: {err}"); } } From 9d52653d5c9d45716dbc333f99f5ef7e25549bf8 Mon Sep 17 00:00:00 2001 From: Hojjat Abdollahi Date: Thu, 2 Jul 2026 09:53:03 -0600 Subject: [PATCH 08/56] revert: "fix: follow the focus after alt+tab to another output" This reverts commit 56f84fba2dc0ff7de191e48e56f29d92ba50e668. --- src/wayland/handlers/toplevel_management.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs index 2b1232d5..3da39952 100644 --- a/src/wayland/handlers/toplevel_management.rs +++ b/src/wayland/handlers/toplevel_management.rs @@ -111,8 +111,10 @@ impl ToplevelManagementHandler for State { std::mem::drop(shell); // move pointer to window if it’s on a different monitor/output - let switching_output = seat.active_output() != *output; - if switching_output && let Some(new_pos) = new_pos { + if seat.active_output() != *output + && self.common.config.cosmic_conf.cursor_follows_focus + && let Some(new_pos) = new_pos + { seat.set_active_output(output); if let Some(ptr) = seat.get_pointer() { let serial = SERIAL_COUNTER.next_serial(); From 012c77ec81f55f8d149639f77ea7254b43f9e20f Mon Sep 17 00:00:00 2001 From: Ericky Dos Santos Date: Mon, 29 Jun 2026 10:25:28 -0400 Subject: [PATCH 09/56] fix(kms): gate overlay scanout per-element by buffer GPU node --- src/backend/kms/surface/mod.rs | 35 +++++------ src/shell/element/surface.rs | 102 ++++++++++++++++++++++++--------- 2 files changed, 95 insertions(+), 42 deletions(-) diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 62ba7c1f..fa196b87 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -8,7 +8,7 @@ use crate::{ init_shaders, output_elements, }, config::ScreenFilter, - shell::Shell, + shell::{Shell, element::surface::ScanoutTargetNodeGuard}, state::SurfaceDmabufFeedback, utils::prelude::*, wayland::handlers::{ @@ -1057,21 +1057,24 @@ impl SurfaceThreadState { vrr = has_active_fullscreen; } - let mut elements = output_elements( - Some(&render_node), - &mut renderer, - &self.shell, - self.clock.now(), - self.mirroring.as_ref().unwrap_or(&self.output), - CursorMode::All, - #[cfg(not(feature = "debug"))] - None, - #[cfg(feature = "debug")] - Some((&self.egui, &self.timings)), - ) - .map_err(|err| { - anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) - })?; + let mut elements = { + let _scanout_node = ScanoutTargetNodeGuard::new(Some(self.target_node)); + output_elements( + Some(&render_node), + &mut renderer, + &self.shell, + self.clock.now(), + self.mirroring.as_ref().unwrap_or(&self.output), + CursorMode::All, + #[cfg(not(feature = "debug"))] + None, + #[cfg(feature = "debug")] + Some((&self.egui, &self.timings)), + ) + .map_err(|err| { + anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) + })? + }; if vrr && fullscreen_drives_refresh_rate && !self.timings.past_min_render_time(&self.clock) { diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 8495fe30..8b0a606e 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -6,6 +6,7 @@ use crate::{ }; use std::{ borrow::Cow, + cell::Cell, sync::{ Mutex, atomic::{AtomicBool, Ordering}, @@ -14,11 +15,17 @@ use std::{ }; use smithay::{ - backend::renderer::{ - ImportAll, Renderer, - element::{ - AsRenderElements, Kind, RenderElementStates, - surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree}, + backend::{ + drm::DrmNode, + renderer::{ + ImportAll, Renderer, + element::{ + AsRenderElements, Kind, RenderElementStates, + surface::{ + KindEvaluation, WaylandSurfaceRenderElement, render_elements_from_surface_tree, + }, + }, + utils::RendererSurfaceStateUserData, }, }, desktop::{ @@ -49,6 +56,7 @@ use smithay::{ SubsurfaceCachedState, SurfaceData, TraversalAction, get_parent, with_states, with_surface_tree_downward, }, + dmabuf::get_dmabuf, seat::WaylandFocus, shell::xdg::{ SurfaceCachedState, ToplevelCachedState, ToplevelSurface, XdgToplevelSurfaceData, @@ -67,6 +75,66 @@ use crate::{ }, }; +thread_local! { + /// The scan-out target [`DrmNode`] of the surface thread that is currently accumulating + /// render elements, if any. + static SCANOUT_TARGET_NODE: Cell> = const { Cell::new(None) }; +} + +/// RAII guard that sets the current thread's scan-out target node and restores the previous value +/// on drop. Hold it only across render-element accumulation so the gate can't outlive a render pass. +/// See [`SCANOUT_TARGET_NODE`]. +pub struct ScanoutTargetNodeGuard(Option); + +impl ScanoutTargetNodeGuard { + pub fn new(node: Option) -> Self { + ScanoutTargetNodeGuard(SCANOUT_TARGET_NODE.with(|n| n.replace(node))) + } +} + +impl Drop for ScanoutTargetNodeGuard { + fn drop(&mut self) { + SCANOUT_TARGET_NODE.with(|n| n.set(self.0)); + } +} + +/// The [`DrmNode`] the surface's currently committed buffer was allocated on, if it is a dmabuf. +fn buffer_node(data: &SurfaceData) -> Option { + let surface_state = data.data_map.get::()?; + let surface_state = surface_state.lock().unwrap(); + surface_state + .buffer() + .and_then(|buffer| get_dmabuf(buffer).ok()) + .and_then(|dmabuf| dmabuf.node()) +} + +/// Build the [`KindEvaluation`] for a window's surface tree. +fn scanout_kind_eval(scanout_override: Option) -> KindEvaluation { + match (scanout_override, SCANOUT_TARGET_NODE.with(|n| n.get())) { + // Forced off. + (Some(false), _) => Kind::Unspecified.into(), + // No node restriction: preserve the previous behaviour exactly. + (Some(true), None) => Kind::ScanoutCandidate.into(), + (None, None) => FRAME_TIME_FILTER, + // Node restriction in effect: only buffers on the scan-out node may be candidates. + (Some(true), Some(node)) => KindEvaluation::Closure(Box::new(move |data| { + if buffer_node(data) == Some(node) { + Kind::ScanoutCandidate + } else { + Kind::Unspecified + } + })), + (None, Some(node)) => KindEvaluation::Closure(Box::new(move |data| { + if buffer_node(data) == Some(node) + { + frame_time_filter_fn(data) + } else { + Kind::Unspecified + } + })), + } +} + #[derive(Debug, Clone, PartialEq, Hash, Eq)] pub struct CosmicSurface(pub Window); @@ -836,7 +904,7 @@ impl CosmicSurface { location + offset, scale, alpha, - FRAME_TIME_FILTER, + scanout_kind_eval(None), ) }) .collect() @@ -868,16 +936,7 @@ impl CosmicSurface { location, scale, alpha, - scanout_override - .map(|val| { - if val { - Kind::ScanoutCandidate - } else { - Kind::Unspecified - } - .into() - }) - .unwrap_or(FRAME_TIME_FILTER), + scanout_kind_eval(scanout_override), ) } WindowSurface::X11(surface) => { @@ -891,16 +950,7 @@ impl CosmicSurface { location, scale, alpha, - scanout_override - .map(|val| { - if val { - Kind::ScanoutCandidate - } else { - Kind::Unspecified - } - .into() - }) - .unwrap_or(FRAME_TIME_FILTER), + scanout_kind_eval(scanout_override), ) } } From 031fdc389aaa4dac8546e0fc429bbea2295cc00d Mon Sep 17 00:00:00 2001 From: Ericky Dos Santos Date: Wed, 1 Jul 2026 11:05:56 -0400 Subject: [PATCH 10/56] kms: change from thread_local approach to wiring scanout node through function calls to detect scanout candidates --- src/backend/kms/device.rs | 1 + src/backend/kms/mod.rs | 3 +++ src/backend/kms/surface/mod.rs | 36 ++++++++++++------------- src/backend/render/mod.rs | 20 ++++++++++++-- src/shell/element/mod.rs | 17 ++++++++++-- src/shell/element/stack.rs | 10 ++++++- src/shell/element/surface.rs | 46 +++++++++++--------------------- src/shell/element/window.rs | 10 ++++++- src/shell/grabs/moving.rs | 11 +++++++- src/shell/layout/floating/mod.rs | 13 ++++++--- src/shell/layout/tiling/mod.rs | 31 ++++++++++++++++----- src/shell/workspace.rs | 10 ++++++- 12 files changed, 140 insertions(+), 68 deletions(-) diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index 2b0e8f9d..44682dc5 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -943,6 +943,7 @@ impl LockedDevice<'_> { output, CursorMode::All, None, + None, ) .with_context(|| "Failed to render outputs")?, None => Vec::new(), diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 13832da8..16559e4e 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -978,6 +978,7 @@ impl KmsGuard<'_> { output, CursorMode::All, None, + None, ) .with_context(|| "Failed to render outputs")?; @@ -1088,6 +1089,7 @@ impl KmsGuard<'_> { output, CursorMode::All, None, + None, ) .with_context(|| "Failed to render outputs")?; @@ -1132,6 +1134,7 @@ impl KmsGuard<'_> { output, CursorMode::All, None, + None, ) .with_context(|| "Failed to render outputs")?; diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index fa196b87..a277af2e 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -8,7 +8,7 @@ use crate::{ init_shaders, output_elements, }, config::ScreenFilter, - shell::{Shell, element::surface::ScanoutTargetNodeGuard}, + shell::Shell, state::SurfaceDmabufFeedback, utils::prelude::*, wayland::handlers::{ @@ -1057,24 +1057,22 @@ impl SurfaceThreadState { vrr = has_active_fullscreen; } - let mut elements = { - let _scanout_node = ScanoutTargetNodeGuard::new(Some(self.target_node)); - output_elements( - Some(&render_node), - &mut renderer, - &self.shell, - self.clock.now(), - self.mirroring.as_ref().unwrap_or(&self.output), - CursorMode::All, - #[cfg(not(feature = "debug"))] - None, - #[cfg(feature = "debug")] - Some((&self.egui, &self.timings)), - ) - .map_err(|err| { - anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) - })? - }; + let mut elements = output_elements( + Some(&render_node), + &mut renderer, + &self.shell, + self.clock.now(), + self.mirroring.as_ref().unwrap_or(&self.output), + CursorMode::All, + #[cfg(not(feature = "debug"))] + None, + #[cfg(feature = "debug")] + Some((&self.egui, &self.timings)), + Some(self.target_node), + ) + .map_err(|err| { + anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) + })?; if vrr && fullscreen_drives_refresh_rate && !self.timings.past_min_render_time(&self.clock) { diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index bfbb21a3..d847f533 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -475,6 +475,7 @@ pub fn cursor_elements<'a, 'frame, R>( output: &Output, mode: CursorMode, exclude_dnd_icon: bool, + scanout_node: Option, ) -> Vec> where R: AsGlowRenderer, @@ -549,7 +550,14 @@ where .lock() .unwrap() .as_ref() - .map(|state| state.render::, R>(renderer, output, theme)) + .map(|state| { + state.render::, R>( + renderer, + output, + theme, + scanout_node, + ) + }) { elements.extend(grab_elements.into_iter().map(|elem| { CosmicElement::MoveGrab(RescaleRenderElement::from_element( @@ -615,6 +623,7 @@ pub fn output_elements( output: &Output, cursor_mode: CursorMode, _fps: Option<(&EguiState, &Timings)>, + scanout_node: Option, ) -> Result>, RenderError> where R: AsGlowRenderer, @@ -690,6 +699,7 @@ where workspace, cursor_mode, element_filter, + scanout_node, )?; #[cfg(feature = "debug")] @@ -713,6 +723,7 @@ pub fn workspace_elements( current: (WorkspaceHandle, usize), cursor_mode: CursorMode, element_filter: ElementFilter, + scanout_node: Option, ) -> Result>, RenderError> where R: AsGlowRenderer, @@ -743,6 +754,7 @@ where output, cursor_mode, element_filter == ElementFilter::ExcludeWorkspaceOverview, + scanout_node, )); let shell = shell.read(); @@ -907,7 +919,7 @@ where elements.extend( layout - .render_popups(renderer, alpha) + .render_popups(renderer, alpha, scanout_node) .into_iter() .map(Into::into) .flat_map(crop_to_output) @@ -951,6 +963,7 @@ where active_hint, alpha, theme.cosmic(), + scanout_node, ) .into_iter() .map(Into::into) @@ -966,6 +979,7 @@ where !move_active && is_active_space, overview.clone(), theme.cosmic(), + scanout_node, ) { Ok(elements) => { elements @@ -995,6 +1009,7 @@ where resize_indicator.clone(), active_hint, theme.cosmic(), + scanout_node, ) { Ok(elements) => { elements @@ -1511,6 +1526,7 @@ where current, cursor_mode, element_filter, + None, )?; if let Some(additional_damage) = additional_damage { diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index f58259bf..8639aeb6 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -8,6 +8,7 @@ use cosmic_comp_config::AppearanceConfig; use id_tree::NodeId; use smithay::{ backend::{ + drm::DrmNode, input::KeyState, renderer::{ element::{ @@ -589,6 +590,7 @@ impl CosmicMapped { location: smithay::utils::Point, scale: smithay::utils::Scale, alpha: f32, + scanout_node: Option, ) -> Vec where R: AsGlowRenderer, @@ -599,11 +601,19 @@ impl CosmicMapped { match &self.element { CosmicMappedInternal::Stack(s) => s .popup_render_elements::>( - renderer, location, scale, alpha, + renderer, + location, + scale, + alpha, + scanout_node, ), CosmicMappedInternal::Window(w) => w .popup_render_elements::>( - renderer, location, scale, alpha, + renderer, + location, + scale, + alpha, + scanout_node, ), _ => unreachable!(), } @@ -664,6 +674,7 @@ impl CosmicMapped { scale: smithay::utils::Scale, alpha: f32, scanout_override: Option, + scanout_node: Option, ) -> Vec where R: AsGlowRenderer, @@ -854,6 +865,7 @@ impl CosmicMapped { scale, alpha, scanout_override, + scanout_node, ), CosmicMappedInternal::Window(w) => w .render_elements::>( @@ -863,6 +875,7 @@ impl CosmicMapped { scale, alpha, scanout_override, + scanout_node, ), _ => unreachable!(), }); diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 9f8584f5..17df320f 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -37,6 +37,7 @@ use cosmic_settings_config::shortcuts; use shortcuts::action::{Direction, FocusDirection}; use smithay::{ backend::{ + drm::DrmNode, input::KeyState, renderer::{ ImportAll, ImportMem, Renderer, @@ -654,6 +655,7 @@ impl CosmicStack { location: Point, scale: Scale, alpha: f32, + scanout_node: Option, ) -> Vec where R: AsGlowRenderer, @@ -667,7 +669,11 @@ impl CosmicStack { windows[active] .popup_render_elements::>( - renderer, window_loc, scale, alpha, + renderer, + window_loc, + scale, + alpha, + scanout_node, ) .into_iter() .map(C::from) @@ -750,6 +756,7 @@ impl CosmicStack { scale: Scale, alpha: f32, scanout_override: Option, + scanout_node: Option, ) -> Vec where R: AsGlowRenderer, @@ -823,6 +830,7 @@ impl CosmicStack { scale, alpha, scanout_override, + scanout_node, ) .into_iter() .map(move |elem| { diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 8b0a606e..2bd72f25 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -6,7 +6,6 @@ use crate::{ }; use std::{ borrow::Cow, - cell::Cell, sync::{ Mutex, atomic::{AtomicBool, Ordering}, @@ -75,29 +74,6 @@ use crate::{ }, }; -thread_local! { - /// The scan-out target [`DrmNode`] of the surface thread that is currently accumulating - /// render elements, if any. - static SCANOUT_TARGET_NODE: Cell> = const { Cell::new(None) }; -} - -/// RAII guard that sets the current thread's scan-out target node and restores the previous value -/// on drop. Hold it only across render-element accumulation so the gate can't outlive a render pass. -/// See [`SCANOUT_TARGET_NODE`]. -pub struct ScanoutTargetNodeGuard(Option); - -impl ScanoutTargetNodeGuard { - pub fn new(node: Option) -> Self { - ScanoutTargetNodeGuard(SCANOUT_TARGET_NODE.with(|n| n.replace(node))) - } -} - -impl Drop for ScanoutTargetNodeGuard { - fn drop(&mut self) { - SCANOUT_TARGET_NODE.with(|n| n.set(self.0)); - } -} - /// The [`DrmNode`] the surface's currently committed buffer was allocated on, if it is a dmabuf. fn buffer_node(data: &SurfaceData) -> Option { let surface_state = data.data_map.get::()?; @@ -109,8 +85,15 @@ fn buffer_node(data: &SurfaceData) -> Option { } /// Build the [`KindEvaluation`] for a window's surface tree. -fn scanout_kind_eval(scanout_override: Option) -> KindEvaluation { - match (scanout_override, SCANOUT_TARGET_NODE.with(|n| n.get())) { +/// +/// `scanout_node`, when set, is the scan-out target [`DrmNode`] of the output currently being +/// rendered: only buffers allocated on that node may be promoted to overlay scan-out candidates. +/// It is `None` for render passes that never scan out to a plane (e.g. screen-copy). +fn scanout_kind_eval( + scanout_override: Option, + scanout_node: Option, +) -> KindEvaluation { + match (scanout_override, scanout_node) { // Forced off. (Some(false), _) => Kind::Unspecified.into(), // No node restriction: preserve the previous behaviour exactly. @@ -125,8 +108,7 @@ fn scanout_kind_eval(scanout_override: Option) -> KindEvaluation { } })), (None, Some(node)) => KindEvaluation::Closure(Box::new(move |data| { - if buffer_node(data) == Some(node) - { + if buffer_node(data) == Some(node) { frame_time_filter_fn(data) } else { Kind::Unspecified @@ -884,6 +866,7 @@ impl CosmicSurface { location: Point, scale: Scale, alpha: f32, + scanout_node: Option, ) -> Vec where R: Renderer + ImportAll, @@ -904,7 +887,7 @@ impl CosmicSurface { location + offset, scale, alpha, - scanout_kind_eval(None), + scanout_kind_eval(None, scanout_node), ) }) .collect() @@ -920,6 +903,7 @@ impl CosmicSurface { scale: Scale, alpha: f32, scanout_override: Option, + scanout_node: Option, ) -> Vec where R: Renderer + ImportAll, @@ -936,7 +920,7 @@ impl CosmicSurface { location, scale, alpha, - scanout_kind_eval(scanout_override), + scanout_kind_eval(scanout_override, scanout_node), ) } WindowSurface::X11(surface) => { @@ -950,7 +934,7 @@ impl CosmicSurface { location, scale, alpha, - scanout_kind_eval(scanout_override), + scanout_kind_eval(scanout_override, scanout_node), ) } } diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 0adbfab7..52ffd16d 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -23,6 +23,7 @@ use cosmic::iced::{Color, Task}; use cosmic_comp_config::AppearanceConfig; use smithay::{ backend::{ + drm::DrmNode, input::KeyState, renderer::{ ImportAll, ImportMem, Renderer, @@ -357,6 +358,7 @@ impl CosmicWindow { location: Point, scale: Scale, alpha: f32, + scanout_node: Option, ) -> Vec where R: Renderer + ImportAll + ImportMem, @@ -374,7 +376,11 @@ impl CosmicWindow { self.0.with_program(|p| { p.window .popup_render_elements::>( - renderer, window_loc, scale, alpha, + renderer, + window_loc, + scale, + alpha, + scanout_node, ) .into_iter() .map(C::from) @@ -470,6 +476,7 @@ impl CosmicWindow { scale: Scale, alpha: f32, scanout_override: Option, + scanout_node: Option, ) -> Vec where R: AsGlowRenderer, @@ -553,6 +560,7 @@ impl CosmicWindow { scale, alpha, scanout_override, + scanout_node, ) }); if window_elements.is_empty() { diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index e73b108d..1fd8a9b6 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -21,6 +21,7 @@ use calloop::LoopHandle; use cosmic::theme::CosmicTheme; use smithay::{ backend::{ + drm::DrmNode, input::ButtonState, renderer::{ ImportAll, ImportMem, Renderer, @@ -68,7 +69,13 @@ pub struct MoveGrabState { impl MoveGrabState { #[profiling::function] - pub fn render(&self, renderer: &mut R, output: &Output, theme: &CosmicTheme) -> Vec + pub fn render( + &self, + renderer: &mut R, + output: &Output, + theme: &CosmicTheme, + scanout_node: Option, + ) -> Vec where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send + Clone + 'static, @@ -198,6 +205,7 @@ impl MoveGrabState { output_scale, alpha, Some(false), + scanout_node, ); let p_elements = self .window @@ -207,6 +215,7 @@ impl MoveGrabState { .to_physical_precise_round(output_scale), output_scale, alpha, + scanout_node, ); let shadow_element = self.window.shadow_render_element( renderer, diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index e3829e58..84677188 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -10,9 +10,12 @@ use cosmic_comp_config::AppearanceConfig; use cosmic_settings_config::shortcuts::action::ResizeDirection; use keyframe::{ease, functions::EaseInOutCubic}; use smithay::{ - backend::renderer::element::{ - AsRenderElements, RenderElement, - utils::{Relocate, RelocateRenderElement, RescaleRenderElement}, + backend::{ + drm::DrmNode, + renderer::element::{ + AsRenderElements, RenderElement, + utils::{Relocate, RelocateRenderElement, RescaleRenderElement}, + }, }, desktop::{PopupKind, Space, WindowSurfaceType, layer_map_for_output, space::SpaceElement}, input::Seat, @@ -1439,6 +1442,7 @@ impl FloatingLayout { &self, renderer: &mut R, alpha: f32, + scanout_node: Option, ) -> Vec> where R: AsGlowRenderer, @@ -1474,6 +1478,7 @@ impl FloatingLayout { .to_physical_precise_round(output_scale), output_scale.into(), alpha, + scanout_node, ), ); } @@ -1490,6 +1495,7 @@ impl FloatingLayout { indicator_thickness: u8, alpha: f32, theme: &cosmic::theme::CosmicTheme, + scanout_node: Option, ) -> Vec> where R: AsGlowRenderer, @@ -1530,6 +1536,7 @@ impl FloatingLayout { output_scale.into(), alpha, None, + scanout_node, ); window_elements.extend( elem.shadow_render_element( diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index e7a7f0f1..45ca985e 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -46,15 +46,18 @@ use keyframe::{ functions::{EaseInOutCubic, Linear}, }; use smithay::{ - backend::renderer::{ - element::{ - AsRenderElements, Id, RenderElement, - utils::{ - ConstrainAlign, ConstrainScaleBehavior, RescaleRenderElement, - constrain_render_elements, + backend::{ + drm::DrmNode, + renderer::{ + element::{ + AsRenderElements, Id, RenderElement, + utils::{ + ConstrainAlign, ConstrainScaleBehavior, RescaleRenderElement, + constrain_render_elements, + }, }, + glow::GlowRenderer, }, - glow::GlowRenderer, }, desktop::{PopupKind, WindowSurfaceType, layer_map_for_output, space::SpaceElement}, input::Seat, @@ -4007,6 +4010,7 @@ impl TilingLayout { resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, theme: &cosmic::theme::CosmicTheme, + scanout_node: Option, ) -> Result>, OutputNotMapped> where R: AsGlowRenderer, @@ -4093,6 +4097,7 @@ impl TilingLayout { indicator_thickness, swap_desc.is_some(), theme, + scanout_node, )); geometries @@ -4149,6 +4154,7 @@ impl TilingLayout { &self.swapping_stack_surface_id, &self.backdrop_id, theme, + scanout_node, )); // tiling hints @@ -4167,6 +4173,7 @@ impl TilingLayout { non_exclusive_zone: Rectangle, overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), theme: &cosmic::theme::CosmicTheme, + scanout_node: Option, ) -> Result>, OutputNotMapped> where R: AsGlowRenderer, @@ -4242,6 +4249,7 @@ impl TilingLayout { output_scale, percentage, swap_desc.is_some(), + scanout_node, )); geometries @@ -4281,6 +4289,7 @@ impl TilingLayout { percentage, overview, swap_desc.clone(), + scanout_node, )); Ok(elements) @@ -4973,6 +4982,7 @@ fn render_old_tree_popups( output_scale: f64, percentage: f32, is_swap_mode: bool, + scanout_node: Option, ) -> Vec> where R: AsGlowRenderer, @@ -4998,6 +5008,7 @@ where - elem_geometry.loc, Scale::from(output_scale), alpha, + scanout_node, ), ); }, @@ -5016,6 +5027,7 @@ fn render_old_tree_windows( indicator_thickness: u8, is_swap_mode: bool, theme: &cosmic::theme::CosmicTheme, + scanout_node: Option, ) -> Vec> where R: AsGlowRenderer, @@ -5052,6 +5064,7 @@ where Scale::from(output_scale), alpha, None, + scanout_node, ); elements.extend(window_elements.into_iter().flat_map(|element| { @@ -5196,6 +5209,7 @@ fn render_new_tree_popups( percentage: f32, overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), swap_desc: Option, + scanout_node: Option, ) -> Vec> where R: AsGlowRenderer, @@ -5234,6 +5248,7 @@ where - elem_geometry.loc, Scale::from(output_scale), alpha, + scanout_node, ), ); } @@ -5262,6 +5277,7 @@ fn render_new_tree_windows( swapping_stack_surface_id: &Id, backdrop_id: &Id, theme: &cosmic::theme::CosmicTheme, + scanout_node: Option, ) -> Vec> where R: AsGlowRenderer, @@ -5597,6 +5613,7 @@ where Scale::from(output_scale), alpha, None, + scanout_node, ); if swap_desc diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 2b4db98c..f4e3dcab 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -31,6 +31,7 @@ use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::Tiling use id_tree::Tree; use indexmap::IndexSet; use keyframe::{ease, functions::EaseInOutCubic}; +use smithay::backend::drm::DrmNode; use smithay::backend::renderer::element::Kind; use smithay::output::WeakOutput; use smithay::utils::user_data::UserDataMap; @@ -1612,6 +1613,7 @@ impl Workspace { resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, theme: &CosmicTheme, + scanout_node: Option, ) -> Result>, OutputNotMapped> where R: AsGlowRenderer, @@ -1700,6 +1702,7 @@ impl Workspace { output_scale.into(), alpha, Some(true), + scanout_node, ) .into_iter() .map(animation_rescale) @@ -1772,6 +1775,7 @@ impl Workspace { indicator_thickness, alpha, theme, + scanout_node, ) .into_iter() .map(WorkspaceRenderElement::from), @@ -1801,6 +1805,7 @@ impl Workspace { resize_indicator, indicator_thickness, theme, + scanout_node, )? .into_iter() .map(WorkspaceRenderElement::from), @@ -1836,6 +1841,7 @@ impl Workspace { render_focus: bool, overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), theme: &CosmicTheme, + scanout_node: Option, ) -> Result>, OutputNotMapped> where R: AsGlowRenderer, @@ -1909,6 +1915,7 @@ impl Workspace { render_loc, output_scale.into(), alpha, + scanout_node, ) .into_iter() .map(Into::into), @@ -1947,7 +1954,7 @@ impl Workspace { elements.extend( self.floating_layer - .render_popups::(renderer, alpha) + .render_popups::(renderer, alpha, scanout_node) .into_iter() .map(WorkspaceRenderElement::from), ); @@ -1961,6 +1968,7 @@ impl Workspace { zone, overview, theme, + scanout_node, )? .into_iter() .map(WorkspaceRenderElement::from), From 29320bdab5d8ae651bd6c445368aff27745c63c2 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 29 Jul 2024 13:56:42 -0700 Subject: [PATCH 11/56] Add `cosmic-keyboard-layout-unstable-v1` protocol https://github.com/pop-os/cosmic-protocols/pull/28 Allows clients like `cosmic-applet-input-sources` to show which layout group is active and update it. Currently this is implemented by update the `xkb_config` to change the layout order. --- Cargo.lock | 20 +-- src/lib.rs | 5 +- src/state.rs | 4 + src/wayland/handlers/keyboard_layout.rs | 10 ++ src/wayland/handlers/mod.rs | 1 + src/wayland/protocols/keyboard_layout.rs | 185 +++++++++++++++++++++++ src/wayland/protocols/mod.rs | 1 + 7 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 src/wayland/handlers/keyboard_layout.rs create mode 100644 src/wayland/protocols/keyboard_layout.rs diff --git a/Cargo.lock b/Cargo.lock index 1bc58958..9f664669 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -788,7 +788,7 @@ dependencies = [ [[package]] name = "cosmic-client-toolkit" version = "0.2.0" -source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#160b086abe03cd34a8a375d7fbe47b24308d1f38" +source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#0d0b0b573fe4836ede4c237dadd365033cd28296" dependencies = [ "bitflags 2.11.0", "cosmic-protocols", @@ -918,7 +918,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.2.0" -source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#160b086abe03cd34a8a375d7fbe47b24308d1f38" +source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#0d0b0b573fe4836ede4c237dadd365033cd28296" dependencies = [ "bitflags 2.11.0", "wayland-backend", @@ -1204,7 +1204,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1518,7 +1518,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2688,7 +2688,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2811,7 +2811,7 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "770919970f7d2f74fea948900d35e2ef64f44129e8ae4015f59de1f0aca7c2a5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3421,7 +3421,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.59.0", ] [[package]] @@ -4502,7 +4502,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5094,7 +5094,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6113,7 +6113,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.48.0", ] [[package]] diff --git a/src/lib.rs b/src/lib.rs index 09d1b961..4f4e5f83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,9 @@ use std::{ time::{Duration, Instant}, }; use tracing::{error, info, warn}; -use wayland::protocols::overlap_notify::OverlapNotifyState; +use wayland::protocols::{ + keyboard_layout::KeyboardLayoutState, overlap_notify::OverlapNotifyState, +}; use crate::wayland::handlers::compositor::client_compositor_state; @@ -331,5 +333,6 @@ fn refresh(state: &mut State) { state::Common::refresh_focus(state); OverlapNotifyState::refresh(state); state.common.update_x11_stacking_order(); + KeyboardLayoutState::refresh(state); state.last_refresh = LastRefresh::At(Instant::now()); } diff --git a/src/state.rs b/src/state.rs index 18de36ba..68824ea7 100644 --- a/src/state.rs +++ b/src/state.rs @@ -19,6 +19,7 @@ use crate::{ corner_radius::CornerRadiusState, drm::WlDrmState, image_capture_source::CosmicImageCaptureSourceState, + keyboard_layout::KeyboardLayoutState, output_configuration::OutputConfigurationState, output_power::OutputPowerState, overlap_notify::OverlapNotifyState, @@ -279,6 +280,7 @@ pub struct Common { pub overlap_notify_state: OverlapNotifyState, pub a11y_state: A11yState, pub dbus_state: DBusState, + pub keyboard_layout_state: KeyboardLayoutState, // shell-related wayland state pub xdg_shell_state: XdgShellState, @@ -682,6 +684,7 @@ impl State { AlphaModifierState::new::(dh); SinglePixelBufferState::new::(dh); FixesState::new::(dh); + let keyboard_layout_state = KeyboardLayoutState::new::(&dh, client_not_sandboxed); let idle_notifier_state = IdleNotifierState::::new(dh, handle.clone()); let idle_inhibit_manager_state = IdleInhibitManagerState::new::(dh); @@ -792,6 +795,7 @@ impl State { xwayland_shell_state, pointer_focus_state: None, dbus_state, + keyboard_layout_state, #[cfg(feature = "logind")] inhibit_lid_fd: None, diff --git a/src/wayland/handlers/keyboard_layout.rs b/src/wayland/handlers/keyboard_layout.rs new file mode 100644 index 00000000..ac1fda87 --- /dev/null +++ b/src/wayland/handlers/keyboard_layout.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use crate::wayland::protocols::keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState}; + +impl KeyboardLayoutHandler for State { + fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState { + &mut self.common.keyboard_layout_state + } +} diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 34d063d0..c6461a2e 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -18,6 +18,7 @@ pub mod idle_notify; pub mod image_capture_source; pub mod image_copy_capture; pub mod input_method; +pub mod keyboard_layout; pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; diff --git a/src/wayland/protocols/keyboard_layout.rs b/src/wayland/protocols/keyboard_layout.rs new file mode 100644 index 00000000..0d674f88 --- /dev/null +++ b/src/wayland/protocols/keyboard_layout.rs @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic_protocols::keyboard_layout::v1::server::{ + zcosmic_keyboard_layout_manager_v1::{self, ZcosmicKeyboardLayoutManagerV1}, + zcosmic_keyboard_layout_v1::{self, ZcosmicKeyboardLayoutV1}, +}; +use smithay::{ + input::{ + SeatHandler, + keyboard::{KeyboardHandle, Layout}, + }, + reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + }, + wayland::{Dispatch2, GlobalDispatch2}, +}; +use std::mem; +use wayland_backend::server::{ClientId, GlobalId}; + +pub trait KeyboardLayoutHandler { + fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState; +} + +#[derive(Debug)] +pub struct KeyboardLayoutState { + pub global: GlobalId, + keyboard_layouts: Vec<(ZcosmicKeyboardLayoutV1, Option)>, +} + +impl KeyboardLayoutState { + pub fn new(dh: &DisplayHandle, client_filter: F) -> Self + where + D: GlobalDispatch + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + LayoutGlobalData { + filter: Box::new(client_filter), + }, + ); + KeyboardLayoutState { + global, + keyboard_layouts: Vec::new(), + } + } + + pub fn refresh(state: &mut D) + where + D: SeatHandler + KeyboardLayoutHandler + 'static, + { + let mut keyboard_layouts = mem::take(&mut state.keyboard_layout_state().keyboard_layouts); + for (keyboard_layout, last_layout) in &mut keyboard_layouts { + if let Some(data) = keyboard_layout.data::>() { + if let Some(handle) = &data.handle { + let active_layout = handle.with_xkb_state(state, |context| { + context.xkb().lock().unwrap().active_layout() + }); + if *last_layout != Some(active_layout) { + keyboard_layout.group(active_layout.0); + *last_layout = Some(active_layout); + } + } + } + } + state.keyboard_layout_state().keyboard_layouts = keyboard_layouts; + } +} + +pub struct LayoutGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +struct LayoutManagerData; + +impl GlobalDispatch2 for LayoutGlobalData +where + D: Dispatch + 'static, +{ + fn bind( + &self, + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, LayoutManagerData); + } + + fn can_view(&self, client: &Client) -> bool { + (self.filter)(client) + } +} + +impl Dispatch2 for LayoutManagerData +where + D: Dispatch>, + D: 'static, + D: SeatHandler, + D: KeyboardLayoutHandler, +{ + fn request( + &self, + state: &mut D, + _client: &Client, + _resource: &ZcosmicKeyboardLayoutManagerV1, + request: zcosmic_keyboard_layout_manager_v1::Request, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_keyboard_layout_manager_v1::Request::GetKeyboardLayout { + keyboard_layout, + keyboard, + } => { + let handle = KeyboardHandle::::from_resource(&keyboard); + let active_layout = handle.as_ref().map(|handle| { + handle.with_xkb_state(state, |context| { + context.xkb().lock().unwrap().active_layout() + }) + }); + let keyboard_layout = data_init.init(keyboard_layout, LayoutUserData { handle }); + if let Some(layout) = active_layout { + keyboard_layout.group(layout.0); + } + state + .keyboard_layout_state() + .keyboard_layouts + .push((keyboard_layout, active_layout)); + } + zcosmic_keyboard_layout_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +#[doc(hidden)] +struct LayoutUserData { + handle: Option>, +} + +impl Dispatch2 for LayoutUserData +where + D: 'static, + D: SeatHandler, + D: KeyboardLayoutHandler, +{ + fn request( + &self, + state: &mut D, + _client: &Client, + _resource: &ZcosmicKeyboardLayoutV1, + request: zcosmic_keyboard_layout_v1::Request, + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_keyboard_layout_v1::Request::SetGroup { group } => { + if let Some(handle) = self.handle.as_ref() { + handle.with_xkb_state(state, |mut context| { + context.set_layout(Layout(group)); + }); + } + } + zcosmic_keyboard_layout_v1::Request::Destroy => {} + _ => unreachable!(), + } + } + + fn destroyed( + &self, + state: &mut D, + _client: ClientId, + keyboard_layout: &ZcosmicKeyboardLayoutV1, + ) { + let keyboard_layouts = &mut state.keyboard_layout_state().keyboard_layouts; + if let Some(idx) = keyboard_layouts + .iter() + .position(|(x, _)| x == keyboard_layout) + { + keyboard_layouts.remove(idx); + } + } +} diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 57e05b8a..c6c0234a 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -4,6 +4,7 @@ pub mod a11y; pub mod corner_radius; pub mod drm; pub mod image_capture_source; +pub mod keyboard_layout; pub mod output_configuration; pub mod output_power; pub mod overlap_notify; From 26ee83aaa7a5dc9016d0d871df53bec04ceec3a5 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 6 Jul 2026 12:34:21 -0700 Subject: [PATCH 12/56] Implement `Debug` and comparison with `CosmicMapped` for `CosmicMappedKey` --- src/shell/element/mod.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 8639aeb6..4c8ca171 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -125,9 +125,9 @@ impl fmt::Debug for CosmicMapped { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct CosmicMappedKey(CosmicMappedKeyInner); -#[derive(Clone)] +#[derive(Clone, Debug)] enum CosmicMappedKeyInner { Window(Weak>>), Stack(Weak>>), @@ -166,6 +166,20 @@ impl PartialEq for CosmicMappedKey { } impl Eq for CosmicMappedKey {} +impl PartialEq for CosmicMapped { + fn eq(&self, other: &CosmicMappedKey) -> bool { + match (&self.element, &other.0) { + (CosmicMappedInternal::Window(window), CosmicMappedKeyInner::Window(weak)) => { + Arc::as_ptr(&window.0.0) == weak.as_ptr() + } + (CosmicMappedInternal::Stack(stack), CosmicMappedKeyInner::Stack(weak)) => { + Arc::as_ptr(&stack.0.0) == weak.as_ptr() + } + _ => false, + } + } +} + impl PartialEq for CosmicMapped { fn eq(&self, other: &Self) -> bool { self.element == other.element From f7656968348ebd4508da8c817b39b22016b02cc9 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 18 Jun 2026 19:43:45 -0700 Subject: [PATCH 13/56] Avoid unnecessarily unwrapping `stack_ref()` after `is_stack()` In a lot of cases, matching on `stack_ref()` also works. --- src/shell/layout/tiling/mod.rs | 12 +++++------ src/shell/mod.rs | 32 +++++++++++------------------- src/shell/workspace.rs | 3 +-- src/wayland/handlers/compositor.rs | 3 +-- 4 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 45ca985e..1c48ab73 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1877,8 +1877,8 @@ impl TilingLayout { if let Some(id) = id { return match tree.get(&id).unwrap().data() { Data::Mapped { mapped, .. } => { - if mapped.is_stack() { - mapped.stack_ref().unwrap().focus_stack(); + if let Some(stack) = mapped.stack_ref() { + stack.focus_stack(); } FocusResult::Some(mapped.clone().into()) } @@ -1986,11 +1986,11 @@ impl TilingLayout { })) } Data::Mapped { mapped, .. } => { - if mapped.is_stack() + if let Some(stack) = mapped.stack_ref() && desc.stack_window.is_none() && replacement_id == &desc.node { - mapped.stack_ref().unwrap().focus_stack(); + stack.focus_stack(); } FocusResult::Some(KeyboardFocusTarget::Element(mapped.clone())) } @@ -2062,7 +2062,7 @@ impl TilingLayout { }); } Data::Mapped { mapped, .. } => { - if mapped.is_stack() + if let Some(stack) = mapped.stack_ref() && swap_desc .as_ref() .map(|desc| { @@ -2071,7 +2071,7 @@ impl TilingLayout { }) .unwrap_or(false) { - mapped.stack_ref().unwrap().focus_stack(); + stack.focus_stack(); } return FocusResult::Some(mapped.clone().into()); } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 89309e88..add9b67a 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -2856,10 +2856,12 @@ impl Shell { let maybe_focused = workspace.focus_stack.get(&seat).iter().next().cloned(); if let Some(FocusTarget::Window(focused)) = maybe_focused - && (focused.is_stack() && !is_dialog && !should_be_maximized) + && let Some(stack) = focused.stack_ref() + && !is_dialog + && !should_be_maximized && !(workspace.is_tiled(&focused.active_window()) && floating_exception) { - focused.stack_ref().unwrap().add_window(window, None, None); + stack.add_window(window, None, None); if was_activated { workspace_state.add_workspace_state(&workspace_handle, WState::Urgent); } @@ -2985,8 +2987,8 @@ impl Shell { .map(|idx| (idx, m.clone())) }); let surface = if let Some((idx, mapped)) = sticky_res { - if mapped.is_stack() { - mapped.stack_ref().unwrap().remove_idx(idx) + if let Some(stack) = mapped.stack_ref() { + stack.remove_idx(idx) } else { set.sticky_layer.unmap(&mapped, None); Some(mapped.active_window()) @@ -2996,20 +2998,13 @@ impl Shell { .iter() .position(|w| w.windows().any(|s| &s == surface)) { - if set + if let Some(stack) = set .minimized_windows - .get(idx) + .get_mut(idx) .unwrap() - .mapped() - .is_some_and(CosmicMapped::is_stack) + .mapped_mut() + .and_then(|m| m.stack_ref()) { - let window = set - .minimized_windows - .get_mut(idx) - .unwrap() - .mapped_mut() - .unwrap(); - let stack = window.stack_ref().unwrap(); let idx = stack.surfaces().position(|s| &s == surface); idx.and_then(|idx| stack.remove_idx(idx)) } else { @@ -4796,12 +4791,9 @@ impl Shell { { let mut from = set.sticky_layer.element_geometry(&mapped).unwrap(); let mut was_maximized = false; - window = if mapped - .stack_ref() - .map(|stack| stack.len() > 1) - .unwrap_or(false) + window = if let Some(stack) = mapped.stack_ref() + && stack.len() > 1 { - let stack = mapped.stack_ref().unwrap(); let surface = stack.surfaces().find(|s| s == surface).unwrap(); stack.remove_window(&surface); surface diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index f4e3dcab..4445b1be 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -713,8 +713,7 @@ impl Workspace { } let mapped = self.element_for_surface(surface)?; - let maybe_stack = mapped.stack_ref().filter(|s| s.len() > 1); - if let Some(stack) = maybe_stack + if let Some(stack) = mapped.stack_ref() && stack.len() > 1 { let idx = stack.surfaces().position(|s| &s == surface); diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index 92b51e58..12bc2e56 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -314,8 +314,7 @@ impl CompositorHandler for State { .then(|| state.element()) }); if let Some(window) = moved_window { - if window.is_stack() { - let stack = window.stack_ref().unwrap(); + if let Some(stack) = window.stack_ref() { if let Some(i) = stack.surfaces().position(|s| { s.wl_surface() .as_deref() From 9e6d46c17e70484ee486e31e798ea01eb51a7062 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 18 Jun 2026 20:41:28 -0700 Subject: [PATCH 14/56] shell: Restore fullscreen surface to stack Previously, a surface that was in a stack that is fullscreened would be removed from the stack, and on unfullscreening, restored to the floating/tiling layer as though it were a new window outside the stack. Instead, if the stack still exists, add the window to it at the same position it previously occupied. --- src/shell/mod.rs | 25 ++++++++++++++++++++++--- src/shell/workspace.rs | 40 ++++++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index add9b67a..8e2740a5 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -2592,9 +2592,21 @@ impl Shell { pub fn remap_unfullscreened_window( &mut self, surface: CosmicSurface, - state: Option, + mut state: Option, loop_handle: &LoopHandle<'static, State>, ) -> CosmicMapped { + if let Some(FullscreenRestoreState::Stack { state: stack_state }) = &state { + if let Some(mapped) = self.mapped().find(|m| **m == stack_state.stack) + && let Some(stack) = mapped.stack_ref() + { + let idx = stack_state.idx.min(stack.len()); + stack.add_window(surface, Some(idx), None); + return mapped.clone(); + } else { + state = None; + } + } + let window = CosmicMapped::from(CosmicWindow::new( surface, loop_handle.clone(), @@ -2637,7 +2649,9 @@ impl Shell { workspace } None => self.workspaces.active_mut(&seat.active_output()).unwrap(), - Some(FullscreenRestoreState::Sticky { .. }) => unreachable!(), + Some(FullscreenRestoreState::Sticky { .. } | FullscreenRestoreState::Stack { .. }) => { + unreachable!() + } }; let fullscreen_geometry = workspace.output.geometry().to_local(&workspace.output); @@ -2752,7 +2766,9 @@ impl Shell { } } } - Some(FullscreenRestoreState::Sticky { .. }) => unreachable!(), + Some(FullscreenRestoreState::Sticky { .. } | FullscreenRestoreState::Stack { .. }) => { + unreachable!() + } } window @@ -4878,6 +4894,9 @@ impl Shell { state, }) } + WorkspaceRestoreData::Stack(stack_state) => { + Some(FullscreenRestoreState::Stack { state: stack_state }) + } WorkspaceRestoreData::Fullscreen(_) => unreachable!(), }, Some(from), diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 4445b1be..bc5071da 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -62,7 +62,7 @@ use wayland_backend::server::ClientId; use super::{ CosmicMappedRenderElement, CosmicSurface, ResizeDirection, ResizeMode, element::{ - CosmicMapped, MaximizedState, resize_indicator::ResizeIndicator, + CosmicMapped, CosmicMappedKey, MaximizedState, resize_indicator::ResizeIndicator, stack::CosmicStackRenderElement, swap_indicator::SwapIndicator, window::CosmicWindowRenderElement, }, @@ -263,6 +263,9 @@ pub enum FullscreenRestoreState { output: WeakOutput, state: FloatingRestoreData, }, + Stack { + state: StackRestoreData, + }, } impl FullscreenRestoreState { @@ -271,6 +274,7 @@ impl FullscreenRestoreState { FullscreenRestoreState::Floating { state, .. } | FullscreenRestoreState::Sticky { state, .. } => state.was_maximized, FullscreenRestoreState::Tiling { state, .. } => state.was_maximized, + FullscreenRestoreState::Stack { .. } => false, } } } @@ -280,16 +284,7 @@ pub enum WorkspaceRestoreData { Fullscreen(Option), Tiling(Option), Floating(Option), -} - -impl From for WorkspaceRestoreData { - fn from(value: ManagedLayer) -> Self { - match value { - ManagedLayer::Floating | ManagedLayer::Sticky => WorkspaceRestoreData::Floating(None), - ManagedLayer::Tiling => WorkspaceRestoreData::Tiling(None), - ManagedLayer::Fullscreen => WorkspaceRestoreData::Fullscreen(None), - } - } + Stack(StackRestoreData), } #[derive(Debug, Clone)] @@ -321,6 +316,12 @@ pub struct TilingRestoreData { pub was_maximized: bool, } +#[derive(Debug, Clone)] +pub struct StackRestoreData { + pub stack: CosmicMappedKey, + pub idx: usize, +} + #[derive(Debug, Clone)] pub struct FullscreenRestoreData { pub previous_state: FullscreenRestoreState, @@ -716,15 +717,14 @@ impl Workspace { if let Some(stack) = mapped.stack_ref() && stack.len() > 1 { - let idx = stack.surfaces().position(|s| &s == surface); - let layer = if self.is_tiled(surface) { - ManagedLayer::Tiling - } else { - ManagedLayer::Floating - }; - return idx - .and_then(|idx| stack.remove_idx(idx)) - .map(|s| (s, layer.into())); + let idx = stack.surfaces().position(|s| &s == surface)?; + return Some(( + stack.remove_idx(idx)?, + WorkspaceRestoreData::Stack(StackRestoreData { + stack: mapped.key(), + idx, + }), + )); } // we know mapped is no stack with more than one element now, From 5c0c22988e79aac5ee66b76ae5bf22e8055fa97b Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 23 Jun 2026 14:49:11 -0700 Subject: [PATCH 15/56] Don't unnecessarily use `Option` in `WorkspaceRestoreData` Unless we expect to need this again in the future, it's best not to have an `Option` here unnecessarily. --- src/shell/mod.rs | 18 +++++++++--------- src/shell/workspace.rs | 18 ++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 8e2740a5..1206b338 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -3251,7 +3251,7 @@ impl Shell { toplevel_enter_workspace(window, to); // we can't restore to a given position - if let WorkspaceRestoreData::Tiling(Some(state)) = &mut window_state { + if let WorkspaceRestoreData::Tiling(state) = &mut window_state { state.state.take(); } // update fullscreen state to restore to the new workspace @@ -3278,7 +3278,7 @@ impl Shell { if is_minimized { let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above let minimized_window = match window_state { - WorkspaceRestoreData::Floating(Some(previous)) => { + WorkspaceRestoreData::Floating(previous) => { let window = CosmicMapped::from(CosmicWindow::new( window.clone(), evlh.clone(), @@ -3288,7 +3288,7 @@ impl Shell { window.set_minimized(true); MinimizedWindow::Floating { window, previous } } - WorkspaceRestoreData::Tiling(Some(previous)) => { + WorkspaceRestoreData::Tiling(previous) => { let window = CosmicMapped::from(CosmicWindow::new( window.clone(), evlh.clone(), @@ -3355,7 +3355,7 @@ impl Shell { self.appearance_conf, )); let position = match window_state { - WorkspaceRestoreData::Floating(Some(data)) => Some( + WorkspaceRestoreData::Floating(data) => Some( data.position_relative(to_workspace.output.geometry().size.as_logical()), ), _ => None, @@ -3455,7 +3455,7 @@ impl Shell { let to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above if !to_workspace.tiling_enabled { let (position, was_maximized, was_snapped) = match &window_state { - WorkspaceRestoreData::Floating(Some(data)) => ( + WorkspaceRestoreData::Floating(data) => ( Some(data.position_relative(to_workspace.output.geometry().size.as_logical())), data.was_maximized, data.was_snapped, @@ -4883,15 +4883,15 @@ impl Shell { &seat, match state { WorkspaceRestoreData::Floating(floating_state) => { - floating_state.map(|state| FullscreenRestoreState::Floating { + Some(FullscreenRestoreState::Floating { workspace: handle, - state, + state: floating_state, }) } WorkspaceRestoreData::Tiling(tiling_state) => { - tiling_state.map(|state| FullscreenRestoreState::Tiling { + Some(FullscreenRestoreState::Tiling { workspace: handle, - state, + state: tiling_state, }) } WorkspaceRestoreData::Stack(stack_state) => { diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index bc5071da..c22daf07 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -282,8 +282,8 @@ impl FullscreenRestoreState { #[derive(Debug, Clone)] pub enum WorkspaceRestoreData { Fullscreen(Option), - Tiling(Option), - Floating(Option), + Tiling(TilingRestoreData), + Floating(FloatingRestoreData), Stack(StackRestoreData), } @@ -643,32 +643,30 @@ impl Workspace { mapped.set_minimized(false); return Some(match state { MinimizedWindow::Floating { previous, .. } => { - WorkspaceRestoreData::Floating(Some(previous)) - } - MinimizedWindow::Tiling { previous, .. } => { - WorkspaceRestoreData::Tiling(Some(previous)) + WorkspaceRestoreData::Floating(previous) } + MinimizedWindow::Tiling { previous, .. } => WorkspaceRestoreData::Tiling(previous), MinimizedWindow::Fullscreen { .. } => unreachable!(), }); } if let Ok(state) = self.tiling_layer.unmap(mapped, None) { - return Some(WorkspaceRestoreData::Tiling(Some(TilingRestoreData { + return Some(WorkspaceRestoreData::Tiling(TilingRestoreData { state, was_maximized: was_maximized.is_some(), - }))); + })); } let was_snapped = *mapped.floating_tiled.lock().unwrap(); // unmaximize_request might have triggered a `floating_layer.refresh()`, // which may have already removed a non-alive surface. if let Some(floating_geometry) = self.floating_layer.unmap(mapped, None).or(was_maximized) { - return Some(WorkspaceRestoreData::Floating(Some(FloatingRestoreData { + return Some(WorkspaceRestoreData::Floating(FloatingRestoreData { geometry: floating_geometry, output_size: self.output.geometry().size.as_logical(), was_maximized: was_maximized.is_some(), was_snapped, - }))); + })); }; None From 98e1dd7f6b209b801f0c8586225f73a730837248 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 23 Jun 2026 15:04:39 -0700 Subject: [PATCH 16/56] shell: Also restore stack element on sticky layer to stack --- src/shell/mod.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 1206b338..97e2e9de 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -4807,11 +4807,18 @@ impl Shell { { let mut from = set.sticky_layer.element_geometry(&mapped).unwrap(); let mut was_maximized = false; + let mut restore_state = None; window = if let Some(stack) = mapped.stack_ref() && stack.len() > 1 { - let surface = stack.surfaces().find(|s| s == surface).unwrap(); - stack.remove_window(&surface); + let idx = stack.surfaces().position(|s| &s == surface)?; + let surface = stack.remove_idx(idx)?; + restore_state = Some(FullscreenRestoreState::Stack { + state: StackRestoreData { + stack: mapped.key(), + idx, + }, + }); surface } else { // Must be set before `map_internal`/`unmap` below, as both may call @@ -4844,7 +4851,7 @@ impl Shell { workspace.map_fullscreen( &window, &seat, - Some(FullscreenRestoreState::Sticky { + Some(restore_state.unwrap_or(FullscreenRestoreState::Sticky { output: old_output, state: FloatingRestoreData { geometry: from, @@ -4852,7 +4859,7 @@ impl Shell { was_maximized, was_snapped: None, }, - }), + })), Some(from), ); } else if let Some(workspace) = self.space_for_mut(&mapped) { From 373a44d43105a2c5563ee80657dae3d312d7f508 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 23 Jun 2026 15:54:37 -0700 Subject: [PATCH 17/56] shell: Restore fullscreen to single-element stack If a surface was previously a single-element stack, restore it normally using the floating/tiling restore data, but map as a single element stack instead of a plain window. --- src/shell/mod.rs | 31 ++++++++++++++++++++++++------- src/shell/workspace.rs | 14 ++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 97e2e9de..60b3d1e1 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -12,7 +12,10 @@ use std::{ use wayland_backend::server::ClientId; use crate::{ - shell::{focus::FocusTarget, grabs::fullscreen_items, layout::tiling::PlaceholderType}, + shell::{ + element::CosmicStack, focus::FocusTarget, grabs::fullscreen_items, + layout::tiling::PlaceholderType, + }, wayland::{ handlers::data_device::{self, get_dnd_icon}, protocols::workspace::{State as WState, WorkspaceCapabilities}, @@ -2607,12 +2610,21 @@ impl Shell { } } - let window = CosmicMapped::from(CosmicWindow::new( - surface, - loop_handle.clone(), - self.theme.clone(), - self.appearance_conf, - )); + let window = if state.as_ref().is_some_and(|s| s.was_stack()) { + CosmicMapped::from(CosmicStack::new( + std::iter::once(surface), + loop_handle.clone(), + self.theme.clone(), + self.appearance_conf, + )) + } else { + CosmicMapped::from(CosmicWindow::new( + surface, + loop_handle.clone(), + self.theme.clone(), + self.appearance_conf, + )) + }; if let Some(FullscreenRestoreState::Sticky { output, state, .. }) = &state { let output = output @@ -3267,6 +3279,7 @@ impl Shell { state: None, was_maximized: previous.was_maximized(), }, + was_stack: previous.was_stack(), }; } else if let FullscreenRestoreState::Tiling { workspace, .. } | FullscreenRestoreState::Floating { workspace, .. } = previous @@ -4808,6 +4821,7 @@ impl Shell { let mut from = set.sticky_layer.element_geometry(&mapped).unwrap(); let mut was_maximized = false; let mut restore_state = None; + let was_stack = mapped.is_stack(); window = if let Some(stack) = mapped.stack_ref() && stack.len() > 1 { @@ -4859,6 +4873,7 @@ impl Shell { was_maximized, was_snapped: None, }, + was_stack, })), Some(from), ); @@ -4893,12 +4908,14 @@ impl Shell { Some(FullscreenRestoreState::Floating { workspace: handle, state: floating_state, + was_stack: mapped.is_stack(), }) } WorkspaceRestoreData::Tiling(tiling_state) => { Some(FullscreenRestoreState::Tiling { workspace: handle, state: tiling_state, + was_stack: mapped.is_stack(), }) } WorkspaceRestoreData::Stack(stack_state) => { diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index c22daf07..e3dcec5a 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -254,14 +254,17 @@ pub enum FullscreenRestoreState { Tiling { workspace: WorkspaceHandle, state: TilingRestoreData, + was_stack: bool, }, Floating { workspace: WorkspaceHandle, state: FloatingRestoreData, + was_stack: bool, }, Sticky { output: WeakOutput, state: FloatingRestoreData, + was_stack: bool, }, Stack { state: StackRestoreData, @@ -277,6 +280,17 @@ impl FullscreenRestoreState { FullscreenRestoreState::Stack { .. } => false, } } + + // Surface was previously a single-window stack + pub fn was_stack(&self) -> bool { + match self { + FullscreenRestoreState::Floating { was_stack, .. } + | FullscreenRestoreState::Sticky { was_stack, .. } => *was_stack, + FullscreenRestoreState::Tiling { was_stack, .. } => *was_stack, + // Stack wasn't removed; surface was removed from the stack + FullscreenRestoreState::Stack { .. } => false, + } + } } #[derive(Debug, Clone)] From 067be10474ff8fc8d0053061e4f8a5f355883de7 Mon Sep 17 00:00:00 2001 From: maskedsyntax Date: Mon, 6 Jul 2026 08:43:27 +0530 Subject: [PATCH 18/56] fix: fall back to legacy X11 names when loading pointer themes Smithay requests modern shape-v1 names while themes like DMZ-White only provide legacy X11 aliases. Try known aliases before failing to load. --- src/backend/render/cursor.rs | 57 ++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 3c75996d..671a32a2 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -114,18 +114,57 @@ enum Error { NoDefaultCursor, #[error("Error opening xcursor file: {0}")] File(#[from] std::io::Error), - #[error("Failed to parse XCursor file")] - Parse, +} + +fn cursor_aliases(name: &str) -> &[&str] { + match name { + "default" => &["default", "left_ptr", "arrow"], + "pointer" => &["pointer", "hand2", "hand"], + "text" => &["text", "xterm"], + "wait" => &["wait", "watch"], + "progress" => &["progress", "left_ptr_watch"], + + "ew-resize" => &["ew-resize", "h_double_arrow", "sb_h_double_arrow"], + "ns-resize" => &["ns-resize", "v_double_arrow", "sb_v_double_arrow"], + "nw-resize" => &["nw-resize", "top_left_corner"], + "ne-resize" => &["ne-resize", "top_right_corner"], + "sw-resize" => &["sw-resize", "bottom_left_corner"], + "se-resize" => &["se-resize", "bottom_right_corner"], + + "w-resize" => &["w-resize", "left_side"], + "e-resize" => &["e-resize", "right_side"], + "n-resize" => &["n-resize", "top_side"], + "s-resize" => &["s-resize", "bottom_side"], + + "move" => &["move", "fleur"], + "not-allowed" => &["not-allowed", "crossed_circle"], + "crosshair" => &["crosshair", "cross"], + "help" => &["help", "question_arrow", "left_ptr_help"], + + _ => &[], + } } fn load_icon(theme: &CursorTheme, shape: CursorIcon) -> Result, Error> { - let icon_path = theme - .load_icon(&shape.to_string()) - .ok_or(Error::NoDefaultCursor)?; - let mut cursor_file = std::fs::File::open(&icon_path)?; - let mut cursor_data = Vec::new(); - cursor_file.read_to_end(&mut cursor_data)?; - parse_xcursor(&cursor_data).ok_or(Error::Parse) + let shape_name = shape.to_string(); + + for name in cursor_aliases(&shape_name) + .iter() + .copied() + .chain(std::iter::once(shape_name.as_str())) + { + if let Some(icon_path) = theme.load_icon(name) { + let mut cursor_file = std::fs::File::open(&icon_path)?; + let mut cursor_data = Vec::new(); + cursor_file.read_to_end(&mut cursor_data)?; + + if let Some(images) = parse_xcursor(&cursor_data) { + return Ok(images); + } + } + } + + Err(Error::NoDefaultCursor) } render_elements! { From 0ce7f1144505da7da93e696f05b98e33e923f7a2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 22 Jan 2026 16:13:52 +0100 Subject: [PATCH 19/56] element/surface: Fix reported corner radius --- src/shell/element/surface.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 2bd72f25..4838bd61 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -204,10 +204,10 @@ impl CosmicSurface { let corners = guard.current().0?; Some([ - corners.bottom_right.min(half_min_dim), - corners.top_right.min(half_min_dim), - corners.bottom_left.min(half_min_dim), corners.top_left.min(half_min_dim), + corners.top_right.min(half_min_dim), + corners.bottom_right.min(half_min_dim), + corners.bottom_left.min(half_min_dim), ]) }) }) From 8b990e565807e16005a66fbcba70a818127d6156 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 4 Mar 2026 16:57:17 +0100 Subject: [PATCH 20/56] render: Merge FromGlesError into AsGlowRenderer --- src/backend/kms/render/gles.rs | 17 +--------- src/backend/render/element.rs | 31 +++++++++---------- src/backend/render/mod.rs | 7 +---- src/shell/element/mod.rs | 19 ++++++------ src/shell/element/stack.rs | 12 +++---- src/shell/element/window.rs | 12 +++---- src/shell/workspace.rs | 10 ++---- .../handlers/image_copy_capture/render.rs | 11 ++----- 8 files changed, 40 insertions(+), 79 deletions(-) diff --git a/src/backend/kms/render/gles.rs b/src/backend/kms/render/gles.rs index 4a75c0ab..f6e44861 100644 --- a/src/backend/kms/render/gles.rs +++ b/src/backend/kms/render/gles.rs @@ -12,10 +12,9 @@ use smithay::{ }, drm::{CreateDrmNodeError, DrmNode}, renderer::{ - RendererSuper, gles::{GlesError, GlesRenderer, ffi}, glow::GlowRenderer, - multigpu::{ApiDevice, Error as MultiError, GraphicsApi}, + multigpu::{ApiDevice, GraphicsApi}, }, }, reexports::drm::control::Device, @@ -32,8 +31,6 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, }; -use crate::backend::render::element::FromGlesError; - /// Errors raised by the [`GbmGlesBackend`] #[derive(Debug, thiserror::Error)] pub enum Error { @@ -213,15 +210,3 @@ impl ApiDevice for GbmGlowDevice { !self.intel_export_quirk } } - -impl FromGlesError - for MultiError, T> -where - T::Error: 'static, - <::Renderer as RendererSuper>::Error: 'static, -{ - #[inline] - fn from_gles_error(err: GlesError) -> MultiError, T> { - MultiError::Render(err) - } -} diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index 496702d0..04f7fd9e 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -1,4 +1,7 @@ -use crate::shell::{CosmicMappedRenderElement, WorkspaceRenderElement}; +use crate::{ + backend::render::GlMultiError, + shell::{CosmicMappedRenderElement, WorkspaceRenderElement}, +}; #[cfg(feature = "debug")] use smithay::backend::renderer::element::texture::TextureRenderElement; @@ -203,7 +206,6 @@ impl RenderElement for CosmicElement where R: AsGlowRenderer, R::TextureId: 'static, - R::Error: FromGlesError, CosmicMappedRenderElement: RenderElement, { fn draw( @@ -237,7 +239,7 @@ where opaque_regions, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } CosmicElement::Zoom(elem) => elem.draw(frame, src, dst, damage, opaque_regions, cache), #[cfg(feature = "debug")] @@ -252,7 +254,7 @@ where opaque_regions, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } } } @@ -293,7 +295,7 @@ where RenderElement::::capture_framebuffer( elem, glow_frame, src, dst, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } CosmicElement::Zoom(elem) => elem.capture_framebuffer(frame, src, dst, cache), #[cfg(feature = "debug")] @@ -302,7 +304,7 @@ where RenderElement::::capture_framebuffer( elem, glow_frame, src, dst, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } } } @@ -365,6 +367,7 @@ pub trait AsGlowRenderer: fn glow_frame_mut<'a, 'frame, 'buffer>( frame: &'a mut Self::Frame<'frame, 'buffer>, ) -> &'a mut GlowFrame<'frame, 'buffer>; + fn from_gles_error(err: GlesError) -> Self::Error; } impl AsGlowRenderer for GlowRenderer { @@ -384,6 +387,9 @@ impl AsGlowRenderer for GlowRenderer { ) -> &'a mut GlowFrame<'frame, 'buffer> { frame } + fn from_gles_error(err: GlesError) -> Self::Error { + err + } } impl AsGlowRenderer for GlMultiRenderer<'_> { @@ -403,6 +409,9 @@ impl AsGlowRenderer for GlMultiRenderer<'_> { ) -> &'b mut GlowFrame<'frame, 'buffer> { frame.as_mut() } + fn from_gles_error(err: GlesError) -> Self::Error { + GlMultiError::Render(err) + } } pub struct DamageElement { @@ -458,13 +467,3 @@ impl RenderElement for DamageElement { Ok(()) } } - -pub trait FromGlesError { - fn from_gles_error(err: GlesError) -> Self; -} - -impl FromGlesError for GlesError { - fn from_gles_error(err: GlesError) -> Self { - err - } -} diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index d847f533..6a2f02da 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -42,7 +42,6 @@ use crate::{ }; use cosmic::Theme; -use element::FromGlesError; use smithay::{ backend::{ allocator::Fourcc, @@ -628,7 +627,6 @@ pub fn output_elements( where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - R::Error: FromGlesError, CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { @@ -655,7 +653,7 @@ where ), scale, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) .map_err(RenderError::Rendering)? .into(), ] @@ -728,7 +726,6 @@ pub fn workspace_elements( where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - R::Error: FromGlesError, CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { @@ -1205,7 +1202,6 @@ pub fn render_output<'d, R>( where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - R::Error: FromGlesError, CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, @@ -1510,7 +1506,6 @@ pub fn render_workspace<'d, R>( where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - R::Error: FromGlesError, CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 4c8ca171..69a8391f 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -1,5 +1,5 @@ use crate::{ - backend::render::element::{AsGlowRenderer, FromGlesError}, + backend::render::element::AsGlowRenderer, state::State, utils::{iced::IcedElementInternal, prelude::*}, }; @@ -1366,7 +1366,6 @@ impl RenderElement for CosmicMappedRenderElement where R: AsGlowRenderer, R::TextureId: 'static, - R::Error: FromGlesError, { fn draw( &self, @@ -1399,7 +1398,7 @@ where opaque_regions, cache, ) - .map_err(FromGlesError::from_gles_error), + .map_err(R::from_gles_error), CosmicMappedRenderElement::MovingStack(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } @@ -1421,7 +1420,7 @@ where opaque_regions, cache, ) - .map_err(FromGlesError::from_gles_error), + .map_err(R::from_gles_error), CosmicMappedRenderElement::Overlay(elem) => RenderElement::::draw( elem, R::glow_frame_mut(frame), @@ -1431,7 +1430,7 @@ where opaque_regions, cache, ) - .map_err(FromGlesError::from_gles_error), + .map_err(R::from_gles_error), CosmicMappedRenderElement::StackHoverIndicator(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } @@ -1447,7 +1446,7 @@ where opaque_regions, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } } } @@ -1510,7 +1509,7 @@ where dst, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } CosmicMappedRenderElement::MovingStack(elem) => { elem.capture_framebuffer(frame, src, dst, cache) @@ -1532,7 +1531,7 @@ where dst, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } CosmicMappedRenderElement::Overlay(elem) => { RenderElement::::capture_framebuffer( @@ -1542,7 +1541,7 @@ where dst, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } CosmicMappedRenderElement::StackHoverIndicator(elem) => { elem.capture_framebuffer(frame, src, dst, cache) @@ -1553,7 +1552,7 @@ where RenderElement::::capture_framebuffer( elem, glow_frame, src, dst, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } } } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 17df320f..f3ae21ad 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -4,11 +4,8 @@ use super::{ }; use crate::{ backend::render::{ - IndicatorShader, Key, Usage, - clipped_surface::ClippedSurfaceRenderElement, - cursor::CursorState, - element::{AsGlowRenderer, FromGlesError}, - shadow::ShadowShader, + IndicatorShader, Key, Usage, clipped_surface::ClippedSurfaceRenderElement, + cursor::CursorState, element::AsGlowRenderer, shadow::ShadowShader, }, hooks::{Decorations, HOOKS}, shell::{ @@ -2039,7 +2036,6 @@ impl RenderElement for CosmicStackRenderElement where R: AsGlowRenderer, R::TextureId: 'static, - R::Error: FromGlesError, { fn draw( &self, @@ -2064,7 +2060,7 @@ where opaque_regions, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } CosmicStackRenderElement::Window(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) @@ -2105,7 +2101,7 @@ where dst, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } CosmicStackRenderElement::Window(elem) => { elem.capture_framebuffer(frame, src, dst, cache) diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 52ffd16d..6ebbafa0 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,10 +1,7 @@ use crate::{ backend::render::{ - IndicatorShader, Key, Usage, - clipped_surface::ClippedSurfaceRenderElement, - cursor::CursorState, - element::{AsGlowRenderer, FromGlesError}, - shadow::ShadowShader, + IndicatorShader, Key, Usage, clipped_surface::ClippedSurfaceRenderElement, + cursor::CursorState, element::AsGlowRenderer, shadow::ShadowShader, }, hooks::{Decorations, HOOKS}, shell::{ @@ -1406,7 +1403,6 @@ impl RenderElement for CosmicWindowRenderElement where R: AsGlowRenderer, R::TextureId: 'static, - R::Error: FromGlesError, { fn draw( &self, @@ -1431,7 +1427,7 @@ where opaque_regions, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } CosmicWindowRenderElement::Window(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) @@ -1472,7 +1468,7 @@ where dst, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } CosmicWindowRenderElement::Window(elem) => { elem.capture_framebuffer(frame, src, dst, cache) diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index e3dcec5a..18ec24cc 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -2,10 +2,7 @@ use crate::shell::focus::FocusTarget; use crate::shell::layout::tiling::RestoreTilingState; use crate::wayland::handlers::xdg_activation::ActivationContext; use crate::{ - backend::render::{ - BackdropShader, - element::{AsGlowRenderer, FromGlesError}, - }, + backend::render::{BackdropShader, element::AsGlowRenderer}, shell::{ ANIMATION_DURATION, OverviewMode, SeatMoveGrabState, layout::{ @@ -2138,7 +2135,6 @@ impl RenderElement for WorkspaceRenderElement where R: AsGlowRenderer, R::TextureId: 'static, - R::Error: FromGlesError, { fn draw( &self, @@ -2171,7 +2167,7 @@ where opaque_regions, cache, ) - .map_err(FromGlesError::from_gles_error), + .map_err(R::from_gles_error), } } @@ -2218,7 +2214,7 @@ where dst, cache, ) - .map_err(FromGlesError::from_gles_error) + .map_err(R::from_gles_error) } } } diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index 21370f31..aa358b7f 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -41,7 +41,7 @@ use tracing::warn; use crate::{ backend::render::{ CursorMode, ElementFilter, RendererRef, cursor, - element::{AsGlowRenderer, CosmicElement, DamageElement, FromGlesError}, + element::{AsGlowRenderer, CosmicElement, DamageElement}, render_workspace, }, shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement}, @@ -105,8 +105,7 @@ pub fn submit_buffer( mut sync: SyncPoint, ) -> Result, R::Error> where - R: ExportMem, - R::Error: FromGlesError, + R: ExportMem + AsGlowRenderer, { let Some(damage) = damage else { frame.success( @@ -164,7 +163,7 @@ where Ok(()) }) - .map_err(|err| R::Error::from_gles_error(GlesError::BufferAccessError(err))) + .map_err(|err| R::from_gles_error(GlesError::BufferAccessError(err))) .and_then(|x| x) { frame.fail(CaptureFailureReason::Unknown); @@ -194,7 +193,6 @@ pub fn render_session( ) -> Result, DTError> where R: AsGlowRenderer, - R::Error: FromGlesError, F: for<'d> FnOnce( &WlBuffer, &mut R, @@ -321,7 +319,6 @@ pub fn render_workspace_to_buffer( where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - R::Error: FromGlesError, CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, @@ -554,7 +551,6 @@ pub fn render_window_to_buffer( where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - R::Error: FromGlesError, CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, { @@ -793,7 +789,6 @@ pub fn render_cursor_to_buffer( where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - R::Error: FromGlesError, CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, { From 178c6593dcf3d56006d287208f7f901ccb1c9e26 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 4 Mar 2026 18:16:48 +0100 Subject: [PATCH 21/56] element: Introduce SurfaceRenderElement --- src/backend/render/cursor.rs | 29 +++-- src/backend/render/element.rs | 5 +- src/backend/render/mod.rs | 4 +- .../render/{ => wayland}/clipped_surface.rs | 16 ++- src/backend/render/wayland/mod.rs | 111 ++++++++++++++++++ src/shell/element/stack.rs | 79 +++---------- src/shell/element/surface.rs | 53 ++++----- src/shell/element/window.rs | 86 ++++---------- src/shell/layout/tiling/mod.rs | 50 ++++---- src/shell/workspace.rs | 2 + src/utils/screenshot.rs | 15 +-- .../handlers/image_copy_capture/render.rs | 17 +-- 12 files changed, 254 insertions(+), 213 deletions(-) rename src/backend/render/{ => wayland}/clipped_surface.rs (95%) create mode 100644 src/backend/render/wayland/mod.rs diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 671a32a2..8373e714 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -1,6 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{utils::prelude::*, wayland::handlers::compositor::FRAME_TIME_FILTER}; +use crate::{ + backend::render::{ + element::AsGlowRenderer, + wayland::{SurfaceRenderElement, render_elements_from_surface_tree}, + }, + utils::prelude::*, + wayland::handlers::compositor::FRAME_TIME_FILTER, +}; use smithay::{ backend::{ allocator::Fourcc, @@ -9,10 +16,10 @@ use smithay::{ element::{ Kind, memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement}, - surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree}, }, }, }, + desktop::utils::bbox_from_surface_tree, input::{ Seat, pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus}, @@ -168,9 +175,9 @@ fn load_icon(theme: &CursorTheme, shape: CursorIcon) -> Result, Error } render_elements! { - pub CursorRenderElement where R: ImportAll + ImportMem; + pub CursorRenderElement where R: ImportAll + ImportMem + AsGlowRenderer; Static=MemoryRenderBufferRenderElement, - Surface=WaylandSurfaceRenderElement, + Surface=SurfaceRenderElement, } pub fn draw_surface_cursor( @@ -180,7 +187,7 @@ pub fn draw_surface_cursor( scale: impl Into>, ) -> Vec<(CursorRenderElement, Point)> where - R: Renderer + ImportAll, + R: Renderer + ImportAll + AsGlowRenderer, R::TextureId: Clone + 'static, { let scale = scale.into(); @@ -199,8 +206,11 @@ where renderer, surface, location.to_physical(scale).to_i32_round(), + bbox_from_surface_tree(surface, location.to_i32_round()).to_f64(), scale, 1.0, + false, + [0; 4], Kind::Cursor, ) .into_iter() @@ -214,9 +224,9 @@ pub fn draw_dnd_icon( surface: &wl_surface::WlSurface, location: Point, scale: impl Into>, -) -> Vec> +) -> Vec> where - R: Renderer + ImportAll, + R: Renderer + ImportAll + AsGlowRenderer, R::TextureId: Clone + 'static, { if get_role(surface) != Some("dnd_icon") { @@ -230,8 +240,11 @@ where renderer, surface, location.to_physical(scale).to_i32_round(), + bbox_from_surface_tree(surface, location.to_i32_round()).to_f64(), scale, 1.0, + false, + [0; 4], FRAME_TIME_FILTER, ) } @@ -319,7 +332,7 @@ pub fn draw_cursor( draw_default: bool, ) -> Vec<(CursorRenderElement, Point)> where - R: Renderer + ImportMem + ImportAll, + R: Renderer + ImportMem + ImportAll + AsGlowRenderer, R::TextureId: Send + Clone + 'static, { // draw the cursor as relevant diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index 04f7fd9e..34a351c9 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -1,5 +1,5 @@ use crate::{ - backend::render::GlMultiError, + backend::render::{GlMultiError, wayland::SurfaceRenderElement}, shell::{CosmicMappedRenderElement, WorkspaceRenderElement}, }; @@ -13,7 +13,6 @@ use smithay::{ element::{ Element, Id, Kind, RenderElement, UnderlyingStorage, memory::MemoryRenderBufferRenderElement, - surface::WaylandSurfaceRenderElement, utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, }, gles::{GlesError, GlesRenderbuffer, GlesTexture, element::TextureShaderElement}, @@ -38,7 +37,7 @@ where RelocateRenderElement>>>, ), Cursor(RescaleRenderElement>>), - Dnd(WaylandSurfaceRenderElement), + Dnd(SurfaceRenderElement), MoveGrab(RescaleRenderElement>), Postprocess( CropRenderElement>>, diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 6a2f02da..458000a5 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -15,9 +15,9 @@ use crate::{ backend::{ kms::render::gles::GbmGlowBackend, render::{ - clipped_surface::{CLIPPING_SHADER, ClippingShader}, element::DamageElement, shadow::{SHADOW_SHADER, ShadowShader}, + wayland::clipped_surface::{CLIPPING_SHADER, ClippingShader}, }, }, config::ScreenFilter, @@ -80,10 +80,10 @@ use smithay::{ use smithay_egui::EguiState; pub mod animations; -pub mod clipped_surface; pub mod cursor; pub mod element; pub mod shadow; +pub mod wayland; use self::element::{AsGlowRenderer, CosmicElement}; use super::kms::Timings; diff --git a/src/backend/render/clipped_surface.rs b/src/backend/render/wayland/clipped_surface.rs similarity index 95% rename from src/backend/render/clipped_surface.rs rename to src/backend/render/wayland/clipped_surface.rs index 8705051e..37eee7f8 100644 --- a/src/backend/render/clipped_surface.rs +++ b/src/backend/render/wayland/clipped_surface.rs @@ -6,7 +6,7 @@ use cgmath::{Matrix3, Vector2}; use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform}; use smithay::{ backend::renderer::{ - ImportAll, ImportMem, Renderer, + ImportAll, Renderer, element::{ Element, Id, Kind, RenderElement, UnderlyingStorage, surface::WaylandSurfaceRenderElement, @@ -19,7 +19,7 @@ use smithay::{ use crate::backend::render::element::AsGlowRenderer; -pub static CLIPPING_SHADER: &str = include_str!("./shaders/clipped_surface.frag"); +pub static CLIPPING_SHADER: &str = include_str!("../shaders/clipped_surface.frag"); pub struct ClippingShader(pub GlesTexProgram); impl ClippingShader { @@ -35,10 +35,7 @@ impl ClippingShader { } #[derive(Debug)] -pub struct ClippedSurfaceRenderElement -where - R: Renderer + ImportAll + ImportMem, -{ +pub struct ClippedSurfaceRenderElement { inner: WaylandSurfaceRenderElement, program: GlesTexProgram, radius: [u8; 4], @@ -48,7 +45,7 @@ where impl ClippedSurfaceRenderElement where - R: Renderer + ImportAll + ImportMem, + R: Renderer + ImportAll, { pub fn new( renderer: &mut R, @@ -174,7 +171,8 @@ where impl Element for ClippedSurfaceRenderElement where - R: Renderer + ImportAll + ImportMem, + R: Renderer + ImportAll + AsGlowRenderer, + R::TextureId: 'static, { fn id(&self) -> &Id { self.inner.id() @@ -247,7 +245,7 @@ where impl RenderElement for ClippedSurfaceRenderElement where - R: AsGlowRenderer + Renderer + ImportAll + ImportMem, + R: AsGlowRenderer + Renderer + ImportAll, R::TextureId: 'static, { fn draw( diff --git a/src/backend/render/wayland/mod.rs b/src/backend/render/wayland/mod.rs new file mode 100644 index 00000000..64c5945f --- /dev/null +++ b/src/backend/render/wayland/mod.rs @@ -0,0 +1,111 @@ +use smithay::{ + backend::renderer::{ + ImportAll, Renderer, + element::surface::{KindEvaluation, WaylandSurfaceRenderElement}, + utils::RendererSurfaceStateUserData, + }, + reexports::wayland_server::protocol::wl_surface, + render_elements, + utils::{Logical, Physical, Point, Rectangle, Scale}, + wayland::compositor::{self, TraversalAction}, +}; +use tracing::warn; + +use crate::backend::render::{ + element::AsGlowRenderer, wayland::clipped_surface::ClippedSurfaceRenderElement, +}; + +pub mod clipped_surface; + +render_elements! { + pub SurfaceRenderElement where R: AsGlowRenderer + ImportAll; + Clipped=ClippedSurfaceRenderElement, + Wayland=WaylandSurfaceRenderElement, +} + +pub fn render_elements_from_surface_tree( + renderer: &mut R, + main_surface: &wl_surface::WlSurface, + location: impl Into>, + geometry: impl Into>, + scale: impl Into>, + alpha: f32, + should_clip: bool, + radii: [u8; 4], + kind: impl Into, +) -> Vec +where + R: Renderer + ImportAll + AsGlowRenderer, + R::TextureId: Clone + 'static, + E: From>, +{ + let location = location.into().to_f64(); + let geometry = geometry.into().to_f64(); + let scale = scale.into(); + let kind = kind.into(); + let mut surfaces: Vec = Vec::new(); + + compositor::with_surface_tree_downward( + main_surface, + location, + |_, states, location| { + let mut location = *location; + let data = states.data_map.get::(); + + if let Some(data) = data { + if let Some(view) = data.lock().unwrap().view() { + location += view.offset.to_f64().to_physical(scale); + TraversalAction::DoChildren(location) + } else { + TraversalAction::SkipChildren + } + } else { + TraversalAction::SkipChildren + } + }, + |surface, states, location| { + let mut location = *location; + let kind = kind.eval(states); + let data = states.data_map.get::(); + + if let Some(data) = data { + let has_view = if let Some(view) = data.lock().unwrap().view() { + location += view.offset.to_f64().to_physical(scale); + + true + } else { + false + }; + + if has_view { + match WaylandSurfaceRenderElement::from_surface( + renderer, surface, states, location, alpha, kind, + ) { + Ok(Some(surface)) => { + let elem: SurfaceRenderElement = if radii.iter().any(|r| *r != 0) + && should_clip + && ClippedSurfaceRenderElement::will_clip( + &surface, scale, geometry, radii, + ) { + ClippedSurfaceRenderElement::new( + renderer, surface, scale, geometry, radii, + ) + .into() + } else { + surface.into() + }; + surfaces.push(elem.into()); + } + Ok(None) => {} // surface is not mapped + Err(err) => { + warn!("Failed to import surface: {}", err); + } + }; + } + } + }, + |_, _, _| true, + ); + + surfaces +} diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index f3ae21ad..fe25a4e6 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -4,8 +4,8 @@ use super::{ }; use crate::{ backend::render::{ - IndicatorShader, Key, Usage, clipped_surface::ClippedSurfaceRenderElement, - cursor::CursorState, element::AsGlowRenderer, shadow::ShadowShader, + IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer, + shadow::ShadowShader, wayland::SurfaceRenderElement, }, hooks::{Decorations, HOOKS}, shell::{ @@ -41,7 +41,6 @@ use smithay::{ element::{ AsRenderElements, Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage, memory::MemoryRenderBufferRenderElement, - surface::WaylandSurfaceRenderElement, }, gles::element::PixelShaderElement, glow::GlowRenderer, @@ -820,32 +819,16 @@ impl CosmicStack { }); border.into_iter().chain( - windows[active] - .render_elements::>( - renderer, - window_loc, - scale, - alpha, - scanout_override, - scanout_node, - ) - .into_iter() - .map(move |elem| { - let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]); - if radii.is_some_and(|radii| { - ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radii) - }) { - CosmicStackRenderElement::Clipped(ClippedSurfaceRenderElement::new( - renderer, - elem, - scale, - geo, - radii.unwrap(), - )) - } else { - CosmicStackRenderElement::Window(elem) - } - }), + windows[active].render_elements::>( + renderer, + window_loc, + scale, + alpha, + scanout_override, + scanout_node, + radii.is_some(), + radii.unwrap_or([0; 4]), + ), ) })); @@ -1885,8 +1868,7 @@ pub enum CosmicStackRenderElement { Header(MemoryRenderBufferRenderElement), Shadow(PixelShaderElement), Border(PixelShaderElement), - Window(WaylandSurfaceRenderElement), - Clipped(ClippedSurfaceRenderElement), + Window(SurfaceRenderElement), } impl From> @@ -1897,25 +1879,18 @@ impl From From> +impl From> for CosmicStackRenderElement { - fn from(value: WaylandSurfaceRenderElement) -> Self { + fn from(value: SurfaceRenderElement) -> Self { Self::Window(value) } } -impl From> - for CosmicStackRenderElement -{ - fn from(value: ClippedSurfaceRenderElement) -> Self { - Self::Clipped(value) - } -} - impl Element for CosmicStackRenderElement where - R: Renderer + ImportAll + ImportMem, + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + R::TextureId: 'static, { fn id(&self) -> &RendererId { match self { @@ -1923,7 +1898,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.id(), CosmicStackRenderElement::Border(elem) => elem.id(), CosmicStackRenderElement::Window(elem) => elem.id(), - CosmicStackRenderElement::Clipped(elem) => elem.id(), } } @@ -1933,7 +1907,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.current_commit(), CosmicStackRenderElement::Border(elem) => elem.current_commit(), CosmicStackRenderElement::Window(elem) => elem.current_commit(), - CosmicStackRenderElement::Clipped(elem) => elem.current_commit(), } } @@ -1943,7 +1916,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.src(), CosmicStackRenderElement::Border(elem) => elem.src(), CosmicStackRenderElement::Window(elem) => elem.src(), - CosmicStackRenderElement::Clipped(elem) => elem.src(), } } @@ -1953,7 +1925,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.geometry(scale), CosmicStackRenderElement::Border(elem) => elem.geometry(scale), CosmicStackRenderElement::Window(elem) => elem.geometry(scale), - CosmicStackRenderElement::Clipped(elem) => elem.geometry(scale), } } @@ -1963,7 +1934,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.location(scale), CosmicStackRenderElement::Border(elem) => elem.location(scale), CosmicStackRenderElement::Window(elem) => elem.location(scale), - CosmicStackRenderElement::Clipped(elem) => elem.location(scale), } } @@ -1973,7 +1943,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.transform(), CosmicStackRenderElement::Border(elem) => elem.transform(), CosmicStackRenderElement::Window(elem) => elem.transform(), - CosmicStackRenderElement::Clipped(elem) => elem.transform(), } } @@ -1987,7 +1956,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Border(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Window(elem) => elem.damage_since(scale, commit), - CosmicStackRenderElement::Clipped(elem) => elem.damage_since(scale, commit), } } @@ -1997,7 +1965,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Border(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Window(elem) => elem.opaque_regions(scale), - CosmicStackRenderElement::Clipped(elem) => elem.opaque_regions(scale), } } @@ -2007,7 +1974,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.alpha(), CosmicStackRenderElement::Border(elem) => elem.alpha(), CosmicStackRenderElement::Window(elem) => elem.alpha(), - CosmicStackRenderElement::Clipped(elem) => elem.alpha(), } } @@ -2017,7 +1983,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.kind(), CosmicStackRenderElement::Border(elem) => elem.kind(), CosmicStackRenderElement::Window(elem) => elem.kind(), - CosmicStackRenderElement::Clipped(elem) => elem.kind(), } } @@ -2027,7 +1992,6 @@ where CosmicStackRenderElement::Shadow(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Border(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Window(elem) => elem.is_framebuffer_effect(), - CosmicStackRenderElement::Clipped(elem) => elem.is_framebuffer_effect(), } } } @@ -2039,7 +2003,7 @@ where { fn draw( &self, - frame: &mut ::Frame<'_, '_>, + frame: &mut R::Frame<'_, '_>, src: Rectangle, dst: Rectangle, damage: &[Rectangle], @@ -2065,9 +2029,6 @@ where CosmicStackRenderElement::Window(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } - CosmicStackRenderElement::Clipped(elem) => { - elem.draw(frame, src, dst, damage, opaque_regions, cache) - } } } @@ -2078,7 +2039,6 @@ where elem.underlying_storage(renderer.glow_renderer_mut()) } CosmicStackRenderElement::Window(elem) => elem.underlying_storage(renderer), - CosmicStackRenderElement::Clipped(elem) => elem.underlying_storage(renderer), } } @@ -2106,9 +2066,6 @@ where CosmicStackRenderElement::Window(elem) => { elem.capture_framebuffer(frame, src, dst, cache) } - CosmicStackRenderElement::Clipped(elem) => { - elem.capture_framebuffer(frame, src, dst, cache) - } } } } diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 4838bd61..374b8ea4 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -1,4 +1,8 @@ use crate::{ + backend::render::{ + element::AsGlowRenderer, + wayland::{SurfaceRenderElement, render_elements_from_surface_tree}, + }, shell::focus::target::PointerFocusTarget, wayland::{ handlers::compositor::frame_time_filter_fn, protocols::corner_radius::CacheableCorners, @@ -18,12 +22,7 @@ use smithay::{ drm::DrmNode, renderer::{ ImportAll, Renderer, - element::{ - AsRenderElements, Kind, RenderElementStates, - surface::{ - KindEvaluation, WaylandSurfaceRenderElement, render_elements_from_surface_tree, - }, - }, + element::{Kind, RenderElementStates, surface::KindEvaluation}, utils::RendererSurfaceStateUserData, }, }, @@ -869,9 +868,9 @@ impl CosmicSurface { scanout_node: Option, ) -> Vec where - R: Renderer + ImportAll, + R: Renderer + ImportAll + AsGlowRenderer, R::TextureId: Clone + 'static, - C: From>, + C: From>, { match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { @@ -880,13 +879,18 @@ impl CosmicSurface { .flat_map(move |(popup, popup_offset)| { let offset = (self.0.geometry().loc + popup_offset - popup.geometry().loc) .to_physical_precise_round(scale); + let mut geometry = popup.geometry().to_f64(); + geometry.loc += location.to_f64().to_logical(scale) + popup_offset.to_f64(); render_elements_from_surface_tree( renderer, popup.wl_surface(), location + offset, + geometry, scale, alpha, + false, + [0; 4], scanout_kind_eval(None, scanout_node), ) }) @@ -904,12 +908,17 @@ impl CosmicSurface { alpha: f32, scanout_override: Option, scanout_node: Option, + should_clip: bool, + radii: [u8; 4], ) -> Vec where - R: Renderer + ImportAll, + R: Renderer + ImportAll + AsGlowRenderer, R::TextureId: Clone + 'static, - C: From>, + C: From>, { + let mut geometry = self.0.geometry().to_f64(); + geometry.loc += location.to_f64().to_logical(scale); + match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { let surface = toplevel.wl_surface(); @@ -918,8 +927,11 @@ impl CosmicSurface { renderer, surface, location, + geometry, scale, alpha, + should_clip, + radii, scanout_kind_eval(scanout_override, scanout_node), ) } @@ -932,8 +944,11 @@ impl CosmicSurface { renderer, &surface, location, + geometry, scale, alpha, + should_clip, + radii, scanout_kind_eval(scanout_override, scanout_node), ) } @@ -1078,24 +1093,6 @@ impl X11Relatable for CosmicSurface { } } -impl AsRenderElements for CosmicSurface -where - R: Renderer + ImportAll, - R::TextureId: Clone + 'static, -{ - type RenderElement = WaylandSurfaceRenderElement; - - fn render_elements>( - &self, - renderer: &mut R, - location: Point, - scale: Scale, - alpha: f32, - ) -> Vec { - self.0.render_elements(renderer, location, scale, alpha) - } -} - fn with_toplevel_state) -> T>( toplevel: &ToplevelSurface, pending: bool, diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 6ebbafa0..cc12e0f9 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,7 +1,7 @@ use crate::{ backend::render::{ - IndicatorShader, Key, Usage, clipped_surface::ClippedSurfaceRenderElement, - cursor::CursorState, element::AsGlowRenderer, shadow::ShadowShader, + IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer, + shadow::ShadowShader, wayland::SurfaceRenderElement, }, hooks::{Decorations, HOOKS}, shell::{ @@ -27,7 +27,6 @@ use smithay::{ element::{ AsRenderElements, Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage, memory::MemoryRenderBufferRenderElement, - surface::WaylandSurfaceRenderElement, }, gles::element::PixelShaderElement, glow::GlowRenderer, @@ -358,7 +357,7 @@ impl CosmicWindow { scanout_node: Option, ) -> Vec where - R: Renderer + ImportAll + ImportMem, + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send + Clone + 'static, C: From>, { @@ -549,36 +548,21 @@ impl CosmicWindow { elements.push(elem); } - let window_elements = self.0.with_program(|p| { - p.window - .render_elements::>( - renderer, - window_loc, - scale, - alpha, - scanout_override, - scanout_node, - ) - }); - if window_elements.is_empty() { - return Vec::new(); + if has_ssd { + radii[1] = 0; + radii[3] = 0; } - - elements.extend(window_elements.into_iter().map(|elem| { - if has_ssd { - radii[1] = 0; - radii[3] = 0; - } - if radii.iter().any(|x| *x != 0) - && clip - && ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radii) - { - CosmicWindowRenderElement::Clipped(ClippedSurfaceRenderElement::new( - renderer, elem, scale, geo, radii, - )) - } else { - CosmicWindowRenderElement::Window(elem) - } + elements.extend(self.0.with_program(|p| { + p.window.render_elements::>( + renderer, + window_loc, + scale, + alpha, + scanout_override, + scanout_node, + clip, + radii, + ) })); if has_ssd { @@ -1252,8 +1236,7 @@ pub enum CosmicWindowRenderElement { Header(MemoryRenderBufferRenderElement), Shadow(PixelShaderElement), Border(PixelShaderElement), - Window(WaylandSurfaceRenderElement), - Clipped(ClippedSurfaceRenderElement), + Window(SurfaceRenderElement), } impl From> @@ -1264,25 +1247,18 @@ impl From From> +impl From> for CosmicWindowRenderElement { - fn from(value: WaylandSurfaceRenderElement) -> Self { + fn from(value: SurfaceRenderElement) -> Self { Self::Window(value) } } -impl From> - for CosmicWindowRenderElement -{ - fn from(value: ClippedSurfaceRenderElement) -> Self { - Self::Clipped(value) - } -} - impl Element for CosmicWindowRenderElement where - R: Renderer + ImportAll + ImportMem, + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + R::TextureId: 'static, { fn id(&self) -> &RendererId { match self { @@ -1290,7 +1266,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.id(), CosmicWindowRenderElement::Border(elem) => elem.id(), CosmicWindowRenderElement::Window(elem) => elem.id(), - CosmicWindowRenderElement::Clipped(elem) => elem.id(), } } @@ -1300,7 +1275,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.current_commit(), CosmicWindowRenderElement::Border(elem) => elem.current_commit(), CosmicWindowRenderElement::Window(elem) => elem.current_commit(), - CosmicWindowRenderElement::Clipped(elem) => elem.current_commit(), } } @@ -1310,7 +1284,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.src(), CosmicWindowRenderElement::Border(elem) => elem.src(), CosmicWindowRenderElement::Window(elem) => elem.src(), - CosmicWindowRenderElement::Clipped(elem) => elem.src(), } } @@ -1320,7 +1293,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.geometry(scale), CosmicWindowRenderElement::Border(elem) => elem.geometry(scale), CosmicWindowRenderElement::Window(elem) => elem.geometry(scale), - CosmicWindowRenderElement::Clipped(elem) => elem.geometry(scale), } } @@ -1330,7 +1302,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.location(scale), CosmicWindowRenderElement::Border(elem) => elem.location(scale), CosmicWindowRenderElement::Window(elem) => elem.location(scale), - CosmicWindowRenderElement::Clipped(elem) => elem.location(scale), } } @@ -1340,7 +1311,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.transform(), CosmicWindowRenderElement::Border(elem) => elem.transform(), CosmicWindowRenderElement::Window(elem) => elem.transform(), - CosmicWindowRenderElement::Clipped(elem) => elem.transform(), } } @@ -1354,7 +1324,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Border(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Window(elem) => elem.damage_since(scale, commit), - CosmicWindowRenderElement::Clipped(elem) => elem.damage_since(scale, commit), } } @@ -1364,7 +1333,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Border(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Window(elem) => elem.opaque_regions(scale), - CosmicWindowRenderElement::Clipped(elem) => elem.opaque_regions(scale), } } @@ -1374,7 +1342,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.alpha(), CosmicWindowRenderElement::Border(elem) => elem.alpha(), CosmicWindowRenderElement::Window(elem) => elem.alpha(), - CosmicWindowRenderElement::Clipped(elem) => elem.alpha(), } } @@ -1384,7 +1351,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.kind(), CosmicWindowRenderElement::Border(elem) => elem.kind(), CosmicWindowRenderElement::Window(elem) => elem.kind(), - CosmicWindowRenderElement::Clipped(elem) => elem.kind(), } } @@ -1394,7 +1360,6 @@ where CosmicWindowRenderElement::Shadow(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Border(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Window(elem) => elem.is_framebuffer_effect(), - CosmicWindowRenderElement::Clipped(elem) => elem.is_framebuffer_effect(), } } } @@ -1432,9 +1397,6 @@ where CosmicWindowRenderElement::Window(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } - CosmicWindowRenderElement::Clipped(elem) => { - elem.draw(frame, src, dst, damage, opaque_regions, cache) - } } } @@ -1445,7 +1407,6 @@ where elem.underlying_storage(renderer.glow_renderer_mut()) } CosmicWindowRenderElement::Window(elem) => elem.underlying_storage(renderer), - CosmicWindowRenderElement::Clipped(elem) => elem.underlying_storage(renderer), } } @@ -1473,9 +1434,6 @@ where CosmicWindowRenderElement::Window(elem) => { elem.capture_framebuffer(frame, src, dst, cache) } - CosmicWindowRenderElement::Clipped(elem) => { - elem.capture_framebuffer(frame, src, dst, cache) - } } } } diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 1c48ab73..14a65ea7 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -5395,29 +5395,33 @@ where (swap_geo.loc.as_logical() - window_geo.loc).to_physical_precise_round(output_scale); swap_elements.extend( - AsRenderElements::render_elements::>( - &window, - renderer, - render_loc, - output_scale.into(), - 1.0, - ) - .into_iter() - .map(|window| { - CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element( - window, - swap_geo - .loc - .as_logical() - .to_physical_precise_round(output_scale), - ease( - Linear, - 1.0, - swap_factor(window_geo.size), - transition.unwrap_or(1.0), - ), - )) - }), + window + .render_elements( + renderer, + render_loc, + output_scale.into(), + 1.0, + None, + scanout_node, + false, + [0; 4], + ) + .into_iter() + .map(|window| { + CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element( + window, + swap_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale), + ease( + Linear, + 1.0, + swap_factor(window_geo.size), + transition.unwrap_or(1.0), + ), + )) + }), ) } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 18ec24cc..c5f5cb90 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1711,6 +1711,8 @@ impl Workspace { alpha, Some(true), scanout_node, + false, + [0; 4], ) .into_iter() .map(animation_rescale) diff --git a/src/utils/screenshot.rs b/src/utils/screenshot.rs index e90fb532..541371cf 100644 --- a/src/utils/screenshot.rs +++ b/src/utils/screenshot.rs @@ -3,9 +3,7 @@ use smithay::{ backend::{ allocator::Fourcc, renderer::{ - ExportMem, ImportAll, Offscreen, Renderer, - damage::OutputDamageTracker, - element::{AsRenderElements, surface::WaylandSurfaceRenderElement}, + ExportMem, ImportAll, Offscreen, Renderer, damage::OutputDamageTracker, gles::GlesRenderbuffer, }, }, @@ -16,7 +14,7 @@ use smithay::{ use tracing::warn; use crate::{ - backend::render::RendererRef, + backend::render::{RendererRef, element::AsGlowRenderer, wayland::SurfaceRenderElement}, shell::element::CosmicSurface, state::{State, advertised_node_for_surface}, }; @@ -24,17 +22,20 @@ use crate::{ pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) { fn render_window(renderer: &mut R, window: &CosmicSurface) -> anyhow::Result<()> where - R: Renderer + ImportAll + Offscreen + ExportMem, + R: Renderer + ImportAll + Offscreen + ExportMem + AsGlowRenderer, R::TextureId: Clone + 'static, R::Error: Send + Sync + 'static, { let bbox = bbox_from_surface_tree(&window.wl_surface().unwrap(), (0, 0)); - let elements = AsRenderElements::::render_elements::>( - window, + let elements = window.render_elements::>( renderer, (-bbox.loc.x, -bbox.loc.y).into(), Scale::from(1.0), 1.0, + None, + None, + false, + [0; 4], ); // TODO: 10-bit diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index aa358b7f..c640a6d7 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -9,8 +9,7 @@ use smithay::{ buffer_dimensions, buffer_type, damage::{Error as DTError, OutputDamageTracker, RenderOutputResult}, element::{ - AsRenderElements, RenderElement, - surface::WaylandSurfaceRenderElement, + RenderElement, utils::{Relocate, RelocateRenderElement}, }, gles::{GlesError, GlesRenderbuffer}, @@ -43,6 +42,7 @@ use crate::{ CursorMode, ElementFilter, RendererRef, cursor, element::{AsGlowRenderer, CosmicElement, DamageElement}, render_workspace, + wayland::SurfaceRenderElement, }, shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement}, state::{Common, KmsNodes, State}, @@ -501,8 +501,8 @@ pub fn render_workspace_to_buffer( } smithay::render_elements! { - pub WindowCaptureElement where R: ImportAll + ImportMem; - WaylandElement=WaylandSurfaceRenderElement, + pub WindowCaptureElement where R: ImportAll + ImportMem + AsGlowRenderer; + WaylandElement=SurfaceRenderElement, CursorElement=RelocateRenderElement>, } @@ -628,14 +628,15 @@ pub fn render_window_to_buffer( } } - elements.extend(AsRenderElements::::render_elements::< - WindowCaptureElement, - >( - toplevel, + elements.extend(toplevel.render_elements::>( renderer, (-geometry.loc.x, -geometry.loc.y).into(), Scale::from(1.0), 1.0, + None, + None, + false, + [0; 4], )); if let Ok(dmabuf) = get_dmabuf(buffer) { From e4c0716951fd69984c582720c4c4572c327b2493 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 4 Mar 2026 18:57:37 +0100 Subject: [PATCH 22/56] element: Refactor element acquisition to be push based --- Cargo.lock | 5 +- Cargo.toml | 1 + src/backend/render/cursor.rs | 41 +- src/backend/render/mod.rs | 356 ++++++------ src/backend/render/wayland/mod.rs | 24 +- src/shell/element/mod.rs | 91 ++- src/shell/element/stack.rs | 86 ++- src/shell/element/surface.rs | 67 +-- src/shell/element/window.rs | 69 ++- src/shell/grabs/menu/mod.rs | 42 +- src/shell/grabs/moving.rs | 231 ++++---- src/shell/layout/floating/mod.rs | 274 +++++---- src/shell/layout/tiling/mod.rs | 521 +++++++++--------- src/shell/workspace.rs | 283 +++++----- src/shell/zoom.rs | 30 +- src/utils/iced/mod.rs | 22 +- src/utils/screenshot.rs | 7 +- .../handlers/image_copy_capture/render.rs | 83 +-- 18 files changed, 1096 insertions(+), 1137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f664669..9030c5e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,6 +841,7 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", + "smallvec", "smithay", "smithay-egui", "thiserror 2.0.18", @@ -4790,9 +4791,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "smithay" diff --git a/Cargo.toml b/Cargo.toml index f1a04f02..a8c2dc2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ logind-zbus = { version = "5.3.2", optional = true } futures-executor = { version = "0.3.32" } futures-util = "0.3.32" cgmath = "0.18.0" +smallvec = "1.15.2" [dependencies.id_tree] branch = "feature/copy_clone" diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 8373e714..e43d99e5 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -3,7 +3,7 @@ use crate::{ backend::render::{ element::AsGlowRenderer, - wayland::{SurfaceRenderElement, render_elements_from_surface_tree}, + wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree}, }, utils::prelude::*, wayland::handlers::compositor::FRAME_TIME_FILTER, @@ -185,8 +185,8 @@ pub fn draw_surface_cursor( surface: &wl_surface::WlSurface, location: Point, scale: impl Into>, -) -> Vec<(CursorRenderElement, Point)> -where + push: &mut dyn FnMut(CursorRenderElement, Point), +) where R: Renderer + ImportAll + AsGlowRenderer, R::TextureId: Clone + 'static, { @@ -202,7 +202,7 @@ where .to_physical_precise_round(scale) }); - render_elements_from_surface_tree( + push_render_elements_from_surface_tree( renderer, surface, location.to_physical(scale).to_i32_round(), @@ -212,10 +212,9 @@ where false, [0; 4], Kind::Cursor, - ) - .into_iter() - .map(|elem| (elem, h)) - .collect() + &mut |elem| push(elem.into(), h), + None, + ); } #[profiling::function] @@ -224,8 +223,8 @@ pub fn draw_dnd_icon( surface: &wl_surface::WlSurface, location: Point, scale: impl Into>, -) -> Vec> -where + push: &mut dyn FnMut(SurfaceRenderElement), +) where R: Renderer + ImportAll + AsGlowRenderer, R::TextureId: Clone + 'static, { @@ -236,7 +235,7 @@ where ); } let scale = scale.into(); - render_elements_from_surface_tree( + push_render_elements_from_surface_tree( renderer, surface, location.to_physical(scale).to_i32_round(), @@ -246,7 +245,9 @@ where false, [0; 4], FRAME_TIME_FILTER, - ) + push, + None, + ); } pub type CursorState = Mutex; @@ -330,8 +331,8 @@ pub fn draw_cursor( buffer_scale: f64, time: Time, draw_default: bool, -) -> Vec<(CursorRenderElement, Point)> -where + push: &mut dyn FnMut(CursorRenderElement, Point), +) where R: Renderer + ImportMem + ImportAll + AsGlowRenderer, R::TextureId: Send + Clone + 'static, { @@ -343,7 +344,7 @@ where let state = &mut *state_ref; if state.hidden { - return Vec::new(); + return; } let named_cursor = state.current_cursor.or(match cursor_status { @@ -352,7 +353,7 @@ where }); if let Some(current_cursor) = named_cursor { if !draw_default && current_cursor == CursorIcon::Default { - return Vec::new(); + return; } let integer_scale = (scale.x.max(scale.y) * buffer_scale).ceil() as u32; @@ -389,7 +390,7 @@ where ); state.current_image = Some(frame); - return vec![( + push( CursorRenderElement::Static( MemoryRenderBufferRenderElement::from_buffer( renderer, @@ -403,11 +404,9 @@ where .expect("Failed to import cursor bitmap"), ), hotspot.to_physical_precise_round(scale), - )]; + ); } else if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { - return draw_surface_cursor(renderer, wl_surface, location, scale); - } else { - Vec::new() + draw_surface_cursor(renderer, wl_surface, location, scale, push); } } diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 458000a5..717fe54e 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -17,7 +17,11 @@ use crate::{ render::{ element::DamageElement, shadow::{SHADOW_SHADER, ShadowShader}, - wayland::clipped_surface::{CLIPPING_SHADER, ClippingShader}, + wayland::{ + SurfaceRenderElement, + clipped_surface::{CLIPPING_SHADER, ClippingShader}, + push_render_elements_from_surface_tree, + }, }, }, config::ScreenFilter, @@ -47,11 +51,10 @@ use smithay::{ allocator::Fourcc, drm::{DrmDeviceFd, DrmNode}, renderer::{ - Color32F, ImportAll, Offscreen, Renderer, Texture, TextureFilter, + Color32F, Offscreen, Texture, TextureFilter, damage::{Error as RenderError, OutputDamageTracker, RenderOutputResult}, element::{ Element, Id, Kind, RenderElement, WeakId, - surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree}, texture::{TextureRenderBuffer, TextureRenderElement}, utils::{ ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, Relocate, @@ -68,6 +71,7 @@ use smithay::{ sync::SyncPoint, }, }, + desktop::utils::bbox_from_surface_tree, input::Seat, output::{Output, OutputModeSource, OutputNoMode}, utils::{ @@ -475,8 +479,8 @@ pub fn cursor_elements<'a, 'frame, R>( mode: CursorMode, exclude_dnd_icon: bool, scanout_node: Option, -) -> Vec> -where + push: &mut dyn FnMut(CosmicElement), +) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, @@ -490,7 +494,6 @@ where ) }) .unwrap_or_else(|| ((0., 0.).into(), 1.)); - let mut elements = Vec::new(); for seat in seats { let pointer = match seat.get_pointer() { @@ -500,19 +503,16 @@ where let location = pointer.current_location() - output.current_location().to_f64(); if mode != CursorMode::None { - elements.extend( - cursor::draw_cursor( - renderer, - seat, - location, - scale.into(), - zoom_scale, - now, - mode != CursorMode::NotDefault, - ) - .into_iter() - .map(|(elem, hotspot)| { - CosmicElement::Cursor(RescaleRenderElement::from_element( + cursor::draw_cursor( + renderer, + seat, + location, + scale.into(), + zoom_scale, + now, + mode != CursorMode::NotDefault, + &mut |elem, hotspot| { + push(CosmicElement::Cursor(RescaleRenderElement::from_element( RelocateRenderElement::from_element( elem, Point::from((-hotspot.x, -hotspot.y)), @@ -523,70 +523,54 @@ where .to_physical(output.current_scale().fractional_scale()) .to_i32_round(), zoom_scale, - )) - }), + ))) + }, ); } if !exclude_dnd_icon && let Some(dnd_icon) = get_dnd_icon(seat) { - elements.extend( - cursor::draw_dnd_icon( - renderer, - &dnd_icon.surface, - (location + dnd_icon.offset.to_f64()).to_i32_round(), - scale, - ) - .into_iter() - .map(CosmicElement::Dnd), + cursor::draw_dnd_icon( + renderer, + &dnd_icon.surface, + (location + dnd_icon.offset.to_f64()).to_i32_round(), + scale, + &mut |elem| push(CosmicElement::Dnd(elem)), ); } let theme = theme.cosmic(); - if let Some(grab_elements) = seat + if let Some(grab_state) = seat .user_data() .get::() .unwrap() .lock() .unwrap() .as_ref() - .map(|state| { - state.render::, R>( - renderer, - output, - theme, - scanout_node, - ) - }) { - elements.extend(grab_elements.into_iter().map(|elem| { - CosmicElement::MoveGrab(RescaleRenderElement::from_element( + grab_state.render(renderer, output, theme, scanout_node, &mut |elem| { + push(CosmicElement::MoveGrab(RescaleRenderElement::from_element( elem, focal_point .as_logical() .to_physical(output.current_scale().fractional_scale()) .to_i32_round(), zoom_scale, - )) - })); + ))); + }) } - if let Some((grab_elements, should_scale)) = seat + if let Some(grab_state) = seat .user_data() .get::() .unwrap() .lock() .unwrap() .as_ref() - .map(|state| { - ( - state.render::, R>(renderer, output), - !state.is_in_screen_space(), - ) - }) { - elements.extend(grab_elements.into_iter().map(|elem| { - CosmicElement::MoveGrab(RescaleRenderElement::from_element( - elem, + let should_scale = !grab_state.is_in_screen_space(); + grab_state.render(renderer, output, &mut |elem| { + push(CosmicElement::MoveGrab(RescaleRenderElement::from_element( + elem.into(), if should_scale { focal_point .as_logical() @@ -596,12 +580,10 @@ where Point::from((0, 0)) }, if should_scale { zoom_scale } else { 1.0 }, - )) - })); + ))); + }) } } - - elements } #[cfg(not(feature = "debug"))] @@ -729,7 +711,7 @@ where CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { - let mut elements = Vec::new(); + let mut elements = Vec::>::new(); let shell_ref = shell.read(); let seats = shell_ref.seats.iter().cloned().collect::>(); @@ -742,7 +724,7 @@ where // that is prone to deadlock with the main-thread on some grabs. std::mem::drop(shell_ref); - elements.extend(cursor_elements( + cursor_elements( renderer, seats.iter(), zoom_level, @@ -752,7 +734,8 @@ where cursor_mode, element_filter == ElementFilter::ExcludeWorkspaceOverview, scanout_node, - )); + &mut |elem| elements.push(elem), + ); let shell = shell.read(); let overview = shell.overview_mode(); @@ -828,72 +811,81 @@ where render_input_order::<()>(&shell, output, previous, current, element_filter, |stage| { match stage { Stage::ZoomUI => { - elements.extend(ZoomState::render(renderer, output)); + ZoomState::render(renderer, output, &mut |elem| { + elements.push(CosmicElement::Zoom(elem)) + }); } Stage::SessionLock(lock_surface) => { - elements.extend( - session_lock_elements(renderer, output, lock_surface) - .into_iter() - .map(Into::into) - .flat_map(crop_to_output) - .map(Into::into), - ); + session_lock_elements(renderer, output, lock_surface, &mut |elem| { + elements.extend(crop_to_output(elem.into()).map(Into::into)) + }) } Stage::LayerPopup { popup, location, .. } => { - elements.extend( - render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( - renderer, - popup.wl_surface(), - location - .to_local(output) - .as_logical() - .to_physical_precise_round(scale), - Scale::from(scale), - 1.0, - FRAME_TIME_FILTER, - ) - .into_iter() - .flat_map(crop_to_output) - .map(Into::into), - ); + let mut geometry = popup.geometry().as_global(); + geometry.loc += location; + + push_render_elements_from_surface_tree( + renderer, + popup.wl_surface(), + location + .to_local(output) + .as_logical() + .to_physical_precise_round(scale), + geometry.to_local(output).as_logical().to_f64(), + Scale::from(scale), + 1.0, + false, + [0; 4], + FRAME_TIME_FILTER, + &mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)), + None, + ) } Stage::LayerSurface { layer, location } => { - elements.extend( - render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( - renderer, - layer.wl_surface(), - location - .to_local(output) - .as_logical() - .to_physical_precise_round(scale), - Scale::from(scale), - 1.0, - FRAME_TIME_FILTER, - ) - .into_iter() - .flat_map(crop_to_output) - .map(Into::into), + let mut geometry = layer.geometry().as_global(); + geometry.loc += location; + + push_render_elements_from_surface_tree( + renderer, + layer.wl_surface(), + location + .to_local(output) + .as_logical() + .to_physical_precise_round(scale), + geometry.to_local(output).as_logical().to_f64(), + Scale::from(scale), + 1.0, + false, + [0; 4], + FRAME_TIME_FILTER, + &mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)), + None, ); } Stage::OverrideRedirect { surface, location } => { - elements.extend(surface.wl_surface().into_iter().flat_map(|surface| { - render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( + if let Some(wl_surface) = surface.wl_surface() { + let mut geometry = surface.geometry().as_global(); + geometry.loc += location; + + push_render_elements_from_surface_tree( renderer, - &surface, + &wl_surface, location .to_local(output) .as_logical() .to_physical_precise_round(scale), + geometry.to_local(output).as_logical().to_f64(), Scale::from(scale), 1.0, + false, + [0; 4], FRAME_TIME_FILTER, - ) - .into_iter() - .flat_map(crop_to_output) - .map(Into::into) - })); + &mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)), + None, + ); + } } Stage::StickyPopups(layout) => { let alpha = match &overview.0 { @@ -914,14 +906,11 @@ where OverviewMode::None => 1.0, }; - elements.extend( - layout - .render_popups(renderer, alpha, scanout_node) - .into_iter() - .map(Into::into) - .flat_map(crop_to_output) - .map(Into::into), - ); + layout.render_popups(renderer, alpha, scanout_node, &mut |elem| { + if let Some(elem) = crop_to_output(elem.into()) { + elements.push(elem.into()) + } + }); } Stage::Sticky(layout) => { let alpha = match &overview.0 { @@ -946,82 +935,66 @@ where .then_some(last_active_seat) .map(|seat| workspace.focus_stack.get(seat)); - elements.extend( - layout - .render( - renderer, - current_focus.as_ref().and_then(|stack| { - stack.last().and_then(|t| match t { - FocusTarget::Window(w) => Some(w), - _ => None, - }) - }), - resize_indicator.clone(), - active_hint, - alpha, - theme.cosmic(), - scanout_node, - ) - .into_iter() - .map(Into::into) - .flat_map(crop_to_output) - .map(Into::into), - ) + layout.render( + renderer, + current_focus.as_ref().and_then(|stack| { + stack.last().and_then(|t| match t { + FocusTarget::Window(w) => Some(w), + _ => None, + }) + }), + resize_indicator.clone(), + active_hint, + alpha, + theme.cosmic(), + scanout_node, + &mut |elem| { + if let Some(elem) = crop_to_output(elem.into()) { + elements.push(elem.into()) + } + }, + ); } Stage::WorkspacePopups { workspace, offset } => { - elements.extend( - match workspace.render_popups( - renderer, - last_active_seat, - !move_active && is_active_space, - overview.clone(), - theme.cosmic(), - scanout_node, - ) { - Ok(elements) => { - elements - .into_iter() - .flat_map(crop_to_output) - .map(|element| { - CosmicElement::Workspace(RelocateRenderElement::from_element( - element, - offset.to_physical_precise_round(scale), - Relocate::Relative, - )) - }) - } - Err(_) => { - return ControlFlow::Break(Err(OutputNoMode)); + workspace.render_popups( + renderer, + last_active_seat, + !move_active && is_active_space, + overview.clone(), + theme.cosmic(), + scanout_node, + &mut |elem| { + if let Some(elem) = crop_to_output(elem) { + elements.push(CosmicElement::Workspace( + RelocateRenderElement::from_element( + elem, + offset.to_physical_precise_round(scale), + Relocate::Relative, + ), + )); } }, ); } Stage::Workspace { workspace, offset } => { - elements.extend( - match workspace.render( - renderer, - last_active_seat, - !move_active && is_active_space, - overview.clone(), - resize_indicator.clone(), - active_hint, - theme.cosmic(), - scanout_node, - ) { - Ok(elements) => { - elements - .into_iter() - .flat_map(crop_to_output) - .map(|element| { - CosmicElement::Workspace(RelocateRenderElement::from_element( - element, - offset.to_physical_precise_round(scale), - Relocate::Relative, - )) - }) - } - Err(_) => { - return ControlFlow::Break(Err(OutputNoMode)); + workspace.render( + renderer, + last_active_seat, + !move_active && is_active_space, + overview.clone(), + resize_indicator.clone(), + active_hint, + theme.cosmic(), + scanout_node, + &mut |elem| { + if let Some(elem) = crop_to_output(elem) { + elements.push(CosmicElement::Workspace( + RelocateRenderElement::from_element( + elem, + offset.to_physical_precise_round(scale), + Relocate::Relative, + ), + )); } }, ); @@ -1038,23 +1011,26 @@ fn session_lock_elements( renderer: &mut R, output: &Output, lock_surface: Option<&LockSurface>, -) -> Vec> -where - R: Renderer + ImportAll, + push: &mut dyn FnMut(SurfaceRenderElement), +) where + R: AsGlowRenderer, R::TextureId: Clone + 'static, { if let Some(surface) = lock_surface { let scale = Scale::from(output.current_scale().fractional_scale()); - render_elements_from_surface_tree( + push_render_elements_from_surface_tree( renderer, surface.wl_surface(), (0, 0), + bbox_from_surface_tree(surface.wl_surface(), (0, 0)).to_f64(), scale, 1.0, + false, + [0; 4], FRAME_TIME_FILTER, + push, + None, ) - } else { - Vec::new() } } diff --git a/src/backend/render/wayland/mod.rs b/src/backend/render/wayland/mod.rs index 64c5945f..d766552a 100644 --- a/src/backend/render/wayland/mod.rs +++ b/src/backend/render/wayland/mod.rs @@ -23,7 +23,7 @@ render_elements! { Wayland=WaylandSurfaceRenderElement, } -pub fn render_elements_from_surface_tree( +pub fn push_render_elements_from_surface_tree( renderer: &mut R, main_surface: &wl_surface::WlSurface, location: impl Into>, @@ -33,17 +33,17 @@ pub fn render_elements_from_surface_tree( should_clip: bool, radii: [u8; 4], kind: impl Into, -) -> Vec -where + push_above: &mut dyn FnMut(SurfaceRenderElement), + mut push_below: Option<&mut dyn FnMut(SurfaceRenderElement)>, +) where R: Renderer + ImportAll + AsGlowRenderer, R::TextureId: Clone + 'static, - E: From>, { let location = location.into().to_f64(); let geometry = geometry.into().to_f64(); let scale = scale.into(); let kind = kind.into(); - let mut surfaces: Vec = Vec::new(); + let mut passed_main = false; compositor::with_surface_tree_downward( main_surface, @@ -94,7 +94,13 @@ where } else { surface.into() }; - surfaces.push(elem.into()); + if let Some(push_below) = push_below.as_mut() + && passed_main + { + push_below(elem); + } else { + push_above(elem); + } } Ok(None) => {} // surface is not mapped Err(err) => { @@ -103,9 +109,11 @@ where }; } } + + if surface == main_surface { + passed_main = true; + } }, |_, _, _| true, ); - - surfaces } diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 69a8391f..11861591 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -598,42 +598,38 @@ impl CosmicMapped { } } - pub fn popup_render_elements( + pub fn push_popup_render_elements( &self, renderer: &mut R, location: smithay::utils::Point, scale: smithay::utils::Scale, alpha: f32, scanout_node: Option, - ) -> Vec - where + push: &mut dyn FnMut(CosmicMappedRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, - C: From>, { match &self.element { - CosmicMappedInternal::Stack(s) => s - .popup_render_elements::>( - renderer, - location, - scale, - alpha, - scanout_node, - ), - CosmicMappedInternal::Window(w) => w - .popup_render_elements::>( - renderer, - location, - scale, - alpha, - scanout_node, - ), + CosmicMappedInternal::Stack(s) => s.push_popup_render_elements( + renderer, + location, + scale, + alpha, + scanout_node, + &mut |elem| push(elem.into()), + ), + CosmicMappedInternal::Window(w) => w.push_popup_render_elements( + renderer, + location, + scale, + alpha, + scanout_node, + &mut |elem| push(elem.into()), + ), _ => unreachable!(), } - .into_iter() - .map(C::from) - .collect() } pub fn shadow_render_element( @@ -680,7 +676,7 @@ impl CosmicMapped { } } - pub fn render_elements( + pub fn push_render_elements( &self, renderer: &mut R, location: smithay::utils::Point, @@ -689,15 +685,15 @@ impl CosmicMapped { alpha: f32, scanout_override: Option, scanout_node: Option, - ) -> Vec - where + push_above: &mut dyn FnMut(CosmicMappedRenderElement), + push_below: &mut dyn FnMut(CosmicMappedRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, - C: From>, { #[cfg(feature = "debug")] - let mut elements = if let Some(debug) = self.debug.lock().unwrap().as_mut() { + if let Some(debug) = self.debug.lock().unwrap().as_mut() { let window = self.active_window(); let window_geo = window.geometry(); let (min_size, max_size, size) = ( @@ -858,21 +854,15 @@ impl CosmicMapped { scale.x, 0.8, ) { - Ok(element) => vec![CosmicMappedRenderElement::from(element)], + Ok(element) => push_above(element.into()), Err(err) => { debug!(?err, "Error rendering debug overlay."); - Vec::new() } } - } else { - Vec::new() }; - #[cfg(not(feature = "debug"))] - let mut elements = Vec::new(); - #[cfg_attr(not(feature = "debug"), allow(unused_mut))] - elements.extend(match &self.element { - CosmicMappedInternal::Stack(s) => s.render_elements::>( + match &self.element { + CosmicMappedInternal::Stack(s) => s.push_render_elements( renderer, location, max_size, @@ -880,21 +870,22 @@ impl CosmicMapped { alpha, scanout_override, scanout_node, + &mut |elem| push_above(elem.into()), + &mut |elem| push_below(elem.into()), + ), + CosmicMappedInternal::Window(w) => w.push_render_elements( + renderer, + location, + max_size, + scale, + alpha, + scanout_override, + scanout_node, + &mut |elem| push_above(elem.into()), + &mut |elem| push_below(elem.into()), ), - CosmicMappedInternal::Window(w) => w - .render_elements::>( - renderer, - location, - max_size, - scale, - alpha, - scanout_override, - scanout_node, - ), _ => unreachable!(), - }); - - elements.into_iter().map(C::from).collect() + } } pub(crate) fn update_theme(&self, theme: cosmic::Theme) { diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index fe25a4e6..58e28753 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -39,8 +39,8 @@ use smithay::{ renderer::{ ImportAll, ImportMem, Renderer, element::{ - AsRenderElements, Element, Id as RendererId, Kind, RenderElement, - UnderlyingStorage, memory::MemoryRenderBufferRenderElement, + Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage, + memory::MemoryRenderBufferRenderElement, }, gles::element::PixelShaderElement, glow::GlowRenderer, @@ -645,35 +645,31 @@ impl CosmicStack { self.0.loop_handle() } - pub fn popup_render_elements( + pub fn push_popup_render_elements( &self, renderer: &mut R, location: Point, scale: Scale, alpha: f32, scanout_node: Option, - ) -> Vec - where + push: &mut dyn FnMut(CosmicStackRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - C: From>, { let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32)); self.0.with_program(|p| { let windows = p.windows.lock().unwrap(); let active = p.active.load(Ordering::SeqCst); - windows[active] - .popup_render_elements::>( - renderer, - window_loc, - scale, - alpha, - scanout_node, - ) - .into_iter() - .map(C::from) - .collect() + windows[active].push_popup_render_elements( + renderer, + window_loc, + scale, + alpha, + scanout_node, + &mut |elem| push(elem.into()), + ) }) } @@ -744,7 +740,7 @@ impl CosmicStack { }) } - pub fn render_elements( + pub fn push_render_elements( &self, renderer: &mut R, location: Point, @@ -753,17 +749,17 @@ impl CosmicStack { alpha: f32, scanout_override: Option, scanout_node: Option, - ) -> Vec - where + push_above: &mut dyn FnMut(CosmicStackRenderElement), + push_below: &mut dyn FnMut(CosmicStackRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - C: From>, { if !self .0 .with_program(|p| p.override_alive.load(Ordering::Acquire)) { - return Vec::new(); + return; } let geometry = self @@ -773,11 +769,12 @@ impl CosmicStack { let stack_loc = location + geometry.loc; let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32)); - let mut elements = AsRenderElements::::render_elements::>( - &self.0, renderer, stack_loc, scale, alpha, - ); + self.0 + .push_render_elements(renderer, stack_loc, scale, alpha, &mut |elem| { + push_above(elem.into()) + }); - elements.extend(self.0.with_program(|p| { + self.0.with_program(|p| { let windows = p.windows.lock().unwrap(); let active = p.active.load(Ordering::SeqCst); let theme = p.theme.lock().unwrap(); @@ -804,9 +801,9 @@ impl CosmicStack { let window_key = CosmicMappedKey(CosmicMappedKeyInner::Stack(Arc::downgrade(&self.0.0))); - let border = (!maximized).then(|| { + if !maximized { let (r, g, b, a) = theme.cosmic().bg_divider().into_components(); - CosmicStackRenderElement::Border(IndicatorShader::element( + push_above(CosmicStackRenderElement::Border(IndicatorShader::element( renderer, Key::Window(Usage::Border, window_key.clone()), geo.to_i32_round().as_local(), @@ -815,24 +812,23 @@ impl CosmicStack { a * alpha, scale.x, [r, g, b], - )) - }); + ))); + }; - border.into_iter().chain( - windows[active].render_elements::>( - renderer, - window_loc, - scale, - alpha, - scanout_override, - scanout_node, - radii.is_some(), - radii.unwrap_or([0; 4]), - ), - ) - })); - - elements.into_iter().map(C::from).collect() + let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]); + windows[active].push_render_elements( + renderer, + window_loc, + scale, + alpha, + scanout_override, + scanout_node, + radii.is_some(), + radii.unwrap_or([0; 4]), + &mut |elem| push_above(elem.into()), + Some(&mut |elem| push_below(elem.into())), + ); + }); } pub(crate) fn set_theme(&self, theme: cosmic::Theme) { diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 374b8ea4..53c3d4bd 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -1,7 +1,7 @@ use crate::{ backend::render::{ element::AsGlowRenderer, - wayland::{SurfaceRenderElement, render_elements_from_surface_tree}, + wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree}, }, shell::focus::target::PointerFocusTarget, wayland::{ @@ -859,48 +859,47 @@ impl CosmicSurface { self.0.user_data() } - pub fn popup_render_elements( + pub fn push_popup_render_elements( &self, renderer: &mut R, location: Point, scale: Scale, alpha: f32, scanout_node: Option, - ) -> Vec - where + push: &mut dyn FnMut(SurfaceRenderElement), + ) where R: Renderer + ImportAll + AsGlowRenderer, R::TextureId: Clone + 'static, - C: From>, { match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { let surface = toplevel.wl_surface(); - PopupManager::popups_for_surface(surface) - .flat_map(move |(popup, popup_offset)| { - let offset = (self.0.geometry().loc + popup_offset - popup.geometry().loc) - .to_physical_precise_round(scale); - let mut geometry = popup.geometry().to_f64(); - geometry.loc += location.to_f64().to_logical(scale) + popup_offset.to_f64(); + for (popup, popup_offset) in PopupManager::popups_for_surface(surface) { + let offset = (self.0.geometry().loc + popup_offset - popup.geometry().loc) + .to_physical_precise_round(scale); + let mut geometry = popup.geometry().to_f64(); + geometry.loc += location.to_f64().to_logical(scale) + popup_offset.to_f64(); - render_elements_from_surface_tree( - renderer, - popup.wl_surface(), - location + offset, - geometry, - scale, - alpha, - false, - [0; 4], - scanout_kind_eval(None, scanout_node), - ) - }) - .collect() + push_render_elements_from_surface_tree( + renderer, + popup.wl_surface(), + location + offset, + geometry, + scale, + alpha, + false, + [0; 4], + scanout_kind_eval(None, scanout_node), + push, + None, + ) + } } - WindowSurface::X11(_) => Vec::new(), + WindowSurface::X11(_) => {} } } - pub fn render_elements( + pub fn push_render_elements( &self, renderer: &mut R, location: Point, @@ -910,11 +909,11 @@ impl CosmicSurface { scanout_node: Option, should_clip: bool, radii: [u8; 4], - ) -> Vec - where + push_above: &mut dyn FnMut(SurfaceRenderElement), + push_below: Option<&mut dyn FnMut(SurfaceRenderElement)>, + ) where R: Renderer + ImportAll + AsGlowRenderer, R::TextureId: Clone + 'static, - C: From>, { let mut geometry = self.0.geometry().to_f64(); geometry.loc += location.to_f64().to_logical(scale); @@ -923,7 +922,7 @@ impl CosmicSurface { WindowSurface::Wayland(toplevel) => { let surface = toplevel.wl_surface(); - render_elements_from_surface_tree( + push_render_elements_from_surface_tree( renderer, surface, location, @@ -933,14 +932,16 @@ impl CosmicSurface { should_clip, radii, scanout_kind_eval(scanout_override, scanout_node), + push_above, + push_below, ) } WindowSurface::X11(surface) => { let Some(surface) = surface.wl_surface() else { - return Vec::new(); + return; }; - render_elements_from_surface_tree( + push_render_elements_from_surface_tree( renderer, &surface, location, @@ -950,6 +951,8 @@ impl CosmicSurface { should_clip, radii, scanout_kind_eval(scanout_override, scanout_node), + push_above, + push_below, ) } } diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index cc12e0f9..48949127 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -25,8 +25,8 @@ use smithay::{ renderer::{ ImportAll, ImportMem, Renderer, element::{ - AsRenderElements, Element, Id as RendererId, Kind, RenderElement, - UnderlyingStorage, memory::MemoryRenderBufferRenderElement, + Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage, + memory::MemoryRenderBufferRenderElement, }, gles::element::PixelShaderElement, glow::GlowRenderer, @@ -348,18 +348,17 @@ impl CosmicWindow { self.0.loop_handle() } - pub fn popup_render_elements( + pub fn push_popup_render_elements( &self, renderer: &mut R, location: Point, scale: Scale, alpha: f32, scanout_node: Option, - ) -> Vec - where - R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + push: &mut dyn FnMut(CosmicWindowRenderElement), + ) where + R: Renderer + AsGlowRenderer + ImportAll + ImportMem, R::TextureId: Send + Clone + 'static, - C: From>, { let has_ssd = self.0.with_program(|p| p.has_ssd(false)); @@ -370,17 +369,14 @@ impl CosmicWindow { }; self.0.with_program(|p| { - p.window - .popup_render_elements::>( - renderer, - window_loc, - scale, - alpha, - scanout_node, - ) - .into_iter() - .map(C::from) - .collect() + p.window.push_popup_render_elements( + renderer, + window_loc, + scale, + alpha, + scanout_node, + &mut |elem| push(elem.into()), + ) }) } @@ -464,7 +460,7 @@ impl CosmicWindow { }) } - pub fn render_elements( + pub fn push_render_elements( &self, renderer: &mut R, location: Point, @@ -473,11 +469,11 @@ impl CosmicWindow { alpha: f32, scanout_override: Option, scanout_node: Option, - ) -> Vec - where + push_above: &mut dyn FnMut(CosmicWindowRenderElement), + push_below: &mut dyn FnMut(CosmicWindowRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, - C: From>, { let (has_ssd, is_tiled, is_maximized, mut radii, appearance) = self.0.with_program(|p| { ( @@ -514,8 +510,6 @@ impl CosmicWindow { location }; - let mut elements = Vec::new(); - let (mut geo, bg_divider) = self.0.with_program(|p| { ( SpaceElement::geometry(&p.window).to_f64(), @@ -545,15 +539,15 @@ impl CosmicWindow { scale.x, [r, g, b], )); - elements.push(elem); + push_above(elem); } - if has_ssd { - radii[1] = 0; - radii[3] = 0; - } - elements.extend(self.0.with_program(|p| { - p.window.render_elements::>( + self.0.with_program(|p| { + if has_ssd { + radii[1] = 0; + radii[3] = 0; + } + p.window.push_render_elements( renderer, window_loc, scale, @@ -562,8 +556,10 @@ impl CosmicWindow { scanout_node, clip, radii, + &mut |elem| push_above(elem.into()), + Some(&mut |elem| push_below(elem.into())), ) - })); + }); if has_ssd { let ssd_loc = location @@ -571,12 +567,11 @@ impl CosmicWindow { .0 .with_program(|p| p.window.geometry().loc) .to_physical_precise_round(scale); - elements.extend(AsRenderElements::::render_elements::< - CosmicWindowRenderElement, - >(&self.0, renderer, ssd_loc, scale, alpha)) + self.0 + .push_render_elements(renderer, ssd_loc, scale, alpha, &mut |elem| { + push_above(elem.into()) + }); } - - elements.into_iter().map(C::from).collect() } pub(crate) fn set_theme(&self, theme: cosmic::Theme) { diff --git a/src/shell/grabs/menu/mod.rs b/src/shell/grabs/menu/mod.rs index 14a9f917..e79734e9 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -18,10 +18,7 @@ use cosmic::{ use smithay::{ backend::{ input::{ButtonState, TouchSlot}, - renderer::{ - ImportMem, Renderer, - element::{AsRenderElements, memory::MemoryRenderBufferRenderElement}, - }, + renderer::{ImportMem, Renderer, element::memory::MemoryRenderBufferRenderElement}, }, desktop::space::SpaceElement, input::{ @@ -65,29 +62,28 @@ pub struct MenuGrabState { pub type SeatMenuGrabState = Mutex>; impl MenuGrabState { - pub fn render(&self, renderer: &mut R, output: &Output) -> Vec - where + pub fn render( + &self, + renderer: &mut R, + output: &Output, + push: &mut dyn FnMut(MemoryRenderBufferRenderElement), + ) where R: Renderer + ImportMem, R::TextureId: Send + Clone + 'static, - I: From>, { let scale = output.current_scale().fractional_scale(); - self.elements - .lock() - .unwrap() - .iter() - .flat_map(|elem| { - elem.iced.render_elements( - renderer, - elem.position - .to_local(output) - .as_logical() - .to_physical_precise_round(scale), - scale.into(), - 1.0, - ) - }) - .collect() + for elem in self.elements.lock().unwrap().iter() { + elem.iced.push_render_elements( + renderer, + elem.position + .to_local(output) + .as_logical() + .to_physical_precise_round(scale), + scale.into(), + 1.0, + push, + ) + } } pub fn is_in_screen_space(&self) -> bool { diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 1fd8a9b6..48ebd4ba 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -19,13 +19,14 @@ use crate::{ use calloop::LoopHandle; use cosmic::theme::CosmicTheme; +use smallvec::SmallVec; use smithay::{ backend::{ drm::DrmNode, input::ButtonState, renderer::{ ImportAll, ImportMem, Renderer, - element::{AsRenderElements, RenderElement, utils::RescaleRenderElement}, + element::{RenderElement, utils::RescaleRenderElement}, }, }, desktop::{WindowSurfaceType, layer_map_for_output, space::SpaceElement}, @@ -69,18 +70,17 @@ pub struct MoveGrabState { impl MoveGrabState { #[profiling::function] - pub fn render( + pub fn render( &self, renderer: &mut R, output: &Output, theme: &CosmicTheme, scanout_node: Option, - ) -> Vec - where + push: &mut dyn FnMut(CosmicMappedRenderElement), + ) where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, - I: From>, { let scale = if self.previous == ManagedLayer::Tiling { 0.6 + ((1.0 @@ -105,7 +105,7 @@ impl MoveGrabState { .intersection(window_geo) .is_none() { - return Vec::new(); + return; } let output_scale: Scale = output.current_scale().fractional_scale().into(); @@ -115,13 +115,32 @@ impl MoveGrabState { + self.window_offset - scaling_offset; + for (indicator, location) in self.stacking_indicator.iter() { + indicator.push_render_elements( + renderer, + location.to_physical_precise_round(output_scale), + output_scale, + 1.0, + &mut |elem| push(elem.into()), + ); + } + + self.window.push_popup_render_elements::( + renderer, + (render_location - self.window.geometry().loc).to_physical_precise_round(output_scale), + output_scale, + alpha, + scanout_node, + push, + ); + let active_window_hint = crate::theme::active_window_hint(theme); let radius = self .element() .corner_radius(window_geo.size, self.indicator_thickness); - let focus_element = if self.indicator_thickness > 0 { - Some(CosmicMappedRenderElement::from( + if self.indicator_thickness > 0 { + push( IndicatorShader::focus_element( renderer, Key::Window(Usage::MoveGrabIndicator, self.window.key()), @@ -144,12 +163,57 @@ impl MoveGrabState { active_window_hint.green, active_window_hint.blue, ], - ), - )) - } else { - None + ) + .into(), + ) + } + + let map_window_element = |elem| match elem { + CosmicMappedRenderElement::Stack(stack) => { + CosmicMappedRenderElement::GrabbedStack(RescaleRenderElement::from_element( + stack, + render_location + .to_physical_precise_round(output.current_scale().fractional_scale()), + scale, + )) + } + CosmicMappedRenderElement::Window(window) => { + CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element( + window, + render_location + .to_physical_precise_round(output.current_scale().fractional_scale()), + scale, + )) + } + x => x, }; + let mut lower_elements = SmallVec::<[CosmicMappedRenderElement; 4]>::new_const(); + self.window.push_render_elements( + renderer, + (render_location - self.window.geometry().loc).to_physical_precise_round(output_scale), + None, + output_scale, + alpha, + Some(false), + scanout_node, + &mut |elem| push(map_window_element(elem)), + &mut |elem| lower_elements.push(map_window_element(elem)), + ); + if let Some(shadow_element) = self.window.shadow_render_element( + renderer, + (render_location - self.window.geometry().loc).to_physical_precise_round(output_scale), + None, + output_scale, + scale, + alpha, + ) { + push(shadow_element); + } + for elem in lower_elements.into_iter() { + push(elem); + } + let non_exclusive_geometry = { let layers = layer_map_for_output(output); layers.non_exclusive_zone() @@ -158,119 +222,46 @@ impl MoveGrabState { let gaps = (theme.gaps.0 as i32, theme.gaps.1 as i32); let thickness = self.indicator_thickness.max(1); - let snapping_indicator = match &self.snapping_zone { - Some(t) if &self.cursor_output == output => { - let base_color = theme.palette.neutral_9; - let overlay_geometry = t.overlay_geometry(non_exclusive_geometry, gaps); - vec![ - CosmicMappedRenderElement::from(IndicatorShader::element( - renderer, - Key::Window(Usage::SnappingIndicator, self.window.key()), - overlay_geometry, - thickness, - [ - theme.radius_s()[0] as u8, - theme.radius_s()[1] as u8, - theme.radius_s()[2] as u8, - theme.radius_s()[3] as u8, - ], - 1.0, - output_scale.x, - [ - active_window_hint.red, - active_window_hint.green, - active_window_hint.blue, - ], - )), - CosmicMappedRenderElement::from(BackdropShader::element( - renderer, - Key::Window(Usage::SnappingIndicator, self.window.key()), - t.overlay_geometry(non_exclusive_geometry, gaps), - theme.radius_s()[0], // TODO: Fix once shaders support 4 corner radii customization - 0.4, - [base_color.red, base_color.green, base_color.blue], - )), - ] - } - _ => vec![], - }; + if let Some(t) = &self.snapping_zone + && &self.cursor_output == output + { + let base_color = theme.palette.neutral_9; + let overlay_geometry = t.overlay_geometry(non_exclusive_geometry, gaps); - let w_elements = self - .window - .render_elements::>( - renderer, - (render_location - self.window.geometry().loc) - .to_physical_precise_round(output_scale), - None, - output_scale, - alpha, - Some(false), - scanout_node, - ); - let p_elements = self - .window - .popup_render_elements::>( - renderer, - (render_location - self.window.geometry().loc) - .to_physical_precise_round(output_scale), - output_scale, - alpha, - scanout_node, - ); - let shadow_element = self.window.shadow_render_element( - renderer, - (render_location - self.window.geometry().loc).to_physical_precise_round(output_scale), - None, - output_scale, - scale, - alpha, - ); - - self.stacking_indicator - .iter() - .flat_map(|(indicator, location)| { - indicator.render_elements( + push( + IndicatorShader::element( renderer, - location.to_physical_precise_round(output_scale), - output_scale, + Key::Window(Usage::SnappingIndicator, self.window.key()), + overlay_geometry, + thickness, + [ + theme.radius_s()[0] as u8, + theme.radius_s()[1] as u8, + theme.radius_s()[2] as u8, + theme.radius_s()[3] as u8, + ], 1.0, + output_scale.x, + [ + active_window_hint.red, + active_window_hint.green, + active_window_hint.blue, + ], ) - }) - .chain(p_elements) - .chain(focus_element) - .chain( - w_elements - .into_iter() - .chain(shadow_element) - .map(|elem| match elem { - CosmicMappedRenderElement::Stack(stack) => { - CosmicMappedRenderElement::GrabbedStack( - RescaleRenderElement::from_element( - stack, - render_location.to_physical_precise_round( - output.current_scale().fractional_scale(), - ), - scale, - ), - ) - } - CosmicMappedRenderElement::Window(window) => { - CosmicMappedRenderElement::GrabbedWindow( - RescaleRenderElement::from_element( - window, - render_location.to_physical_precise_round( - output.current_scale().fractional_scale(), - ), - scale, - ), - ) - } - x => x, - }), + .into(), + ); + push( + BackdropShader::element( + renderer, + Key::Window(Usage::SnappingIndicator, self.window.key()), + t.overlay_geometry(non_exclusive_geometry, gaps), + theme.radius_s()[0], // TODO: Fix once shaders support 4 corner radii customization + 0.4, + [base_color.red, base_color.green, base_color.blue], + ) + .into(), ) - .chain(snapping_indicator) - .map(I::from) - .collect() + } } pub fn element(&self) -> CosmicMapped { diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 84677188..aad738f8 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -9,11 +9,12 @@ use std::{ use cosmic_comp_config::AppearanceConfig; use cosmic_settings_config::shortcuts::action::ResizeDirection; use keyframe::{ease, functions::EaseInOutCubic}; +use smallvec::SmallVec; use smithay::{ backend::{ drm::DrmNode, renderer::element::{ - AsRenderElements, RenderElement, + RenderElement, utils::{Relocate, RelocateRenderElement, RescaleRenderElement}, }, }, @@ -1443,8 +1444,8 @@ impl FloatingLayout { renderer: &mut R, alpha: f32, scanout_node: Option, - ) -> Vec> - where + push: &mut dyn FnMut(CosmicMappedRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, @@ -1454,8 +1455,6 @@ impl FloatingLayout { let output = self.space.outputs().next().unwrap(); let output_scale = output.current_scale().fractional_scale(); - let mut elements = Vec::default(); - for elem in self .animations .iter() @@ -1470,20 +1469,17 @@ impl FloatingLayout { .unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha)); let render_location = geometry.loc - elem.geometry().loc.as_local(); - elements.extend( - elem.popup_render_elements( - renderer, - render_location - .as_logical() - .to_physical_precise_round(output_scale), - output_scale.into(), - alpha, - scanout_node, - ), + elem.push_popup_render_elements( + renderer, + render_location + .as_logical() + .to_physical_precise_round(output_scale), + output_scale.into(), + alpha, + scanout_node, + push, ); } - - elements } #[profiling::function] @@ -1496,8 +1492,8 @@ impl FloatingLayout { alpha: f32, theme: &cosmic::theme::CosmicTheme, scanout_node: Option, - ) -> Vec> - where + push: &mut dyn FnMut(CosmicMappedRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, @@ -1510,8 +1506,7 @@ impl FloatingLayout { layers.non_exclusive_zone() }; let output_scale = output.current_scale().fractional_scale(); - - let mut elements = Vec::default(); + let mut lower_elements = SmallVec::<[_; 4]>::new_const(); for elem in self .animations @@ -1525,122 +1520,9 @@ impl FloatingLayout { .get(elem) .map(|anim| (*anim.previous_geometry(), alpha * anim.alpha())) .unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha)); - let render_location = geometry.loc - elem.geometry().loc.as_local(); - let mut window_elements = elem.render_elements( - renderer, - render_location - .as_logical() - .to_physical_precise_round(output_scale), - None, - output_scale.into(), - alpha, - None, - scanout_node, - ); - window_elements.extend( - elem.shadow_render_element( - renderer, - render_location - .as_logical() - .to_physical_precise_round(output_scale), - None, - output_scale.into(), - 1., - alpha, - ), - ); - - if let Some(anim) = self.animations.get(elem) { - let original_geo = anim.previous_geometry(); - geometry = anim.geometry( - output_geometry, - self.space - .element_geometry(elem) - .map(RectExt::as_local) - .unwrap_or(geometry), - elem.floating_tiled.lock().unwrap().as_ref(), - self.gaps(), - ); - - let buffer_size = elem.geometry().size; - let scale = Scale { - x: geometry.size.w as f64 / buffer_size.w as f64, - y: geometry.size.h as f64 / buffer_size.h as f64, - }; - - window_elements = window_elements - .into_iter() - .map(|element| match element { - CosmicMappedRenderElement::Stack(elem) => { - CosmicMappedRenderElement::MovingStack({ - let rescaled = RescaleRenderElement::from_element( - elem, - original_geo - .loc - .as_logical() - .to_physical_precise_round(output_scale), - scale, - ); - - RelocateRenderElement::from_element( - rescaled, - (geometry.loc - original_geo.loc) - .as_logical() - .to_physical_precise_round(output_scale), - Relocate::Relative, - ) - }) - } - CosmicMappedRenderElement::Window(elem) => { - CosmicMappedRenderElement::MovingWindow({ - let rescaled = RescaleRenderElement::from_element( - elem, - original_geo - .loc - .as_logical() - .to_physical_precise_round(output_scale), - scale, - ); - - RelocateRenderElement::from_element( - rescaled, - (geometry.loc - original_geo.loc) - .as_logical() - .to_physical_precise_round(output_scale), - Relocate::Relative, - ) - }) - } - x => x, - }) - .collect(); - } if focused == Some(elem) && !elem.is_maximized(false) { - if let Some((mode, resize)) = resize_indicator.as_mut() { - let mut resize_geometry = geometry; - resize_geometry.loc -= (18, 18).into(); - resize_geometry.size += (36, 36).into(); - - resize.resize(resize_geometry.size.as_logical()); - resize.output_enter(output, Rectangle::default() /* unused */); - window_elements = resize - .render_elements::>( - renderer, - resize_geometry - .loc - .as_logical() - .to_physical_precise_round(output_scale), - output_scale.into(), - alpha * mode.alpha().unwrap_or(1.0), - ) - .into_iter() - .map(CosmicMappedRenderElement::Window) - .chain(window_elements.into_iter()) - .collect(); - } - let active_window_hint = crate::theme::active_window_hint(theme); let radius = elem.corner_radius(geometry.size.as_logical(), indicator_thickness); if indicator_thickness > 0 { @@ -1658,14 +1540,130 @@ impl FloatingLayout { active_window_hint.blue, ], ); - window_elements.insert(0, element.into()); + push(element.into()); + } + + if let Some((mode, resize)) = resize_indicator.as_mut() { + let mut resize_geometry = geometry; + resize_geometry.loc -= (18, 18).into(); + resize_geometry.size += (36, 36).into(); + + resize.resize(resize_geometry.size.as_logical()); + resize.output_enter(output, Rectangle::default() /* unused */); + resize.push_render_elements( + renderer, + resize_geometry + .loc + .as_logical() + .to_physical_precise_round(output_scale), + output_scale.into(), + alpha * mode.alpha().unwrap_or(1.0), + &mut |elem| push(CosmicMappedRenderElement::Window(elem.into())), + ); } } - elements.extend(window_elements); - } + let maybe_map = if let Some(anim) = self.animations.get(elem) { + let original_geo = anim.previous_geometry(); + geometry = anim.geometry( + output_geometry, + self.space + .element_geometry(elem) + .map(RectExt::as_local) + .unwrap_or(geometry), + elem.floating_tiled.lock().unwrap().as_ref(), + self.gaps(), + ); - elements + let buffer_size = elem.geometry().size; + let scale = Scale { + x: geometry.size.w as f64 / buffer_size.w as f64, + y: geometry.size.h as f64 / buffer_size.h as f64, + }; + + Some(move |element| match element { + CosmicMappedRenderElement::Stack(elem) => { + CosmicMappedRenderElement::MovingStack({ + let rescaled = RescaleRenderElement::from_element( + elem, + original_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale), + scale, + ); + + RelocateRenderElement::from_element( + rescaled, + (geometry.loc - original_geo.loc) + .as_logical() + .to_physical_precise_round(output_scale), + Relocate::Relative, + ) + }) + } + CosmicMappedRenderElement::Window(elem) => { + CosmicMappedRenderElement::MovingWindow({ + let rescaled = RescaleRenderElement::from_element( + elem, + original_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale), + scale, + ); + + RelocateRenderElement::from_element( + rescaled, + (geometry.loc - original_geo.loc) + .as_logical() + .to_physical_precise_round(output_scale), + Relocate::Relative, + ) + }) + } + x => x, + }) + } else { + None + }; + let map_anim = |elem| { + if let Some(map) = maybe_map { + map(elem) + } else { + elem + } + }; + + elem.push_render_elements( + renderer, + render_location + .as_logical() + .to_physical_precise_round(output_scale), + None, + output_scale.into(), + alpha, + None, + scanout_node, + &mut |elem| push(map_anim(elem)), + &mut |elem| lower_elements.push(map_anim(elem)), + ); + if let Some(shadow_element) = elem.shadow_render_element( + renderer, + render_location + .as_logical() + .to_physical_precise_round(output_scale), + None, + output_scale.into(), + 1., + alpha, + ) { + push(map_anim(shadow_element)); + } + for elem in lower_elements.drain(..) { + push(elem); + } + } } pub fn snap_to_corner(&self, mapped: &CosmicMapped, corners: &TiledCorners) { diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 14a65ea7..fbcec105 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -6,8 +6,7 @@ use crate::{ element::AsGlowRenderer, }, shell::{ - CosmicSurface, Direction, FocusResult, MoveResult, OutputNotMapped, OverviewMode, - ResizeMode, Trigger, + CosmicSurface, Direction, FocusResult, MoveResult, OverviewMode, ResizeMode, Trigger, element::{ CosmicMapped, CosmicMappedRenderElement, CosmicStack, CosmicWindow, resize_indicator::ResizeIndicator, @@ -45,12 +44,13 @@ use keyframe::{ ease, functions::{EaseInOutCubic, Linear}, }; +use smallvec::SmallVec; use smithay::{ backend::{ drm::DrmNode, renderer::{ element::{ - AsRenderElements, Id, RenderElement, + Id, RenderElement, utils::{ ConstrainAlign, ConstrainScaleBehavior, RescaleRenderElement, constrain_render_elements, @@ -3424,8 +3424,8 @@ impl TilingLayout { None, None, self.theme.cosmic(), - ) - .0; + &mut |_| {}, + ); let mut result = None; let mut lookup = Some(root.clone()); @@ -4011,8 +4011,8 @@ impl TilingLayout { indicator_thickness: u8, theme: &cosmic::theme::CosmicTheme, scanout_node: Option, - ) -> Result>, OutputNotMapped> - where + push: &mut dyn FnMut(CosmicMappedRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, @@ -4052,8 +4052,6 @@ impl TilingLayout { }; let draw_groups = overview.0.alpha(); - let mut elements = Vec::default(); - 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(|(_, zone)| zone)); @@ -4065,7 +4063,7 @@ impl TilingLayout { // all gone windows and fade them out let old_geometries = if let Some(reference_tree) = reference_tree.as_ref() { - let (geometries, _) = if let Some(transition) = draw_groups { + let geometries = if let Some(transition) = draw_groups { Some(geometries_for_groupview( reference_tree, &mut *renderer, @@ -4080,14 +4078,14 @@ impl TilingLayout { swap_desc.clone(), overview.1.as_ref().and_then(|(_, tree)| *tree), theme, + &mut |_elem| {}, )) } else { None - } - .unzip(); + }; // all old windows we want to fade out - elements.extend(render_old_tree_windows( + render_old_tree_windows( reference_tree, target_tree, renderer, @@ -4098,14 +4096,16 @@ impl TilingLayout { swap_desc.is_some(), theme, scanout_node, - )); + push, + ); geometries } else { None }; - let (geometries, group_elements) = if let Some(transition) = draw_groups { + let mut group_elements = SmallVec::<[_; 4]>::new_const(); + let geometries = if let Some(transition) = draw_groups { Some(geometries_for_groupview( target_tree, &mut *renderer, @@ -4119,14 +4119,14 @@ impl TilingLayout { swap_desc.clone(), overview.1.as_ref().and_then(|(_, tree)| *tree), theme, + &mut |elem| group_elements.push(elem), )) } else { None - } - .unzip(); + }; // all alive windows - elements.extend(render_new_tree_windows( + render_new_tree_windows( target_tree, reference_tree, renderer, @@ -4155,14 +4155,13 @@ impl TilingLayout { &self.backdrop_id, theme, scanout_node, - )); + push, + ); // tiling hints - if let Some(group_elements) = group_elements { - elements.extend(group_elements); + for elem in group_elements.into_iter() { + push(elem); } - - Ok(elements) } #[profiling::function] @@ -4174,8 +4173,8 @@ impl TilingLayout { overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), theme: &cosmic::theme::CosmicTheme, scanout_node: Option, - ) -> Result>, OutputNotMapped> - where + push: &mut dyn FnMut(CosmicMappedRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, @@ -4207,8 +4206,6 @@ impl TilingLayout { }; let draw_groups = overview.0.alpha(); - let mut elements = Vec::default(); - let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_)))) .then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone)); let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() { @@ -4219,7 +4216,7 @@ impl TilingLayout { // all gone windows and fade them out let old_geometries = if let Some(reference_tree) = reference_tree.as_ref() { - let (geometries, _) = if let Some(transition) = draw_groups { + let geometries = if let Some(transition) = draw_groups { Some(geometries_for_groupview( reference_tree, &mut *renderer, @@ -4234,14 +4231,14 @@ impl TilingLayout { swap_desc.clone(), overview.1.as_ref().and_then(|(_, tree)| *tree), theme, + &mut |_| {}, )) } else { None - } - .unzip(); + }; // all old windows we want to fade out - elements.extend(render_old_tree_popups( + render_old_tree_popups( reference_tree, target_tree, renderer, @@ -4250,14 +4247,15 @@ impl TilingLayout { percentage, swap_desc.is_some(), scanout_node, - )); + push, + ); geometries } else { None }; - let (geometries, _) = if let Some(transition) = draw_groups { + let geometries = if let Some(transition) = draw_groups { Some(geometries_for_groupview( target_tree, &mut *renderer, @@ -4271,14 +4269,14 @@ impl TilingLayout { swap_desc.clone(), overview.1.as_ref().and_then(|(_, tree)| *tree), theme, + &mut |_| {}, )) } else { None - } - .unzip(); + }; // all alive windows - elements.extend(render_new_tree_popups( + render_new_tree_popups( target_tree, reference_tree, renderer, @@ -4290,9 +4288,8 @@ impl TilingLayout { overview, swap_desc.clone(), scanout_node, - )); - - Ok(elements) + push, + ); } fn gaps(&self) -> (i32, i32) { @@ -4347,10 +4344,8 @@ fn geometries_for_groupview<'a, R>( swap_desc: Option, swap_tree: Option<&Tree>, _theme: &cosmic::theme::CosmicTheme, -) -> ( - HashMap>, - Vec>, -) + push: &mut dyn FnMut(CosmicMappedRenderElement), +) -> HashMap> where R: AsGlowRenderer + 'a, R::TextureId: 'static, @@ -4373,11 +4368,18 @@ where // push bogos value, that will get ignored anyway stack.push((Rectangle::from_size((320, 240).into()), 0)); } - if root.is_some() { + + let has_root = root.is_some(); + if has_root { stack.push((non_exclusive_zone, 0)); } - let mut elements = Vec::new(); + let mut push = |elem| { + if has_root { + push(elem) + } + }; + let mut geometries: HashMap> = HashMap::new(); let alpha = alpha * transition; @@ -4532,7 +4534,7 @@ where if let Some(renderer) = renderer.as_mut() { if (render_potential_group || render_active_child) && Some(&node_id) != root { - elements.push( + push( IndicatorShader::element( *renderer, Key::Group(Arc::downgrade(alive)), @@ -4550,7 +4552,7 @@ where && pill_indicator.is_some() && Some(&node_id) != root { - elements.push( + push( IndicatorShader::element( *renderer, Key::Group(Arc::downgrade(alive)), @@ -4614,7 +4616,7 @@ where }; if draw_outline { - elements.push( + push( IndicatorShader::element( *renderer, Key::Group(Arc::downgrade(alive)), @@ -4675,7 +4677,7 @@ where }; if let Some(renderer) = renderer.as_mut() { - elements.push( + push( BackdropShader::element( *renderer, backdrop_id.clone(), @@ -4693,7 +4695,7 @@ where if matches!(swap_desc, Some(ref desc) if desc.node == node_id) { if let Some(renderer) = renderer.as_mut() { - elements.push( + push( BackdropShader::element( *renderer, Key::Group(Arc::downgrade(alive)), @@ -4778,7 +4780,7 @@ where .unwrap_or(false) { if let Some(renderer) = renderer.as_mut() { - elements.push( + push( BackdropShader::element( *renderer, backdrop_id.clone(), @@ -4820,7 +4822,7 @@ where .unwrap_or(false) { if let Some(renderer) = renderer.as_mut() { - elements.push( + push( BackdropShader::element( *renderer, backdrop_id.clone(), @@ -4851,7 +4853,7 @@ where if let Some(renderer) = renderer.as_mut() { if render_potential_group { - elements.push( + push( IndicatorShader::element( *renderer, Key::Window(Usage::PotentialGroupIndicator, mapped.key()), @@ -4891,7 +4893,7 @@ where geo.loc += (WINDOW_BACKDROP_BORDER, WINDOW_BACKDROP_BORDER).into(); geo.size -= (WINDOW_BACKDROP_BORDER * 2, WINDOW_BACKDROP_BORDER * 2).into(); - elements.push( + push( BackdropShader::element( *renderer, Key::Window(Usage::OverviewBackdrop, mapped.key()), @@ -4948,7 +4950,7 @@ where if let Some(renderer) = renderer.as_mut() { geo.loc += (WINDOW_BACKDROP_BORDER, WINDOW_BACKDROP_BORDER).into(); geo.size -= (WINDOW_BACKDROP_BORDER * 2, WINDOW_BACKDROP_BORDER * 2).into(); - elements.push( + push( BackdropShader::element( *renderer, id.clone(), @@ -4967,11 +4969,7 @@ where } } - if root.is_none() { - elements.clear(); - } - - (geometries, elements) + geometries } fn render_old_tree_popups( @@ -4983,16 +4981,14 @@ fn render_old_tree_popups( percentage: f32, is_swap_mode: bool, scanout_node: Option, -) -> Vec> -where + push: &mut dyn FnMut(CosmicMappedRenderElement), +) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, CosmicWindowRenderElement: RenderElement, CosmicStackRenderElement: RenderElement, { - let mut elements = Vec::default(); - render_old_tree( reference_tree, target_tree, @@ -5001,20 +4997,16 @@ where percentage, is_swap_mode, |mapped, elem_geometry, geo, alpha, _| { - elements.extend( - mapped.popup_render_elements::>( - renderer, - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - Scale::from(output_scale), - alpha, - scanout_node, - ), - ); + mapped.push_popup_render_elements( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, + Scale::from(output_scale), + alpha, + scanout_node, + push, + ) }, - ); - - elements + ) } fn render_old_tree_windows( @@ -5028,8 +5020,8 @@ fn render_old_tree_windows( is_swap_mode: bool, theme: &cosmic::theme::CosmicTheme, scanout_node: Option, -) -> Vec> -where + push: &mut dyn FnMut(CosmicMappedRenderElement), +) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, @@ -5037,8 +5029,35 @@ where CosmicStackRenderElement: RenderElement, { let window_hint = crate::theme::active_window_hint(theme); - let mut elements = Vec::default(); - let mut shadow_elements = Vec::default(); + let mut lower_elements = Vec::default(); + let mut shadow_elements = SmallVec::<[_; 4]>::new_const(); + + let window_map = + |elem, geo: Rectangle, elem_geometry: Rectangle| match elem { + CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( + std::iter::once(elem), + geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, + geo.as_logical().to_physical_precise_round(output_scale), + elem_geometry, + ConstrainScaleBehavior::Stretch, + ConstrainAlign::CENTER, + output_scale, + ) + .next() + .map(CosmicMappedRenderElement::TiledStack), + CosmicMappedRenderElement::Window(elem) => constrain_render_elements( + std::iter::once(elem), + geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, + geo.as_logical().to_physical_precise_round(output_scale), + elem_geometry, + ConstrainScaleBehavior::Stretch, + ConstrainAlign::CENTER, + output_scale, + ) + .next() + .map(CosmicMappedRenderElement::TiledWindow), + x => Some(x), + }; render_old_tree( reference_tree, @@ -5048,57 +5067,9 @@ where percentage, is_swap_mode, |mapped, elem_geometry, geo, alpha, is_minimizing| { - shadow_elements.extend(mapped.shadow_render_element( - renderer, - geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, - Some(geo.size.as_logical()), - Scale::from(output_scale), - 1., - alpha, - )); - - let window_elements = mapped.render_elements::>( - renderer, - geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, - Some(geo.size.as_logical()), - Scale::from(output_scale), - alpha, - None, - scanout_node, - ); - - elements.extend(window_elements.into_iter().flat_map(|element| { - match element { - CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( - std::iter::once(elem), - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - geo.as_logical().to_physical_precise_round(output_scale), - elem_geometry, - ConstrainScaleBehavior::Stretch, - ConstrainAlign::CENTER, - output_scale, - ) - .next() - .map(CosmicMappedRenderElement::TiledStack), - CosmicMappedRenderElement::Window(elem) => constrain_render_elements( - std::iter::once(elem), - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - geo.as_logical().to_physical_precise_round(output_scale), - elem_geometry, - ConstrainScaleBehavior::Stretch, - ConstrainAlign::CENTER, - output_scale, - ) - .next() - .map(CosmicMappedRenderElement::TiledWindow), - x => Some(x), - } - })); let radius = mapped.corner_radius(geo.size.as_logical(), indicator_thickness); if is_minimizing && indicator_thickness > 0 { - elements.push(CosmicMappedRenderElement::FocusIndicator( + push(CosmicMappedRenderElement::FocusIndicator( IndicatorShader::focus_element( renderer, Key::Window(Usage::FocusIndicator, mapped.clone().key()), @@ -5111,10 +5082,44 @@ where ), )); } + + mapped.push_render_elements( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, + Some(geo.size.as_logical()), + Scale::from(output_scale), + alpha, + None, + scanout_node, + &mut |elem| { + if let Some(elem) = window_map(elem, geo, elem_geometry) { + push(elem); + } + }, + &mut |elem| { + if let Some(elem) = window_map(elem, geo, elem_geometry) { + lower_elements.push(elem); + } + }, + ); + + shadow_elements.extend(mapped.shadow_render_element( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, + Some(geo.size.as_logical()), + Scale::from(output_scale), + 1., + alpha, + )); }, ); - shadow_elements.into_iter().chain(elements).collect() + for elem in shadow_elements { + push(elem); + } + for elem in lower_elements { + push(elem); + } } fn render_old_tree( @@ -5210,15 +5215,14 @@ fn render_new_tree_popups( overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), swap_desc: Option, scanout_node: Option, -) -> Vec> -where + push: &mut dyn FnMut(CosmicMappedRenderElement), +) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, CosmicWindowRenderElement: RenderElement, CosmicStackRenderElement: RenderElement, { - let mut popup_elements = Vec::new(); let output_scale = output.current_scale().fractional_scale(); let is_active_output = seat @@ -5241,21 +5245,18 @@ where if let Data::Mapped { mapped, .. } = data { let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); - popup_elements.extend( - mapped.popup_render_elements::>( - renderer, - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - Scale::from(output_scale), - alpha, - scanout_node, - ), + mapped.push_popup_render_elements( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + Scale::from(output_scale), + alpha, + scanout_node, + push, ); } }, ); - - popup_elements } fn render_new_tree_windows( @@ -5278,8 +5279,8 @@ fn render_new_tree_windows( backdrop_id: &Id, theme: &cosmic::theme::CosmicTheme, scanout_node: Option, -) -> Vec> -where + push: &mut dyn FnMut(CosmicMappedRenderElement), +) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, @@ -5319,14 +5320,18 @@ where .map(|seat| &seat.active_output() == output) .unwrap_or(false); - let mut animating_window_elements = Vec::new(); - let mut window_elements = Vec::new(); + let mut animating_window_upper_elements = Vec::new(); + let mut animating_window_lower_elements = Vec::new(); + let mut animating_shadow_elements = SmallVec::<[CosmicMappedRenderElement; 4]>::new_const(); + + let mut window_upper_elements = Vec::new(); + let mut window_lower_elements = Vec::new(); + let mut shadow_elements = SmallVec::<[CosmicMappedRenderElement; 4]>::new_const(); let mut group_backdrop = None; - let mut indicators = Vec::new(); - let mut resize_elements = None; - let mut swap_elements = Vec::new(); - let mut shadow_elements = Vec::new(); + let mut indicators = SmallVec::<[CosmicMappedRenderElement; 2]>::new_const(); + let mut resize_element = None; + let mut swap_elements = SmallVec::<[CosmicMappedRenderElement; 4]>::new_const(); let output_geo = output.geometry(); let output_scale = output.current_scale().fractional_scale(); @@ -5339,7 +5344,7 @@ where // render placeholder, if we are swapping to an empty workspace if target_tree.root_node_id().is_none() && swap_desc.is_some() { - window_elements.push( + window_upper_elements.push( BackdropShader::element( renderer, backdrop_id.clone(), @@ -5394,22 +5399,19 @@ where let render_loc = (swap_geo.loc.as_logical() - window_geo.loc).to_physical_precise_round(output_scale); - swap_elements.extend( - window - .render_elements( - renderer, - render_loc, - output_scale.into(), - 1.0, - None, - scanout_node, - false, - [0; 4], - ) - .into_iter() - .map(|window| { - CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element( - window, + window.push_render_elements( + renderer, + render_loc, + output_scale.into(), + 1.0, + None, + scanout_node, + false, + [0, 0, 0, 0], + &mut |elem| { + swap_elements.push(CosmicMappedRenderElement::GrabbedWindow( + RescaleRenderElement::from_element( + elem.into(), swap_geo .loc .as_logical() @@ -5420,9 +5422,11 @@ where swap_factor(window_geo.size), transition.unwrap_or(1.0), ), - )) - }), - ) + ), + )); + }, + None, + ); } // render actual tree nodes @@ -5538,15 +5542,12 @@ where { swap.resize(geo.size.as_logical()); swap.output_enter(output, output_geo.as_logical()); - swap_elements.extend( - swap.render_elements::>( - renderer, - geo.loc.as_logical().to_physical_precise_round(output_scale), - output_scale.into(), - alpha * overview.0.alpha().unwrap_or(1.0), - ) - .into_iter() - .map(CosmicMappedRenderElement::from), + swap.push_render_elements( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale), + output_scale.into(), + alpha * overview.0.alpha().unwrap_or(1.0), + &mut |elem| swap_elements.push(elem.into()), ); } } @@ -5572,17 +5573,14 @@ where }) { resize.force_update(); } - resize_elements = Some( - resize - .render_elements::>( - renderer, - geo.loc.as_logical().to_physical_precise_round(output_scale), - output_scale.into(), - alpha * mode.alpha().unwrap_or(1.0), - ) - .into_iter() - .map(CosmicMappedRenderElement::from) - .collect::>(), + resize.push_render_elements( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale), + output_scale.into(), + alpha * mode.alpha().unwrap_or(1.0), + &mut |elem| { + resize_element = Some(elem.into()); + }, ); } } @@ -5608,52 +5606,6 @@ where scale.x.min(scale.y), alpha, ); - let mut elements = mapped.render_elements::>( - renderer, - //original_location, - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - max_size, - Scale::from(output_scale), - alpha, - None, - scanout_node, - ); - - if swap_desc - .as_ref() - .filter(|swap_desc| swap_desc.node == node_id) - .and_then(|swap_desc| swap_desc.stack_window.as_ref()) - .zip(focused.as_ref()) - .map(|(stack_window, focused_id)| { - target_tree - .get(focused_id) - .ok() - .map(|focused| match focused.data() { - Data::Mapped { mapped, .. } => mapped - .stack_ref() - .map(|stack| &stack.active() == stack_window) - .unwrap_or(false), - _ => false, - }) - .unwrap_or(false) - }) - .unwrap_or(false) - { - let mut active_geo = mapped.active_window_geometry().as_local(); - active_geo.loc += geo.loc - mapped.geometry().loc.as_local(); - elements.insert( - 0, - CosmicMappedRenderElement::Overlay(BackdropShader::element( - renderer, - Key::Window(Usage::Overlay, mapped.key()), - active_geo, - 0.0, - 0.3, - group_color, - )), - ) - } let (behavior, align) = if is_overview { (ConstrainScaleBehavior::Fit, ConstrainAlign::CENTER) @@ -5663,7 +5615,7 @@ where (ConstrainScaleBehavior::CutOff, ConstrainAlign::TOP_LEFT) }; - let elements = elements.into_iter().flat_map(|element| match element { + let map_elem = |element| match element { CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( std::iter::once(elem), geo.loc.as_logical().to_physical_precise_round(output_scale) @@ -5701,7 +5653,66 @@ where .next() .map(CosmicMappedRenderElement::TiledOverlay), x => Some(x), - }); + }; + + let mut upper_elements = SmallVec::<[CosmicMappedRenderElement; 4]>::new_const(); + let mut lower_elements = SmallVec::<[CosmicMappedRenderElement; 4]>::new_const(); + mapped.push_render_elements( + renderer, + //original_location, + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + max_size, + Scale::from(output_scale), + alpha, + None, + scanout_node, + &mut |elem| { + if let Some(elem) = map_elem(elem) { + upper_elements.push(elem) + } + }, + &mut |elem| { + if let Some(elem) = map_elem(elem) { + lower_elements.push(elem) + } + }, + ); + + if swap_desc + .as_ref() + .filter(|swap_desc| swap_desc.node == node_id) + .and_then(|swap_desc| swap_desc.stack_window.as_ref()) + .zip(focused.as_ref()) + .map(|(stack_window, focused_id)| { + target_tree + .get(focused_id) + .ok() + .map(|focused| match focused.data() { + Data::Mapped { mapped, .. } => mapped + .stack_ref() + .map(|stack| &stack.active() == stack_window) + .unwrap_or(false), + _ => false, + }) + .unwrap_or(false) + }) + .unwrap_or(false) + { + let mut active_geo = mapped.active_window_geometry().as_local(); + active_geo.loc += geo.loc - mapped.geometry().loc.as_local(); + upper_elements.insert( + 0, + CosmicMappedRenderElement::Overlay(BackdropShader::element( + renderer, + Key::Window(Usage::Overlay, mapped.key()), + active_geo, + 0.0, + 0.3, + group_color, + )), + ) + } if swap_desc .as_ref() @@ -5714,30 +5725,36 @@ where }) .unwrap_or(false) { + swap_elements.extend(upper_elements); swap_elements.extend(shadow_element); - swap_elements.extend(elements); + swap_elements.extend(lower_elements); + } else if animating { + animating_window_upper_elements.extend(upper_elements); + animating_shadow_elements.extend(shadow_element); + animating_window_lower_elements.extend(lower_elements); } else { + window_upper_elements.extend(upper_elements); shadow_elements.extend(shadow_element); - if animating { - animating_window_elements.extend(elements); - } else { - window_elements.extend(elements); - } + window_lower_elements.extend(lower_elements); } } }, ); - resize_elements + for elem in resize_element .into_iter() - .flatten() .chain(swap_elements) .chain(indicators) - .chain(window_elements) - .chain(animating_window_elements) + .chain(window_upper_elements) .chain(shadow_elements) + .chain(window_lower_elements) + .chain(animating_window_upper_elements) + .chain(animating_shadow_elements) + .chain(animating_window_lower_elements) .chain(group_backdrop.into_iter().map(Into::into)) - .collect() + { + push(elem); + } } fn render_new_tree( diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index c5f5cb90..6407935d 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,3 +1,4 @@ +use crate::backend::render::wayland::SurfaceRenderElement; use crate::shell::focus::FocusTarget; use crate::shell::layout::tiling::RestoreTilingState; use crate::wayland::handlers::xdg_activation::ActivationContext; @@ -28,6 +29,7 @@ use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::Tiling use id_tree::Tree; use indexmap::IndexSet; use keyframe::{ease, functions::EaseInOutCubic}; +use smallvec::SmallVec; use smithay::backend::drm::DrmNode; use smithay::backend::renderer::element::Kind; use smithay::output::WeakOutput; @@ -35,8 +37,7 @@ use smithay::utils::user_data::UserDataMap; use smithay::{ backend::renderer::{ element::{ - Element, Id, RenderElement, surface::WaylandSurfaceRenderElement, - texture::TextureRenderElement, utils::RescaleRenderElement, + Element, Id, RenderElement, texture::TextureRenderElement, utils::RescaleRenderElement, }, gles::GlesTexture, glow::GlowRenderer, @@ -1622,8 +1623,8 @@ impl Workspace { indicator_thickness: u8, theme: &CosmicTheme, scanout_node: Option, - ) -> Result>, OutputNotMapped> - where + push: &mut dyn FnMut(WorkspaceRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, @@ -1631,80 +1632,83 @@ impl Workspace { CosmicStackRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { - let mut elements = Vec::default(); - let output_scale = self.output.current_scale().fractional_scale(); let zone = { let layer_map = layer_map_for_output(&self.output); layer_map.non_exclusive_zone().as_local() }; let focused = self.focus_stack.get(last_active_seat).last().cloned(); + let fullscreen_focused = matches!(focused, Some(FocusTarget::Fullscreen(_))); - let render_fullscreen = |fullscreen: &FullscreenSurface, - renderer: &mut R, - output_scale: f64| - -> Vec> { - let fullscreen_geo = self.fullscreen_geometry_for(fullscreen); - let previous_geo = fullscreen - .previous_geometry - .as_ref() - .unwrap_or(&fullscreen_geo); + let mut fullscreen_elements = SmallVec::<[WorkspaceRenderElement; 2]>::new_const(); + let mut render_fullscreen = + |fullscreen: &FullscreenSurface, renderer: &mut R, output_scale: f64| { + let fullscreen_geo = self.fullscreen_geometry_for(fullscreen); + let previous_geo = fullscreen + .previous_geometry + .as_ref() + .unwrap_or(&fullscreen_geo); - let (target_geo, alpha) = match (fullscreen.start_at, fullscreen.ended_at) { - (Some(started), _) => { - let duration = Instant::now().duration_since(started).as_secs_f64() - / FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); - ( - ease( - EaseInOutCubic, - EaseRectangle(*previous_geo), - EaseRectangle(fullscreen_geo), - duration, + let (target_geo, alpha) = match (fullscreen.start_at, fullscreen.ended_at) { + (Some(started), _) => { + let duration = Instant::now().duration_since(started).as_secs_f64() + / FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); + ( + ease( + EaseInOutCubic, + EaseRectangle(*previous_geo), + EaseRectangle(fullscreen_geo), + duration, + ) + .0, + ease(EaseInOutCubic, 0.0, 1.0, duration), ) - .0, - ease(EaseInOutCubic, 0.0, 1.0, duration), - ) - } - (_, Some(ended)) => { - let duration = Instant::now().duration_since(ended).as_secs_f64() - / FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); - ( - ease( - EaseInOutCubic, - EaseRectangle(fullscreen_geo), - EaseRectangle(*previous_geo), - duration, + } + (_, Some(ended)) => { + let duration = Instant::now().duration_since(ended).as_secs_f64() + / FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); + ( + ease( + EaseInOutCubic, + EaseRectangle(fullscreen_geo), + EaseRectangle(*previous_geo), + duration, + ) + .0, + ease(EaseInOutCubic, 1.0, 0.0, duration), ) - .0, - ease(EaseInOutCubic, 1.0, 0.0, duration), - ) - } - (None, None) => (fullscreen_geo, 1.0), - }; + } + (None, None) => (fullscreen_geo, 1.0), + }; - let render_loc = target_geo - .loc - .as_logical() - .to_physical_precise_round(output_scale); + let render_loc = target_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale); - // Only rescale geometry when animating - let animation_rescale = |elem| { - if fullscreen.is_animating() { - let fullscreen_geo = fullscreen.surface.0.geometry(); - let scale = Scale { - x: target_geo.size.w as f64 / fullscreen_geo.size.w as f64, - y: target_geo.size.h as f64 / fullscreen_geo.size.h as f64, - }; + // Only rescale geometry when animating + let animation_rescale = |elem| { + if fullscreen.is_animating() { + let fullscreen_geo = fullscreen.surface.0.geometry(); + let scale = Scale { + x: target_geo.size.w as f64 / fullscreen_geo.size.w as f64, + y: target_geo.size.h as f64 / fullscreen_geo.size.h as f64, + }; - RescaleRenderElement::from_element(elem, render_loc, scale).into() - } else { - Into::>::into(elem) - } - }; + RescaleRenderElement::from_element(elem, render_loc, scale).into() + } else { + Into::>::into(elem) + } + }; - fullscreen - .surface - .render_elements::>( + let mut fullscreen_push = |elem: SurfaceRenderElement| { + if fullscreen_focused { + push(animation_rescale(elem.into())) + } else { + fullscreen_elements.push(animation_rescale(elem.into())) + } + }; + fullscreen.surface.push_render_elements( renderer, render_loc, output_scale.into(), @@ -1712,30 +1716,24 @@ impl Workspace { Some(true), scanout_node, false, - [0; 4], - ) - .into_iter() - .map(animation_rescale) - .collect::>() - }; + [0, 0, 0, 0], + &mut fullscreen_push, + None, + ); + }; let top_fullscreen = self.get_fullscreen(last_active_seat); - let mut fullscreen_elements: Vec> = Vec::new(); if let Some(fs) = top_fullscreen { - fullscreen_elements.extend(render_fullscreen(fs, renderer, output_scale)); + render_fullscreen(fs, renderer, output_scale) } // Also render any animating (entering/exiting) fullscreens for fs in self.fullscreen_surfaces.iter().filter(|f| f.is_animating()) { if top_fullscreen.is_none_or(|top| top.surface != fs.surface) { - fullscreen_elements.extend(render_fullscreen(fs, renderer, output_scale)); + render_fullscreen(fs, renderer, output_scale); }; } - if matches!(focused, Some(FocusTarget::Fullscreen(_))) { - elements.append(&mut fullscreen_elements); - } - let any_fullscreen_animating = self .fullscreen_surfaces .iter() @@ -1766,29 +1764,21 @@ impl Workspace { OverviewMode::None => 1.0, }; - elements.extend( - self.floating_layer - .render::( - renderer, - render_focus - .then(|| { - focused.as_ref().and_then(|target| { - if let FocusTarget::Window(mapped) = target { - Some(mapped) - } else { - None - } - }) - }) - .flatten(), - resize_indicator.clone(), - indicator_thickness, - alpha, - theme, - scanout_node, - ) - .into_iter() - .map(WorkspaceRenderElement::from), + self.floating_layer.render( + renderer, + focused.as_ref().and_then(|target| { + if let FocusTarget::Window(mapped) = target { + Some(mapped) + } else { + None + } + }), + resize_indicator.clone(), + indicator_thickness, + alpha, + theme, + scanout_node, + &mut |elem| push(elem.into()), ); let alpha = match &overview.0 { @@ -1805,24 +1795,20 @@ impl Workspace { }; //tiling surfaces - elements.extend( - self.tiling_layer - .render::( - renderer, - render_focus.then_some(last_active_seat), - zone, - overview, - resize_indicator, - indicator_thickness, - theme, - scanout_node, - )? - .into_iter() - .map(WorkspaceRenderElement::from), + self.tiling_layer.render( + renderer, + render_focus.then_some(last_active_seat), + zone, + overview, + resize_indicator, + indicator_thickness, + theme, + scanout_node, + &mut |elem| push(elem.into()), ); if let Some(alpha) = alpha { - elements.push( + push( Into::>::into(BackdropShader::element( renderer, self.backdrop_id.clone(), @@ -1836,11 +1822,9 @@ impl Workspace { } } - if !matches!(focused, Some(FocusTarget::Fullscreen(_))) { - elements.extend(fullscreen_elements.into_iter()); + for elem in fullscreen_elements { + push(elem); } - - Ok(elements) } #[profiling::function] @@ -1852,8 +1836,8 @@ impl Workspace { overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), theme: &CosmicTheme, scanout_node: Option, - ) -> Result>, OutputNotMapped> - where + push: &mut dyn FnMut(WorkspaceRenderElement), + ) where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, @@ -1861,8 +1845,6 @@ impl Workspace { CosmicStackRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { - let mut elements = Vec::default(); - let output_scale = self.output.current_scale().fractional_scale(); let zone = { let layer_map = layer_map_for_output(&self.output); @@ -1917,18 +1899,13 @@ impl Workspace { .as_logical() .to_physical_precise_round(output_scale); - elements.extend( - fullscreen - .surface - .popup_render_elements::>( - renderer, - render_loc, - output_scale.into(), - alpha, - scanout_node, - ) - .into_iter() - .map(Into::into), + fullscreen.surface.push_popup_render_elements( + renderer, + render_loc, + output_scale.into(), + alpha, + scanout_node, + &mut |elem| push(WorkspaceRenderElement::FullscreenPopup(elem.into())), ); } @@ -1962,30 +1939,20 @@ impl Workspace { OverviewMode::None => 1.0, }; - elements.extend( - self.floating_layer - .render_popups::(renderer, alpha, scanout_node) - .into_iter() - .map(WorkspaceRenderElement::from), - ); + self.floating_layer + .render_popups(renderer, alpha, scanout_node, &mut |elem| push(elem.into())); //tiling surfaces - elements.extend( - self.tiling_layer - .render_popups::( - renderer, - render_focus.then_some(last_active_seat), - zone, - overview, - theme, - scanout_node, - )? - .into_iter() - .map(WorkspaceRenderElement::from), + self.tiling_layer.render_popups( + renderer, + render_focus.then_some(last_active_seat), + zone, + overview, + theme, + scanout_node, + &mut |elem| push(elem.into()), ); } - - Ok(elements) } } @@ -2006,7 +1973,7 @@ where R: AsGlowRenderer, R::TextureId: 'static, { - OverrideRedirect(WaylandSurfaceRenderElement), + OverrideRedirect(SurfaceRenderElement), Fullscreen(RescaleRenderElement>), FullscreenPopup(CosmicWindowRenderElement), Window(CosmicMappedRenderElement), @@ -2244,13 +2211,13 @@ where } } -impl From> for WorkspaceRenderElement +impl From> for WorkspaceRenderElement where R: AsGlowRenderer, R::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - fn from(elem: WaylandSurfaceRenderElement) -> Self { + fn from(elem: SurfaceRenderElement) -> Self { WorkspaceRenderElement::OverrideRedirect(elem) } } diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs index ffeed63b..02d1be55 100644 --- a/src/shell/zoom.rs +++ b/src/shell/zoom.rs @@ -11,7 +11,7 @@ use cosmic_comp_config::ZoomMovement; use cosmic_config::ConfigSet; use keyframe::{ease, functions::Linear}; use smithay::{ - backend::renderer::{ImportMem, Renderer, element::AsRenderElements}, + backend::renderer::{ImportMem, Renderer, element::memory::MemoryRenderBufferRenderElement}, desktop::space::SpaceElement, input::{ Seat, @@ -192,9 +192,12 @@ impl OutputZoomState { }); } - fn render(&mut self, renderer: &mut R, output: &Output) -> Vec - where - C: From< as AsRenderElements>::RenderElement>, + fn render( + &mut self, + renderer: &mut R, + output: &Output, + push: &mut dyn FnMut(MemoryRenderBufferRenderElement), + ) where R: Renderer + ImportMem, R::TextureId: Send + Clone + 'static, { @@ -208,8 +211,13 @@ impl OutputZoomState { .to_physical(scale.fractional_scale()) .to_i32_round(); - self.element - .render_elements(renderer, location, scale.fractional_scale().into(), 1.0) + self.element.push_render_elements( + renderer, + location, + scale.fractional_scale().into(), + 1.0, + push, + ) } } @@ -382,14 +390,16 @@ impl ZoomState { None } - pub fn render(renderer: &mut R, output: &Output) -> Vec - where - C: From< as AsRenderElements>::RenderElement>, + pub fn render( + renderer: &mut R, + output: &Output, + push: &mut dyn FnMut(MemoryRenderBufferRenderElement), + ) where R: Renderer + ImportMem, R::TextureId: Send + Clone + 'static, { let output_state = output.user_data().get::>().unwrap(); - output_state.lock().unwrap().render(renderer, output) + output_state.lock().unwrap().render(renderer, output, push) } } diff --git a/src/utils/iced/mod.rs b/src/utils/iced/mod.rs index 44a722db..2ee79733 100644 --- a/src/utils/iced/mod.rs +++ b/src/utils/iced/mod.rs @@ -35,7 +35,7 @@ use smithay::{ renderer::{ ImportMem, Renderer, element::{ - AsRenderElements, Kind, + Kind, memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement}, }, }, @@ -910,21 +910,18 @@ impl SpaceElement for IcedElement

{ } } -impl AsRenderElements for IcedElement

-where - P: Program + Send + 'static, - R: Renderer + ImportMem, - R::TextureId: Send + Clone + 'static, -{ - type RenderElement = MemoryRenderBufferRenderElement; - - fn render_elements>( +impl IcedElement

{ + pub fn push_render_elements( &self, renderer: &mut R, location: Point, mut scale: Scale, alpha: f32, - ) -> Vec { + push: &mut dyn FnMut(MemoryRenderBufferRenderElement), + ) where + R: Renderer + ImportMem, + R::TextureId: Send + Clone + 'static, + { let mut internal = self.0.lock().unwrap(); // makes partial borrows easier let internal_ref = &mut *internal; @@ -1055,11 +1052,10 @@ where Kind::Unspecified, ) { Ok(buffer) => { - return vec![C::from(buffer)]; + push(buffer); } Err(err) => tracing::warn!("What? {:?}", err), } } - Vec::new() } } diff --git a/src/utils/screenshot.rs b/src/utils/screenshot.rs index 541371cf..eb7730c7 100644 --- a/src/utils/screenshot.rs +++ b/src/utils/screenshot.rs @@ -14,7 +14,7 @@ use smithay::{ use tracing::warn; use crate::{ - backend::render::{RendererRef, element::AsGlowRenderer, wayland::SurfaceRenderElement}, + backend::render::{RendererRef, element::AsGlowRenderer}, shell::element::CosmicSurface, state::{State, advertised_node_for_surface}, }; @@ -27,7 +27,8 @@ pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) { R::Error: Send + Sync + 'static, { let bbox = bbox_from_surface_tree(&window.wl_surface().unwrap(), (0, 0)); - let elements = window.render_elements::>( + let mut elements = Vec::new(); + window.push_render_elements( renderer, (-bbox.loc.x, -bbox.loc.y).into(), Scale::from(1.0), @@ -36,6 +37,8 @@ pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) { None, false, [0; 4], + &mut |elem| elements.push(elem), + None, ); // TODO: 10-bit diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index c640a6d7..85759165 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use calloop::LoopHandle; +use smallvec::SmallVec; use smithay::{ backend::{ allocator::{Buffer, Fourcc, format::get_transparent}, @@ -39,7 +40,8 @@ use tracing::warn; use crate::{ backend::render::{ - CursorMode, ElementFilter, RendererRef, cursor, + CursorMode, ElementFilter, RendererRef, + cursor::{self, CursorRenderElement}, element::{AsGlowRenderer, CosmicElement, DamageElement}, render_workspace, wayland::SurfaceRenderElement, @@ -591,44 +593,49 @@ pub fn render_window_to_buffer( if let Some(location) = location { if draw_cursor { - elements.extend( - cursor::draw_cursor( - renderer, - &seat, - location, - 1.0.into(), - 1.0, - common.clock.now(), - true, - ) - .into_iter() - .map(|(elem, hotspot)| { - WindowCaptureElement::CursorElement(RelocateRenderElement::from_element( - elem, - Point::from((-hotspot.x, -hotspot.y)), - Relocate::Relative, - )) - }), + cursor::draw_cursor( + renderer, + &seat, + location, + 1.0.into(), + 1.0, + common.clock.now(), + true, + &mut |elem, hotspot| { + elements.push(WindowCaptureElement::CursorElement( + RelocateRenderElement::from_element( + elem, + Point::from((-hotspot.x, -hotspot.y)), + Relocate::Relative, + ), + )); + }, ); } // TODO cosmic-workspaces wants to omit, but metadata cursor capture in portal should // still include dnd surface in window capture buffer? if draw_cursor && let Some(dnd_icon) = get_dnd_icon(&seat) { - elements.extend( - cursor::draw_dnd_icon( - renderer, - &dnd_icon.surface, - (location + dnd_icon.offset.to_f64()).to_i32_round(), - 1.0, - ) - .into_iter() - .map(WindowCaptureElement::from), + cursor::draw_dnd_icon( + renderer, + &dnd_icon.surface, + (location + dnd_icon.offset.to_f64()).to_i32_round(), + 1.0, + &mut |elem| { + elements.push( + RelocateRenderElement::from_element( + CursorRenderElement::Surface(elem), + Point::new(0, 0), + Relocate::Relative, + ) + .into(), + ) + }, ); } } - elements.extend(toplevel.render_elements::>( + toplevel.push_render_elements( renderer, (-geometry.loc.x, -geometry.loc.y).into(), Scale::from(1.0), @@ -637,7 +644,9 @@ pub fn render_window_to_buffer( None, false, [0; 4], - )); + &mut |elem| elements.push(elem.into()), + None, + ); if let Ok(dmabuf) = get_dmabuf(buffer) { let mut dmabuf_clone = dmabuf.clone(); @@ -803,7 +812,8 @@ pub fn render_cursor_to_buffer( .collect(); dt.damage_output(age, &additional_damage_elements)?; - let elements = cursor::draw_cursor( + let mut elements = SmallVec::<[WindowCaptureElement; 4]>::new_const(); + cursor::draw_cursor( renderer, seat, Point::from((0.0, 0.0)), @@ -811,11 +821,12 @@ pub fn render_cursor_to_buffer( 1.0, common.clock.now(), true, - ) - .into_iter() - .map(|(elem, _)| RelocateRenderElement::from_element(elem, (0, 0), Relocate::Relative)) - .map(WindowCaptureElement::from) - .collect::>(); + &mut |elem, _| { + elements.push( + RelocateRenderElement::from_element(elem, (0, 0), Relocate::Relative).into(), + ) + }, + ); if let Ok(dmabuf) = get_dmabuf(buffer) { let mut dmabuf_clone = dmabuf.clone(); From a9bf89d8b2676119ac444ed10edbb4dcbb0d242c Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 4 Mar 2026 19:02:29 +0100 Subject: [PATCH 23/56] element/wayland: Add blur background-effect --- src/backend/render/mod.rs | 4 + .../render/shaders/blur_downsample.frag | 26 + src/backend/render/shaders/blur_upsample.frag | 27 + .../render/shaders/clipped_surface.frag | 32 +- src/backend/render/wayland/blur_effect.rs | 578 ++++++++++++++++++ src/backend/render/wayland/clipped_surface.rs | 1 + src/backend/render/wayland/mod.rs | 19 +- src/state.rs | 5 + src/wayland/handlers/background_effect.rs | 59 ++ src/wayland/handlers/mod.rs | 1 + 10 files changed, 747 insertions(+), 5 deletions(-) create mode 100644 src/backend/render/shaders/blur_downsample.frag create mode 100644 src/backend/render/shaders/blur_upsample.frag create mode 100644 src/backend/render/wayland/blur_effect.rs create mode 100644 src/wayland/handlers/background_effect.rs diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 717fe54e..f737afe1 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -19,6 +19,7 @@ use crate::{ shadow::{SHADOW_SHADER, ShadowShader}, wayland::{ SurfaceRenderElement, + blur_effect::BlurShaders, clipped_surface::{CLIPPING_SHADER, ClippingShader}, push_render_elements_from_surface_tree, }, @@ -425,6 +426,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { UniformName::new("geo_size", UniformType::_2f), UniformName::new("corner_radius", UniformType::_4f), UniformName::new("input_to_geo", UniformType::Matrix3x3), + UniformName::new("noise", UniformType::_1f), ], )?; let shadow_shader = renderer.compile_custom_pixel_shader( @@ -440,6 +442,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { UniformName::new("window_corner_radius", UniformType::_4f), ], )?; + let blur_shaders = BlurShaders::compile(renderer)?; let egl_context = renderer.egl_context(); egl_context @@ -457,6 +460,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { egl_context .user_data() .insert_if_missing(|| ShadowShader(shadow_shader)); + egl_context.user_data().insert_if_missing(|| blur_shaders); Ok(()) } diff --git a/src/backend/render/shaders/blur_downsample.frag b/src/backend/render/shaders/blur_downsample.frag new file mode 100644 index 00000000..1fa24beb --- /dev/null +++ b/src/backend/render/shaders/blur_downsample.frag @@ -0,0 +1,26 @@ +#version 100 + +//_DEFINES_ + +precision highp float; + +varying vec2 v_coords; + +uniform sampler2D tex; +uniform vec2 half_pixel; +uniform float offset; + +uniform float alpha; +#if defined(DEBUG_FLAGS) +uniform float tint; +#endif + +void main() { + vec4 sum = texture2D(tex, v_coords) * 4.0; + sum += texture2D(tex, v_coords - half_pixel * offset); + sum += texture2D(tex, v_coords + half_pixel * offset); + sum += texture2D(tex, v_coords + vec2(half_pixel.x, -half_pixel.y) * offset); + sum += texture2D(tex, v_coords - vec2(half_pixel.x, -half_pixel.y) * offset); + + gl_FragColor = sum / sum.a; +} diff --git a/src/backend/render/shaders/blur_upsample.frag b/src/backend/render/shaders/blur_upsample.frag new file mode 100644 index 00000000..ac026d19 --- /dev/null +++ b/src/backend/render/shaders/blur_upsample.frag @@ -0,0 +1,27 @@ +#version 100 + +precision highp float; + +varying vec2 v_coords; + +uniform sampler2D tex; +uniform vec2 half_pixel; +uniform float offset; + +uniform float alpha; +#if defined(DEBUG_FLAGS) +uniform float tint; +#endif + +void main() { + vec4 sum = texture2D(tex, v_coords + vec2(-half_pixel.x * 2.0, 0.0) * offset); + sum += texture2D(tex, v_coords + vec2(-half_pixel.x, half_pixel.y) * offset) * 2.0; + sum += texture2D(tex, v_coords + vec2(0.0, half_pixel.y * 2.0) * offset); + sum += texture2D(tex, v_coords + vec2(half_pixel.x, half_pixel.y) * offset) * 2.0; + sum += texture2D(tex, v_coords + vec2(half_pixel.x * 2.0, 0.0) * offset); + sum += texture2D(tex, v_coords + vec2(half_pixel.x, -half_pixel.y) * offset) * 2.0; + sum += texture2D(tex, v_coords + vec2(0.0, -half_pixel.y * 2.0) * offset); + sum += texture2D(tex, v_coords + vec2(-half_pixel.x, -half_pixel.y) * offset) * 2.0; + + gl_FragColor = sum / sum.a; +} diff --git a/src/backend/render/shaders/clipped_surface.frag b/src/backend/render/shaders/clipped_surface.frag index dd863d23..d99d1a56 100644 --- a/src/backend/render/shaders/clipped_surface.frag +++ b/src/backend/render/shaders/clipped_surface.frag @@ -25,6 +25,7 @@ uniform float tint; uniform vec2 geo_size; uniform vec4 corner_radius; uniform mat3 input_to_geo; +uniform float noise; float rounding_alpha(vec2 coords, vec2 size) { vec2 center; @@ -51,15 +52,26 @@ float rounding_alpha(vec2 coords, vec2 size) { return 1.0 - smoothstep(radius - half_px, radius + half_px, dist); } -void main() { - vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0); +float hash(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * 727.727); + p3 += dot(p3, p3.xyz + 33.33); + return fract((p3.x + p3.y) * p3.z); +} + +void main() { + if (alpha <= 0.0) { + discard; + } - // Sample the texture. vec4 color = texture2D(tex, v_coords); #if defined(NO_ALPHA) color = vec4(color.rgb, 1.0); #endif + if (color.a <= 0.0) { + discard; + } + vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0); if (coords_geo.x < 0.0 || 1.0 < coords_geo.x || coords_geo.y < 0.0 || 1.0 < coords_geo.y) { // Clip outside geometry. color = vec4(0.0); @@ -68,8 +80,20 @@ void main() { color = color * rounding_alpha(coords_geo.xy * geo_size, geo_size); } + if (color.a <= 0.0) { + discard; + } + + if (noise > 0.0) { + // Add noise fx + // This can be used to achieve a glass look + float noiseHash = hash(v_coords); + float noiseAmount = (mod(noiseHash, 1.0) - 0.5); + color.rgb += noiseAmount * noise; + } + // Apply final alpha and tint. - color = color * alpha; + color *= alpha; #if defined(DEBUG_FLAGS) if (tint == 1.0) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs new file mode 100644 index 00000000..895cd19e --- /dev/null +++ b/src/backend/render/wayland/blur_effect.rs @@ -0,0 +1,578 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + sync::{ + Arc, Mutex, + atomic::{AtomicBool, Ordering}, + }, +}; + +use cgmath::{Matrix3, SquareMatrix, Vector2}; +use smithay::{ + backend::{ + allocator::Fourcc, + renderer::{ + Bind, BlitFrame, Color32F, ContextId, Frame, FrameContext, ImportAll, Offscreen, + Renderer, Texture, TextureFilter, + element::{Element, Id, Kind, RenderElement}, + gles::{ + GlesError, GlesFrame, GlesRenderer, GlesTexProgram, GlesTexture, Uniform, + UniformName, UniformType, UniformValue, ffi, + }, + sync::SyncPoint, + utils::{CommitCounter, DamageSet}, + }, + }, + utils::{ + Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform, user_data::UserDataMap, + }, + wayland::compositor::SurfaceData, +}; + +use crate::{ + backend::render::{element::AsGlowRenderer, wayland::clipped_surface::ClippingShader}, + wayland::handlers::background_effect::ComputedBlurRegionCachedState, +}; + +pub static BLUR_DOWNSAMPLE_SHADER: &str = include_str!("../shaders/blur_downsample.frag"); +pub static BLUR_UPSAMPLE_SHADER: &str = include_str!("../shaders/blur_upsample.frag"); + +const PASSES: usize = 3; +const OFFSET: f64 = 1.1; +const NOISE: f32 = 0.03; + +#[derive(Debug, Clone)] +pub struct BlurShaders { + down: GlesTexProgram, + up: GlesTexProgram, +} + +impl BlurShaders { + pub fn compile(renderer: &mut GlesRenderer) -> Result { + let up = renderer.compile_custom_texture_shader( + BLUR_UPSAMPLE_SHADER, + &[ + UniformName::new("half_pixel", UniformType::_2f), + UniformName::new("offset", UniformType::_1f), + ], + )?; + let down = renderer.compile_custom_texture_shader( + BLUR_DOWNSAMPLE_SHADER, + &[ + UniformName::new("half_pixel", UniformType::_2f), + UniformName::new("offset", UniformType::_1f), + ], + )?; + + Ok(BlurShaders { up, down }) + } + + pub fn get(renderer: &R) -> Self { + Borrow::::borrow(renderer.glow_renderer()) + .egl_context() + .user_data() + .get::() + .expect("Custom Shaders not initialized") + .clone() + } +} + +type BlurTexture = Mutex>; + +struct BlurState { + id: Id, + renderer_id: Option>, + src: Size, + offset: f64, + passes: usize, + region: Vec>, + dirty: Arc, +} + +unsafe impl Send for BlurState {} +unsafe impl Sync for BlurState {} + +impl BlurState { + fn new() -> Self { + BlurState { + id: Id::new(), + renderer_id: None, + src: Size::new(0., 0.), + offset: 0., + passes: 0, + region: Vec::new(), + dirty: Arc::new(AtomicBool::new(true)), + } + } +} + +pub struct BlurElement { + id: Id, + src: Size, + geometry: Rectangle, + scaling_shaders: BlurShaders, + render_shader: GlesTexProgram, + dirty: Arc, + region: Vec>, + offset: f64, + passes: usize, + uniforms: Vec>, +} + +impl BlurElement { + pub fn from_surface( + renderer: &mut R, + states: &SurfaceData, + geometry: Rectangle, + output_scale: f64, + radii: [u8; 4], + ) -> Result, R::Error> { + let mut blur_region_state = states.cached_state.get::(); + let Some(region) = blur_region_state.current().blur_region.as_ref() else { + return Ok(None); + }; + + let geo = geometry.to_physical_precise_round(output_scale); + let mut extended_geo = geo; + let radius = OFFSET * 2.0f64.powf(PASSES as f64); + extended_geo.loc -= Point::::new(radius, radius); + extended_geo.size += Size::::new(radius, radius).upscale(2.); + + // compute input_to_geo so that it crops the extended capture radius + let geo_scale = { + let Scale { x, y } = geo.size / extended_geo.size; + Matrix3::from_nonuniform_scale(x as f32, y as f32) + .invert() + .unwrap() + }; + let geo_translation = { + let offset = geo.loc - extended_geo.loc; + Matrix3::from_translation(Vector2::new( + (offset.x / extended_geo.size.w) as f32, + (offset.y / extended_geo.size.h) as f32, + )) + .invert() + .unwrap() + }; + let input_to_geo = geo_scale * geo_translation; + + let uniforms = vec![ + Uniform::new("geo_size", (geometry.size.w as f32, geometry.size.h as f32)), + Uniform::new( + "corner_radius", + [ + radii[3] as f32, + radii[1] as f32, + radii[0] as f32, + radii[2] as f32, + ], + ), + Uniform::new( + "input_to_geo", + UniformValue::Matrix3x3 { + matrices: vec![*AsRef::<[f32; 9]>::as_ref(&input_to_geo)], + transpose: false, + }, + ), + Uniform::new("noise", UniformValue::_1f(NOISE)), + ]; + + let state = states + .data_map + .get_or_insert_threadsafe::, _>(|| Mutex::new(BlurState::new())); + + Ok(Some(Self::from_state( + renderer, + extended_geo.to_logical(output_scale), + ®ion, + output_scale, + &mut *state.lock().unwrap(), + uniforms, + )?)) + } + + fn from_state( + renderer: &mut R, + geometry: Rectangle, + region: &Vec>, + output_scale: f64, + state: &mut BlurState, + uniforms: Vec>, + ) -> Result { + let renderer_id = renderer.glow_renderer().context_id(); + let src = geometry.size.to_buffer(output_scale, Transform::Normal); + + let dirty = !(state + .renderer_id + .as_ref() + .is_some_and(|id| id == &renderer_id) + && &state.region == region + && state.src == src); + state.renderer_id = Some(renderer_id); + state.offset = OFFSET * output_scale; // we want the blur result to be stable across scaling; + state.passes = PASSES; + state.region = region.clone(); + state.src = src; + if dirty { + state.dirty.store(dirty, Ordering::Release); + } + + Ok(BlurElement { + id: state.id.clone(), + src, + geometry, + scaling_shaders: BlurShaders::get(renderer), + render_shader: ClippingShader::get(renderer), + dirty: state.dirty.clone(), + offset: state.offset, + passes: state.passes, + region: region + .iter() + .map(|rect| rect.to_physical_precise_round(output_scale)) + .collect(), + uniforms, + }) + } +} + +impl Element for BlurElement { + fn id(&self) -> &Id { + &self.id + } + + fn current_commit(&self) -> CommitCounter { + Default::default() + } + + fn src(&self) -> Rectangle { + Rectangle::from_size(self.src) + } + + fn geometry(&self, scale: Scale) -> Rectangle { + self.geometry.to_physical_precise_round(scale) + } + + fn transform(&self) -> Transform { + Transform::Normal + } + + fn damage_since( + &self, + scale: Scale, + commit: Option, + ) -> DamageSet { + if commit.is_none() || self.dirty.load(Ordering::Acquire) { + DamageSet::from_slice(&[self.geometry.to_physical_precise_round(scale)]) + } else { + DamageSet::default() + } + } + + fn alpha(&self) -> f32 { + 1.0 + } + + fn kind(&self) -> Kind { + Kind::default() + } + + fn is_framebuffer_effect(&self) -> bool { + true + } +} + +impl RenderElement for BlurElement { + fn capture_framebuffer( + &self, + frame: &mut ::Frame<'_, '_>, + src: Rectangle, + dst: Rectangle, + cache: &UserDataMap, + ) -> Result<(), ::Error> { + let glow_frame = ::glow_frame_mut(frame); + let gles_frame = BorrowMut::>::borrow_mut(glow_frame); + let transform = gles_frame.transformation(); + let tex_size = self.src.to_i32_round(); + + let texture_ref = cache.get_or_insert_threadsafe(BlurTexture::default); + let mut texture_entry = texture_ref.lock().unwrap(); + if self.dirty.swap(false, Ordering::Acquire) { + texture_entry.take(); + } + + let mut renderer = gles_frame.renderer(); + if texture_entry.is_none() { + *texture_entry = Some( + renderer + .as_mut() + .create_buffer(Fourcc::Abgr8888, tex_size) + .map_err(R::from_gles_error)?, + ); + } + let texture = texture_entry.as_mut().unwrap(); + let mut off_texture = renderer + .as_mut() + .create_buffer(Fourcc::Abgr8888, tex_size) + .map_err(R::from_gles_error)?; + std::mem::drop(renderer); + + let sync = blit_from_active_fb(gles_frame, src, dst, transform, texture) + .map_err(R::from_gles_error)?; + gles_frame.wait(&sync).map_err(R::from_gles_error)?; + + let mut textures = [texture, &mut off_texture]; + render_blur( + gles_frame.renderer().as_mut(), + &self.scaling_shaders, + &mut textures, + self.offset, + self.passes, + ) + .map_err(R::from_gles_error)?; + + Ok(()) + } + + fn draw( + &self, + frame: &mut R::Frame<'_, '_>, + src: Rectangle, + dst: Rectangle, + damage: &[Rectangle], + opaque_regions: &[Rectangle], + cache: Option<&UserDataMap>, + ) -> Result<(), R::Error> { + let glow_frame = ::glow_frame_mut(frame); + let damage = self + .region + .iter() + .flat_map(|rect| damage.iter().flat_map(|r| r.intersection(*rect))) + .collect::>(); + let cache = cache.expect("Framebuffer element without cache?"); + let Some(texture) = cache.get::() else { + return Err(R::from_gles_error(GlesError::BlitError)); + }; + let texture_ref = texture.lock().unwrap(); + + BorrowMut::>::borrow_mut(glow_frame) + .render_texture_from_to( + texture_ref + .as_ref() + .ok_or(R::from_gles_error(GlesError::BlitError))?, + src, + dst, + &damage, + opaque_regions, + Transform::Normal, + 1.0, + Some(&self.render_shader), + &self.uniforms, + ) + .map_err(R::from_gles_error) + } +} + +fn blit_from_active_fb( + frame: &mut GlesFrame<'_, '_>, + src: Rectangle, + dst: Rectangle, + transform: Transform, + to_texture: &mut GlesTexture, +) -> Result { + if transform != Transform::Normal { + let tex_size = to_texture + .size() + .to_logical(1, Transform::Normal) + .to_physical(1); + let mut renderer = frame.renderer(); + let mut tmp_texture = renderer.as_mut().create_buffer( + Fourcc::Abgr8888, + dst.size.to_logical(1).to_buffer(1, Transform::Normal), + )?; + let mut fb_tmp = renderer.as_mut().bind(&mut tmp_texture)?; + let mut fb = renderer.as_mut().bind(to_texture)?; + std::mem::drop(renderer); + + frame.blit_to( + &mut fb_tmp, + dst, + Rectangle::from_size(dst.size), + TextureFilter::Linear, + )?; + std::mem::drop(fb_tmp); + + let mut renderer = frame.renderer(); + let mut frame = renderer + .as_mut() + .render(&mut fb, tex_size, Transform::Normal)?; + Frame::render_texture_from_to( + &mut frame, + &tmp_texture, + Rectangle::from_size( + dst.size + .to_logical(1) + .to_buffer(1, Transform::Normal) + .to_f64(), + ), + src.to_logical(1., Transform::Normal, &src.size) + .to_physical(1.) + .to_i32_round(), + &[Rectangle::from_size(dst.size)], + &[Rectangle::from_size(dst.size)], + transform, + 1.0, + )?; + std::mem::drop(tmp_texture); + frame.finish() + } else { + let mut fb = frame.renderer().as_mut().bind(to_texture)?; + + frame + .blit_to( + &mut fb, + dst, + src.to_logical(1., Transform::Normal, &src.size) + .to_physical(1.) + .to_i32_round(), + TextureFilter::Linear, + ) + .map(|_| SyncPoint::signaled()) + } +} + +fn render_blur( + renderer: &mut GlesRenderer, + shaders: &BlurShaders, + textures: &mut [&mut GlesTexture; 2], + offset: f64, + passes: usize, +) -> Result<(), GlesError> { + for i in 0..passes { + let tex_size = textures[0].size(); + let [src_tex, target_tex] = textures; + let mut fb = renderer.bind(*target_tex)?; + + let adjusted_tex_size = tex_size.downscale(1 << i); + let target_tex_size = tex_size + .downscale(1 << i + 1) + .to_logical(1, Transform::Normal) + .to_physical(1); + let half_pixel = [ + 0.5 / (adjusted_tex_size.w as f32), + 0.5 / (adjusted_tex_size.h as f32), + ]; + + let mut frame = renderer.render( + &mut fb, + tex_size.to_logical(1, Transform::Normal).to_physical(1), + Transform::Normal, + )?; + frame.clear( + Color32F::new(0., 0., 0., 0.), + &[Rectangle::from_size( + tex_size.to_logical(1, Transform::Normal).to_physical(1), + )], + )?; + frame.with_context(|gl| unsafe { + gl.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_WRAP_S, + ffi::CLAMP_TO_EDGE as i32, + ); + gl.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_WRAP_T, + ffi::CLAMP_TO_EDGE as i32, + ); + })?; + frame.render_texture_from_to( + src_tex, + Rectangle::from_size(adjusted_tex_size.to_f64()), + Rectangle::from_size(target_tex_size), + &[Rectangle::from_size(target_tex_size)], + &[Rectangle::from_size(target_tex_size)], + Transform::Normal, + 1.0, + Some(&shaders.down), + &[ + Uniform::new("half_pixel", half_pixel), + Uniform::new("offset", (offset / (1 << i) as f64) as f32), + ], + )?; + frame.with_context(|gl| unsafe { + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::REPEAT as i32); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::REPEAT as i32); + })?; + let sync = frame.finish()?; + std::mem::drop(fb); + renderer.wait(&sync)?; + + textures.swap(0, 1); + } + + for i in 0..passes { + let tex_size = textures[0].size(); + let [src_tex, target_tex] = textures; + let mut fb = renderer.bind(*target_tex)?; + + let adjusted_tex_size = tex_size.downscale(1 << (passes - i)); + let target_tex_size = tex_size + .downscale(1 << (passes - i - 1)) + .to_logical(1, Transform::Normal) + .to_physical(1); + let half_pixel = [ + 0.5 / (adjusted_tex_size.w as f32), + 0.5 / (adjusted_tex_size.h as f32), + ]; + + let mut frame = renderer.render( + &mut fb, + tex_size.to_logical(1, Transform::Normal).to_physical(1), + Transform::Normal, + )?; + frame.clear( + Color32F::new(0., 0., 0., 0.), + &[Rectangle::from_size( + tex_size.to_logical(1, Transform::Normal).to_physical(1), + )], + )?; + frame.with_context(|gl| unsafe { + gl.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_WRAP_S, + ffi::CLAMP_TO_EDGE as i32, + ); + gl.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_WRAP_T, + ffi::CLAMP_TO_EDGE as i32, + ); + })?; + frame.render_texture_from_to( + src_tex, + Rectangle::from_size(adjusted_tex_size.to_f64()), + Rectangle::from_size(target_tex_size), + &[Rectangle::from_size(target_tex_size)], + &[Rectangle::from_size(target_tex_size)], + Transform::Normal, + 1.0, + Some(&shaders.up), + &[ + Uniform::new("half_pixel", half_pixel), + Uniform::new("offset", (offset / (1 << (passes - i)) as f64) as f32), + ], + )?; + frame.with_context(|gl| unsafe { + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::REPEAT as i32); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::REPEAT as i32); + })?; + let sync = frame.finish()?; + std::mem::drop(fb); + renderer.wait(&sync)?; + + textures.swap(0, 1); + } + + // textures always end up the right way around with `self.texture` containing our final render, + // since we render PASSES * 2 (downscale and upscale), so the number of swaps is always even. + Ok(()) +} diff --git a/src/backend/render/wayland/clipped_surface.rs b/src/backend/render/wayland/clipped_surface.rs index 37eee7f8..92e64ad0 100644 --- a/src/backend/render/wayland/clipped_surface.rs +++ b/src/backend/render/wayland/clipped_surface.rs @@ -111,6 +111,7 @@ where transpose: false, }, ), + Uniform::new("noise", UniformValue::_1f(0.0)), ]; Self { diff --git a/src/backend/render/wayland/mod.rs b/src/backend/render/wayland/mod.rs index d766552a..b9e07a80 100644 --- a/src/backend/render/wayland/mod.rs +++ b/src/backend/render/wayland/mod.rs @@ -12,13 +12,16 @@ use smithay::{ use tracing::warn; use crate::backend::render::{ - element::AsGlowRenderer, wayland::clipped_surface::ClippedSurfaceRenderElement, + element::AsGlowRenderer, + wayland::{blur_effect::BlurElement, clipped_surface::ClippedSurfaceRenderElement}, }; +pub mod blur_effect; pub mod clipped_surface; render_elements! { pub SurfaceRenderElement where R: AsGlowRenderer + ImportAll; + Blur=BlurElement, Clipped=ClippedSurfaceRenderElement, Wayland=WaylandSurfaceRenderElement, } @@ -67,6 +70,7 @@ pub fn push_render_elements_from_surface_tree( let mut location = *location; let kind = kind.eval(states); let data = states.data_map.get::(); + let mut blur = Ok(None); if let Some(data) = data { let has_view = if let Some(view) = data.lock().unwrap().view() { @@ -82,6 +86,9 @@ pub fn push_render_elements_from_surface_tree( renderer, surface, states, location, alpha, kind, ) { Ok(Some(surface)) => { + blur = BlurElement::from_surface( + renderer, states, geometry, scale.x, radii, + ); let elem: SurfaceRenderElement = if radii.iter().any(|r| *r != 0) && should_clip && ClippedSurfaceRenderElement::will_clip( @@ -113,6 +120,16 @@ pub fn push_render_elements_from_surface_tree( if surface == main_surface { passed_main = true; } + + if let Ok(Some(elem)) = blur { + if let Some(push_below) = push_below.as_mut() + && passed_main + { + push_below(elem.into()); + } else { + push_above(elem.into()); + } + } }, |_, _, _| true, ); diff --git a/src/state.rs b/src/state.rs index 68824ea7..f377010c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -73,6 +73,7 @@ use smithay::{ utils::{Clock, Monotonic, Point}, wayland::{ alpha_modifier::AlphaModifierState, + background_effect::BackgroundEffectState, compositor::{CompositorClientState, CompositorState, SurfaceData}, cursor_shape::CursorShapeManagerState, dmabuf::{DmabufFeedback, DmabufGlobal, DmabufState}, @@ -281,6 +282,7 @@ pub struct Common { pub a11y_state: A11yState, pub dbus_state: DBusState, pub keyboard_layout_state: KeyboardLayoutState, + pub background_effect_state: BackgroundEffectState, // shell-related wayland state pub xdg_shell_state: XdgShellState, @@ -686,6 +688,8 @@ impl State { FixesState::new::(dh); let keyboard_layout_state = KeyboardLayoutState::new::(&dh, client_not_sandboxed); + let background_effect_state = BackgroundEffectState::new::(dh); + let idle_notifier_state = IdleNotifierState::::new(dh, handle.clone()); let idle_inhibit_manager_state = IdleInhibitManagerState::new::(dh); let idle_inhibiting_surfaces = HashSet::new(); @@ -789,6 +793,7 @@ impl State { xdg_activation_state, xdg_foreign_state, workspace_state, + background_effect_state, a11y_state, xwayland_scale: None, xwayland_state: None, diff --git a/src/wayland/handlers/background_effect.rs b/src/wayland/handlers/background_effect.rs new file mode 100644 index 00000000..28682e03 --- /dev/null +++ b/src/wayland/handlers/background_effect.rs @@ -0,0 +1,59 @@ +use smithay::{ + reexports::wayland_server::{DisplayHandle, protocol::wl_surface::WlSurface}, + utils::{Logical, Rectangle}, + wayland::{ + background_effect::{Capability, ExtBackgroundEffectHandler}, + compositor::{Cacheable, RectangleKind, RegionAttributes, with_states}, + }, +}; + +use crate::state::State; + +#[derive(Debug, Clone, Default)] +pub struct ComputedBlurRegionCachedState { + /// Region of the surface that will have its background blurred. + pub blur_region: Option>>, +} + +impl Cacheable for ComputedBlurRegionCachedState { + fn commit(&mut self, _dh: &DisplayHandle) -> Self { + self.clone() + } + + fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) { + *into = self; + } +} + +impl ExtBackgroundEffectHandler for State { + fn capabilities(&self) -> Capability { + Capability::Blur + } + + fn set_blur_region(&mut self, surface: WlSurface, region: RegionAttributes) { + with_states(&surface, |states| { + let mut blur_state = states.cached_state.get::(); + + blur_state.pending().blur_region = Some({ + let (added, subtracted) = region + .rects + .iter() + .cloned() + .partition::, _>(|(op, _)| matches!(op, RectangleKind::Add)); + let added = added.into_iter().map(|(_, rect)| rect).collect::>(); + Rectangle::subtract_rects_many_in_place( + added, + subtracted.into_iter().map(|(_, rect)| rect), + ) + }) + }) + } + + fn unset_blur_region(&mut self, surface: WlSurface) { + with_states(&surface, |states| { + let mut blur_state = states.cached_state.get::(); + + blur_state.pending().blur_region.take(); + }) + } +} diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index c6461a2e..651eadc9 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod a11y; +pub mod background_effect; pub mod buffer; pub mod compositor; pub mod corner_radius; From eb41b076ed4b79811a45b524e66d72f53444d9b3 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 6 Mar 2026 17:19:12 +0100 Subject: [PATCH 24/56] render: Use NamespaceElements to fix blurred lower layer-shell surfaces --- src/backend/render/mod.rs | 27 ++++++++++++++---- src/input/mod.rs | 9 ++++-- src/shell/focus/order.rs | 58 ++++++++++++++++++++++++++++++--------- src/shell/mod.rs | 3 +- src/shell/workspace.rs | 32 ++++++++++++++++++++- src/state.rs | 50 +++++++++++++++++---------------- 6 files changed, 134 insertions(+), 45 deletions(-) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index f737afe1..a22289f4 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -55,7 +55,7 @@ use smithay::{ Color32F, Offscreen, Texture, TextureFilter, damage::{Error as RenderError, OutputDamageTracker, RenderOutputResult}, element::{ - Element, Id, Kind, RenderElement, WeakId, + Element, Id, Kind, NamespacedElement, RenderElement, WeakId, texture::{TextureRenderBuffer, TextureRenderElement}, utils::{ ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, Relocate, @@ -825,7 +825,10 @@ where }) } Stage::LayerPopup { - popup, location, .. + popup, + location, + workspace_idx, + .. } => { let mut geometry = popup.geometry().as_global(); geometry.loc += location; @@ -843,11 +846,20 @@ where false, [0; 4], FRAME_TIME_FILTER, - &mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)), + &mut |elem| { + elements.extend( + crop_to_output(NamespacedElement::new(elem, workspace_idx).into()) + .map(Into::into), + ) + }, None, ) } - Stage::LayerSurface { layer, location } => { + Stage::LayerSurface { + layer, + location, + workspace_idx, + } => { let mut geometry = layer.geometry().as_global(); geometry.loc += location; @@ -864,7 +876,12 @@ where false, [0; 4], FRAME_TIME_FILTER, - &mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)), + &mut |elem| { + elements.extend( + crop_to_output(NamespacedElement::new(elem, workspace_idx).into()) + .map(Into::into), + ) + }, None, ); } diff --git a/src/input/mod.rs b/src/input/mod.rs index da3014ae..69b5064c 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2132,6 +2132,7 @@ impl State { layer, popup, location, + .. } => { if layer.can_receive_keyboard_focus() { let surface = popup.wl_surface(); @@ -2149,7 +2150,9 @@ impl State { } } } - Stage::LayerSurface { layer, location } => { + Stage::LayerSurface { + layer, location, .. + } => { if under_from_surface_tree( layer.wl_surface(), global_pos.as_logical(), @@ -2301,7 +2304,9 @@ impl State { )))); } } - Stage::LayerSurface { layer, location } => { + Stage::LayerSurface { + layer, location, .. + } => { let surface = layer.wl_surface(); if let Some((surface, surface_loc)) = under_from_surface_tree( surface, diff --git a/src/shell/focus/order.rs b/src/shell/focus/order.rs index d02b310e..fb904c01 100644 --- a/src/shell/focus/order.rs +++ b/src/shell/focus/order.rs @@ -32,10 +32,12 @@ pub enum Stage<'a> { layer: LayerSurface, popup: &'a PopupKind, location: Point, + workspace_idx: usize, }, LayerSurface { layer: LayerSurface, location: Point, + workspace_idx: usize, }, OverrideRedirect { surface: &'a X11Surface, @@ -95,10 +97,15 @@ fn render_input_order_internal( layer, popup: &popup, location, + workspace_idx: current.1, })?; } for (layer, location) in layer_surfaces(output, Layer::Overlay, element_filter) { - callback(Stage::LayerSurface { layer, location })?; + callback(Stage::LayerSurface { + layer, + location, + workspace_idx: current.1, + })?; } // calculate a bunch of stuff for workspace transitions @@ -187,7 +194,7 @@ fn render_input_order_internal( }); ( - Some((previous, has_fullscreen, offset)), + Some((previous, previous_idx, has_fullscreen, offset)), Point::::from(match (layout, forward) { (WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y), (WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)), @@ -206,6 +213,7 @@ fn render_input_order_internal( layer, popup: &popup, location, + workspace_idx: current.1, })?; } } @@ -236,7 +244,7 @@ fn render_input_order_internal( if element_filter != ElementFilter::LayerShellOnly { // previous workspace popups - if let Some((previous_handle, _, offset)) = previous.as_ref() { + if let Some((previous_handle, _, _, offset)) = previous.as_ref() { let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else { return ControlFlow::Break(Err(OutputNoMode)); }; @@ -265,11 +273,12 @@ fn render_input_order_internal( layer, popup: &popup, location, + workspace_idx: current.1, })?; } } - if let Some((_, has_fullscreen, offset)) = previous.as_ref() + if let Some((_, idx, has_fullscreen, offset)) = previous.as_ref() && !has_fullscreen { // previous bottom layer popups @@ -278,6 +287,7 @@ fn render_input_order_internal( layer, popup: &popup, location: location + offset.as_global(), + workspace_idx: **idx, })?; } } @@ -289,11 +299,12 @@ fn render_input_order_internal( layer, popup: &popup, location, + workspace_idx: current.1, })?; } } - if let Some((_, has_fullscreen, offset)) = previous.as_ref() + if let Some((_, idx, has_fullscreen, offset)) = previous.as_ref() && !has_fullscreen { // previous background layer popups @@ -302,6 +313,7 @@ fn render_input_order_internal( layer, popup: &popup, location: location + offset.as_global(), + workspace_idx: **idx, })?; } } @@ -309,7 +321,11 @@ fn render_input_order_internal( if !has_focused_fullscreen { // top-layer shell for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) { - callback(Stage::LayerSurface { layer, location })?; + callback(Stage::LayerSurface { + layer, + location, + workspace_idx: current.1, + })?; } } @@ -326,7 +342,7 @@ fn render_input_order_internal( })?; // previous workspace windows - if let Some((previous_handle, _, offset)) = previous.as_ref() { + if let Some((previous_handle, _, _, offset)) = previous.as_ref() { let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else { return ControlFlow::Break(Err(OutputNoMode)); }; @@ -341,17 +357,25 @@ fn render_input_order_internal( // bottom layer for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { location += current_offset.as_global(); - callback(Stage::LayerSurface { layer, location })?; + callback(Stage::LayerSurface { + layer, + location, + workspace_idx: current.1, + })?; } } - if let Some((_, has_fullscreen, offset)) = previous.as_ref() + if let Some((_, idx, has_fullscreen, offset)) = previous.as_ref() && !has_fullscreen { // previous bottom layer for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { location += offset.as_global(); - callback(Stage::LayerSurface { layer, location })?; + callback(Stage::LayerSurface { + layer, + location, + workspace_idx: **idx, + })?; } } @@ -359,17 +383,25 @@ fn render_input_order_internal( // background layer for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) { location += current_offset.as_global(); - callback(Stage::LayerSurface { layer, location })?; + callback(Stage::LayerSurface { + layer, + location, + workspace_idx: current.1, + })?; } } - if let Some((_, has_fullscreen, offset)) = previous.as_ref() + if let Some((_, idx, has_fullscreen, offset)) = previous.as_ref() && !has_fullscreen { // previous background layer for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) { location += offset.as_global(); - callback(Stage::LayerSurface { layer, location })?; + callback(Stage::LayerSurface { + layer, + location, + workspace_idx: **idx, + })?; } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 60b3d1e1..b4559dce 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -5038,13 +5038,14 @@ impl Shell { let map = smithay::desktop::layer_map_for_output(output); for layer_surface in map.layers() { + let namespace = self.workspaces.active_num(output).1; layer_surface.take_presentation_feedback( &mut output_presentation_feedback, surface_primary_scanout_output, |surface, _| { surface_presentation_feedback_flags_from_states( surface, - None, + Some(namespace), render_element_states, ) }, diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 6407935d..b8b104c5 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -31,7 +31,7 @@ use indexmap::IndexSet; use keyframe::{ease, functions::EaseInOutCubic}; use smallvec::SmallVec; use smithay::backend::drm::DrmNode; -use smithay::backend::renderer::element::Kind; +use smithay::backend::renderer::element::{Kind, NamespacedElement}; use smithay::output::WeakOutput; use smithay::utils::user_data::UserDataMap; use smithay::{ @@ -1974,6 +1974,7 @@ where R::TextureId: 'static, { OverrideRedirect(SurfaceRenderElement), + LowerLayerShell(NamespacedElement>), Fullscreen(RescaleRenderElement>), FullscreenPopup(CosmicWindowRenderElement), Window(CosmicMappedRenderElement), @@ -1988,6 +1989,7 @@ where fn id(&self) -> &smithay::backend::renderer::element::Id { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.id(), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.id(), WorkspaceRenderElement::Fullscreen(elem) => elem.id(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.id(), WorkspaceRenderElement::Window(elem) => elem.id(), @@ -1998,6 +2000,7 @@ where fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.current_commit(), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.current_commit(), WorkspaceRenderElement::Fullscreen(elem) => elem.current_commit(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.current_commit(), WorkspaceRenderElement::Window(elem) => elem.current_commit(), @@ -2008,6 +2011,7 @@ where fn src(&self) -> Rectangle { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.src(), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.src(), WorkspaceRenderElement::Fullscreen(elem) => elem.src(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.src(), WorkspaceRenderElement::Window(elem) => elem.src(), @@ -2018,6 +2022,7 @@ where fn geometry(&self, scale: Scale) -> Rectangle { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.geometry(scale), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.geometry(scale), WorkspaceRenderElement::Fullscreen(elem) => elem.geometry(scale), WorkspaceRenderElement::FullscreenPopup(elem) => elem.geometry(scale), WorkspaceRenderElement::Window(elem) => elem.geometry(scale), @@ -2028,6 +2033,7 @@ where fn location(&self, scale: Scale) -> Point { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.location(scale), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.location(scale), WorkspaceRenderElement::Fullscreen(elem) => elem.location(scale), WorkspaceRenderElement::FullscreenPopup(elem) => elem.location(scale), WorkspaceRenderElement::Window(elem) => elem.location(scale), @@ -2038,6 +2044,7 @@ where fn transform(&self) -> smithay::utils::Transform { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.transform(), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.transform(), WorkspaceRenderElement::Fullscreen(elem) => elem.transform(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.transform(), WorkspaceRenderElement::Window(elem) => elem.transform(), @@ -2052,6 +2059,7 @@ where ) -> DamageSet { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.damage_since(scale, commit), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.damage_since(scale, commit), WorkspaceRenderElement::Fullscreen(elem) => elem.damage_since(scale, commit), WorkspaceRenderElement::FullscreenPopup(elem) => elem.damage_since(scale, commit), WorkspaceRenderElement::Window(elem) => elem.damage_since(scale, commit), @@ -2062,6 +2070,7 @@ where fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.opaque_regions(scale), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.opaque_regions(scale), WorkspaceRenderElement::Fullscreen(elem) => elem.opaque_regions(scale), WorkspaceRenderElement::FullscreenPopup(elem) => elem.opaque_regions(scale), WorkspaceRenderElement::Window(elem) => elem.opaque_regions(scale), @@ -2072,6 +2081,7 @@ where fn alpha(&self) -> f32 { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.alpha(), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.alpha(), WorkspaceRenderElement::Fullscreen(elem) => elem.alpha(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.alpha(), WorkspaceRenderElement::Window(elem) => elem.alpha(), @@ -2082,6 +2092,7 @@ where fn kind(&self) -> Kind { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.kind(), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.kind(), WorkspaceRenderElement::Fullscreen(elem) => elem.kind(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.kind(), WorkspaceRenderElement::Window(elem) => elem.kind(), @@ -2092,6 +2103,7 @@ where fn is_framebuffer_effect(&self) -> bool { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.is_framebuffer_effect(), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.is_framebuffer_effect(), WorkspaceRenderElement::Fullscreen(elem) => elem.is_framebuffer_effect(), WorkspaceRenderElement::FullscreenPopup(elem) => elem.is_framebuffer_effect(), WorkspaceRenderElement::Window(elem) => elem.is_framebuffer_effect(), @@ -2118,6 +2130,9 @@ where WorkspaceRenderElement::OverrideRedirect(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } + WorkspaceRenderElement::LowerLayerShell(elem) => { + elem.draw(frame, src, dst, damage, opaque_regions, cache) + } WorkspaceRenderElement::Fullscreen(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } @@ -2146,6 +2161,7 @@ where ) -> Option> { match self { WorkspaceRenderElement::OverrideRedirect(elem) => elem.underlying_storage(renderer), + WorkspaceRenderElement::LowerLayerShell(elem) => elem.underlying_storage(renderer), WorkspaceRenderElement::Fullscreen(elem) => elem.underlying_storage(renderer), WorkspaceRenderElement::FullscreenPopup(elem) => elem.underlying_storage(renderer), WorkspaceRenderElement::Window(elem) => elem.underlying_storage(renderer), @@ -2166,6 +2182,9 @@ where WorkspaceRenderElement::OverrideRedirect(elem) => { elem.capture_framebuffer(frame, src, dst, cache) } + WorkspaceRenderElement::LowerLayerShell(elem) => { + elem.capture_framebuffer(frame, src, dst, cache) + } WorkspaceRenderElement::Fullscreen(elem) => { elem.capture_framebuffer(frame, src, dst, cache) } @@ -2222,6 +2241,17 @@ where } } +impl From>> for WorkspaceRenderElement +where + R: AsGlowRenderer, + R::TextureId: 'static, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: NamespacedElement>) -> Self { + WorkspaceRenderElement::LowerLayerShell(elem) + } +} + impl From> for WorkspaceRenderElement where R: AsGlowRenderer, diff --git a/src/state.rs b/src/state.rs index f377010c..cf66aeaa 100644 --- a/src/state.rs +++ b/src/state.rs @@ -939,19 +939,22 @@ impl Common { render_element_states: &RenderElementStates, ) { let shell = self.shell.read(); - let processor = |surface: &WlSurface, states: &SurfaceData| { - let primary_scanout_output = update_surface_primary_scanout_output( - surface, - output, - states, - None, - render_element_states, - primary_scanout_output_compare, - ); - if let Some(output) = primary_scanout_output { - with_fractional_scale(states, |fraction_scale| { - fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); - }); + let processor = |namespace: Option| { + move |surface: &WlSurface, states: &SurfaceData| { + let primary_scanout_output = update_surface_primary_scanout_output( + surface, + output, + states, + namespace, + render_element_states, + primary_scanout_output_compare, + ); + if let Some(output) = primary_scanout_output { + with_fractional_scale(states, |fraction_scale| { + fraction_scale + .set_preferred_scale(output.current_scale().fractional_scale()); + }); + } } }; @@ -959,7 +962,7 @@ impl Common { if let Some(session_lock) = shell.session_lock.as_ref() && let Some(lock_surface) = session_lock.surfaces.get(output) { - with_surfaces_surface_tree(lock_surface.wl_surface(), processor) + with_surfaces_surface_tree(lock_surface.wl_surface(), processor(None)) } for seat in shell @@ -971,7 +974,7 @@ impl Common { // cursor ... if let CursorImageStatus::Surface(wl_surface) = cursor_status { - with_surfaces_surface_tree(&wl_surface, processor); + with_surfaces_surface_tree(&wl_surface, processor(None)); } // grabs @@ -979,12 +982,12 @@ impl Common { && let Some(grab_state) = move_grab.lock().unwrap().as_ref() { for (window, _) in grab_state.element().windows() { - window.with_surfaces(processor); + window.with_surfaces(processor(None)); } } if let Some(icon) = get_dnd_icon(seat) { - with_surfaces_surface_tree(&icon.surface, processor); + with_surfaces_surface_tree(&icon.surface, processor(None)); } } @@ -992,7 +995,7 @@ impl Common { for set in shell.workspaces.sets.values() { set.sticky_layer.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { - window.with_surfaces(processor); + window.with_surfaces(processor(None)); } }); } @@ -1000,16 +1003,16 @@ impl Common { // normal windows for space in shell.workspaces.spaces() { if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) { - fs.surface.with_surfaces(processor); + fs.surface.with_surfaces(processor(None)); } space.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { - window.with_surfaces(processor); + window.with_surfaces(processor(None)); } }); space.minimized_windows.iter().for_each(|m| { for window in m.windows() { - window.with_surfaces(processor); + window.with_surfaces(processor(None)); } }) } @@ -1017,15 +1020,16 @@ impl Common { // OR windows shell.override_redirect_windows.iter().for_each(|or| { if let Some(wl_surface) = or.wl_surface() { - with_surfaces_surface_tree(&wl_surface, processor); + with_surfaces_surface_tree(&wl_surface, processor(None)); } }); // layer surfaces for o in shell.outputs() { + let namespace = shell.workspaces.active_num(o).1; let map = smithay::desktop::layer_map_for_output(o); for layer_surface in map.layers() { - layer_surface.with_surfaces(processor); + layer_surface.with_surfaces(processor(Some(namespace))); } } } From 1c5ef576f413631a3ec5fc7b195c92624d7db69b Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 10 Mar 2026 17:10:38 +0100 Subject: [PATCH 25/56] render: Don't use separate `damage_output` calls --- src/backend/render/element.rs | 30 +++++++++++++++++++ .../handlers/image_copy_capture/render.rs | 13 ++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index 34a351c9..103fa52f 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -43,6 +43,7 @@ where CropRenderElement>>, ), Zoom(MemoryRenderBufferRenderElement), + Damage(DamageElement), #[cfg(feature = "debug")] Egui(TextureRenderElement), } @@ -61,6 +62,7 @@ where CosmicElement::MoveGrab(elem) => elem.id(), CosmicElement::Postprocess(elem) => elem.id(), CosmicElement::Zoom(elem) => elem.id(), + CosmicElement::Damage(elem) => elem.id(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.id(), } @@ -74,6 +76,7 @@ where CosmicElement::MoveGrab(elem) => elem.current_commit(), CosmicElement::Postprocess(elem) => elem.current_commit(), CosmicElement::Zoom(elem) => elem.current_commit(), + CosmicElement::Damage(elem) => elem.current_commit(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.current_commit(), } @@ -87,6 +90,7 @@ where CosmicElement::MoveGrab(elem) => elem.src(), CosmicElement::Postprocess(elem) => elem.src(), CosmicElement::Zoom(elem) => elem.src(), + CosmicElement::Damage(elem) => elem.src(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.src(), } @@ -100,6 +104,7 @@ where CosmicElement::MoveGrab(elem) => elem.geometry(scale), CosmicElement::Postprocess(elem) => elem.geometry(scale), CosmicElement::Zoom(elem) => elem.geometry(scale), + CosmicElement::Damage(elem) => elem.geometry(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.geometry(scale), } @@ -113,6 +118,7 @@ where CosmicElement::MoveGrab(elem) => elem.location(scale), CosmicElement::Postprocess(elem) => elem.location(scale), CosmicElement::Zoom(elem) => elem.location(scale), + CosmicElement::Damage(elem) => elem.location(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.location(scale), } @@ -126,6 +132,7 @@ where CosmicElement::MoveGrab(elem) => elem.transform(), CosmicElement::Postprocess(elem) => elem.transform(), CosmicElement::Zoom(elem) => elem.transform(), + CosmicElement::Damage(elem) => elem.transform(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.transform(), } @@ -143,6 +150,7 @@ where CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit), CosmicElement::Postprocess(elem) => elem.damage_since(scale, commit), CosmicElement::Zoom(elem) => elem.damage_since(scale, commit), + CosmicElement::Damage(elem) => elem.damage_since(scale, commit), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.damage_since(scale, commit), } @@ -156,6 +164,7 @@ where CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale), CosmicElement::Postprocess(elem) => elem.opaque_regions(scale), CosmicElement::Zoom(elem) => elem.opaque_regions(scale), + CosmicElement::Damage(elem) => elem.opaque_regions(scale), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.opaque_regions(scale), } @@ -169,6 +178,7 @@ where CosmicElement::MoveGrab(elem) => elem.alpha(), CosmicElement::Postprocess(elem) => elem.alpha(), CosmicElement::Zoom(elem) => elem.alpha(), + CosmicElement::Damage(elem) => elem.alpha(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.alpha(), } @@ -182,6 +192,7 @@ where CosmicElement::MoveGrab(elem) => elem.kind(), CosmicElement::Postprocess(elem) => elem.kind(), CosmicElement::Zoom(elem) => elem.kind(), + CosmicElement::Damage(elem) => elem.kind(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.kind(), } @@ -195,6 +206,7 @@ where CosmicElement::MoveGrab(elem) => elem.is_framebuffer_effect(), CosmicElement::Postprocess(elem) => elem.is_framebuffer_effect(), CosmicElement::Zoom(elem) => elem.is_framebuffer_effect(), + CosmicElement::Damage(elem) => elem.is_framebuffer_effect(), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => elem.is_framebuffer_effect(), } @@ -241,6 +253,9 @@ where .map_err(R::from_gles_error) } CosmicElement::Zoom(elem) => elem.draw(frame, src, dst, damage, opaque_regions, cache), + CosmicElement::Damage(elem) => { + RenderElement::::draw(elem, frame, src, dst, damage, opaque_regions, cache) + } #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { let glow_frame = R::glow_frame_mut(frame); @@ -269,6 +284,7 @@ where elem.underlying_storage(glow_renderer) } CosmicElement::Zoom(elem) => elem.underlying_storage(renderer), + CosmicElement::Damage(elem) => elem.underlying_storage(renderer), #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { let glow_renderer = renderer.glow_renderer_mut(); @@ -297,6 +313,9 @@ where .map_err(R::from_gles_error) } CosmicElement::Zoom(elem) => elem.capture_framebuffer(frame, src, dst, cache), + CosmicElement::Damage(elem) => { + RenderElement::::capture_framebuffer(elem, frame, src, dst, cache) + } #[cfg(feature = "debug")] CosmicElement::Egui(elem) => { let glow_frame = R::glow_frame_mut(frame); @@ -336,6 +355,17 @@ where } } +impl From for CosmicElement +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + R::TextureId: 'static, + CosmicMappedRenderElement: RenderElement, +{ + fn from(value: DamageElement) -> Self { + Self::Damage(value) + } +} + #[cfg(feature = "debug")] impl From> for CosmicElement where diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index 85759165..4b249a36 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only use calloop::LoopHandle; -use smallvec::SmallVec; use smithay::{ backend::{ allocator::{Buffer, Fourcc, format::get_transparent}, @@ -506,6 +505,7 @@ smithay::render_elements! { pub WindowCaptureElement where R: ImportAll + ImportMem + AsGlowRenderer; WaylandElement=SurfaceRenderElement, CursorElement=RelocateRenderElement>, + DamageElement=DamageElement, } pub fn render_window_to_buffer( @@ -556,7 +556,7 @@ pub fn render_window_to_buffer( CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, { - let additional_damage_elements: Vec<_> = additional_damage + let mut elements: Vec<_> = additional_damage .into_iter() .filter_map(|rect| { let logical_rect = rect.to_logical( @@ -567,8 +567,8 @@ pub fn render_window_to_buffer( logical_rect.intersection(Rectangle::from_size(geometry.size)) }) .map(DamageElement::new) + .map(WindowCaptureElement::::from) .collect(); - dt.damage_output(age, &additional_damage_elements)?; let shell = common.shell.read(); let seat = shell.seats.last_active().clone(); @@ -589,8 +589,6 @@ pub fn render_window_to_buffer( }; std::mem::drop(shell); - let mut elements = Vec::new(); - if let Some(location) = location { if draw_cursor { cursor::draw_cursor( @@ -802,17 +800,16 @@ pub fn render_cursor_to_buffer( CosmicElement: RenderElement, CosmicMappedRenderElement: RenderElement, { - let additional_damage_elements: Vec<_> = additional_damage + let mut 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) + .map(WindowCaptureElement::from) .collect(); - dt.damage_output(age, &additional_damage_elements)?; - let mut elements = SmallVec::<[WindowCaptureElement; 4]>::new_const(); cursor::draw_cursor( renderer, seat, From de8b47839c8b6e7cb6b547e96ac15b821e4da9fc Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 10 Mar 2026 17:16:48 +0100 Subject: [PATCH 26/56] render: Make blur_strength configurable --- src/backend/render/cursor.rs | 7 ++- src/backend/render/mod.rs | 35 +++++++---- src/backend/render/wayland/blur_effect.rs | 61 +++++++++++++++++-- src/backend/render/wayland/mod.rs | 8 ++- src/shell/element/stack.rs | 2 + src/shell/element/surface.rs | 5 ++ src/shell/element/window.rs | 2 + src/shell/layout/tiling/mod.rs | 2 + src/shell/workspace.rs | 2 + src/utils/screenshot.rs | 1 + .../handlers/image_copy_capture/render.rs | 21 ++++--- 11 files changed, 119 insertions(+), 27 deletions(-) diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index e43d99e5..360991de 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -185,6 +185,7 @@ pub fn draw_surface_cursor( surface: &wl_surface::WlSurface, location: Point, scale: impl Into>, + blur_strength: usize, push: &mut dyn FnMut(CursorRenderElement, Point), ) where R: Renderer + ImportAll + AsGlowRenderer, @@ -211,6 +212,7 @@ pub fn draw_surface_cursor( 1.0, false, [0; 4], + blur_strength, Kind::Cursor, &mut |elem| push(elem.into(), h), None, @@ -223,6 +225,7 @@ pub fn draw_dnd_icon( surface: &wl_surface::WlSurface, location: Point, scale: impl Into>, + blur_strength: usize, push: &mut dyn FnMut(SurfaceRenderElement), ) where R: Renderer + ImportAll + AsGlowRenderer, @@ -244,6 +247,7 @@ pub fn draw_dnd_icon( 1.0, false, [0; 4], + blur_strength, FRAME_TIME_FILTER, push, None, @@ -330,6 +334,7 @@ pub fn draw_cursor( scale: Scale, buffer_scale: f64, time: Time, + blur_strength: usize, draw_default: bool, push: &mut dyn FnMut(CursorRenderElement, Point), ) where @@ -406,7 +411,7 @@ pub fn draw_cursor( hotspot.to_physical_precise_round(scale), ); } else if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { - draw_surface_cursor(renderer, wl_surface, location, scale, push); + draw_surface_cursor(renderer, wl_surface, location, scale, blur_strength, push); } } diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index a22289f4..7c1c1404 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -478,6 +478,7 @@ pub fn cursor_elements<'a, 'frame, R>( seats: impl Iterator>, zoom_state: Option<&ZoomState>, theme: &Theme, + blur_strength: usize, now: Time, output: &Output, mode: CursorMode, @@ -514,6 +515,7 @@ pub fn cursor_elements<'a, 'frame, R>( scale.into(), zoom_scale, now, + blur_strength, mode != CursorMode::NotDefault, &mut |elem, hotspot| { push(CosmicElement::Cursor(RescaleRenderElement::from_element( @@ -538,6 +540,7 @@ pub fn cursor_elements<'a, 'frame, R>( &dnd_icon.surface, (location + dnd_icon.offset.to_f64()).to_i32_round(), scale, + blur_strength, &mut |elem| push(CosmicElement::Dnd(elem)), ); } @@ -723,6 +726,7 @@ where return Ok(Vec::new()); } let theme = shell_ref.theme().clone(); + let blur_strength = 9; // TODO let scale = output.current_scale().fractional_scale(); // we don't want to hold a shell lock across `cursor_elements`, // that is prone to deadlock with the main-thread on some grabs. @@ -733,6 +737,7 @@ where seats.iter(), zoom_level, &theme, + blur_strength, now, output, cursor_mode, @@ -845,6 +850,7 @@ where 1.0, false, [0; 4], + blur_strength, FRAME_TIME_FILTER, &mut |elem| { elements.extend( @@ -875,6 +881,7 @@ where 1.0, false, [0; 4], + blur_strength, FRAME_TIME_FILTER, &mut |elem| { elements.extend( @@ -902,6 +909,7 @@ where 1.0, false, [0; 4], + blur_strength, FRAME_TIME_FILTER, &mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)), None, @@ -1048,6 +1056,7 @@ fn session_lock_elements( 1.0, false, [0; 4], + 0, FRAME_TIME_FILTER, push, None, @@ -1507,7 +1516,19 @@ where CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { - let elements: Vec> = workspace_elements( + let mut elements: Vec> = if let Some(additional_damage) = additional_damage { + let output_geo = output.geometry().to_local(output).as_logical(); + additional_damage + .into_iter() + .filter_map(|rect| rect.intersection(output_geo)) + .map(DamageElement::new) + .map(CosmicElement::from) + .collect() + } else { + Vec::new() + }; + + elements.extend(workspace_elements( gpu, renderer, shell, @@ -1519,17 +1540,7 @@ where cursor_mode, element_filter, None, - )?; - - if let Some(additional_damage) = additional_damage { - let output_geo = output.geometry().to_local(output).as_logical(); - 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( renderer, diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index 895cd19e..2a0be977 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -1,7 +1,7 @@ use std::{ borrow::{Borrow, BorrowMut}, sync::{ - Arc, Mutex, + Arc, LazyLock, Mutex, atomic::{AtomicBool, Ordering}, }, }; @@ -27,6 +27,7 @@ use smithay::{ }, wayland::compositor::SurfaceData, }; +use tracing::info; use crate::{ backend::render::{element::AsGlowRenderer, wayland::clipped_surface::ClippingShader}, @@ -36,9 +37,50 @@ use crate::{ pub static BLUR_DOWNSAMPLE_SHADER: &str = include_str!("../shaders/blur_downsample.frag"); pub static BLUR_UPSAMPLE_SHADER: &str = include_str!("../shaders/blur_upsample.frag"); -const PASSES: usize = 3; -const OFFSET: f64 = 1.1; const NOISE: f32 = 0.03; +const MAX_STEPS: usize = 15; + +#[derive(Debug, Clone, Copy, PartialEq)] +struct BlurParameters { + passes: usize, + offset: f64, + extended_radius: i32, +} + +static BLUR_PARAMS: LazyLock> = LazyLock::new(|| { + let mut params = Vec::new(); + + let mut remaining_steps = MAX_STEPS as isize; + let offsets = [ + // min offset, max offset, extended radius to avoid artifacts + (1.0, 2.0, 10), + (2.0, 3.0, 20), + (2.0, 5.0, 50), + (3.0, 8.0, 150), + ]; + + let sum = offsets.iter().map(|(min, max, _)| *max - *min).sum::(); + for (i, (min, max, extended_radius)) in offsets.into_iter().enumerate() { + let mut iter_num = f64::ceil((max - min) / sum * (MAX_STEPS as f64)) as usize; + remaining_steps -= iter_num as isize; + + if remaining_steps < 0 { + iter_num = iter_num.saturating_add_signed(remaining_steps); + } + + let diff = max - min; + for j in 1..=iter_num { + params.push(BlurParameters { + passes: i + 1, + offset: min + (diff / iter_num as f64) * j as f64, + extended_radius, + }); + } + } + + info!("Computed blur values: {:#?}", ¶ms); + params +}); #[derive(Debug, Clone)] pub struct BlurShaders { @@ -125,6 +167,7 @@ impl BlurElement { geometry: Rectangle, output_scale: f64, radii: [u8; 4], + strength: usize, ) -> Result, R::Error> { let mut blur_region_state = states.cached_state.get::(); let Some(region) = blur_region_state.current().blur_region.as_ref() else { @@ -133,7 +176,7 @@ impl BlurElement { let geo = geometry.to_physical_precise_round(output_scale); let mut extended_geo = geo; - let radius = OFFSET * 2.0f64.powf(PASSES as f64); + let radius = BLUR_PARAMS[strength.min(MAX_STEPS - 1)].extended_radius as f64; extended_geo.loc -= Point::::new(radius, radius); extended_geo.size += Size::::new(radius, radius).upscale(2.); @@ -187,6 +230,7 @@ impl BlurElement { output_scale, &mut *state.lock().unwrap(), uniforms, + strength, )?)) } @@ -197,19 +241,24 @@ impl BlurElement { output_scale: f64, state: &mut BlurState, uniforms: Vec>, + strength: usize, ) -> Result { let renderer_id = renderer.glow_renderer().context_id(); let src = geometry.size.to_buffer(output_scale, Transform::Normal); + let params = &BLUR_PARAMS[strength.min(MAX_STEPS - 1)]; let dirty = !(state .renderer_id .as_ref() .is_some_and(|id| id == &renderer_id) + && state.offset == params.offset + && state.passes == params.passes && &state.region == region && state.src == src); + state.renderer_id = Some(renderer_id); - state.offset = OFFSET * output_scale; // we want the blur result to be stable across scaling; - state.passes = PASSES; + state.offset = params.offset; + state.passes = params.passes; state.region = region.clone(); state.src = src; if dirty { diff --git a/src/backend/render/wayland/mod.rs b/src/backend/render/wayland/mod.rs index b9e07a80..9ac1109c 100644 --- a/src/backend/render/wayland/mod.rs +++ b/src/backend/render/wayland/mod.rs @@ -35,6 +35,7 @@ pub fn push_render_elements_from_surface_tree( alpha: f32, should_clip: bool, radii: [u8; 4], + blur_strength: usize, kind: impl Into, push_above: &mut dyn FnMut(SurfaceRenderElement), mut push_below: Option<&mut dyn FnMut(SurfaceRenderElement)>, @@ -87,7 +88,12 @@ pub fn push_render_elements_from_surface_tree( ) { Ok(Some(surface)) => { blur = BlurElement::from_surface( - renderer, states, geometry, scale.x, radii, + renderer, + states, + geometry, + scale.x, + radii, + blur_strength, ); let elem: SurfaceRenderElement = if radii.iter().any(|r| *r != 0) && should_clip diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 58e28753..0980facb 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -668,6 +668,7 @@ impl CosmicStack { scale, alpha, scanout_node, + 9, // TODO &mut |elem| push(elem.into()), ) }) @@ -825,6 +826,7 @@ impl CosmicStack { scanout_node, radii.is_some(), radii.unwrap_or([0; 4]), + 9, // as TODO &mut |elem| push_above(elem.into()), Some(&mut |elem| push_below(elem.into())), ); diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 53c3d4bd..4a88d2df 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -866,6 +866,7 @@ impl CosmicSurface { scale: Scale, alpha: f32, scanout_node: Option, + blur_strength: usize, push: &mut dyn FnMut(SurfaceRenderElement), ) where R: Renderer + ImportAll + AsGlowRenderer, @@ -889,6 +890,7 @@ impl CosmicSurface { alpha, false, [0; 4], + blur_strength, scanout_kind_eval(None, scanout_node), push, None, @@ -909,6 +911,7 @@ impl CosmicSurface { scanout_node: Option, should_clip: bool, radii: [u8; 4], + blur_strength: usize, push_above: &mut dyn FnMut(SurfaceRenderElement), push_below: Option<&mut dyn FnMut(SurfaceRenderElement)>, ) where @@ -931,6 +934,7 @@ impl CosmicSurface { alpha, should_clip, radii, + blur_strength, scanout_kind_eval(scanout_override, scanout_node), push_above, push_below, @@ -950,6 +954,7 @@ impl CosmicSurface { alpha, should_clip, radii, + blur_strength, scanout_kind_eval(scanout_override, scanout_node), push_above, push_below, diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 48949127..de949b8e 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -375,6 +375,7 @@ impl CosmicWindow { scale, alpha, scanout_node, + 9, // TODO &mut |elem| push(elem.into()), ) }) @@ -556,6 +557,7 @@ impl CosmicWindow { scanout_node, clip, radii, + 9, // TODO &mut |elem| push_above(elem.into()), Some(&mut |elem| push_below(elem.into())), ) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index fbcec105..3a3633db 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -5408,6 +5408,8 @@ fn render_new_tree_windows( scanout_node, false, [0, 0, 0, 0], + // TODO + 0, &mut |elem| { swap_elements.push(CosmicMappedRenderElement::GrabbedWindow( RescaleRenderElement::from_element( diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index b8b104c5..dc5d9f63 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1717,6 +1717,7 @@ impl Workspace { scanout_node, false, [0, 0, 0, 0], + 0, &mut fullscreen_push, None, ); @@ -1905,6 +1906,7 @@ impl Workspace { output_scale.into(), alpha, scanout_node, + 0, &mut |elem| push(WorkspaceRenderElement::FullscreenPopup(elem.into())), ); } diff --git a/src/utils/screenshot.rs b/src/utils/screenshot.rs index eb7730c7..de544b72 100644 --- a/src/utils/screenshot.rs +++ b/src/utils/screenshot.rs @@ -37,6 +37,7 @@ pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) { None, false, [0; 4], + 0, &mut |elem| elements.push(elem), None, ); diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index 4b249a36..9018f8ce 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -571,6 +571,7 @@ pub fn render_window_to_buffer( .collect(); let shell = common.shell.read(); + let blur_strength = 9; // TODO let seat = shell.seats.last_active().clone(); let pointer = seat.get_pointer().unwrap(); let pointer_loc = pointer.current_location().to_i32_round().as_global(); @@ -598,6 +599,7 @@ pub fn render_window_to_buffer( 1.0.into(), 1.0, common.clock.now(), + blur_strength, true, &mut |elem, hotspot| { elements.push(WindowCaptureElement::CursorElement( @@ -619,15 +621,18 @@ pub fn render_window_to_buffer( &dnd_icon.surface, (location + dnd_icon.offset.to_f64()).to_i32_round(), 1.0, + blur_strength, &mut |elem| { - elements.push( - RelocateRenderElement::from_element( - CursorRenderElement::Surface(elem), - Point::new(0, 0), - Relocate::Relative, + elements + .push( + RelocateRenderElement::from_element( + CursorRenderElement::Surface(elem), + Point::new(0, 0), + Relocate::Relative, + ) + .into(), ) - .into(), - ) + .into() }, ); } @@ -642,6 +647,7 @@ pub fn render_window_to_buffer( None, false, [0; 4], + blur_strength, &mut |elem| elements.push(elem.into()), None, ); @@ -817,6 +823,7 @@ pub fn render_cursor_to_buffer( 1.0.into(), 1.0, common.clock.now(), + 0, true, &mut |elem, _| { elements.push( From cfed174e9bc86533dc26c167af030ccbd9e9398e Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 30 Mar 2026 14:17:22 +0200 Subject: [PATCH 27/56] chore: Fix clippy lints --- src/backend/render/wayland/blur_effect.rs | 6 +++--- .../handlers/image_copy_capture/render.rs | 16 +++++++--------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index 2a0be977..a2937b7b 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -226,9 +226,9 @@ impl BlurElement { Ok(Some(Self::from_state( renderer, extended_geo.to_logical(output_scale), - ®ion, + region, output_scale, - &mut *state.lock().unwrap(), + &mut state.lock().unwrap(), uniforms, strength, )?)) @@ -502,7 +502,7 @@ fn render_blur( let adjusted_tex_size = tex_size.downscale(1 << i); let target_tex_size = tex_size - .downscale(1 << i + 1) + .downscale(1 << (i + 1)) .to_logical(1, Transform::Normal) .to_physical(1); let half_pixel = [ diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index 9018f8ce..0b77cbf5 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -623,16 +623,14 @@ pub fn render_window_to_buffer( 1.0, blur_strength, &mut |elem| { - elements - .push( - RelocateRenderElement::from_element( - CursorRenderElement::Surface(elem), - Point::new(0, 0), - Relocate::Relative, - ) - .into(), + elements.push( + RelocateRenderElement::from_element( + CursorRenderElement::Surface(elem), + Point::new(0, 0), + Relocate::Relative, ) - .into() + .into(), + ) }, ); } From d4cd9b674c0e3eb266ebf9dc09d9f6c1a9f5e214 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 10 Apr 2026 17:48:45 +0200 Subject: [PATCH 28/56] chore: Adjust log level for blur debugging --- src/backend/render/wayland/blur_effect.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index a2937b7b..977f82f3 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -27,7 +27,7 @@ use smithay::{ }, wayland::compositor::SurfaceData, }; -use tracing::info; +use tracing::trace; use crate::{ backend::render::{element::AsGlowRenderer, wayland::clipped_surface::ClippingShader}, @@ -78,7 +78,7 @@ static BLUR_PARAMS: LazyLock> = LazyLock::new(|| { } } - info!("Computed blur values: {:#?}", ¶ms); + trace!("Computed blur values: {:#?}", ¶ms); params }); From 6868c245014c53f19c709aaac750919bb656bee2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 10 Apr 2026 17:50:18 +0200 Subject: [PATCH 29/56] renderer/blur: Use proper commit counter --- src/backend/render/wayland/blur_effect.rs | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index 977f82f3..ca236f76 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -1,9 +1,6 @@ use std::{ borrow::{Borrow, BorrowMut}, - sync::{ - Arc, LazyLock, Mutex, - atomic::{AtomicBool, Ordering}, - }, + sync::{LazyLock, Mutex}, }; use cgmath::{Matrix3, SquareMatrix, Vector2}; @@ -127,7 +124,7 @@ struct BlurState { offset: f64, passes: usize, region: Vec>, - dirty: Arc, + commit: CommitCounter, } unsafe impl Send for BlurState {} @@ -142,18 +139,18 @@ impl BlurState { offset: 0., passes: 0, region: Vec::new(), - dirty: Arc::new(AtomicBool::new(true)), + commit: CommitCounter::default(), } } } pub struct BlurElement { id: Id, + commit: CommitCounter, src: Size, geometry: Rectangle, scaling_shaders: BlurShaders, render_shader: GlesTexProgram, - dirty: Arc, region: Vec>, offset: f64, passes: usize, @@ -262,16 +259,16 @@ impl BlurElement { state.region = region.clone(); state.src = src; if dirty { - state.dirty.store(dirty, Ordering::Release); + state.commit.increment(); } Ok(BlurElement { id: state.id.clone(), + commit: state.commit, src, geometry, scaling_shaders: BlurShaders::get(renderer), render_shader: ClippingShader::get(renderer), - dirty: state.dirty.clone(), offset: state.offset, passes: state.passes, region: region @@ -289,7 +286,7 @@ impl Element for BlurElement { } fn current_commit(&self) -> CommitCounter { - Default::default() + self.commit } fn src(&self) -> Rectangle { @@ -309,7 +306,7 @@ impl Element for BlurElement { scale: Scale, commit: Option, ) -> DamageSet { - if commit.is_none() || self.dirty.load(Ordering::Acquire) { + if self.commit.distance(commit).is_none_or(|d| d > 0) { DamageSet::from_slice(&[self.geometry.to_physical_precise_round(scale)]) } else { DamageSet::default() @@ -344,7 +341,10 @@ impl RenderElement for BlurElement { let texture_ref = cache.get_or_insert_threadsafe(BlurTexture::default); let mut texture_entry = texture_ref.lock().unwrap(); - if self.dirty.swap(false, Ordering::Acquire) { + if texture_entry + .as_ref() + .is_some_and(|tex| tex.size() != tex_size) + { texture_entry.take(); } From 667414bd922d50a82d8da70c6655459d275b4ef1 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 10 Apr 2026 17:50:58 +0200 Subject: [PATCH 30/56] renderer/blur: Fix incorrectly offset blur region --- src/backend/render/wayland/blur_effect.rs | 35 ++++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index ca236f76..e7c52395 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -148,10 +148,11 @@ pub struct BlurElement { id: Id, commit: CommitCounter, src: Size, + extended_offset: Point, geometry: Rectangle, scaling_shaders: BlurShaders, render_shader: GlesTexProgram, - region: Vec>, + region: Vec>, offset: f64, passes: usize, uniforms: Vec>, @@ -223,6 +224,7 @@ impl BlurElement { Ok(Some(Self::from_state( renderer, extended_geo.to_logical(output_scale), + Point::::new(radius, radius).to_logical(output_scale), region, output_scale, &mut state.lock().unwrap(), @@ -234,6 +236,7 @@ impl BlurElement { fn from_state( renderer: &mut R, geometry: Rectangle, + extended_offset: Point, region: &Vec>, output_scale: f64, state: &mut BlurState, @@ -267,13 +270,18 @@ impl BlurElement { commit: state.commit, src, geometry, + extended_offset, scaling_shaders: BlurShaders::get(renderer), render_shader: ClippingShader::get(renderer), offset: state.offset, passes: state.passes, region: region .iter() - .map(|rect| rect.to_physical_precise_round(output_scale)) + .cloned() + .map(|mut rect| { + rect.loc += extended_offset.to_i32_round(); + rect + }) .collect(), uniforms, }) @@ -307,7 +315,15 @@ impl Element for BlurElement { commit: Option, ) -> DamageSet { if self.commit.distance(commit).is_none_or(|d| d > 0) { - DamageSet::from_slice(&[self.geometry.to_physical_precise_round(scale)]) + DamageSet::from_slice(&[Rectangle::new( + self.extended_offset.to_physical_precise_round(scale), + self.geometry.size.to_physical_precise_round(scale) + - self + .extended_offset + .to_size() + .upscale(2.) + .to_physical_precise_round(scale), + )]) } else { DamageSet::default() } @@ -391,10 +407,21 @@ impl RenderElement for BlurElement { cache: Option<&UserDataMap>, ) -> Result<(), R::Error> { let glow_frame = ::glow_frame_mut(frame); + let src_to_geo = self.geometry.size / self.src; + let src_log = src + .upscale(src_to_geo) + .to_logical(1., Transform::Normal, &Size::default()); + let scale = dst.size.to_f64() / src_log.size; + let damage = self .region .iter() - .flat_map(|rect| damage.iter().flat_map(|r| r.intersection(*rect))) + .flat_map(|rect| { + let mut rect = rect.to_f64().intersection(src_log)?; + rect.loc -= src_log.loc; + Some(rect.to_physical_precise_round(scale)) + }) + .flat_map(|rect| damage.iter().flat_map(move |r| r.intersection(rect))) .collect::>(); let cache = cache.expect("Framebuffer element without cache?"); let Some(texture) = cache.get::() else { From 55d57ddba2188f9b14b9ebd13cb2a5621e418fad Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 17 Apr 2026 11:48:11 +0200 Subject: [PATCH 31/56] chore: theme-v2 libcosmic update --- Cargo.lock | 187 ++++++++++++---------- Cargo.toml | 5 +- examples/customized-window-decorations.rs | 24 +-- rust-toolchain.toml | 2 +- src/backend/render/mod.rs | 2 +- src/backend/render/wayland/blur_effect.rs | 124 ++++++++------ src/shell/element/resize_indicator.rs | 186 +++++++++------------ src/shell/element/stack.rs | 137 +++++++++++++--- src/shell/element/stack/tab.rs | 55 +++++-- src/shell/element/stack/tabs.rs | 44 ++--- src/shell/element/stack_hover.rs | 34 ++-- src/shell/element/swap_indicator.rs | 38 +++-- src/shell/element/window.rs | 81 ++++++++-- src/shell/grabs/menu/item.rs | 14 +- src/shell/grabs/menu/mod.rs | 16 +- src/shell/workspace.rs | 12 +- src/shell/zoom.rs | 6 +- src/utils/iced/mod.rs | 4 +- src/utils/iced/state.rs | 24 +-- 19 files changed, 609 insertions(+), 386 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9030c5e0..36146511 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,9 +330,9 @@ dependencies = [ [[package]] name = "auto_enums" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c170965892137a3a9aeb000b4524aa3cc022a310e709d848b6e1cdce4ab4781" +checksum = "65398a2893f41bce5c9259f6e1a4f03fbae40637c1bdc755b4f387f48c613b03" dependencies = [ "derive_utils", "proc-macro2", @@ -785,19 +785,6 @@ dependencies = [ "libm", ] -[[package]] -name = "cosmic-client-toolkit" -version = "0.2.0" -source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#0d0b0b573fe4836ede4c237dadd365033cd28296" -dependencies = [ - "bitflags 2.11.0", - "cosmic-protocols", - "libc", - "smithay-client-toolkit", - "wayland-client", - "wayland-protocols", -] - [[package]] name = "cosmic-comp" version = "1.0.0" @@ -875,7 +862,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "1.0.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "atomicwrites", "calloop", @@ -896,7 +883,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "1.0.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "quote", "syn", @@ -905,7 +892,7 @@ dependencies = [ [[package]] name = "cosmic-freedesktop-icons" version = "0.4.0" -source = "git+https://github.com/pop-os/freedesktop-icons#7a61a704f6d1ec41f71cbe766e3cc484858523fa" +source = "git+https://github.com/pop-os/freedesktop-icons#ab4c57b8e416c6af9297cb04d101889896fd9a92" dependencies = [ "bstr", "btoi", @@ -923,7 +910,6 @@ source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#0d0b0b573f dependencies = [ "bitflags 2.11.0", "wayland-backend", - "wayland-client", "wayland-protocols", "wayland-protocols-wlr", "wayland-scanner", @@ -956,7 +942,7 @@ dependencies = [ [[package]] name = "cosmic-settings-daemon" version = "0.1.0" -source = "git+https://github.com/pop-os/dbus-settings-bindings#507e342c21d3ce6ae41b1d4f3fa2f0ad5ee23e75" +source = "git+https://github.com/pop-os/dbus-settings-bindings#eed01dd3609e90e3c8cd043656734c500956c793" dependencies = [ "zbus", ] @@ -973,8 +959,9 @@ dependencies = [ [[package]] name = "cosmic-text" -version = "0.18.2" -source = "git+https://github.com/pop-os/cosmic-text.git#d5a972a2b63649fad11ea3a7e80f7dc4c592f01a" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" dependencies = [ "bitflags 2.11.0", "fontdb", @@ -997,13 +984,14 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "1.0.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "almost", "configparser", "cosmic-config", "csscolorparser", "dirs", + "hex_color", "palette", "ron 0.12.0", "serde", @@ -1053,7 +1041,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "cryoglyph" version = "0.1.0" -source = "git+https://github.com/pop-os/glyphon.git?tag=cosmic-0.14#c49de15bce4d8254ac136d1be9911960cc85ce12" +source = "git+https://github.com/iced-rs/cryoglyph.git?rev=e429a025df36ab8145708acb309080ae3deec17a#e429a025df36ab8145708acb309080ae3deec17a" dependencies = [ "cosmic-text", "etagere", @@ -1080,9 +1068,9 @@ checksum = "42aaeae719fd78ce501d77c6cdf01f7e96f26bcd5617a4903a1c2b97e388543a" [[package]] name = "csscolorparser" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a878cfd6075649977f548ed439564cfdbe1f190a6656a00b48af7740f2f148" +checksum = "199f851bd3cb5004c09474252c7f74e7c047441ed0979bf3688a7106a13da952" dependencies = [ "num-traits", "phf 0.13.1", @@ -2119,6 +2107,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex_color" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0" +dependencies = [ + "arrayvec", + "rand 0.8.5", + "serde", +] + [[package]] name = "hexf-parse" version = "0.2.1" @@ -2237,7 +2236,7 @@ dependencies = [ [[package]] name = "iced" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "dnd", "iced_core", @@ -2256,7 +2255,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "bitflags 2.11.0", "bytes", @@ -2272,6 +2271,7 @@ dependencies = [ "serde", "smol_str", "thiserror 2.0.18", + "unicode-segmentation", "web-time", "window_clipboard", ] @@ -2279,7 +2279,7 @@ dependencies = [ [[package]] name = "iced_debug" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "iced_core", "iced_futures", @@ -2289,7 +2289,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "futures", "iced_core", @@ -2302,7 +2302,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "bitflags 2.11.0", "bytemuck", @@ -2323,7 +2323,7 @@ dependencies = [ [[package]] name = "iced_program" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "iced_graphics", "iced_runtime", @@ -2332,7 +2332,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2344,7 +2344,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "bytes", "dnd", @@ -2358,7 +2358,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "bytemuck", "cosmic-text", @@ -2375,12 +2375,11 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.14.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "as-raw-xcb-connection", "bitflags 2.11.0", "bytemuck", - "cosmic-client-toolkit", "cryoglyph", "futures", "glam", @@ -2406,7 +2405,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.14.2" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "dnd", "iced_renderer", @@ -2544,9 +2543,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.9" +version = "0.25.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ "bytemuck", "byteorder-lite", @@ -2808,9 +2807,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "known-folders" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770919970f7d2f74fea948900d35e2ef64f44129e8ae4015f59de1f0aca7c2a5" +checksum = "7a1886916523694cd6ea3d175f03a1e5010699a2a4cc13696d83d7bea1d80638" dependencies = [ "windows-sys 0.59.0", ] @@ -2886,7 +2885,7 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libcosmic" version = "1.0.0" -source = "git+https://github.com/pop-os/libcosmic#0bb006c5bbf7eb89491891d45bfc8f21f8eb1305" +source = "git+https://github.com/pop-os/libcosmic#d922ac380134c00208d2acf93947d44fb217eec5" dependencies = [ "apply", "auto_enums", @@ -2896,6 +2895,7 @@ dependencies = [ "cosmic-theme", "css-color", "derive_setters", + "enumflags2", "float-cmp 0.10.0", "futures", "i18n-embed", @@ -2912,7 +2912,6 @@ dependencies = [ "log", "palette", "phf 0.13.1", - "raw-window-handle", "rust-embed", "serde", "slotmap", @@ -3292,9 +3291,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.11" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" dependencies = [ "num-traits", "pxfm", @@ -3308,9 +3307,9 @@ checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" [[package]] name = "naga" -version = "27.0.3" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" +checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" dependencies = [ "arrayvec", "bit-set", @@ -4159,6 +4158,8 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -4168,7 +4169,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", + "rand_chacha 0.9.0", "rand_core 0.9.5", ] @@ -4183,6 +4184,16 @@ dependencies = [ "rand_core 0.10.0", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -4198,6 +4209,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] [[package]] name = "rand_core" @@ -4856,14 +4870,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ "bitflags 2.11.0", - "bytemuck", "calloop", "calloop-wayland-source", "cursor-icon", "libc", "log", "memmap2", - "pkg-config", "rustix 1.1.4", "thiserror 2.0.18", "wayland-backend", @@ -4875,7 +4887,6 @@ dependencies = [ "wayland-protocols-misc", "wayland-protocols-wlr", "wayland-scanner", - "xkbcommon 0.8.0", "xkeysym", ] @@ -4926,7 +4937,7 @@ checksum = "27207bb65232eda1f588cf46db2fee75c0808d557f6b3cf19a75f5d6d7c94df1" [[package]] name = "softbuffer" version = "0.4.1" -source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#a3f77e251e7422803f693df6e3fc313c010c4dcb" +source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#c2b2c19ddb38ff17495643699f97cb1f2064a1be" dependencies = [ "as-raw-xcb-connection", "bytemuck", @@ -5528,9 +5539,9 @@ checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-vo" @@ -5998,12 +6009,13 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "wgpu" -version = "27.0.1" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" +checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f" dependencies = [ "arrayvec", "bitflags 2.11.0", + "bytemuck", "cfg-if", "cfg_aliases", "document-features", @@ -6021,9 +6033,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "27.0.3" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" +checksum = "d23f4642f53f666adcfd2d3218ab174d1e6681101aef18696b90cbe64d1c10f9" dependencies = [ "arrayvec", "bit-set", @@ -6051,18 +6063,18 @@ dependencies = [ [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "27.0.0" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" +checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "27.0.4" +version = "28.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" +checksum = "44d6cb474beb218824dcc9e1ce679d973f719262789bfb27407da560cac20eeb" dependencies = [ "bitflags 2.11.0", "cfg-if", @@ -6080,15 +6092,14 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "27.0.1" +version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" dependencies = [ "bitflags 2.11.0", "bytemuck", "js-sys", "log", - "thiserror 2.0.18", "web-sys", ] @@ -6736,6 +6747,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -6925,9 +6945,6 @@ name = "xkeysym" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" -dependencies = [ - "bytemuck", -] [[package]] name = "xml-rs" @@ -6978,9 +6995,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.14.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" dependencies = [ "async-broadcast", "async-executor", @@ -7005,7 +7022,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 0.7.14", + "winnow 1.0.3", "zbus_macros", "zbus_names", "zvariant", @@ -7013,9 +7030,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.14.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -7028,12 +7045,12 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow 0.7.14", + "winnow 1.0.3", "zvariant", ] @@ -7156,23 +7173,23 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.10.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" dependencies = [ "endi", "enumflags2", "serde", - "winnow 0.7.14", + "winnow 1.0.3", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.10.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -7183,13 +7200,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" dependencies = [ "proc-macro2", "quote", "serde", "syn", - "winnow 0.7.14", + "winnow 1.0.3", ] diff --git a/Cargo.toml b/Cargo.toml index a8c2dc2f..7023d1d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ edition = "2024" license = "GPL-3.0-only" name = "cosmic-comp" version = "1.0.0" -rust-version = "1.90" +rust-version = "1.93" [workspace] members = ["cosmic-comp-config"] @@ -28,7 +28,7 @@ cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daem cosmic-settings-daemon-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", features = [ "greeter", ] } -cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git", features = [ +cosmic-text = { version = "0.19", features = [ "shape-run-cache", ] } libdisplay-info = "0.3.0" @@ -141,7 +141,6 @@ lto = "fat" [patch."https://github.com/pop-os/cosmic-protocols"] cosmic-protocols = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" } -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 = "8eb4076" } diff --git a/examples/customized-window-decorations.rs b/examples/customized-window-decorations.rs index ffe677f1..364221fe 100644 --- a/examples/customized-window-decorations.rs +++ b/examples/customized-window-decorations.rs @@ -23,7 +23,7 @@ struct Circle { color: Color, } -impl +impl canvas::Program for Circle { type State = (); @@ -50,17 +50,17 @@ impl cosmic::Element<'_, Message> { let orig = self.lower.view(window); - widget::row() - .push( - widget::column() - .push(canvas(Circle { - radius: (self.height as f32 / 2.) * 0.8, - color: Color::from_rgba(1.0, 0.0, 0.0, 1.0), - })) - .width(self.height as f32), - ) - .push(orig) - .into() + widget::row([ + widget::column([canvas(Circle { + radius: (self.height as f32 / 2.) * 0.8, + color: Color::from_rgba(1.0, 0.0, 0.0, 1.0), + }) + .into()]) + .width(self.height as f32) + .into(), + orig, + ]) + .into() } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9c8eecdc..ae16192b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.90" +channel = "1.93" components = [ "rust-src" ] diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 7c1c1404..60917112 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -726,7 +726,7 @@ where return Ok(Vec::new()); } let theme = shell_ref.theme().clone(); - let blur_strength = 9; // TODO + let blur_strength = (theme.cosmic().frosted as u8 + 1) as usize; let scale = output.current_scale().fractional_scale(); // we don't want to hold a shell lock across `cursor_elements`, // that is prone to deadlock with the main-thread on some grabs. diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index e7c52395..47f5be88 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -117,21 +117,22 @@ impl BlurShaders { type BlurTexture = Mutex>; -struct BlurState { - id: Id, - renderer_id: Option>, - src: Size, - offset: f64, - passes: usize, - region: Vec>, - commit: CommitCounter, +#[derive(Debug)] +pub struct BlurState { + pub id: Id, + pub renderer_id: Option>, + pub src: Size, + pub offset: f64, + pub passes: usize, + pub region: Vec>, + pub commit: CommitCounter, } unsafe impl Send for BlurState {} unsafe impl Sync for BlurState {} -impl BlurState { - fn new() -> Self { +impl Default for BlurState { + fn default() -> Self { BlurState { id: Id::new(), renderer_id: None, @@ -159,6 +160,27 @@ pub struct BlurElement { } impl BlurElement { + pub fn from_state( + renderer: &mut R, + state: &mut BlurState, + geometry: Rectangle, + output_scale: f64, + radii: [u8; 4], + strength: usize, + ) -> Result, R::Error> { + let region = vec![Rectangle::from_size(geometry.size.to_i32_round())]; + + Self::internal( + renderer, + state, + geometry, + ®ion, + output_scale, + radii, + strength, + ) + } + pub fn from_surface( renderer: &mut R, states: &SurfaceData, @@ -172,9 +194,37 @@ impl BlurElement { return Ok(None); }; + let state = states + .data_map + .get_or_insert_threadsafe::, _>(Default::default); + + Self::internal( + renderer, + &mut state.lock().unwrap(), + geometry, + region, + output_scale, + radii, + strength, + ) + } + + pub fn internal( + renderer: &mut R, + state: &mut BlurState, + geometry: Rectangle, + region: &Vec>, + output_scale: f64, + radii: [u8; 4], + strength: usize, + ) -> Result, R::Error> { + if strength == 0 { + return Ok(None); + } + let geo = geometry.to_physical_precise_round(output_scale); let mut extended_geo = geo; - let radius = BLUR_PARAMS[strength.min(MAX_STEPS - 1)].extended_radius as f64; + let radius = BLUR_PARAMS[(strength + 2).min(MAX_STEPS - 1)].extended_radius as f64; extended_geo.loc -= Point::::new(radius, radius); extended_geo.size += Size::::new(radius, radius).upscale(2.); @@ -217,32 +267,9 @@ impl BlurElement { Uniform::new("noise", UniformValue::_1f(NOISE)), ]; - let state = states - .data_map - .get_or_insert_threadsafe::, _>(|| Mutex::new(BlurState::new())); + let geometry = extended_geo.to_logical(output_scale); + let extended_offset = Point::::new(radius, radius).to_logical(output_scale); - Ok(Some(Self::from_state( - renderer, - extended_geo.to_logical(output_scale), - Point::::new(radius, radius).to_logical(output_scale), - region, - output_scale, - &mut state.lock().unwrap(), - uniforms, - strength, - )?)) - } - - fn from_state( - renderer: &mut R, - geometry: Rectangle, - extended_offset: Point, - region: &Vec>, - output_scale: f64, - state: &mut BlurState, - uniforms: Vec>, - strength: usize, - ) -> Result { let renderer_id = renderer.glow_renderer().context_id(); let src = geometry.size.to_buffer(output_scale, Transform::Normal); let params = &BLUR_PARAMS[strength.min(MAX_STEPS - 1)]; @@ -265,7 +292,7 @@ impl BlurElement { state.commit.increment(); } - Ok(BlurElement { + Ok(Some(BlurElement { id: state.id.clone(), commit: state.commit, src, @@ -284,7 +311,7 @@ impl BlurElement { }) .collect(), uniforms, - }) + })) } } @@ -468,7 +495,7 @@ fn blit_from_active_fb( let mut fb = renderer.as_mut().bind(to_texture)?; std::mem::drop(renderer); - frame.blit_to( + let sync = frame.blit_to( &mut fb_tmp, dst, Rectangle::from_size(dst.size), @@ -480,6 +507,7 @@ fn blit_from_active_fb( let mut frame = renderer .as_mut() .render(&mut fb, tex_size, Transform::Normal)?; + frame.wait(&sync)?; Frame::render_texture_from_to( &mut frame, &tmp_texture, @@ -502,16 +530,14 @@ fn blit_from_active_fb( } else { let mut fb = frame.renderer().as_mut().bind(to_texture)?; - frame - .blit_to( - &mut fb, - dst, - src.to_logical(1., Transform::Normal, &src.size) - .to_physical(1.) - .to_i32_round(), - TextureFilter::Linear, - ) - .map(|_| SyncPoint::signaled()) + frame.blit_to( + &mut fb, + dst, + src.to_logical(1., Transform::Normal, &src.size) + .to_physical(1.) + .to_i32_round(), + TextureFilter::Linear, + ) } } diff --git a/src/shell/element/resize_indicator.rs b/src/shell/element/resize_indicator.rs index 49dc4781..d1839e6e 100644 --- a/src/shell/element/resize_indicator.rs +++ b/src/shell/element/resize_indicator.rs @@ -1,4 +1,4 @@ -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use crate::{ config::Config, @@ -12,9 +12,9 @@ use cosmic::{ Apply, iced::{ Alignment, - widget::{column, container, row, space}, + core::{Background, Border, Color, Length}, + widget::{container, row, space}, }, - iced_core::{Background, Border, Color, Length}, theme, widget::{icon::from_name, text}, }; @@ -67,120 +67,90 @@ impl Program for ResizeIndicatorInternal { type Message = (); fn view(&self) -> cosmic::Element<'_, Self::Message> { - let edges = self.edges.lock().unwrap(); - let icon_container_style = || { - theme::Container::custom(|theme| container::Style { + row(vec![ + text::heading(&self.shortcut1).into(), + text::body(fl!("grow-window")).into(), + space::horizontal().width(40).into(), + text::heading(&self.shortcut2).into(), + text::body(fl!("shrink-window")).into(), + ]) + .apply(container) + .align_x(Alignment::Center) + .align_y(Alignment::Center) + .padding(16) + .apply(container) + .class(theme::Container::custom(|theme| { + let mut background = theme.cosmic().accent_color(); + if theme.transparent { + background.alpha = theme + .cosmic() + .alpha_map + .blurred_alpha(theme.cosmic().frosted); + } + + container::Style { snap: true, icon_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)), - background: Some(Background::Color(theme.cosmic().accent_color().into())), + background: Some(Background::Color(background.into())), border: Border { radius: 18.0.into(), width: 0.0, color: Color::TRANSPARENT, }, shadow: Default::default(), - }) - }; - - column(vec![ - if edges.contains(ResizeEdge::TOP) { - from_name(if self.direction == ResizeDirection::Outwards { - "go-up-symbolic" - } else { - "go-down-symbolic" - }) - .size(20) - .prefer_svg(true) - .apply(container) - .padding(8) - .class(icon_container_style()) - .width(Length::Shrink) - .apply(container) - .center_x(Length::Fill) - .into() - } else { - space::vertical().height(36).into() - }, - row(vec![ - if edges.contains(ResizeEdge::LEFT) { - from_name(if self.direction == ResizeDirection::Outwards { - "go-previous-symbolic" - } else { - "go-next-symbolic" - }) - .size(20) - .prefer_svg(true) - .apply(container) - .padding(8) - .class(icon_container_style()) - .width(Length::Shrink) - .apply(container) - .center_y(Length::Fill) - .into() - } else { - space::horizontal().width(36).into() - }, - row(vec![ - text::heading(&self.shortcut1).into(), - text::body(fl!("grow-window")).into(), - space::horizontal().width(40).into(), - text::heading(&self.shortcut2).into(), - text::body(fl!("shrink-window")).into(), - ]) - .apply(container) - .align_x(Alignment::Center) - .align_y(Alignment::Center) - .padding(16) - .apply(container) - .class(icon_container_style()) - .width(Length::Shrink) - .height(Length::Shrink) - .apply(container) - .center_x(Length::Fill) - .center_y(Length::Fill) - .into(), - if edges.contains(ResizeEdge::RIGHT) { - from_name(if self.direction == ResizeDirection::Outwards { - "go-next-symbolic" - } else { - "go-previous-symbolic" - }) - .size(20) - .prefer_svg(true) - .apply(container) - .padding(8) - .class(icon_container_style()) - .height(Length::Shrink) - .apply(container) - .center_y(Length::Fill) - .into() - } else { - space::horizontal().width(36).into() - }, - ]) - .width(Length::Fill) - .height(Length::Fill) - .into(), - if edges.contains(ResizeEdge::BOTTOM) { - from_name(if self.direction == ResizeDirection::Outwards { - "go-down-symbolic" - } else { - "go-up-symbolic" - }) - .size(20) - .prefer_svg(true) - .apply(container) - .padding(8) - .class(icon_container_style()) - .width(Length::Shrink) - .apply(container) - .center_x(Length::Fill) - .into() - } else { - space::vertical().height(36).into() - }, - ]) + } + })) + .width(Length::Shrink) + .height(Length::Shrink) + .into() + } +} + +pub struct ResizeIndicatorArrow { + direction: Arc>, + icon_outwards: &'static str, + icon_inwards: &'static str, +} + +impl Program for ResizeIndicatorArrow { + type Message = (); + + fn view(&self) -> cosmic::Element<'_, Self::Message> { + from_name( + if *self.direction.lock().unwrap() == ResizeDirection::Outwards { + self.icon_outwards + } else { + self.icon_inwards + }, + ) + .size(20) + .prefer_svg(true) + .apply(container) + .padding(8) + .class(theme::Container::custom(|theme| { + let mut background = theme.cosmic().accent_color(); + if theme.transparent { + background.alpha = theme + .cosmic() + .alpha_map + .blurred_alpha(theme.cosmic().frosted); + } + + container::Style { + snap: true, + icon_color: Some(Color::from(theme.cosmic().accent.on)), + text_color: Some(Color::from(theme.cosmic().accent.on)), + background: Some(Background::Color(background.into())), + border: Border { + radius: theme.cosmic().radius_s().into(), + width: 0.0, + color: Color::TRANSPARENT, + }, + shadow: Default::default(), + } + })) + .width(Length::Shrink) .into() } } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 0980facb..3b1aab87 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -4,8 +4,14 @@ use super::{ }; use crate::{ backend::render::{ - IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer, - shadow::ShadowShader, wayland::SurfaceRenderElement, + IndicatorShader, Key, Usage, + cursor::CursorState, + element::AsGlowRenderer, + shadow::ShadowShader, + wayland::{ + SurfaceRenderElement, + blur_effect::{BlurElement, BlurState}, + }, }, hooks::{Decorations, HOOKS}, shell::{ @@ -23,10 +29,13 @@ use crate::{ use calloop::LoopHandle; use cosmic::{ Apply, Element as CosmicElement, Theme, - iced::{Alignment, id::Id, widget as iced_widget}, - iced_core::{Background, Border, Color, Length, border::Radius}, - iced_runtime::Task, - iced_widget::scrollable::AbsoluteOffset, + iced::{ + Alignment, + core::{Background, Border, Color, Length, border::Radius}, + id::Id, + runtime::Task, + widget::{self as iced_widget, scrollable::AbsoluteOffset}, + }, theme, widget as cosmic_widget, }; use cosmic_comp_config::AppearanceConfig; @@ -120,6 +129,7 @@ pub struct CosmicStackInternal { tiled: AtomicBool, theme: Mutex, appearance_conf: Mutex, + blur_state: Mutex, } impl CosmicStackInternal { @@ -178,6 +188,7 @@ impl CosmicStack { tiled: AtomicBool::new(false), theme: Mutex::new(theme.clone()), appearance_conf: Mutex::new(appearance), + blur_state: Default::default(), }, (width, TAB_HEIGHT), handle, @@ -661,6 +672,12 @@ impl CosmicStack { self.0.with_program(|p| { let windows = p.windows.lock().unwrap(); let active = p.active.load(Ordering::SeqCst); + let theme = p.theme.lock().unwrap(); + let frosted = if theme.cosmic().frosted_windows { + (theme.cosmic().frosted as u8 + 1) as usize + } else { + 0 + }; windows[active].push_popup_render_elements( renderer, @@ -668,7 +685,7 @@ impl CosmicStack { scale, alpha, scanout_node, - 9, // TODO + frosted, &mut |elem| push(elem.into()), ) }) @@ -769,6 +786,30 @@ impl CosmicStack { .to_physical_precise_round(scale); let stack_loc = location + geometry.loc; let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32)); + let frosted = self.0.with_program(|p| { + let theme = p.theme.lock().unwrap(); + if theme.cosmic().frosted_windows { + (theme.cosmic().frosted as u8 + 1) as usize + } else { + 0 + } + }); + let radii = self.0.with_program(|p| { + let windows = p.windows.lock().unwrap(); + let active = p.active.load(Ordering::SeqCst); + let appearance = p.appearance_conf.lock().unwrap(); + let theme = p.theme.lock().unwrap(); + let tiled = p.tiled.load(Ordering::Acquire); + let maximized = windows[active].is_maximized(false); + let round = (appearance.clip_tiled_windows || !tiled) && !maximized; + round.then(|| { + theme + .cosmic() + .radius_s() + .map(|x| if x < 4.0 { x } else { x + 4.0 }) + .map(|x| x.round() as u8) + }) + }); self.0 .push_render_elements(renderer, stack_loc, scale, alpha, &mut |elem| { @@ -779,19 +820,8 @@ impl CosmicStack { let windows = p.windows.lock().unwrap(); let active = p.active.load(Ordering::SeqCst); let theme = p.theme.lock().unwrap(); - let appearance = p.appearance_conf.lock().unwrap(); - let tiled = p.tiled.load(Ordering::Acquire); let maximized = windows[active].is_maximized(false); - let round = (appearance.clip_tiled_windows || !tiled) && !maximized; - let radii = round.then(|| { - theme - .cosmic() - .radius_s() - .map(|x| if x < 4.0 { x } else { x + 4.0 }) - .map(|x| x.round() as u8) - }); - let mut geo = SpaceElement::geometry(&windows[active]).to_f64(); geo.loc += location.to_f64().to_logical(scale); geo.size.h += TAB_HEIGHT as f64; @@ -809,7 +839,7 @@ impl CosmicStack { Key::Window(Usage::Border, window_key.clone()), geo.to_i32_round().as_local(), 1, - radii.unwrap_or([0, 0, 0, 0]), + radii.unwrap_or([0; 4]), a * alpha, scale.x, [r, g, b], @@ -826,11 +856,34 @@ impl CosmicStack { scanout_node, radii.is_some(), radii.unwrap_or([0; 4]), - 9, // as TODO + frosted, &mut |elem| push_above(elem.into()), Some(&mut |elem| push_below(elem.into())), ); }); + + if frosted > 0 { + let geometry = Rectangle::new( + stack_loc.to_f64().to_logical(scale), + Size::new( + geometry.size.to_f64().to_logical(scale).w, + TAB_HEIGHT as f64, + ), + ); + if let Ok(Some(elem)) = self.0.with_program(|p| { + let mut state = p.blur_state.lock().unwrap(); + BlurElement::from_state( + renderer, + &mut state, + geometry, + scale.x, + radii.map(|[_, b, _, d]| [0, b, 0, d]).unwrap_or([0; 4]), + frosted, + ) + }) { + push_below(elem.into()); + } + } } pub(crate) fn set_theme(&self, theme: cosmic::Theme) { @@ -1342,16 +1395,29 @@ impl Decorations for DefaultDecorations { .class(theme::Container::custom(move |theme| { let cosmic_theme = theme.cosmic(); - let background = if group_focused { + let mut background = if group_focused { cosmic_theme.accent_color() } else { cosmic_theme.primary_container_color() }; + if cosmic_theme.frosted_windows { + background.alpha = cosmic_theme.alpha_map.blurred_alpha(cosmic_theme.frosted); + } iced_widget::container::Style { snap: true, - icon_color: Some(cosmic_theme.background.on.into()), - text_color: Some(cosmic_theme.background.on.into()), + icon_color: Some( + cosmic_theme + .background(cosmic_theme.frosted_windows) + .on + .into(), + ), + text_color: Some( + cosmic_theme + .background(cosmic_theme.frosted_windows) + .on + .into(), + ), background: Some(Background::Color(background.into())), border: Border { radius, @@ -1864,6 +1930,7 @@ impl TouchTarget for CosmicStack { pub enum CosmicStackRenderElement { Header(MemoryRenderBufferRenderElement), + HeaderBlur(BlurElement), Shadow(PixelShaderElement), Border(PixelShaderElement), Window(SurfaceRenderElement), @@ -1877,6 +1944,12 @@ impl From From for CosmicStackRenderElement { + fn from(value: BlurElement) -> Self { + Self::HeaderBlur(value) + } +} + impl From> for CosmicStackRenderElement { @@ -1893,6 +1966,7 @@ where fn id(&self) -> &RendererId { match self { CosmicStackRenderElement::Header(elem) => elem.id(), + CosmicStackRenderElement::HeaderBlur(elem) => elem.id(), CosmicStackRenderElement::Shadow(elem) => elem.id(), CosmicStackRenderElement::Border(elem) => elem.id(), CosmicStackRenderElement::Window(elem) => elem.id(), @@ -1902,6 +1976,7 @@ where fn current_commit(&self) -> CommitCounter { match self { CosmicStackRenderElement::Header(elem) => elem.current_commit(), + CosmicStackRenderElement::HeaderBlur(elem) => elem.current_commit(), CosmicStackRenderElement::Shadow(elem) => elem.current_commit(), CosmicStackRenderElement::Border(elem) => elem.current_commit(), CosmicStackRenderElement::Window(elem) => elem.current_commit(), @@ -1911,6 +1986,7 @@ where fn src(&self) -> Rectangle { match self { CosmicStackRenderElement::Header(elem) => elem.src(), + CosmicStackRenderElement::HeaderBlur(elem) => elem.src(), CosmicStackRenderElement::Shadow(elem) => elem.src(), CosmicStackRenderElement::Border(elem) => elem.src(), CosmicStackRenderElement::Window(elem) => elem.src(), @@ -1920,6 +1996,7 @@ where fn geometry(&self, scale: Scale) -> Rectangle { match self { CosmicStackRenderElement::Header(elem) => elem.geometry(scale), + CosmicStackRenderElement::HeaderBlur(elem) => elem.geometry(scale), CosmicStackRenderElement::Shadow(elem) => elem.geometry(scale), CosmicStackRenderElement::Border(elem) => elem.geometry(scale), CosmicStackRenderElement::Window(elem) => elem.geometry(scale), @@ -1929,6 +2006,7 @@ where fn location(&self, scale: Scale) -> Point { match self { CosmicStackRenderElement::Header(elem) => elem.location(scale), + CosmicStackRenderElement::HeaderBlur(elem) => elem.location(scale), CosmicStackRenderElement::Shadow(elem) => elem.location(scale), CosmicStackRenderElement::Border(elem) => elem.location(scale), CosmicStackRenderElement::Window(elem) => elem.location(scale), @@ -1938,6 +2016,7 @@ where fn transform(&self) -> Transform { match self { CosmicStackRenderElement::Header(elem) => elem.transform(), + CosmicStackRenderElement::HeaderBlur(elem) => elem.transform(), CosmicStackRenderElement::Shadow(elem) => elem.transform(), CosmicStackRenderElement::Border(elem) => elem.transform(), CosmicStackRenderElement::Window(elem) => elem.transform(), @@ -1951,6 +2030,7 @@ where ) -> DamageSet { match self { CosmicStackRenderElement::Header(elem) => elem.damage_since(scale, commit), + CosmicStackRenderElement::HeaderBlur(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Shadow(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Border(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Window(elem) => elem.damage_since(scale, commit), @@ -1960,6 +2040,7 @@ where fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { match self { CosmicStackRenderElement::Header(elem) => elem.opaque_regions(scale), + CosmicStackRenderElement::HeaderBlur(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Shadow(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Border(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Window(elem) => elem.opaque_regions(scale), @@ -1969,6 +2050,7 @@ where fn alpha(&self) -> f32 { match self { CosmicStackRenderElement::Header(elem) => elem.alpha(), + CosmicStackRenderElement::HeaderBlur(elem) => elem.alpha(), CosmicStackRenderElement::Shadow(elem) => elem.alpha(), CosmicStackRenderElement::Border(elem) => elem.alpha(), CosmicStackRenderElement::Window(elem) => elem.alpha(), @@ -1978,6 +2060,7 @@ where fn kind(&self) -> Kind { match self { CosmicStackRenderElement::Header(elem) => elem.kind(), + CosmicStackRenderElement::HeaderBlur(elem) => elem.kind(), CosmicStackRenderElement::Shadow(elem) => elem.kind(), CosmicStackRenderElement::Border(elem) => elem.kind(), CosmicStackRenderElement::Window(elem) => elem.kind(), @@ -1987,6 +2070,7 @@ where fn is_framebuffer_effect(&self) -> bool { match self { CosmicStackRenderElement::Header(elem) => elem.is_framebuffer_effect(), + CosmicStackRenderElement::HeaderBlur(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Shadow(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Border(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Window(elem) => elem.is_framebuffer_effect(), @@ -2012,6 +2096,9 @@ where CosmicStackRenderElement::Header(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } + CosmicStackRenderElement::HeaderBlur(elem) => { + RenderElement::::draw(elem, frame, src, dst, damage, opaque_regions, cache) + } CosmicStackRenderElement::Shadow(elem) | CosmicStackRenderElement::Border(elem) => { RenderElement::::draw( elem, @@ -2033,6 +2120,7 @@ where fn underlying_storage(&self, renderer: &mut R) -> Option> { match self { CosmicStackRenderElement::Header(elem) => elem.underlying_storage(renderer), + CosmicStackRenderElement::HeaderBlur(elem) => elem.underlying_storage(renderer), CosmicStackRenderElement::Shadow(elem) | CosmicStackRenderElement::Border(elem) => { elem.underlying_storage(renderer.glow_renderer_mut()) } @@ -2051,6 +2139,9 @@ where CosmicStackRenderElement::Header(elem) => { elem.capture_framebuffer(frame, src, dst, cache) } + CosmicStackRenderElement::HeaderBlur(elem) => { + RenderElement::::capture_framebuffer(elem, frame, src, dst, cache) + } CosmicStackRenderElement::Shadow(elem) | CosmicStackRenderElement::Border(elem) => { RenderElement::::capture_framebuffer( elem, diff --git a/src/shell/element/stack/tab.rs b/src/shell/element/stack/tab.rs index 99b0c247..f8fda525 100644 --- a/src/shell/element/stack/tab.rs +++ b/src/shell/element/stack/tab.rs @@ -3,16 +3,15 @@ use cosmic::{ font::Font, iced::{ Background, - widget::{self, container::draw_background, rule::FillMode}, + core::{ + Border, Clipboard, Color, Length, Rectangle, Shell, Size, alignment, event, + layout::{Layout, Limits, Node}, + mouse, overlay, renderer, + text::{Ellipsize, EllipsizeHeightLimit, Shaping, Wrapping}, + widget::{Id, Widget, operation::Operation, tree::Tree}, + }, + widget::{self, container::draw_background, rule::FillMode, scrollable::AbsoluteOffset}, }, - iced_core::{ - Border, Clipboard, Color, Length, Rectangle, Shell, Size, alignment, event, - layout::{Layout, Limits, Node}, - mouse, overlay, renderer, - text::{Ellipsize, EllipsizeHeightLimit, Shaping, Wrapping}, - widget::{Id, Widget, operation::Operation, tree::Tree}, - }, - iced_widget::scrollable::AbsoluteOffset, theme, widget::{Icon, icon::from_name, text}, }; @@ -64,9 +63,20 @@ impl From for theme::Container<'_> { snap: true, icon_color: Some(Color::from(theme.cosmic().accent_text_color())), text_color: Some(Color::from(theme.cosmic().accent_text_color())), - background: Some(Background::Color( - theme.cosmic().primary.component.selected.into(), - )), + background: Some(Background::Color({ + let mut color = theme + .cosmic() + .primary(theme.cosmic().frosted_windows) + .component + .selected; + if theme.cosmic().frosted_windows { + color.alpha = theme + .cosmic() + .alpha_map + .blurred_alpha(theme.cosmic().frosted); + } + color.into() + })), border: Border { radius: 0.0.into(), width: 0.0, @@ -80,9 +90,20 @@ impl From for theme::Container<'_> { snap: true, icon_color: None, text_color: None, - background: Some(Background::Color( - theme.cosmic().primary.component.base.into(), - )), + background: Some(Background::Color({ + let mut color = theme + .cosmic() + .primary(theme.cosmic().frosted_windows) + .component + .base; + if theme.cosmic().frosted_windows { + color.alpha = theme + .cosmic() + .alpha_map + .blurred_alpha(theme.cosmic().frosted); + } + color.into() + })), border: Border { radius: 0.0.into(), width: 0.0, @@ -289,8 +310,8 @@ where .min_height(size.height) .width(size.width) .height(size.height); - cosmic::iced_core::layout::flex::resolve( - cosmic::iced_core::layout::flex::Axis::Horizontal, + cosmic::iced::core::layout::flex::resolve( + cosmic::iced::core::layout::flex::Axis::Horizontal, renderer, &limits, Length::Fill, diff --git a/src/shell/element/stack/tabs.rs b/src/shell/element/stack/tabs.rs index f411fe26..86218db6 100644 --- a/src/shell/element/stack/tabs.rs +++ b/src/shell/element/stack/tabs.rs @@ -1,24 +1,30 @@ use super::tab::{MIN_ACTIVE_TAB_WIDTH, Tab, TabBackgroundTheme, TabMessage, TabRuleTheme}; use cosmic::{ Apply, - iced::{Element, id::Id, widget}, - iced_core::{ - Background, Border, Clipboard, Color, Length, Point, Rectangle, Renderer, Shell, Size, - Vector, event, - layout::{Layout, Limits, Node}, - mouse, overlay, renderer, - widget::{ - Widget, - operation::{ - Operation, Scrollable, - scrollable::{AbsoluteOffset, RelativeOffset}, + iced::{ + Element, + core::{ + Background, Border, Clipboard, Color, Length, Point, Rectangle, Renderer, Shell, Size, + Vector, event, + layout::{Layout, Limits, Node}, + mouse, overlay, renderer, + widget::{ + Widget, + operation::{ + Operation, Scrollable, + scrollable::{AbsoluteOffset, RelativeOffset}, + }, + tree::{self, Tree}, }, - tree::{self, Tree}, + }, + id::Id, + widget::{ + self, + container::{Catalog, draw_background}, }, }, - iced_widget::container::draw_background, theme, - widget::{container::Catalog, icon::from_name}, + widget::icon::from_name, }; use keyframe::{ ease, @@ -379,8 +385,8 @@ where >= MIN_ACTIVE_TAB_WIDTH { // just use a flex layout - cosmic::iced_core::layout::flex::resolve( - cosmic::iced_core::layout::flex::Axis::Horizontal, + cosmic::iced::core::layout::flex::resolve( + cosmic::iced::core::layout::flex::Axis::Horizontal, renderer, &limits, self.width, @@ -513,13 +519,11 @@ where let background_style = Catalog::style( theme, - &theme::Container::custom(|theme| widget::container::Style { + &theme::Container::custom(|_theme| widget::container::Style { snap: true, icon_color: None, text_color: None, - background: Some(Background::Color( - theme.cosmic().primary_container_color().into(), - )), + background: Some(Background::Color(Color::TRANSPARENT)), border: Border { radius: 0.0.into(), width: 0.0, diff --git a/src/shell/element/stack_hover.rs b/src/shell/element/stack_hover.rs index 9a86709c..d6a057c4 100644 --- a/src/shell/element/stack_hover.rs +++ b/src/shell/element/stack_hover.rs @@ -8,9 +8,9 @@ use cosmic::{ Apply, iced::{ Alignment, + core::{Background, Border, Color, Length}, widget::{container, row}, }, - iced_core::{Background, Border, Color, Length}, theme, widget::{icon::from_name, space, text}, }; @@ -47,17 +47,27 @@ impl Program for StackHoverInternal { .align_y(Alignment::Center) .padding(16) .apply(container) - .class(theme::Container::custom(|theme| container::Style { - snap: true, - icon_color: Some(Color::from(theme.cosmic().accent.on)), - text_color: Some(Color::from(theme.cosmic().accent.on)), - background: Some(Background::Color(theme.cosmic().accent_color().into())), - border: Border { - radius: 18.0.into(), - width: 0.0, - color: Color::TRANSPARENT, - }, - shadow: Default::default(), + .class(theme::Container::custom(|theme| { + let mut background = theme.cosmic().accent_color(); + if theme.transparent { + background.alpha = theme + .cosmic() + .alpha_map + .blurred_alpha(theme.cosmic().frosted); + } + + container::Style { + snap: true, + icon_color: Some(Color::from(theme.cosmic().accent.on)), + text_color: Some(Color::from(theme.cosmic().accent.on)), + background: Some(Background::Color(background.into())), + border: Border { + radius: theme.cosmic().radius_s().into(), + width: 0.0, + color: Color::TRANSPARENT, + }, + shadow: Default::default(), + } })) .width(Length::Shrink) .height(Length::Shrink) diff --git a/src/shell/element/swap_indicator.rs b/src/shell/element/swap_indicator.rs index 0db66cda..e6fb0197 100644 --- a/src/shell/element/swap_indicator.rs +++ b/src/shell/element/swap_indicator.rs @@ -6,8 +6,10 @@ use crate::{ use calloop::LoopHandle; use cosmic::{ Apply, - iced::widget::{container, row, space}, - iced_core::{Alignment, Background, Border, Color, Length}, + iced::{ + core::{Alignment, Background, Border, Color, Length}, + widget::{container, row, space}, + }, theme, widget::{icon::from_name, text}, }; @@ -43,17 +45,27 @@ impl Program for SwapIndicatorInternal { .align_y(Alignment::Center) .padding(16) .apply(container) - .class(theme::Container::custom(|theme| container::Style { - snap: true, - icon_color: Some(Color::from(theme.cosmic().accent.on)), - text_color: Some(Color::from(theme.cosmic().accent.on)), - background: Some(Background::Color(theme.cosmic().accent_color().into())), - border: Border { - radius: 18.0.into(), - width: 0.0, - color: Color::TRANSPARENT, - }, - shadow: Default::default(), + .class(theme::Container::custom(|theme| { + let mut background = theme.cosmic().accent_color(); + if theme.transparent { + background.alpha = theme + .cosmic() + .alpha_map + .blurred_alpha(theme.cosmic().frosted); + } + + container::Style { + snap: true, + icon_color: Some(Color::from(theme.cosmic().accent.on)), + text_color: Some(Color::from(theme.cosmic().accent.on)), + background: Some(Background::Color(background.into())), + border: Border { + radius: theme.cosmic().radius_s().into(), + width: 0.0, + color: Color::TRANSPARENT, + }, + shadow: Default::default(), + } })) .width(Length::Shrink) .height(Length::Shrink) diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index de949b8e..b978cfd3 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,7 +1,13 @@ use crate::{ backend::render::{ - IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer, - shadow::ShadowShader, wayland::SurfaceRenderElement, + IndicatorShader, Key, Usage, + cursor::CursorState, + element::AsGlowRenderer, + shadow::ShadowShader, + wayland::{ + SurfaceRenderElement, + blur_effect::{BlurElement, BlurState}, + }, }, hooks::{Decorations, HOOKS}, shell::{ @@ -93,6 +99,7 @@ pub struct CosmicWindowInternal { tiled: AtomicBool, theme: Mutex, appearance_conf: Mutex, + blur_state: Mutex, } #[repr(u8)] @@ -213,6 +220,7 @@ impl CosmicWindow { tiled: AtomicBool::new(false), theme: Mutex::new(theme.clone()), appearance_conf: Mutex::new(appearance), + blur_state: Mutex::default(), }, (width, SSD_HEIGHT), handle, @@ -369,13 +377,20 @@ impl CosmicWindow { }; self.0.with_program(|p| { + let theme = p.theme.lock().unwrap(); + let frosted = if theme.cosmic().frosted_windows { + (theme.cosmic().frosted as u8 + 1) as usize + } else { + 0 + }; + p.window.push_popup_render_elements( renderer, window_loc, scale, alpha, scanout_node, - 9, // TODO + frosted, &mut |elem| push(elem.into()), ) }) @@ -543,11 +558,17 @@ impl CosmicWindow { push_above(elem); } - self.0.with_program(|p| { + let frosted = self.0.with_program(|p| { if has_ssd { radii[1] = 0; radii[3] = 0; } + let theme = p.theme.lock().unwrap(); + let frosted = if theme.cosmic().frosted_windows { + (theme.cosmic().frosted as u8 + 1) as usize + } else { + 0 + }; p.window.push_render_elements( renderer, window_loc, @@ -557,18 +578,31 @@ impl CosmicWindow { scanout_node, clip, radii, - 9, // TODO + frosted, &mut |elem| push_above(elem.into()), Some(&mut |elem| push_below(elem.into())), - ) + ); + + frosted }); if has_ssd { let ssd_loc = location + self .0 - .with_program(|p| p.window.geometry().loc) - .to_physical_precise_round(scale); + .with_program(|p| p.window.geometry().loc.to_physical_precise_round(scale)); + if frosted > 0 { + let geometry = Rectangle::new( + ssd_loc.to_f64().to_logical(scale), + Size::new(geo.size.w, SSD_HEIGHT as f64), + ); + if let Ok(Some(elem)) = self.0.with_program(|p| { + let mut state = p.blur_state.lock().unwrap(); + BlurElement::from_state(renderer, &mut state, geometry, scale.x, radii, frosted) + }) { + push_below(elem.into()); + } + } self.0 .push_render_elements(renderer, ssd_loc, scale, alpha, &mut |elem| { push_above(elem.into()) @@ -812,7 +846,11 @@ impl Program for CosmicWindowInternal { fn background_color(&self, theme: &cosmic::Theme) -> Color { if self.window.is_maximized(false) { - theme.cosmic().background.base.into() + theme + .cosmic() + .background(theme.cosmic().frosted_windows) + .base + .into() } else { Color::TRANSPARENT } @@ -1231,6 +1269,7 @@ impl WaylandFocus for CosmicWindow { pub enum CosmicWindowRenderElement { Header(MemoryRenderBufferRenderElement), + HeaderBlur(BlurElement), Shadow(PixelShaderElement), Border(PixelShaderElement), Window(SurfaceRenderElement), @@ -1244,6 +1283,12 @@ impl From From for CosmicWindowRenderElement { + fn from(value: BlurElement) -> Self { + Self::HeaderBlur(value) + } +} + impl From> for CosmicWindowRenderElement { @@ -1260,6 +1305,7 @@ where fn id(&self) -> &RendererId { match self { CosmicWindowRenderElement::Header(elem) => elem.id(), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.id(), CosmicWindowRenderElement::Shadow(elem) => elem.id(), CosmicWindowRenderElement::Border(elem) => elem.id(), CosmicWindowRenderElement::Window(elem) => elem.id(), @@ -1269,6 +1315,7 @@ where fn current_commit(&self) -> CommitCounter { match self { CosmicWindowRenderElement::Header(elem) => elem.current_commit(), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.current_commit(), CosmicWindowRenderElement::Shadow(elem) => elem.current_commit(), CosmicWindowRenderElement::Border(elem) => elem.current_commit(), CosmicWindowRenderElement::Window(elem) => elem.current_commit(), @@ -1278,6 +1325,7 @@ where fn src(&self) -> Rectangle { match self { CosmicWindowRenderElement::Header(elem) => elem.src(), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.src(), CosmicWindowRenderElement::Shadow(elem) => elem.src(), CosmicWindowRenderElement::Border(elem) => elem.src(), CosmicWindowRenderElement::Window(elem) => elem.src(), @@ -1287,6 +1335,7 @@ where fn geometry(&self, scale: Scale) -> Rectangle { match self { CosmicWindowRenderElement::Header(elem) => elem.geometry(scale), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.geometry(scale), CosmicWindowRenderElement::Shadow(elem) => elem.geometry(scale), CosmicWindowRenderElement::Border(elem) => elem.geometry(scale), CosmicWindowRenderElement::Window(elem) => elem.geometry(scale), @@ -1296,6 +1345,7 @@ where fn location(&self, scale: Scale) -> Point { match self { CosmicWindowRenderElement::Header(elem) => elem.location(scale), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.location(scale), CosmicWindowRenderElement::Shadow(elem) => elem.location(scale), CosmicWindowRenderElement::Border(elem) => elem.location(scale), CosmicWindowRenderElement::Window(elem) => elem.location(scale), @@ -1305,6 +1355,7 @@ where fn transform(&self) -> Transform { match self { CosmicWindowRenderElement::Header(elem) => elem.transform(), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.transform(), CosmicWindowRenderElement::Shadow(elem) => elem.transform(), CosmicWindowRenderElement::Border(elem) => elem.transform(), CosmicWindowRenderElement::Window(elem) => elem.transform(), @@ -1318,6 +1369,7 @@ where ) -> DamageSet { match self { CosmicWindowRenderElement::Header(elem) => elem.damage_since(scale, commit), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Shadow(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Border(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Window(elem) => elem.damage_since(scale, commit), @@ -1327,6 +1379,7 @@ where fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { match self { CosmicWindowRenderElement::Header(elem) => elem.opaque_regions(scale), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Shadow(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Border(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Window(elem) => elem.opaque_regions(scale), @@ -1336,6 +1389,7 @@ where fn alpha(&self) -> f32 { match self { CosmicWindowRenderElement::Header(elem) => elem.alpha(), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.alpha(), CosmicWindowRenderElement::Shadow(elem) => elem.alpha(), CosmicWindowRenderElement::Border(elem) => elem.alpha(), CosmicWindowRenderElement::Window(elem) => elem.alpha(), @@ -1345,6 +1399,7 @@ where fn kind(&self) -> Kind { match self { CosmicWindowRenderElement::Header(elem) => elem.kind(), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.kind(), CosmicWindowRenderElement::Shadow(elem) => elem.kind(), CosmicWindowRenderElement::Border(elem) => elem.kind(), CosmicWindowRenderElement::Window(elem) => elem.kind(), @@ -1354,6 +1409,7 @@ where fn is_framebuffer_effect(&self) -> bool { match self { CosmicWindowRenderElement::Header(elem) => elem.is_framebuffer_effect(), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Shadow(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Border(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Window(elem) => elem.is_framebuffer_effect(), @@ -1379,6 +1435,9 @@ where CosmicWindowRenderElement::Header(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } + CosmicWindowRenderElement::HeaderBlur(elem) => { + RenderElement::::draw(elem, frame, src, dst, damage, opaque_regions, cache) + } CosmicWindowRenderElement::Shadow(elem) | CosmicWindowRenderElement::Border(elem) => { RenderElement::::draw( elem, @@ -1400,6 +1459,7 @@ where fn underlying_storage(&self, renderer: &mut R) -> Option> { match self { CosmicWindowRenderElement::Header(elem) => elem.underlying_storage(renderer), + CosmicWindowRenderElement::HeaderBlur(elem) => elem.underlying_storage(renderer), CosmicWindowRenderElement::Shadow(elem) | CosmicWindowRenderElement::Border(elem) => { elem.underlying_storage(renderer.glow_renderer_mut()) } @@ -1418,6 +1478,9 @@ where CosmicWindowRenderElement::Header(elem) => { elem.capture_framebuffer(frame, src, dst, cache) } + CosmicWindowRenderElement::HeaderBlur(elem) => { + RenderElement::::capture_framebuffer(elem, frame, src, dst, cache) + } CosmicWindowRenderElement::Shadow(elem) | CosmicWindowRenderElement::Border(elem) => { RenderElement::::capture_framebuffer( elem, diff --git a/src/shell/grabs/menu/item.rs b/src/shell/grabs/menu/item.rs index baa61fde..90614f6b 100644 --- a/src/shell/grabs/menu/item.rs +++ b/src/shell/grabs/menu/item.rs @@ -1,10 +1,12 @@ use cosmic::{ - iced::Element, - iced_core::{ - Background, Border, Clipboard, Color, Event, Layout, Length, Rectangle, - Renderer as IcedRenderer, Shell, Size, layout, mouse, overlay, - renderer::{Quad, Style}, - widget::{Id, Tree, Widget, tree}, + iced::{ + Element, + core::{ + Background, Border, Clipboard, Color, Event, Layout, Length, Rectangle, + Renderer as IcedRenderer, Shell, Size, layout, mouse, overlay, + renderer::{Quad, Style}, + widget::{Id, Tree, Widget, tree}, + }, }, widget::button::Catalog, }; diff --git a/src/shell/grabs/menu/mod.rs b/src/shell/grabs/menu/mod.rs index e79734e9..ec82bb07 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -9,9 +9,11 @@ use std::{ use calloop::LoopHandle; use cosmic::{ Apply as _, Task, - iced::{Alignment, Background}, - iced_core::{Border, Length, Rectangle as IcedRectangle, alignment::Horizontal}, - iced_widget::{self, Column, Row, text::Style as TextStyle}, + iced::{ + Alignment, Background, + core::{Border, Length, Rectangle as IcedRectangle, alignment::Horizontal}, + widget::{self as iced_widget, Column, Row, text::Style as TextStyle}, + }, theme, widget::{button, divider, icon::from_name, space, text}, }; @@ -424,10 +426,11 @@ impl Program for ContextMenu { .width(mode) .class(if *disabled { theme::Text::Custom(|theme| { - let mut color = theme.cosmic().background.component.on; + let mut color = theme.cosmic().background(false).component.on; color.alpha *= 0.5; TextStyle { color: Some(color.into()), + ..Default::default() } }) } else { @@ -442,10 +445,11 @@ impl Program for ContextMenu { .align_x(Horizontal::Right) .width(Length::Shrink) .class(theme::Text::Custom(|theme| { - let mut color = theme.cosmic().background.component.on; + let mut color = theme.cosmic().background(false).component.on; color.alpha *= 0.75; TextStyle { color: Some(color.into()), + ..Default::default() } })) .into(), @@ -470,7 +474,7 @@ impl Program for ContextMenu { .padding(1) .class(theme::Container::custom(|theme| { let cosmic = theme.cosmic(); - let component = &cosmic.background.component; + let component = &cosmic.background(false).component; iced_widget::container::Style { snap: true, icon_color: Some(cosmic.accent.base.into()), diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index dc5d9f63..ea5dbad7 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1686,15 +1686,15 @@ impl Workspace { .as_logical() .to_physical_precise_round(output_scale); + let fullscreen_geo = fullscreen.surface.0.geometry(); + let scale = Scale { + x: target_geo.size.w as f64 / fullscreen_geo.size.w as f64, + y: target_geo.size.h as f64 / fullscreen_geo.size.h as f64, + }; + // Only rescale geometry when animating let animation_rescale = |elem| { if fullscreen.is_animating() { - let fullscreen_geo = fullscreen.surface.0.geometry(); - let scale = Scale { - x: target_geo.size.w as f64 / fullscreen_geo.size.w as f64, - y: target_geo.size.h as f64 / fullscreen_geo.size.h as f64, - }; - RescaleRenderElement::from_element(elem, render_loc, scale).into() } else { Into::>::into(elem) diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs index 02d1be55..e0d3f1d1 100644 --- a/src/shell/zoom.rs +++ b/src/shell/zoom.rs @@ -3,8 +3,8 @@ use std::{sync::Mutex, time::Instant}; use calloop::LoopHandle; use cosmic::{ Apply, - iced::{Alignment, Background, Border, Length, alignment::Vertical}, - iced_widget, theme, + iced::{Alignment, Background, Border, Length, alignment::Vertical, widget as iced_widget}, + theme, widget::{self, icon::Named}, }; use cosmic_comp_config::ZoomMovement; @@ -515,7 +515,7 @@ impl Program for ZoomProgram { .padding(8) .class(theme::Container::custom(|theme| { let cosmic = theme.cosmic(); - let component = &cosmic.background.component; + let component = &cosmic.background(false).component; iced_widget::container::Style { snap: true, icon_color: Some(component.on.into()), diff --git a/src/utils/iced/mod.rs b/src/utils/iced/mod.rs index 2ee79733..9d57b46d 100644 --- a/src/utils/iced/mod.rs +++ b/src/utils/iced/mod.rs @@ -12,15 +12,15 @@ use cosmic::{ iced::{ Limits, Point as IcedPoint, Size as IcedSize, Task, advanced::{graphics::text::font_system, widget::Tree}, + core::{Color, Length, Pixels, clipboard::Null as NullClipboard, id::Id, renderer::Style}, event::Event, futures::{FutureExt, StreamExt}, keyboard::{Event as KeyboardEvent, Modifiers as IcedModifiers}, mouse::{Button as MouseButton, Cursor, Event as MouseEvent, ScrollDelta}, + runtime::{Action, task::into_stream}, touch::{Event as TouchEvent, Finger}, window::Event as WindowEvent, }, - iced_core::{Color, Length, Pixels, clipboard::Null as NullClipboard, id::Id, renderer::Style}, - iced_runtime::{Action, task::into_stream}, }; use iced_tiny_skia::{ Layer, diff --git a/src/utils/iced/state.rs b/src/utils/iced/state.rs index db67d8f3..2af3a993 100644 --- a/src/utils/iced/state.rs +++ b/src/utils/iced/state.rs @@ -1,12 +1,16 @@ use super::IcedProgram as Program; -use cosmic::iced::core::event::{self, Event}; -use cosmic::iced::core::mouse; -use cosmic::iced::core::renderer; -use cosmic::iced::core::widget::operation::{self, Operation}; -use cosmic::iced::core::{Clipboard, Size}; -use cosmic::iced_core; -use cosmic::iced_runtime::Task; -use cosmic::iced_runtime::user_interface::{self, UserInterface}; +use cosmic::iced::{ + core::{ + self as iced_core, Clipboard, Size, + event::{self, Event}, + mouse, renderer, + widget::operation::{self, Operation}, + }, + runtime::{ + Task, + user_interface::{self, UserInterface}, + }, +}; /// The execution state of a [`Program`]. It leverages caching, event /// processing, and rendering primitive storage. @@ -128,7 +132,7 @@ where messages.append(&mut self.queued_messages); let task = if messages.is_empty() { - if let cosmic::iced_runtime::user_interface::State::Updated { + if let cosmic::iced::runtime::user_interface::State::Updated { mouse_interaction, .. } = state { @@ -154,7 +158,7 @@ where let mut user_interface = build_user_interface(id, &mut self.program, temp_cache, renderer, bounds); - if let cosmic::iced_runtime::user_interface::State::Updated { + if let cosmic::iced::runtime::user_interface::State::Updated { mouse_interaction, .. } = state { From 5ee2fa911a760d63dbe777a12c061060f873ab7d Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 17 Apr 2026 17:57:48 +0200 Subject: [PATCH 32/56] wayland: Support cosmic-corner-radius v2 --- Cargo.lock | 2 +- src/backend/render/cursor.rs | 2 + src/backend/render/mod.rs | 29 +- src/backend/render/wayland/mod.rs | 7 +- src/shell/element/surface.rs | 30 +- src/wayland/handlers/corner_radius.rs | 109 ++++- src/wayland/protocols/corner_radius.rs | 647 +++++++++++++++++++++---- 7 files changed, 679 insertions(+), 147 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36146511..ed27a859 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -906,7 +906,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.2.0" -source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#0d0b0b573fe4836ede4c237dadd365033cd28296" +source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#32283d76a8d0342da74c4cc022a533c52dcf378f" dependencies = [ "bitflags 2.11.0", "wayland-backend", diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 360991de..0585e664 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -212,6 +212,7 @@ pub fn draw_surface_cursor( 1.0, false, [0; 4], + None, blur_strength, Kind::Cursor, &mut |elem| push(elem.into(), h), @@ -247,6 +248,7 @@ pub fn draw_dnd_icon( 1.0, false, [0; 4], + None, blur_strength, FRAME_TIME_FILTER, push, diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 60917112..ac49af7d 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -39,6 +39,7 @@ use crate::{ wayland::{ handlers::{ compositor::FRAME_TIME_FILTER, + corner_radius::{pad_rect, surface_corners, surface_padding}, data_device::get_dnd_icon, image_copy_capture::{FrameHolder, SessionData, render_session}, }, @@ -78,7 +79,7 @@ use smithay::{ utils::{ IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size, Time, Transform, }, - wayland::{dmabuf::get_dmabuf, session_lock::LockSurface}, + wayland::{compositor::with_states, dmabuf::get_dmabuf, session_lock::LockSurface}, }; #[cfg(feature = "debug")] @@ -838,6 +839,11 @@ where let mut geometry = popup.geometry().as_global(); geometry.loc += location; + let radii = with_states(popup.wl_surface(), |states| { + surface_corners(states, geometry.size.as_logical()) + }) + .unwrap_or([0; 4]); + push_render_elements_from_surface_tree( renderer, popup.wl_surface(), @@ -849,7 +855,8 @@ where Scale::from(scale), 1.0, false, - [0; 4], + radii, + None, blur_strength, FRAME_TIME_FILTER, &mut |elem| { @@ -868,6 +875,17 @@ where } => { let mut geometry = layer.geometry().as_global(); geometry.loc += location; + let geometry = geometry.to_local(output).as_logical(); + + let padded = with_states(layer.wl_surface(), |states| { + surface_padding(states, geometry.size) + .and_then(|padding| pad_rect(geometry, &padding)) + }) + .unwrap_or(geometry); + let radii = with_states(layer.wl_surface(), |states| { + surface_corners(states, padded.size) + }) + .unwrap_or([0; 4]); push_render_elements_from_surface_tree( renderer, @@ -876,11 +894,12 @@ where .to_local(output) .as_logical() .to_physical_precise_round(scale), - geometry.to_local(output).as_logical().to_f64(), + geometry.to_f64(), Scale::from(scale), 1.0, false, - [0; 4], + radii, + padded.to_f64(), blur_strength, FRAME_TIME_FILTER, &mut |elem| { @@ -909,6 +928,7 @@ where 1.0, false, [0; 4], + None, blur_strength, FRAME_TIME_FILTER, &mut |elem| elements.extend(crop_to_output(elem.into()).map(Into::into)), @@ -1056,6 +1076,7 @@ fn session_lock_elements( 1.0, false, [0; 4], + None, 0, FRAME_TIME_FILTER, push, diff --git a/src/backend/render/wayland/mod.rs b/src/backend/render/wayland/mod.rs index 9ac1109c..9f07d08b 100644 --- a/src/backend/render/wayland/mod.rs +++ b/src/backend/render/wayland/mod.rs @@ -35,6 +35,7 @@ pub fn push_render_elements_from_surface_tree( alpha: f32, should_clip: bool, radii: [u8; 4], + blur_geometry: impl Into>>, blur_strength: usize, kind: impl Into, push_above: &mut dyn FnMut(SurfaceRenderElement), @@ -44,7 +45,8 @@ pub fn push_render_elements_from_surface_tree( R::TextureId: Clone + 'static, { let location = location.into().to_f64(); - let geometry = geometry.into().to_f64(); + let geometry = geometry.into(); + let blur_geometry = blur_geometry.into(); let scale = scale.into(); let kind = kind.into(); let mut passed_main = false; @@ -87,10 +89,11 @@ pub fn push_render_elements_from_surface_tree( renderer, surface, states, location, alpha, kind, ) { Ok(Some(surface)) => { + let blur_geo = blur_geometry.unwrap_or(geometry); blur = BlurElement::from_surface( renderer, states, - geometry, + blur_geo, scale.x, radii, blur_strength, diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 4a88d2df..4db3e60a 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -4,9 +4,7 @@ use crate::{ wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree}, }, shell::focus::target::PointerFocusTarget, - wayland::{ - handlers::compositor::frame_time_filter_fn, protocols::corner_radius::CacheableCorners, - }, + wayland::handlers::{compositor::frame_time_filter_fn, corner_radius::surface_corners}, }; use std::{ borrow::Cow, @@ -193,22 +191,7 @@ impl CosmicSurface { pub fn corner_radius(&self, geometry_size: Size) -> Option<[u8; 4]> { self.wl_surface().and_then(|surface| { - with_states(&surface, |states| { - let mut guard = states.cached_state.get::(); - - // guard against corner radius being too large, potentially disconnecting the outline - let half_min_dim = - u8::try_from(geometry_size.w.min(geometry_size.h) / 2).unwrap_or(u8::MAX); - - let corners = guard.current().0?; - - Some([ - corners.top_left.min(half_min_dim), - corners.top_right.min(half_min_dim), - corners.bottom_right.min(half_min_dim), - corners.bottom_left.min(half_min_dim), - ]) - }) + with_states(&surface, |states| surface_corners(states, geometry_size)) }) } @@ -880,6 +863,10 @@ impl CosmicSurface { .to_physical_precise_round(scale); let mut geometry = popup.geometry().to_f64(); geometry.loc += location.to_f64().to_logical(scale) + popup_offset.to_f64(); + let radii = with_states(popup.wl_surface(), |states| { + surface_corners(states, geometry.size.to_i32_round()) + }) + .unwrap_or([0; 4]); push_render_elements_from_surface_tree( renderer, @@ -889,7 +876,8 @@ impl CosmicSurface { scale, alpha, false, - [0; 4], + radii, + None, blur_strength, scanout_kind_eval(None, scanout_node), push, @@ -934,6 +922,7 @@ impl CosmicSurface { alpha, should_clip, radii, + None, blur_strength, scanout_kind_eval(scanout_override, scanout_node), push_above, @@ -954,6 +943,7 @@ impl CosmicSurface { alpha, should_clip, radii, + None, blur_strength, scanout_kind_eval(scanout_override, scanout_node), push_above, diff --git a/src/wayland/handlers/corner_radius.rs b/src/wayland/handlers/corner_radius.rs index dbcae2dc..140b86ff 100644 --- a/src/wayland/handlers/corner_radius.rs +++ b/src/wayland/handlers/corner_radius.rs @@ -1,7 +1,9 @@ -use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1; +use smithay::utils::{Logical, Rectangle, Size}; +use smithay::wayland::compositor::SurfaceData; use crate::wayland::protocols::corner_radius::{ - CornerRadiusData, CornerRadiusHandler, CornerRadiusState, delegate_corner_radius, + CacheableCorners, CacheablePadding, CornerRadiusData, CornerRadiusHandler, CornerRadiusState, + CornerRadiusSurface, delegate_corner_radius, }; use crate::state::State; @@ -11,21 +13,25 @@ impl CornerRadiusHandler for State { &mut self.common.corner_radius_state } - fn set_corner_radius( - &mut self, - _: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, - data: &CornerRadiusData, - ) { + fn set_corner_radius(&mut self, data: &CornerRadiusData) { if force_redraw(self, data).is_none() { tracing::warn!("Failed to force redraw for corner radius change."); } } - fn unset_corner_radius( - &mut self, - _: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, - data: &CornerRadiusData, - ) { + fn unset_corner_radius(&mut self, data: &CornerRadiusData) { + if force_redraw(self, data).is_none() { + tracing::warn!("Failed to force redraw for corner radius reset."); + } + } + + fn set_padding(&mut self, data: &CornerRadiusData) { + if force_redraw(self, data).is_none() { + tracing::warn!("Failed to force redraw for corner radius change."); + } + } + + fn unset_padding(&mut self, data: &CornerRadiusData) { if force_redraw(self, data).is_none() { tracing::warn!("Failed to force redraw for corner radius reset."); } @@ -34,16 +40,83 @@ impl CornerRadiusHandler for State { fn force_redraw(state: &mut State, data: &CornerRadiusData) -> Option<()> { let guard = data.lock().unwrap(); + let shell = state.common.shell.read(); - let toplevel = guard.toplevel.upgrade().ok()?; - - let surface = state.common.xdg_shell_state.get_toplevel(&toplevel)?; - - let guard = state.common.shell.read(); - let output = guard.visible_output_for_surface(surface.wl_surface())?; + let output = match &guard.surface { + CornerRadiusSurface::Toplevel(toplevel) => { + let toplevel = toplevel.upgrade().ok()?; + let surface = state.common.xdg_shell_state.get_toplevel(&toplevel)?; + shell.visible_output_for_surface(surface.wl_surface())? + } + CornerRadiusSurface::Popup(popup) => { + let popup = popup.upgrade().ok()?; + let surface = state.common.xdg_shell_state.get_popup(&popup)?; + shell.visible_output_for_surface(surface.wl_surface())? + } + CornerRadiusSurface::Layer(layer) => { + let layer = layer.upgrade().ok()?; + let surface = state + .common + .layer_shell_state + .layer_surfaces() + .find(|l| l.shell_surface() == &layer)?; + shell.visible_output_for_surface(surface.wl_surface())? + } + }; state.backend.schedule_render(output); Some(()) } +pub fn surface_corners(states: &SurfaceData, size: Size) -> Option<[u8; 4]> { + let mut guard = states.cached_state.get::(); + + // guard against corner radius being too large, potentially disconnecting the outline + let half_min_dim = u8::try_from(size.w.min(size.h) / 2).unwrap_or(u8::MAX); + let corners = guard.current().0?; + + Some([ + u8::try_from(corners.top_left) + .unwrap_or(u8::MAX) + .min(half_min_dim), + u8::try_from(corners.top_right) + .unwrap_or(u8::MAX) + .min(half_min_dim), + u8::try_from(corners.bottom_right) + .unwrap_or(u8::MAX) + .min(half_min_dim), + u8::try_from(corners.bottom_left) + .unwrap_or(u8::MAX) + .min(half_min_dim), + ]) +} + +pub fn surface_padding(states: &SurfaceData, size: Size) -> Option<[i32; 4]> { + let mut guard = states.cached_state.get::(); + + // guard against padding being too large + let half_min_dim = size.w.min(size.h) / 2; + let padding = guard.current().0?; + + Some([ + padding.top.min(half_min_dim), + padding.right.min(half_min_dim), + padding.bottom.min(half_min_dim), + padding.left.min(half_min_dim), + ]) +} + +pub fn pad_rect( + mut rect: Rectangle, + padding: &[i32; 4], +) -> Option> { + rect.size.h = rect.size.h.checked_sub(padding[0])?; + rect.loc.x += padding[0]; + rect.size.w = rect.size.w.checked_sub(padding[1])?; + rect.size.h = rect.size.h.checked_sub(padding[2])?; + rect.size.w = rect.size.w.checked_sub(padding[3])?; + rect.loc.y += padding[3]; + Some(rect) +} + delegate_corner_radius!(State); diff --git a/src/wayland/protocols/corner_radius.rs b/src/wayland/protocols/corner_radius.rs index 5baafa23..f0edf343 100644 --- a/src/wayland/protocols/corner_radius.rs +++ b/src/wayland/protocols/corner_radius.rs @@ -1,12 +1,21 @@ +use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_layer_v1::{ + self, CosmicCornerRadiusLayerV1, +}; use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1; use cosmic_protocols::corner_radius::v1::server::{ cosmic_corner_radius_manager_v1, cosmic_corner_radius_toplevel_v1, }; -use smithay::utils::HookId; +use smithay::desktop::utils::bbox_from_surface_tree; +use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_popup::XdgPopup; +use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; +use smithay::reexports::wayland_server::New; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::utils::{HookId, Logical, Point, Rectangle}; use smithay::wayland::compositor::Cacheable; use smithay::wayland::compositor::add_pre_commit_hook; use smithay::wayland::compositor::with_states; -use smithay::wayland::shell::xdg::SurfaceCachedState; +use smithay::wayland::shell::wlr_layer::WlrLayerShellHandler; +use smithay::wayland::shell::xdg::{SurfaceCachedState, XdgShellSurfaceUserData}; use smithay::{ reexports::{ wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel, @@ -18,6 +27,7 @@ use std::sync::Mutex; use wayland_backend::server::GlobalId; type ToplevelHookId = Mutex)>>; +type LayerHookId = Mutex)>>; #[derive(Debug)] pub struct CornerRadiusState { @@ -33,12 +43,13 @@ impl CornerRadiusState { + Dispatch< cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, CornerRadiusData, - > + CornerRadiusHandler + > + Dispatch + + CornerRadiusHandler + 'static, { let global = dh .create_global::( - 1, + 2, (), ); CornerRadiusState { @@ -52,18 +63,12 @@ impl CornerRadiusState { } } -pub trait CornerRadiusHandler: XdgShellHandler { +pub trait CornerRadiusHandler: XdgShellHandler + WlrLayerShellHandler { fn corner_radius_state(&mut self) -> &mut CornerRadiusState; - fn set_corner_radius( - &mut self, - toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, - data: &CornerRadiusData, - ); - fn unset_corner_radius( - &mut self, - toplevel: &cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1, - data: &CornerRadiusData, - ); + fn set_corner_radius(&mut self, data: &CornerRadiusData); + fn unset_corner_radius(&mut self, data: &CornerRadiusData); + fn set_padding(&mut self, data: &CornerRadiusData); + fn unset_padding(&mut self, data: &CornerRadiusData); } impl GlobalDispatch @@ -72,6 +77,7 @@ where D: GlobalDispatch + Dispatch + Dispatch + + Dispatch + CornerRadiusHandler + 'static, { @@ -96,6 +102,7 @@ where D: GlobalDispatch + Dispatch + Dispatch + + Dispatch + CornerRadiusHandler + 'static, { @@ -115,6 +122,71 @@ where } cosmic_corner_radius_manager_v1::Request::GetCornerRadius { id, toplevel } => { if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { + new_xdg( + surface.wl_surface(), + CornerRadiusSurface::Toplevel(surface.xdg_toplevel().downgrade()), + resource, + data_init, + id, + ) + } // TODO: can this fail? + } + cosmic_corner_radius_manager_v1::Request::GetCornerRadiusSurface { id, surface } => { + if let Some(toplevel) = + state + .xdg_shell_state() + .toplevel_surfaces() + .iter() + .find(|toplevel| { + toplevel + .xdg_toplevel() + .data::() + .unwrap() + .xdg_surface() + == &surface + }) + { + new_xdg( + toplevel.wl_surface(), + CornerRadiusSurface::Toplevel(toplevel.xdg_toplevel().downgrade()), + resource, + data_init, + id, + ) + } else if let Some(popup) = + state + .xdg_shell_state() + .popup_surfaces() + .iter() + .find(|popup| { + popup + .xdg_popup() + .data::() + .unwrap() + .xdg_surface() + == &surface + }) + { + new_xdg( + popup.wl_surface(), + CornerRadiusSurface::Popup(popup.xdg_popup().downgrade()), + resource, + data_init, + id, + ) + } else { + resource.post_error( + cosmic_corner_radius_manager_v1::Error::NoRole as u32, + "xdg_surface has no known role object".to_string(), + ); + } + } + cosmic_corner_radius_manager_v1::Request::GetCornerRadiusLayer { id, layer } => { + if let Some(surface) = state + .shell_state() + .layer_surfaces() + .find(|surface| surface.shell_surface() == &layer) + { let radius_exists = with_states(surface.wl_surface(), |surface_data| { let hook_id = surface_data .data_map @@ -124,63 +196,33 @@ where }); if radius_exists.unwrap_or_default() { resource.post_error( - cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32, - format!("{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface"), - ); + cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32, + format!( + "{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface" + ), + ); } let data = Mutex::new(CornerRadiusInternal { - toplevel: toplevel.downgrade(), + surface: CornerRadiusSurface::Layer(layer.downgrade()), corners: None, + padding: None, }); let obj = data_init.init(id, data); let obj_downgrade = obj.downgrade(); let needs_hook = radius_exists.is_none(); if needs_hook { - let hook_id = add_pre_commit_hook::( - surface.wl_surface(), - move |_, _dh, surface| { - let corner_radii_too_big = with_states(surface, |surface_data| { - let corners = *surface_data - .cached_state - .get::() - .pending(); - surface_data - .cached_state - .get::() - .pending() - .geometry - .zip(corners.0.as_ref()) - .is_some_and(|(geo, corners)| { - let half_min_dim = - u8::try_from(geo.size.w.min(geo.size.h) / 2) - .unwrap_or(u8::MAX); - corners.top_right > half_min_dim - || corners.top_left > half_min_dim - || corners.bottom_right > half_min_dim - || corners.bottom_left > half_min_dim - }) - }); - - if corner_radii_too_big { - obj.post_error( - cosmic_corner_radius_toplevel_v1::Error::RadiusTooLarge - as u32, - format!("{obj:?} corner radius too large"), - ); - } - }, - ); - + let hook_id = + add_pre_commit_hook::(surface.wl_surface(), layer_radius_hook); with_states(surface.wl_surface(), |surface_data| { let hook_ids = surface_data .data_map - .get_or_insert_threadsafe(|| ToplevelHookId::new(None)); + .get_or_insert_threadsafe(|| LayerHookId::new(None)); let mut guard = hook_ids.lock().unwrap(); *guard = Some((hook_id, obj_downgrade)); }); } - } + } // TODO: can this fail? } _ => unimplemented!(), } @@ -197,6 +239,55 @@ where } } +fn new_xdg( + wl_surface: &WlSurface, + surface: CornerRadiusSurface, + resource: &cosmic_corner_radius_manager_v1::CosmicCornerRadiusManagerV1, + data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>, + id: New, +) where + D: GlobalDispatch + + Dispatch + + Dispatch + + CornerRadiusHandler + + 'static, +{ + let radius_exists = with_states(wl_surface, |surface_data| { + let hook_id = surface_data + .data_map + .get_or_insert_threadsafe(|| ToplevelHookId::new(None)); + let guard = hook_id.lock().unwrap(); + guard.as_ref().map(|(_, t)| t.upgrade().is_ok()) + }); + if radius_exists.unwrap_or_default() { + resource.post_error( + cosmic_corner_radius_manager_v1::Error::CornerRadiusExists as u32, + format!( + "{resource:?} CosmicCornerRadiusToplevelV1 object already exists for the surface" + ), + ); + } + let data = Mutex::new(CornerRadiusInternal { + surface, + corners: None, + padding: None, + }); + let obj = data_init.init(id, data); + let obj_downgrade = obj.downgrade(); + + let needs_hook = radius_exists.is_none(); + if needs_hook { + let hook_id = add_pre_commit_hook::(wl_surface, xdg_radius_hook); + with_states(wl_surface, |surface_data| { + let hook_ids = surface_data + .data_map + .get_or_insert_threadsafe(|| ToplevelHookId::new(None)); + let mut guard = hook_ids.lock().unwrap(); + *guard = Some((hook_id, obj_downgrade)); + }); + } +} + impl Dispatch for CornerRadiusState @@ -221,30 +312,35 @@ where let mut guard = data.lock().unwrap(); guard.corners = None; - let Ok(toplevel) = guard.toplevel.upgrade() else { + let Some(wl_surface) = (match &guard.surface { + CornerRadiusSurface::Toplevel(toplevel) => toplevel + .upgrade() + .ok() + .and_then(|toplevel| state.xdg_shell_state().get_toplevel(&toplevel)) + .map(|toplevel| toplevel.wl_surface().clone()), + CornerRadiusSurface::Popup(popup) => popup + .upgrade() + .ok() + .and_then(|popup| state.xdg_shell_state().get_popup(&popup)) + .map(|popup| popup.wl_surface().clone()), + CornerRadiusSurface::Layer(_) => unreachable!(), + }) else { return; }; - if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { - with_states(surface.wl_surface(), |surface_data| { - if let Some(hook_ids_mutex) = surface_data.data_map.get::() - { - let mut hook_id = hook_ids_mutex.lock().unwrap(); - *hook_id = None; - } - }); - } + with_states(&wl_surface, |surface_data| { + if let Some(hook_ids_mutex) = surface_data.data_map.get::() { + let mut hook_id = hook_ids_mutex.lock().unwrap(); + *hook_id = None; + } - if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { - with_states(surface.wl_surface(), |s| { - let mut cached = s.cached_state.get::(); - let pending = cached.pending(); - *pending = CacheableCorners(None); - }); - } + let mut cached = surface_data.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(None); + }); drop(guard); - state.unset_corner_radius(resource, data); + state.unset_corner_radius(data); } cosmic_corner_radius_toplevel_v1::Request::SetRadius { top_left, @@ -254,66 +350,388 @@ where } => { let mut guard = data.lock().unwrap(); guard.set_corner_radius(top_left, top_right, bottom_right, bottom_left); - let Ok(toplevel) = guard.toplevel.upgrade() else { + + let Some(wl_surface) = (match &guard.surface { + CornerRadiusSurface::Toplevel(toplevel) => toplevel + .upgrade() + .ok() + .and_then(|toplevel| state.xdg_shell_state().get_toplevel(&toplevel)) + .map(|toplevel| toplevel.wl_surface().clone()), + CornerRadiusSurface::Popup(popup) => popup + .upgrade() + .ok() + .and_then(|popup| state.xdg_shell_state().get_popup(&popup)) + .map(|popup| popup.wl_surface().clone()), + CornerRadiusSurface::Layer(_) => unreachable!(), + }) else { resource.post_error( cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32, format!("{:?} No toplevel found", resource), ); return; }; - - if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { - with_states(surface.wl_surface(), |s| { - let mut cached = s.cached_state.get::(); - let pending = cached.pending(); - *pending = CacheableCorners(guard.corners); - }); - } + with_states(&wl_surface, |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(guard.corners); + }); drop(guard); - state.set_corner_radius(resource, data); + state.set_corner_radius(data); } cosmic_corner_radius_toplevel_v1::Request::UnsetRadius => { let mut guard = data.lock().unwrap(); guard.corners = None; - let Ok(toplevel) = guard.toplevel.upgrade() else { + + let Some(wl_surface) = (match &guard.surface { + CornerRadiusSurface::Toplevel(toplevel) => toplevel + .upgrade() + .ok() + .and_then(|toplevel| state.xdg_shell_state().get_toplevel(&toplevel)) + .map(|toplevel| toplevel.wl_surface().clone()), + CornerRadiusSurface::Popup(popup) => popup + .upgrade() + .ok() + .and_then(|popup| state.xdg_shell_state().get_popup(&popup)) + .map(|popup| popup.wl_surface().clone()), + CornerRadiusSurface::Layer(_) => unreachable!(), + }) else { resource.post_error( cosmic_corner_radius_toplevel_v1::Error::ToplevelDestroyed as u32, format!("{:?} No toplevel found", resource), ); + return; }; - if let Some(surface) = state.xdg_shell_state().get_toplevel(&toplevel) { - with_states(surface.wl_surface(), |s| { - let mut cached = s.cached_state.get::(); - let pending = cached.pending(); - *pending = CacheableCorners(None); - }); - } + with_states(&wl_surface, |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(None); + }); drop(guard); - state.unset_corner_radius(resource, data); + state.unset_corner_radius(data); } _ => unimplemented!(), } } } +impl Dispatch + for CornerRadiusState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + CornerRadiusHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1, + request: ::Request, + data: &CornerRadiusData, + _dhandle: &DisplayHandle, + _data_init: &mut smithay::reexports::wayland_server::DataInit<'_, D>, + ) { + match request { + cosmic_corner_radius_layer_v1::Request::Destroy => { + let mut guard = data.lock().unwrap(); + guard.corners = None; + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |surface_data| { + if let Some(hook_ids_mutex) = surface_data.data_map.get::() { + let mut hook_id = hook_ids_mutex.lock().unwrap(); + *hook_id = None; + } + + let mut cached = surface_data.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(None); + + let mut cached = surface_data.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheablePadding(None); + }); + drop(guard); + + state.unset_corner_radius(data); + state.unset_padding(data); + } + cosmic_corner_radius_layer_v1::Request::SetRadius { + top_left, + top_right, + bottom_right, + bottom_left, + } => { + let mut guard = data.lock().unwrap(); + guard.set_corner_radius(top_left, top_right, bottom_right, bottom_left); + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(guard.corners); + }); + drop(guard); + + state.set_corner_radius(data); + } + cosmic_corner_radius_layer_v1::Request::UnsetRadius => { + let mut guard = data.lock().unwrap(); + guard.corners = None; + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheableCorners(None); + }); + drop(guard); + + state.unset_corner_radius(data); + } + cosmic_corner_radius_layer_v1::Request::SetPadding { + top, + right, + bottom, + left, + } => { + let mut guard = data.lock().unwrap(); + guard.set_padding(top, right, bottom, left); + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheablePadding(guard.padding); + }); + drop(guard); + + state.set_padding(data); + } + cosmic_corner_radius_layer_v1::Request::UnsetPadding => { + let mut guard = data.lock().unwrap(); + guard.corners = None; + + let CornerRadiusSurface::Layer(layer_surface) = &guard.surface else { + unreachable!("corner_radius_layer without layer shell?"); + }; + let Some(layer_surface) = layer_surface.upgrade().ok().and_then(|layer| { + state + .shell_state() + .layer_surfaces() + .find(|s| s.shell_surface() == &layer) + }) else { + resource.post_error( + cosmic_corner_radius_layer_v1::Error::LayerDestroyed as u32, + format!("{:?} No layer found", resource), + ); + return; + }; + + with_states(layer_surface.wl_surface(), |s| { + let mut cached = s.cached_state.get::(); + let pending = cached.pending(); + *pending = CacheablePadding(None); + }); + drop(guard); + + state.unset_padding(data); + } + _ => unimplemented!(), + } + } +} + +fn xdg_radius_hook(_state: &mut D, _dh: &DisplayHandle, surface: &WlSurface) { + with_states(surface, |surface_data| { + let corners = *surface_data + .cached_state + .get::() + .pending(); + if surface_data + .cached_state + .get::() + .pending() + .geometry + .zip(corners.0.as_ref()) + .is_some_and(|(geo, corners)| { + let half_min_dim = (geo.size.w.min(geo.size.h) / 2) as u32; + corners.top_right > half_min_dim + || corners.top_left > half_min_dim + || corners.bottom_right > half_min_dim + || corners.bottom_left > half_min_dim + }) + && let Some(hook) = surface_data.data_map.get::() + { + let hook_ref = hook.lock().unwrap(); + if let Some((_, obj)) = hook_ref.as_ref() + && let Ok(obj) = obj.upgrade() + { + obj.post_error( + cosmic_corner_radius_toplevel_v1::Error::RadiusTooLarge as u32, + format!("{obj:?} corner radius too large"), + ); + } + } + }); +} + +fn pad_rect( + mut rect: Rectangle, + padding: &Padding, +) -> Option> { + rect.size.h = rect.size.h.checked_sub(padding.top)?; + rect.loc.x += padding.top; + rect.size.w = rect.size.w.checked_sub(padding.left)?; + rect.loc.y += padding.left; + rect.size.h = rect.size.h.checked_sub(padding.bottom)?; + rect.size.w = rect.size.w.checked_sub(padding.right)?; + Some(rect) +} + +fn layer_radius_hook(_state: &mut D, _dh: &DisplayHandle, surface: &WlSurface) { + let bbox = bbox_from_surface_tree(surface, Point::default()); + with_states(surface, |surface_data| { + let corners = *surface_data + .cached_state + .get::() + .pending(); + let padding = *surface_data + .cached_state + .get::() + .pending(); + let empty = Padding::default(); + let Some(padded_box) = pad_rect(bbox, padding.0.as_ref().unwrap_or(&empty)) else { + if let Some(hook) = surface_data.data_map.get::() { + let hook_ref = hook.lock().unwrap(); + if let Some((_, obj)) = hook_ref.as_ref() + && let Ok(obj) = obj.upgrade() + { + obj.post_error( + cosmic_corner_radius_layer_v1::Error::PaddingTooLarge as u32, + format!("{obj:?} padding too large"), + ); + } + } + return; + }; + + if corners.0.as_ref().is_some_and(|corners| { + let half_min_dim = (padded_box.size.w.min(padded_box.size.h) / 2) as u32; + corners.top_right > half_min_dim + || corners.top_left > half_min_dim + || corners.bottom_right > half_min_dim + || corners.bottom_left > half_min_dim + }) && let Some(hook) = surface_data.data_map.get::() + { + let hook_ref = hook.lock().unwrap(); + if let Some((_, obj)) = hook_ref.as_ref() + && let Ok(obj) = obj.upgrade() + { + obj.post_error( + cosmic_corner_radius_layer_v1::Error::RadiusTooLarge as u32, + format!("{obj:?} corner radius too large"), + ); + } + } + }); +} + pub type CornerRadiusData = Mutex; +#[derive(Debug)] +pub enum CornerRadiusSurface { + Toplevel(Weak), + Popup(Weak), + Layer(Weak), +} + #[derive(Debug)] pub struct CornerRadiusInternal { - pub toplevel: Weak, + pub surface: CornerRadiusSurface, pub corners: Option, + pub padding: Option, } #[derive(Debug, Copy, Clone)] pub struct Corners { - pub top_left: u8, - pub top_right: u8, - pub bottom_right: u8, - pub bottom_left: u8, + pub top_left: u32, + pub top_right: u32, + pub bottom_right: u32, + pub bottom_left: u32, +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct Padding { + pub top: i32, + pub right: i32, + pub bottom: i32, + pub left: i32, } #[derive(Default, Debug, Copy, Clone)] @@ -328,6 +746,18 @@ impl Cacheable for CacheableCorners { } } +#[derive(Default, Debug, Copy, Clone)] +pub struct CacheablePadding(pub Option); + +impl Cacheable for CacheablePadding { + fn commit(&mut self, _dh: &DisplayHandle) -> Self { + *self + } + fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) { + *into = self; + } +} + impl CornerRadiusInternal { fn set_corner_radius( &mut self, @@ -337,13 +767,23 @@ impl CornerRadiusInternal { bottom_left: u32, ) { let corners = Corners { - top_left: top_left.clamp(u8::MIN as u32, u8::MAX as u32) as u8, - top_right: top_right.clamp(u8::MIN as u32, u8::MAX as u32) as u8, - bottom_right: bottom_right.clamp(u8::MIN as u32, u8::MAX as u32) as u8, - bottom_left: bottom_left.clamp(u8::MIN as u32, u8::MAX as u32) as u8, + top_left, + top_right, + bottom_right, + bottom_left, }; self.corners = Some(corners); } + + fn set_padding(&mut self, top: i32, right: i32, bottom: i32, left: i32) { + let padding = Padding { + top, + right, + bottom, + left, + }; + self.padding = Some(padding); + } } macro_rules! delegate_corner_radius { @@ -357,6 +797,9 @@ macro_rules! delegate_corner_radius { smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v1::CosmicCornerRadiusToplevelV1: CornerRadiusData ] => $crate::wayland::protocols::corner_radius::CornerRadiusState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_layer_v1::CosmicCornerRadiusLayerV1: CornerRadiusData + ] => $crate::wayland::protocols::corner_radius::CornerRadiusState); }; } pub(crate) use delegate_corner_radius; From ab9f13b36f7fd5ba5378e7d6c4d55ebfe3639d46 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 21 Apr 2026 18:16:43 +0200 Subject: [PATCH 33/56] iced: Handle blur internally --- src/backend/render/element.rs | 8 ++-- src/shell/element/mod.rs | 12 +++--- src/utils/iced/mod.rs | 69 ++++++++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index 103fa52f..51d282c1 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -1,6 +1,7 @@ use crate::{ backend::render::{GlMultiError, wayland::SurfaceRenderElement}, shell::{CosmicMappedRenderElement, WorkspaceRenderElement}, + utils::iced::IcedRenderElement, }; #[cfg(feature = "debug")] @@ -12,7 +13,6 @@ use smithay::{ Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, element::{ Element, Id, Kind, RenderElement, UnderlyingStorage, - memory::MemoryRenderBufferRenderElement, utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, }, gles::{GlesError, GlesRenderbuffer, GlesTexture, element::TextureShaderElement}, @@ -42,7 +42,7 @@ where Postprocess( CropRenderElement>>, ), - Zoom(MemoryRenderBufferRenderElement), + Zoom(IcedRenderElement), Damage(DamageElement), #[cfg(feature = "debug")] Egui(TextureRenderElement), @@ -344,13 +344,13 @@ where } } -impl From> for CosmicElement +impl From> for CosmicElement where R: AsGlowRenderer, R::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - fn from(value: MemoryRenderBufferRenderElement) -> Self { + fn from(value: IcedRenderElement) -> Self { Self::Zoom(value) } } diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 11861591..f5691b29 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -1,7 +1,10 @@ use crate::{ backend::render::element::AsGlowRenderer, state::State, - utils::{iced::IcedElementInternal, prelude::*}, + utils::{ + iced::{IcedElementInternal, IcedRenderElement}, + prelude::*, + }, }; use calloop::LoopHandle; use cosmic_comp_config::AppearanceConfig; @@ -13,7 +16,6 @@ use smithay::{ renderer::{ element::{ Element, Kind, RenderElement, UnderlyingStorage, - memory::MemoryRenderBufferRenderElement, utils::{CropRenderElement, RelocateRenderElement, RescaleRenderElement}, }, gles::element::PixelShaderElement, @@ -1127,7 +1129,7 @@ where GrabbedWindow(RescaleRenderElement>), FocusIndicator(PixelShaderElement), Overlay(PixelShaderElement), - StackHoverIndicator(MemoryRenderBufferRenderElement), + StackHoverIndicator(IcedRenderElement), #[cfg(feature = "debug")] Egui(TextureRenderElement), } @@ -1581,13 +1583,13 @@ where } } -impl From> for CosmicMappedRenderElement +impl From> for CosmicMappedRenderElement where R: AsGlowRenderer, R::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - fn from(elem: MemoryRenderBufferRenderElement) -> Self { + fn from(elem: IcedRenderElement) -> Self { CosmicMappedRenderElement::StackHoverIndicator(elem) } } diff --git a/src/utils/iced/mod.rs b/src/utils/iced/mod.rs index 9d57b46d..b03e1682 100644 --- a/src/utils/iced/mod.rs +++ b/src/utils/iced/mod.rs @@ -33,7 +33,7 @@ use smithay::{ allocator::Fourcc, input::{ButtonState, KeyState}, renderer::{ - ImportMem, Renderer, + ImportMem, element::{ Kind, memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement}, @@ -57,13 +57,20 @@ use smithay::{ }, output::Output, reexports::calloop::{self, LoopHandle, RegistrationToken, futures::Scheduler}, + render_elements, utils::{ Buffer as BufferCoords, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, Transform, }, }; -use crate::utils::iced::state::State; +use crate::{ + backend::render::{ + element::AsGlowRenderer, + wayland::blur_effect::{BlurElement, BlurState}, + }, + utils::iced::state::State, +}; static ID: LazyLock = LazyLock::new(|| Id::new("Program")); @@ -173,6 +180,7 @@ pub(crate) struct IcedElementInternal { outputs: HashSet, buffers: HashMap, (MemoryRenderBuffer, Option<(Vec, Color)>)>, pending_realloc: bool, + blur: BlurState, // state size: Size, @@ -225,6 +233,7 @@ impl Clone for IcedElementInternal

{ outputs: self.outputs.clone(), buffers: self.buffers.clone(), pending_realloc: self.pending_realloc, + blur: BlurState::default(), size: self.size, last_seat: self.last_seat.clone(), cursor_pos: self.cursor_pos, @@ -310,6 +319,7 @@ impl IcedElement

{ outputs: HashSet::new(), buffers: HashMap::new(), pending_realloc: false, + blur: BlurState::default(), size, cursor_pos: None, last_seat, @@ -342,7 +352,6 @@ impl IcedElement

{ let node = element .as_widget_mut() .layout( - // TODO Avoid creating a new tree here? &mut tree, &internal.renderer, &Limits::new(IcedSize::ZERO, IcedSize::INFINITE) @@ -386,6 +395,11 @@ impl IcedElement

{ self.0.lock().unwrap().update(true); } + pub fn with_theme(&self, f: impl FnOnce(&Theme) -> R) -> R { + let guard = self.0.lock().unwrap(); + f(&guard.theme) + } + pub fn set_theme(&self, theme: cosmic::Theme) { let mut guard = self.0.lock().unwrap(); guard.theme = theme.clone(); @@ -917,9 +931,11 @@ impl IcedElement

{ location: Point, mut scale: Scale, alpha: f32, - push: &mut dyn FnMut(MemoryRenderBufferRenderElement), + mut radii: [u8; 4], + push_above: &mut dyn FnMut(IcedRenderElement), + push_below: Option<&mut dyn FnMut(IcedRenderElement)>, ) where - R: Renderer + ImportMem, + R: AsGlowRenderer + ImportMem, R::TextureId: Send + Clone + 'static, { let mut internal = self.0.lock().unwrap(); @@ -1052,10 +1068,51 @@ impl IcedElement

{ Kind::Unspecified, ) { Ok(buffer) => { - push(buffer); + push_above(buffer.into()); } Err(err) => tracing::warn!("What? {:?}", err), } + + if internal_ref.theme.transparent { + for radius in radii.iter_mut() { + *radius = ((*radius as f64) * internal_ref.additional_scale).round() as u8; + } + + match BlurElement::from_state( + renderer, + &mut internal_ref.blur, + Rectangle::new( + location + .to_f64() + .to_logical(scale) + .upscale(internal_ref.additional_scale), + internal_ref + .size + .to_f64() + .upscale(internal_ref.additional_scale) + .to_i32_round(), + ), + scale.x, + radii, + (internal_ref.theme.cosmic().frosted as u8 + 1) as usize, + ) { + Ok(Some(elem)) => { + if let Some(push_below) = push_below { + push_below(elem.into()) + } else { + push_above(elem.into()) + } + } + Ok(None) => {} + Err(err) => tracing::warn!("Blur elem error: {:?}", err), + } + } } } } + +render_elements! { + pub IcedRenderElement where R: ImportMem + AsGlowRenderer; + UI=MemoryRenderBufferRenderElement, + Blur=BlurElement, +} From 6c76fdaa79b8f3ba05bfe0a012b243fe6714f55f Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 21 Apr 2026 18:18:39 +0200 Subject: [PATCH 34/56] element/window/stack: Use new IcedRenderElement --- src/shell/element/stack.rs | 92 ++++++++----------------------------- src/shell/element/window.rs | 89 ++++++++++------------------------- 2 files changed, 43 insertions(+), 138 deletions(-) diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 3b1aab87..92d29b75 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -4,14 +4,8 @@ use super::{ }; use crate::{ backend::render::{ - IndicatorShader, Key, Usage, - cursor::CursorState, - element::AsGlowRenderer, - shadow::ShadowShader, - wayland::{ - SurfaceRenderElement, - blur_effect::{BlurElement, BlurState}, - }, + IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer, + shadow::ShadowShader, wayland::SurfaceRenderElement, }, hooks::{Decorations, HOOKS}, shell::{ @@ -22,7 +16,7 @@ use crate::{ }, state::State, utils::{ - iced::{IcedElement, Program}, + iced::{IcedElement, IcedRenderElement, Program}, prelude::*, }, }; @@ -47,10 +41,7 @@ use smithay::{ input::KeyState, renderer::{ ImportAll, ImportMem, Renderer, - element::{ - Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage, - memory::MemoryRenderBufferRenderElement, - }, + element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage}, gles::element::PixelShaderElement, glow::GlowRenderer, utils::{CommitCounter, DamageSet, OpaqueRegions}, @@ -129,7 +120,6 @@ pub struct CosmicStackInternal { tiled: AtomicBool, theme: Mutex, appearance_conf: Mutex, - blur_state: Mutex, } impl CosmicStackInternal { @@ -156,9 +146,10 @@ impl CosmicStack { pub fn new>( windows: impl Iterator, handle: LoopHandle<'static, crate::state::State>, - theme: cosmic::Theme, + mut theme: cosmic::Theme, appearance: AppearanceConfig, ) -> CosmicStack { + theme.transparent = theme.cosmic().frosted_windows; let windows = windows.map(Into::into).collect::>(); assert!(!windows.is_empty()); @@ -188,7 +179,6 @@ impl CosmicStack { tiled: AtomicBool::new(false), theme: Mutex::new(theme.clone()), appearance_conf: Mutex::new(appearance), - blur_state: Default::default(), }, (width, TAB_HEIGHT), handle, @@ -811,11 +801,6 @@ impl CosmicStack { }) }); - self.0 - .push_render_elements(renderer, stack_loc, scale, alpha, &mut |elem| { - push_above(elem.into()) - }); - self.0.with_program(|p| { let windows = p.windows.lock().unwrap(); let active = p.active.load(Ordering::SeqCst); @@ -862,31 +847,19 @@ impl CosmicStack { ); }); - if frosted > 0 { - let geometry = Rectangle::new( - stack_loc.to_f64().to_logical(scale), - Size::new( - geometry.size.to_f64().to_logical(scale).w, - TAB_HEIGHT as f64, - ), - ); - if let Ok(Some(elem)) = self.0.with_program(|p| { - let mut state = p.blur_state.lock().unwrap(); - BlurElement::from_state( - renderer, - &mut state, - geometry, - scale.x, - radii.map(|[_, b, _, d]| [0, b, 0, d]).unwrap_or([0; 4]), - frosted, - ) - }) { - push_below(elem.into()); - } - } + self.0.push_render_elements( + renderer, + stack_loc, + scale, + alpha, + radii.map(|[_, b, _, d]| [0, b, 0, d]).unwrap_or([0; 4]), + &mut |elem| push_above(elem.into()), + Some(&mut |elem| push_below(elem.into())), + ); } - pub(crate) fn set_theme(&self, theme: cosmic::Theme) { + pub(crate) fn set_theme(&self, mut theme: cosmic::Theme) { + theme.transparent = theme.cosmic().frosted_windows; self.0.with_program(|p| { *p.theme.lock().unwrap() = theme.clone(); }); @@ -1929,27 +1902,20 @@ impl TouchTarget for CosmicStack { } pub enum CosmicStackRenderElement { - Header(MemoryRenderBufferRenderElement), - HeaderBlur(BlurElement), + Header(IcedRenderElement), Shadow(PixelShaderElement), Border(PixelShaderElement), Window(SurfaceRenderElement), } -impl From> +impl From> for CosmicStackRenderElement { - fn from(value: MemoryRenderBufferRenderElement) -> Self { + fn from(value: IcedRenderElement) -> Self { Self::Header(value) } } -impl From for CosmicStackRenderElement { - fn from(value: BlurElement) -> Self { - Self::HeaderBlur(value) - } -} - impl From> for CosmicStackRenderElement { @@ -1966,7 +1932,6 @@ where fn id(&self) -> &RendererId { match self { CosmicStackRenderElement::Header(elem) => elem.id(), - CosmicStackRenderElement::HeaderBlur(elem) => elem.id(), CosmicStackRenderElement::Shadow(elem) => elem.id(), CosmicStackRenderElement::Border(elem) => elem.id(), CosmicStackRenderElement::Window(elem) => elem.id(), @@ -1976,7 +1941,6 @@ where fn current_commit(&self) -> CommitCounter { match self { CosmicStackRenderElement::Header(elem) => elem.current_commit(), - CosmicStackRenderElement::HeaderBlur(elem) => elem.current_commit(), CosmicStackRenderElement::Shadow(elem) => elem.current_commit(), CosmicStackRenderElement::Border(elem) => elem.current_commit(), CosmicStackRenderElement::Window(elem) => elem.current_commit(), @@ -1986,7 +1950,6 @@ where fn src(&self) -> Rectangle { match self { CosmicStackRenderElement::Header(elem) => elem.src(), - CosmicStackRenderElement::HeaderBlur(elem) => elem.src(), CosmicStackRenderElement::Shadow(elem) => elem.src(), CosmicStackRenderElement::Border(elem) => elem.src(), CosmicStackRenderElement::Window(elem) => elem.src(), @@ -1996,7 +1959,6 @@ where fn geometry(&self, scale: Scale) -> Rectangle { match self { CosmicStackRenderElement::Header(elem) => elem.geometry(scale), - CosmicStackRenderElement::HeaderBlur(elem) => elem.geometry(scale), CosmicStackRenderElement::Shadow(elem) => elem.geometry(scale), CosmicStackRenderElement::Border(elem) => elem.geometry(scale), CosmicStackRenderElement::Window(elem) => elem.geometry(scale), @@ -2006,7 +1968,6 @@ where fn location(&self, scale: Scale) -> Point { match self { CosmicStackRenderElement::Header(elem) => elem.location(scale), - CosmicStackRenderElement::HeaderBlur(elem) => elem.location(scale), CosmicStackRenderElement::Shadow(elem) => elem.location(scale), CosmicStackRenderElement::Border(elem) => elem.location(scale), CosmicStackRenderElement::Window(elem) => elem.location(scale), @@ -2016,7 +1977,6 @@ where fn transform(&self) -> Transform { match self { CosmicStackRenderElement::Header(elem) => elem.transform(), - CosmicStackRenderElement::HeaderBlur(elem) => elem.transform(), CosmicStackRenderElement::Shadow(elem) => elem.transform(), CosmicStackRenderElement::Border(elem) => elem.transform(), CosmicStackRenderElement::Window(elem) => elem.transform(), @@ -2030,7 +1990,6 @@ where ) -> DamageSet { match self { CosmicStackRenderElement::Header(elem) => elem.damage_since(scale, commit), - CosmicStackRenderElement::HeaderBlur(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Shadow(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Border(elem) => elem.damage_since(scale, commit), CosmicStackRenderElement::Window(elem) => elem.damage_since(scale, commit), @@ -2040,7 +1999,6 @@ where fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { match self { CosmicStackRenderElement::Header(elem) => elem.opaque_regions(scale), - CosmicStackRenderElement::HeaderBlur(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Shadow(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Border(elem) => elem.opaque_regions(scale), CosmicStackRenderElement::Window(elem) => elem.opaque_regions(scale), @@ -2050,7 +2008,6 @@ where fn alpha(&self) -> f32 { match self { CosmicStackRenderElement::Header(elem) => elem.alpha(), - CosmicStackRenderElement::HeaderBlur(elem) => elem.alpha(), CosmicStackRenderElement::Shadow(elem) => elem.alpha(), CosmicStackRenderElement::Border(elem) => elem.alpha(), CosmicStackRenderElement::Window(elem) => elem.alpha(), @@ -2060,7 +2017,6 @@ where fn kind(&self) -> Kind { match self { CosmicStackRenderElement::Header(elem) => elem.kind(), - CosmicStackRenderElement::HeaderBlur(elem) => elem.kind(), CosmicStackRenderElement::Shadow(elem) => elem.kind(), CosmicStackRenderElement::Border(elem) => elem.kind(), CosmicStackRenderElement::Window(elem) => elem.kind(), @@ -2070,7 +2026,6 @@ where fn is_framebuffer_effect(&self) -> bool { match self { CosmicStackRenderElement::Header(elem) => elem.is_framebuffer_effect(), - CosmicStackRenderElement::HeaderBlur(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Shadow(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Border(elem) => elem.is_framebuffer_effect(), CosmicStackRenderElement::Window(elem) => elem.is_framebuffer_effect(), @@ -2096,9 +2051,6 @@ where CosmicStackRenderElement::Header(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } - CosmicStackRenderElement::HeaderBlur(elem) => { - RenderElement::::draw(elem, frame, src, dst, damage, opaque_regions, cache) - } CosmicStackRenderElement::Shadow(elem) | CosmicStackRenderElement::Border(elem) => { RenderElement::::draw( elem, @@ -2120,7 +2072,6 @@ where fn underlying_storage(&self, renderer: &mut R) -> Option> { match self { CosmicStackRenderElement::Header(elem) => elem.underlying_storage(renderer), - CosmicStackRenderElement::HeaderBlur(elem) => elem.underlying_storage(renderer), CosmicStackRenderElement::Shadow(elem) | CosmicStackRenderElement::Border(elem) => { elem.underlying_storage(renderer.glow_renderer_mut()) } @@ -2139,9 +2090,6 @@ where CosmicStackRenderElement::Header(elem) => { elem.capture_framebuffer(frame, src, dst, cache) } - CosmicStackRenderElement::HeaderBlur(elem) => { - RenderElement::::capture_framebuffer(elem, frame, src, dst, cache) - } CosmicStackRenderElement::Shadow(elem) | CosmicStackRenderElement::Border(elem) => { RenderElement::::capture_framebuffer( elem, diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index b978cfd3..09c59f90 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,13 +1,7 @@ use crate::{ backend::render::{ - IndicatorShader, Key, Usage, - cursor::CursorState, - element::AsGlowRenderer, - shadow::ShadowShader, - wayland::{ - SurfaceRenderElement, - blur_effect::{BlurElement, BlurState}, - }, + IndicatorShader, Key, Usage, cursor::CursorState, element::AsGlowRenderer, + shadow::ShadowShader, wayland::SurfaceRenderElement, }, hooks::{Decorations, HOOKS}, shell::{ @@ -17,7 +11,7 @@ use crate::{ }, state::State, utils::{ - iced::{IcedElement, Program}, + iced::{IcedElement, IcedRenderElement, Program}, prelude::*, }, }; @@ -30,10 +24,7 @@ use smithay::{ input::KeyState, renderer::{ ImportAll, ImportMem, Renderer, - element::{ - Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage, - memory::MemoryRenderBufferRenderElement, - }, + element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage}, gles::element::PixelShaderElement, glow::GlowRenderer, utils::{CommitCounter, DamageSet, OpaqueRegions}, @@ -99,7 +90,6 @@ pub struct CosmicWindowInternal { tiled: AtomicBool, theme: Mutex, appearance_conf: Mutex, - blur_state: Mutex, } #[repr(u8)] @@ -200,12 +190,13 @@ impl CosmicWindow { pub fn new( window: impl Into, handle: LoopHandle<'static, crate::state::State>, - theme: cosmic::Theme, + mut theme: cosmic::Theme, appearance: AppearanceConfig, ) -> CosmicWindow { let window = window.into(); let width = window.geometry().size.w; let last_title = window.title(); + theme.transparent = theme.cosmic().frosted_windows; if appearance.clip_floating_windows { window.set_tiled(true); @@ -220,7 +211,6 @@ impl CosmicWindow { tiled: AtomicBool::new(false), theme: Mutex::new(theme.clone()), appearance_conf: Mutex::new(appearance), - blur_state: Mutex::default(), }, (width, SSD_HEIGHT), handle, @@ -558,7 +548,7 @@ impl CosmicWindow { push_above(elem); } - let frosted = self.0.with_program(|p| { + self.0.with_program(|p| { if has_ssd { radii[1] = 0; radii[3] = 0; @@ -582,8 +572,6 @@ impl CosmicWindow { &mut |elem| push_above(elem.into()), Some(&mut |elem| push_below(elem.into())), ); - - frosted }); if has_ssd { @@ -591,26 +579,20 @@ impl CosmicWindow { + self .0 .with_program(|p| p.window.geometry().loc.to_physical_precise_round(scale)); - if frosted > 0 { - let geometry = Rectangle::new( - ssd_loc.to_f64().to_logical(scale), - Size::new(geo.size.w, SSD_HEIGHT as f64), - ); - if let Ok(Some(elem)) = self.0.with_program(|p| { - let mut state = p.blur_state.lock().unwrap(); - BlurElement::from_state(renderer, &mut state, geometry, scale.x, radii, frosted) - }) { - push_below(elem.into()); - } - } - self.0 - .push_render_elements(renderer, ssd_loc, scale, alpha, &mut |elem| { - push_above(elem.into()) - }); + self.0.push_render_elements( + renderer, + ssd_loc, + scale, + alpha, + radii, + &mut |elem| push_above(elem.into()), + Some(&mut |elem| push_below(elem.into())), + ); } } - pub(crate) fn set_theme(&self, theme: cosmic::Theme) { + pub(crate) fn set_theme(&self, mut theme: cosmic::Theme) { + theme.transparent = theme.cosmic().frosted_windows; self.0.with_program(|p| { *p.theme.lock().unwrap() = theme.clone(); }); @@ -1267,29 +1249,22 @@ impl WaylandFocus for CosmicWindow { } } -pub enum CosmicWindowRenderElement { - Header(MemoryRenderBufferRenderElement), - HeaderBlur(BlurElement), +pub enum CosmicWindowRenderElement { + Header(IcedRenderElement), Shadow(PixelShaderElement), Border(PixelShaderElement), Window(SurfaceRenderElement), } -impl From> +impl From> for CosmicWindowRenderElement { - fn from(value: MemoryRenderBufferRenderElement) -> Self { + fn from(value: IcedRenderElement) -> Self { Self::Header(value) } } -impl From for CosmicWindowRenderElement { - fn from(value: BlurElement) -> Self { - Self::HeaderBlur(value) - } -} - -impl From> +impl From> for CosmicWindowRenderElement { fn from(value: SurfaceRenderElement) -> Self { @@ -1305,7 +1280,6 @@ where fn id(&self) -> &RendererId { match self { CosmicWindowRenderElement::Header(elem) => elem.id(), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.id(), CosmicWindowRenderElement::Shadow(elem) => elem.id(), CosmicWindowRenderElement::Border(elem) => elem.id(), CosmicWindowRenderElement::Window(elem) => elem.id(), @@ -1315,7 +1289,6 @@ where fn current_commit(&self) -> CommitCounter { match self { CosmicWindowRenderElement::Header(elem) => elem.current_commit(), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.current_commit(), CosmicWindowRenderElement::Shadow(elem) => elem.current_commit(), CosmicWindowRenderElement::Border(elem) => elem.current_commit(), CosmicWindowRenderElement::Window(elem) => elem.current_commit(), @@ -1325,7 +1298,6 @@ where fn src(&self) -> Rectangle { match self { CosmicWindowRenderElement::Header(elem) => elem.src(), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.src(), CosmicWindowRenderElement::Shadow(elem) => elem.src(), CosmicWindowRenderElement::Border(elem) => elem.src(), CosmicWindowRenderElement::Window(elem) => elem.src(), @@ -1335,7 +1307,6 @@ where fn geometry(&self, scale: Scale) -> Rectangle { match self { CosmicWindowRenderElement::Header(elem) => elem.geometry(scale), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.geometry(scale), CosmicWindowRenderElement::Shadow(elem) => elem.geometry(scale), CosmicWindowRenderElement::Border(elem) => elem.geometry(scale), CosmicWindowRenderElement::Window(elem) => elem.geometry(scale), @@ -1345,7 +1316,6 @@ where fn location(&self, scale: Scale) -> Point { match self { CosmicWindowRenderElement::Header(elem) => elem.location(scale), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.location(scale), CosmicWindowRenderElement::Shadow(elem) => elem.location(scale), CosmicWindowRenderElement::Border(elem) => elem.location(scale), CosmicWindowRenderElement::Window(elem) => elem.location(scale), @@ -1355,7 +1325,6 @@ where fn transform(&self) -> Transform { match self { CosmicWindowRenderElement::Header(elem) => elem.transform(), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.transform(), CosmicWindowRenderElement::Shadow(elem) => elem.transform(), CosmicWindowRenderElement::Border(elem) => elem.transform(), CosmicWindowRenderElement::Window(elem) => elem.transform(), @@ -1369,7 +1338,6 @@ where ) -> DamageSet { match self { CosmicWindowRenderElement::Header(elem) => elem.damage_since(scale, commit), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Shadow(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Border(elem) => elem.damage_since(scale, commit), CosmicWindowRenderElement::Window(elem) => elem.damage_since(scale, commit), @@ -1379,7 +1347,6 @@ where fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { match self { CosmicWindowRenderElement::Header(elem) => elem.opaque_regions(scale), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Shadow(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Border(elem) => elem.opaque_regions(scale), CosmicWindowRenderElement::Window(elem) => elem.opaque_regions(scale), @@ -1389,7 +1356,6 @@ where fn alpha(&self) -> f32 { match self { CosmicWindowRenderElement::Header(elem) => elem.alpha(), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.alpha(), CosmicWindowRenderElement::Shadow(elem) => elem.alpha(), CosmicWindowRenderElement::Border(elem) => elem.alpha(), CosmicWindowRenderElement::Window(elem) => elem.alpha(), @@ -1399,7 +1365,6 @@ where fn kind(&self) -> Kind { match self { CosmicWindowRenderElement::Header(elem) => elem.kind(), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.kind(), CosmicWindowRenderElement::Shadow(elem) => elem.kind(), CosmicWindowRenderElement::Border(elem) => elem.kind(), CosmicWindowRenderElement::Window(elem) => elem.kind(), @@ -1409,7 +1374,6 @@ where fn is_framebuffer_effect(&self) -> bool { match self { CosmicWindowRenderElement::Header(elem) => elem.is_framebuffer_effect(), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Shadow(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Border(elem) => elem.is_framebuffer_effect(), CosmicWindowRenderElement::Window(elem) => elem.is_framebuffer_effect(), @@ -1435,9 +1399,6 @@ where CosmicWindowRenderElement::Header(elem) => { elem.draw(frame, src, dst, damage, opaque_regions, cache) } - CosmicWindowRenderElement::HeaderBlur(elem) => { - RenderElement::::draw(elem, frame, src, dst, damage, opaque_regions, cache) - } CosmicWindowRenderElement::Shadow(elem) | CosmicWindowRenderElement::Border(elem) => { RenderElement::::draw( elem, @@ -1459,7 +1420,6 @@ where fn underlying_storage(&self, renderer: &mut R) -> Option> { match self { CosmicWindowRenderElement::Header(elem) => elem.underlying_storage(renderer), - CosmicWindowRenderElement::HeaderBlur(elem) => elem.underlying_storage(renderer), CosmicWindowRenderElement::Shadow(elem) | CosmicWindowRenderElement::Border(elem) => { elem.underlying_storage(renderer.glow_renderer_mut()) } @@ -1478,9 +1438,6 @@ where CosmicWindowRenderElement::Header(elem) => { elem.capture_framebuffer(frame, src, dst, cache) } - CosmicWindowRenderElement::HeaderBlur(elem) => { - RenderElement::::capture_framebuffer(elem, frame, src, dst, cache) - } CosmicWindowRenderElement::Shadow(elem) | CosmicWindowRenderElement::Border(elem) => { RenderElement::::capture_framebuffer( elem, From db06770a6b9156d662b0d0b7c0c4e798f465b788 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 21 Apr 2026 18:19:01 +0200 Subject: [PATCH 35/56] element: Refactor remaining libcosmic elements for blur --- src/shell/element/resize_indicator.rs | 264 ++++++++++++++++++++++---- src/shell/element/stack_hover.rs | 78 ++++++-- src/shell/element/swap_indicator.rs | 80 ++++++-- src/shell/grabs/menu/mod.rs | 19 +- src/shell/grabs/moving.rs | 22 +-- src/shell/layout/floating/mod.rs | 40 ++-- src/shell/layout/tiling/mod.rs | 32 ++-- src/shell/mod.rs | 13 +- src/shell/zoom.rs | 38 ++-- 9 files changed, 444 insertions(+), 142 deletions(-) diff --git a/src/shell/element/resize_indicator.rs b/src/shell/element/resize_indicator.rs index d1839e6e..fb5441c3 100644 --- a/src/shell/element/resize_indicator.rs +++ b/src/shell/element/resize_indicator.rs @@ -1,10 +1,11 @@ use std::sync::{Arc, Mutex}; use crate::{ + backend::render::element::AsGlowRenderer, config::Config, fl, shell::grabs::ResizeEdge, - utils::iced::{IcedElement, Program}, + utils::iced::{IcedElement, IcedRenderElement, Program}, }; use calloop::LoopHandle; @@ -19,48 +20,233 @@ use cosmic::{ widget::{icon::from_name, text}, }; use cosmic_settings_config::shortcuts::action::{Action, ResizeDirection}; -use smithay::utils::Size; +use smithay::{ + backend::renderer::ImportMem, + desktop::space::SpaceElement, + output::Output, + utils::{Logical, Physical, Point, Rectangle, Scale, Size}, +}; -pub type ResizeIndicator = IcedElement; +#[derive(Debug, Clone)] +pub struct ResizeIndicator { + edges: ResizeEdge, + direction: Arc>, + size: Size, -pub fn resize_indicator( - direction: ResizeDirection, - config: &Config, - evlh: LoopHandle<'static, crate::state::State>, - theme: cosmic::Theme, -) -> ResizeIndicator { - ResizeIndicator::new( - ResizeIndicatorInternal { - edges: Mutex::new(ResizeEdge::all()), - direction, - shortcut1: config - .shortcuts - .iter() - .find_map(|(pattern, action)| { - (*action == Action::Resizing(ResizeDirection::Outwards)).then_some(pattern) - }) - .map(|pattern| format!("{}: ", pattern.to_string())) - .unwrap_or_else(|| crate::fl!("unknown-keybinding")), - shortcut2: config - .shortcuts - .iter() - .find_map(|(pattern, action)| { - (*action == Action::Resizing(ResizeDirection::Inwards)).then_some(pattern) - }) - .map(|pattern| format!("{}: ", pattern.to_string())) - .unwrap_or_else(|| crate::fl!("unknown-keybinding")), - }, - Size::from((1, 1)), - evlh, - theme, - ) + center_elem: IcedElement, + left_elem: IcedElement, + top_elem: IcedElement, + right_elem: IcedElement, + down_elem: IcedElement, +} + +const ARROW_SIZE: i32 = 36; + +impl ResizeIndicator { + pub fn new( + direction: ResizeDirection, + config: &Config, + evlh: LoopHandle<'static, crate::state::State>, + mut theme: cosmic::Theme, + ) -> ResizeIndicator { + theme.transparent = theme.cosmic().frosted_system_interface; + let direction = Arc::new(Mutex::new(direction)); + + ResizeIndicator { + edges: ResizeEdge::all(), + direction: direction.clone(), + size: Size::default(), + center_elem: IcedElement::new( + ResizeIndicatorInternal { + shortcut1: config + .shortcuts + .iter() + .find_map(|(pattern, action)| { + (*action == Action::Resizing(ResizeDirection::Outwards)) + .then_some(pattern) + }) + .map(|pattern| format!("{}: ", pattern.to_string())) + .unwrap_or_else(|| crate::fl!("unknown-keybinding")), + shortcut2: config + .shortcuts + .iter() + .find_map(|(pattern, action)| { + (*action == Action::Resizing(ResizeDirection::Inwards)) + .then_some(pattern) + }) + .map(|pattern| format!("{}: ", pattern.to_string())) + .unwrap_or_else(|| crate::fl!("unknown-keybinding")), + }, + Size::from((1, 1)), + evlh.clone(), + theme.clone(), + ), + left_elem: IcedElement::new( + ResizeIndicatorArrow { + direction: direction.clone(), + icon_outwards: "go-previous-symbolic", + icon_inwards: "go-next-symbolic", + }, + Size::from((ARROW_SIZE, ARROW_SIZE)), + evlh.clone(), + theme.clone(), + ), + top_elem: IcedElement::new( + ResizeIndicatorArrow { + direction: direction.clone(), + icon_outwards: "go-up-symbolic", + icon_inwards: "go-down-symbolic", + }, + Size::from((ARROW_SIZE, ARROW_SIZE)), + evlh.clone(), + theme.clone(), + ), + right_elem: IcedElement::new( + ResizeIndicatorArrow { + direction: direction.clone(), + icon_outwards: "go-next-symbolic", + icon_inwards: "go-previous-symbolic", + }, + Size::from((ARROW_SIZE, ARROW_SIZE)), + evlh.clone(), + theme.clone(), + ), + down_elem: IcedElement::new( + ResizeIndicatorArrow { + direction, + icon_outwards: "go-down-symbolic", + icon_inwards: "go-up-symbolic", + }, + Size::from((ARROW_SIZE, ARROW_SIZE)), + evlh.clone(), + theme.clone(), + ), + } + } + + pub fn resize(&mut self, size: Size) { + let minimum = self.center_elem.minimum_size(); + let new_size = Size::::new(size.w.min(minimum.w), size.h.min(minimum.h)); + self.center_elem.resize(new_size); + self.size = size; + } + + pub fn push_render_elements( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + alpha: f32, + push_above: &mut dyn FnMut(IcedRenderElement), + mut push_below: Option<&mut dyn FnMut(IcedRenderElement)>, + ) where + R: AsGlowRenderer + ImportMem, + R::TextureId: Send + Clone + 'static, + { + let elem_size = self.center_elem.current_size(); + let center_location = Point::new( + self.size.w.saturating_sub(elem_size.w) / 2, + self.size.h.saturating_sub(elem_size.h) / 2, + ); + let left_location = Point::new(0, self.size.h.saturating_sub(ARROW_SIZE) / 2); + let top_location = Point::new(self.size.w.saturating_sub(ARROW_SIZE) / 2, 0); + let right_location = Point::new( + self.size.w.saturating_sub(ARROW_SIZE), + self.size.h.saturating_sub(ARROW_SIZE) / 2, + ); + let down_location = Point::new( + self.size.w.saturating_sub(ARROW_SIZE) / 2, + self.size.h.saturating_sub(ARROW_SIZE), + ); + let radii = self + .center_elem + .with_theme(|theme| theme.cosmic().radius_s()) + .map(|x| x.round() as u8); + + self.center_elem.push_render_elements( + renderer, + location + center_location.to_physical_precise_round(scale), + scale, + alpha, + radii, + push_above, + if let Some(push_below) = push_below.as_mut() { + Some(&mut **push_below) + } else { + None + }, + ); + let mut render = move |elem: &IcedElement, + loc: Point| { + elem.push_render_elements( + renderer, + location + loc.to_physical_precise_round(scale), + scale, + alpha, + radii, + push_above, + if let Some(push_below) = push_below.as_mut() { + Some(&mut **push_below) + } else { + None + }, + ); + }; + + if self.edges.contains(ResizeEdge::LEFT) { + render(&self.left_elem, left_location); + } + if self.edges.contains(ResizeEdge::TOP) { + render(&self.top_elem, top_location); + } + if self.edges.contains(ResizeEdge::RIGHT) { + render(&self.right_elem, right_location); + } + if self.edges.contains(ResizeEdge::BOTTOM) { + render(&self.down_elem, down_location); + } + } + + pub fn set_edges(&mut self, edges: ResizeEdge) { + self.edges = edges; + } + + pub fn set_direction(&self, direction: ResizeDirection) { + let mut dir_ref = self.direction.lock().unwrap(); + if *dir_ref != direction { + *dir_ref = direction; + self.left_elem.force_redraw(); + self.top_elem.force_redraw(); + self.right_elem.force_redraw(); + self.down_elem.force_redraw(); + } + } + + pub fn output_enter(&self, output: &Output) { + self.center_elem + .output_enter(output, Rectangle::default() /*unused*/); + self.left_elem + .output_enter(output, Rectangle::default() /*unused*/); + self.top_elem + .output_enter(output, Rectangle::default() /*unused*/); + self.right_elem + .output_enter(output, Rectangle::default() /*unused*/); + self.down_elem + .output_enter(output, Rectangle::default() /*unused*/); + } + + pub fn output_leave(&self, output: &Output) { + self.center_elem.output_leave(output); + self.left_elem.output_leave(output); + self.top_elem.output_leave(output); + self.right_elem.output_leave(output); + self.down_elem.output_leave(output); + } } pub struct ResizeIndicatorInternal { - pub edges: Mutex, - pub direction: ResizeDirection, - pub shortcut1: String, - pub shortcut2: String, + shortcut1: String, + shortcut2: String, } impl Program for ResizeIndicatorInternal { @@ -94,7 +280,7 @@ impl Program for ResizeIndicatorInternal { text_color: Some(Color::from(theme.cosmic().accent.on)), background: Some(Background::Color(background.into())), border: Border { - radius: 18.0.into(), + radius: theme.cosmic().radius_s().into(), width: 0.0, color: Color::TRANSPARENT, }, diff --git a/src/shell/element/stack_hover.rs b/src/shell/element/stack_hover.rs index d6a057c4..f1238404 100644 --- a/src/shell/element/stack_hover.rs +++ b/src/shell/element/stack_hover.rs @@ -1,6 +1,7 @@ use crate::{ + backend::render::element::AsGlowRenderer, fl, - utils::iced::{IcedElement, Program}, + utils::iced::{IcedElement, IcedRenderElement, Program}, }; use calloop::LoopHandle; @@ -14,16 +15,72 @@ use cosmic::{ theme, widget::{icon::from_name, space, text}, }; -use smithay::utils::{Logical, Size}; +use smithay::{ + backend::renderer::ImportMem, + desktop::space::SpaceElement, + output::Output, + utils::{Logical, Physical, Point, Rectangle, Scale, Size}, +}; -pub type StackHover = IcedElement; +#[derive(Debug)] +pub struct StackHover { + location: Point, + elem: IcedElement, +} -pub fn stack_hover( - evlh: LoopHandle<'static, crate::state::State>, - size: Size, - theme: cosmic::Theme, -) -> StackHover { - StackHover::new(StackHoverInternal, size, evlh, theme) +impl StackHover { + pub fn new( + evlh: LoopHandle<'static, crate::state::State>, + size: Size, + mut theme: cosmic::Theme, + ) -> StackHover { + theme.transparent = theme.cosmic().frosted_system_interface; + + let elem = IcedElement::new(StackHoverInternal, size, evlh, theme); + let minimum = elem.minimum_size(); + let new_size = Size::::new(size.w.min(minimum.w), size.h.min(minimum.h)); + let location = Point::new( + size.w.saturating_sub(new_size.w) / 2, + size.h.saturating_sub(new_size.h) / 2, + ); + elem.resize(new_size); + + StackHover { location, elem } + } + + pub fn push_render_elements( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + alpha: f32, + push_above: &mut dyn FnMut(IcedRenderElement), + push_below: Option<&mut dyn FnMut(IcedRenderElement)>, + ) where + R: AsGlowRenderer + ImportMem, + R::TextureId: Send + Clone + 'static, + { + self.elem.push_render_elements( + renderer, + location + self.location.to_physical_precise_round(scale), + scale, + alpha, + self.elem + .with_theme(|theme| theme.cosmic().radius_s()) + .map(|x| x.round() as u8), + push_above, + push_below, + ); + } + + pub fn output_enter(&self, output: &Output) { + self.elem + .output_enter(output, Rectangle::default() /*unused*/); + } + + pub fn output_leave(&self, output: &Output) { + self.elem.output_leave(output); + } } pub struct StackHoverInternal; @@ -71,9 +128,6 @@ impl Program for StackHoverInternal { })) .width(Length::Shrink) .height(Length::Shrink) - .apply(container) - .center_x(Length::Fill) - .center_y(Length::Fill) .into() } } diff --git a/src/shell/element/swap_indicator.rs b/src/shell/element/swap_indicator.rs index e6fb0197..6f9498da 100644 --- a/src/shell/element/swap_indicator.rs +++ b/src/shell/element/swap_indicator.rs @@ -1,6 +1,7 @@ use crate::{ + backend::render::element::AsGlowRenderer, fl, - utils::iced::{IcedElement, Program}, + utils::iced::{IcedElement, IcedRenderElement, Program}, }; use calloop::LoopHandle; @@ -13,15 +14,75 @@ use cosmic::{ theme, widget::{icon::from_name, text}, }; -use smithay::utils::Size; +use smithay::{ + backend::renderer::ImportMem, + desktop::space::SpaceElement, + output::Output, + utils::{Logical, Physical, Point, Rectangle, Scale, Size}, +}; -pub type SwapIndicator = IcedElement; +#[derive(Debug, Clone)] +pub struct SwapIndicator { + location: Point, + elem: IcedElement, +} -pub fn swap_indicator( - evlh: LoopHandle<'static, crate::state::State>, - theme: cosmic::Theme, -) -> SwapIndicator { - SwapIndicator::new(SwapIndicatorInternal, Size::from((1, 1)), evlh, theme) +impl SwapIndicator { + pub fn new( + evlh: LoopHandle<'static, crate::state::State>, + mut theme: cosmic::Theme, + ) -> SwapIndicator { + theme.transparent = theme.cosmic().frosted_system_interface; + SwapIndicator { + location: Point::default(), + elem: IcedElement::new(SwapIndicatorInternal, Size::from((1, 1)), evlh, theme), + } + } + + pub fn resize(&mut self, size: Size) { + let minimum = self.elem.minimum_size(); + let new_size = Size::::new(size.w.min(minimum.w), size.h.min(minimum.h)); + let location = Point::new( + size.w.saturating_sub(new_size.w) / 2, + size.h.saturating_sub(new_size.h) / 2, + ); + self.elem.resize(new_size); + self.location = location; + } + + pub fn push_render_elements( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + alpha: f32, + push_above: &mut dyn FnMut(IcedRenderElement), + push_below: Option<&mut dyn FnMut(IcedRenderElement)>, + ) where + R: AsGlowRenderer + ImportMem, + R::TextureId: Send + Clone + 'static, + { + self.elem.push_render_elements( + renderer, + location + self.location.to_physical_precise_round(scale), + scale, + alpha, + self.elem + .with_theme(|theme| theme.cosmic().radius_s()) + .map(|x| x.round() as u8), + push_above, + push_below, + ); + } + + pub fn output_enter(&self, output: &Output) { + self.elem + .output_enter(output, Rectangle::default() /*unused*/); + } + + pub fn output_leave(&self, output: &Output) { + self.elem.output_leave(output); + } } pub struct SwapIndicatorInternal; @@ -69,9 +130,6 @@ impl Program for SwapIndicatorInternal { })) .width(Length::Shrink) .height(Length::Shrink) - .apply(container) - .center_x(Length::Fill) - .center_y(Length::Fill) .into() } } diff --git a/src/shell/grabs/menu/mod.rs b/src/shell/grabs/menu/mod.rs index ec82bb07..efd66886 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -20,7 +20,7 @@ use cosmic::{ use smithay::{ backend::{ input::{ButtonState, TouchSlot}, - renderer::{ImportMem, Renderer, element::memory::MemoryRenderBufferRenderElement}, + renderer::ImportMem, }, desktop::space::SpaceElement, input::{ @@ -42,10 +42,11 @@ use smithay::{ }; use crate::{ + backend::render::element::AsGlowRenderer, shell::{SeatExt, focus::target::PointerFocusTarget}, state::State, utils::{ - iced::{IcedElement, Program}, + iced::{IcedElement, IcedRenderElement, Program}, prelude::*, }, }; @@ -68,9 +69,9 @@ impl MenuGrabState { &self, renderer: &mut R, output: &Output, - push: &mut dyn FnMut(MemoryRenderBufferRenderElement), + push: &mut dyn FnMut(IcedRenderElement), ) where - R: Renderer + ImportMem, + R: AsGlowRenderer + ImportMem, R::TextureId: Send + Clone + 'static, { let scale = output.current_scale().fractional_scale(); @@ -83,7 +84,11 @@ impl MenuGrabState { .to_physical_precise_round(scale), scale.into(), 1.0, + elem.iced + .with_theme(|theme| theme.cosmic().radius_s()) + .map(|x| x.round() as u8), push, + None, ) } } @@ -274,11 +279,13 @@ impl Program for ContextMenu { let mut elements = grab_state.elements.lock().unwrap(); let position = elements.last().unwrap().position; + let mut theme = state.common.theme.clone(); + theme.transparent = theme.cosmic().frosted_system_interface; let element = IcedElement::new( ContextMenu::new(items), Size::default(), state.common.event_loop_handle.clone(), - state.common.theme.clone(), + theme, ); let min_size = element.minimum_size(); @@ -474,7 +481,7 @@ impl Program for ContextMenu { .padding(1) .class(theme::Container::custom(|theme| { let cosmic = theme.cosmic(); - let component = &cosmic.background(false).component; + let component = &cosmic.background(theme.cosmic().frosted_windows).component; iced_widget::container::Style { snap: true, icon_color: Some(cosmic.accent.base.into()), diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 48ebd4ba..d8714830 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -6,10 +6,7 @@ use crate::{ }, shell::{ CosmicMapped, CosmicSurface, Direction, ManagedLayer, - element::{ - CosmicMappedRenderElement, - stack_hover::{StackHover, stack_hover}, - }, + element::{CosmicMappedRenderElement, stack_hover::StackHover}, focus::target::{KeyboardFocusTarget, PointerFocusTarget}, layout::floating::TiledCorners, }, @@ -25,7 +22,7 @@ use smithay::{ drm::DrmNode, input::ButtonState, renderer::{ - ImportAll, ImportMem, Renderer, + ImportAll, ImportMem, element::{RenderElement, utils::RescaleRenderElement}, }, }, @@ -78,7 +75,7 @@ impl MoveGrabState { scanout_node: Option, push: &mut dyn FnMut(CosmicMappedRenderElement), ) where - R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + R: AsGlowRenderer + ImportAll + ImportMem, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, { @@ -122,6 +119,7 @@ impl MoveGrabState { output_scale, 1.0, &mut |elem| push(elem.into()), + None, ); } @@ -441,7 +439,7 @@ impl MoveGrab { if let Some(indicator) = grab_state.stacking_indicator.as_ref().map(|x| &x.0) { - indicator.output_enter(output, overlap); + indicator.output_enter(output); } } } else if self.window_outputs.remove(output) { @@ -455,16 +453,14 @@ impl MoveGrab { let indicator_location = shell.stacking_indicator(¤t_output, self.previous); if indicator_location.is_some() != grab_state.stacking_indicator.is_some() { grab_state.stacking_indicator = indicator_location.map(|geo| { - let element = stack_hover( + let size = geo.size.as_logical(); + let element = StackHover::new( state.common.event_loop_handle.clone(), - geo.size.as_logical(), + size, state.common.theme.clone(), ); for output in &self.window_outputs { - element.output_enter( - output, - Rectangle::from_size(output.geometry().size.as_logical()), - ); + element.output_enter(output); } (element, geo.loc.as_logical()) }); diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index aad738f8..07ce73cf 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -1525,6 +1525,27 @@ impl FloatingLayout { if focused == Some(elem) && !elem.is_maximized(false) { let active_window_hint = crate::theme::active_window_hint(theme); let radius = elem.corner_radius(geometry.size.as_logical(), indicator_thickness); + + if let Some((mode, resize)) = resize_indicator.as_mut() { + let mut resize_geometry = geometry; + resize_geometry.loc -= (18, 18).into(); + resize_geometry.size += (36, 36).into(); + + resize.resize(resize_geometry.size.as_logical()); + resize.output_enter(output); + resize.push_render_elements( + renderer, + resize_geometry + .loc + .as_logical() + .to_physical_precise_round(output_scale), + output_scale.into(), + alpha * mode.alpha().unwrap_or(1.0), + &mut |elem| push(CosmicMappedRenderElement::Window(elem.into())), + None, + ); + } + if indicator_thickness > 0 { let element = IndicatorShader::focus_element( renderer, @@ -1542,25 +1563,6 @@ impl FloatingLayout { ); push(element.into()); } - - if let Some((mode, resize)) = resize_indicator.as_mut() { - let mut resize_geometry = geometry; - resize_geometry.loc -= (18, 18).into(); - resize_geometry.size += (36, 36).into(); - - resize.resize(resize_geometry.size.as_logical()); - resize.output_enter(output, Rectangle::default() /* unused */); - resize.push_render_elements( - renderer, - resize_geometry - .loc - .as_logical() - .to_physical_precise_round(output_scale), - output_scale.into(), - alpha * mode.alpha().unwrap_or(1.0), - &mut |elem| push(CosmicMappedRenderElement::Window(elem.into())), - ); - } } let maybe_map = if let Some(anim) = self.animations.get(elem) { diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 3a3633db..5b425948 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -5330,13 +5330,12 @@ fn render_new_tree_windows( let mut group_backdrop = None; let mut indicators = SmallVec::<[CosmicMappedRenderElement; 2]>::new_const(); - let mut resize_element = None; + let mut resize_elements = SmallVec::<[CosmicMappedRenderElement; 10]>::new_const(); let mut swap_elements = SmallVec::<[CosmicMappedRenderElement; 4]>::new_const(); - let output_geo = output.geometry(); let output_scale = output.current_scale().fractional_scale(); - let (swap_indicator, swap_tree) = overview.1.unzip(); + let (mut swap_indicator, swap_tree) = overview.1.unzip(); let swap_desc = swap_desc.filter(|_| is_active_output); let swap_tree = swap_tree.flatten().filter(|_| is_active_output); let window_hint = crate::theme::active_window_hint(theme); @@ -5540,16 +5539,18 @@ fn render_new_tree_windows( .unwrap_or(false) }) .unwrap_or(false)) - && let Some(swap) = swap_indicator.as_ref() + && let Some(swap) = swap_indicator.as_mut() { - swap.resize(geo.size.as_logical()); - swap.output_enter(output, output_geo.as_logical()); + let size = geo.size.as_logical(); + swap.resize(size); + swap.output_enter(output); swap.push_render_elements( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale), output_scale.into(), alpha * overview.0.alpha().unwrap_or(1.0), &mut |elem| swap_elements.push(elem.into()), + None, ); } } @@ -5560,29 +5561,20 @@ fn render_new_tree_windows( geo.size += (36, 36).into(); resize.resize(geo.size.as_logical()); - resize.output_enter(output, output_geo.as_logical()); + resize.output_enter(output); let possible_edges = TilingLayout::possible_resizes(target_tree, node_id.clone()); if !possible_edges.is_empty() { - if resize.with_program(|internal| { - let mut edges = internal.edges.lock().unwrap(); - if *edges != possible_edges { - *edges = possible_edges; - true - } else { - false - } - }) { - resize.force_update(); - } + resize.set_edges(possible_edges); resize.push_render_elements( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale), output_scale.into(), alpha * mode.alpha().unwrap_or(1.0), &mut |elem| { - resize_element = Some(elem.into()); + resize_elements.push(elem.into()); }, + None, ); } } @@ -5743,7 +5735,7 @@ fn render_new_tree_windows( }, ); - for elem in resize_element + for elem in resize_elements .into_iter() .chain(swap_elements) .chain(indicators) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index b4559dce..1dfc528c 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -102,9 +102,8 @@ use self::zoom::{OutputZoomState, ZoomState}; use self::{ element::{ - CosmicWindow, MaximizedState, - resize_indicator::{ResizeIndicator, resize_indicator}, - swap_indicator::{SwapIndicator, swap_indicator}, + CosmicWindow, MaximizedState, resize_indicator::ResizeIndicator, + swap_indicator::SwapIndicator, }, focus::target::{KeyboardFocusTarget, PointerFocusTarget}, grabs::{ @@ -2291,7 +2290,7 @@ impl Shell { OverviewMode::Started(_, _) | OverviewMode::Active(_) ) { if matches!(trigger, Trigger::KeyboardSwap(_, _)) { - self.swap_indicator = Some(swap_indicator(evlh, self.theme.clone())); + self.swap_indicator = Some(SwapIndicator::new(evlh, self.theme.clone())); } self.overview_mode = OverviewMode::Started(trigger, Instant::now()); } @@ -2344,7 +2343,7 @@ impl Shell { } else { self.resize_mode = ResizeMode::Started(pattern, Instant::now(), direction); } - self.resize_indicator = Some(resize_indicator( + self.resize_indicator = Some(ResizeIndicator::new( direction, config, evlh, @@ -3668,6 +3667,8 @@ impl Shell { return None; }; + let mut theme = self.theme.clone(); + theme.transparent = theme.cosmic().frosted_windows; let grab = MenuGrab::new( GrabStartData::Pointer(start_data), seat, @@ -3676,7 +3677,7 @@ impl Shell { MenuAlignment::CORNER, None, evlh.clone(), - self.theme.clone(), + theme, ); Some((grab, Focus::Keep)) diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs index e0d3f1d1..11114e2d 100644 --- a/src/shell/zoom.rs +++ b/src/shell/zoom.rs @@ -11,7 +11,7 @@ use cosmic_comp_config::ZoomMovement; use cosmic_config::ConfigSet; use keyframe::{ease, functions::Linear}; use smithay::{ - backend::renderer::{ImportMem, Renderer, element::memory::MemoryRenderBufferRenderElement}, + backend::renderer::ImportMem, desktop::space::SpaceElement, input::{ Seat, @@ -32,9 +32,10 @@ use smithay::{ use tracing::error; use crate::{ + backend::render::element::AsGlowRenderer, state::State, utils::{ - iced::{IcedElement, Program}, + iced::{IcedElement, IcedRenderElement, Program}, prelude::*, tween::EasePoint, }, @@ -71,8 +72,9 @@ impl OutputZoomState { increment: u32, movement: ZoomMovement, loop_handle: LoopHandle<'static, State>, - theme: cosmic::Theme, + mut theme: cosmic::Theme, ) -> OutputZoomState { + theme.transparent = theme.cosmic().frosted_system_interface; let cursor_position = seat.get_pointer().unwrap().current_location().as_global(); let output_geometry = output.geometry().to_f64(); let focal_point = if output_geometry.contains(cursor_position) { @@ -108,8 +110,7 @@ impl OutputZoomState { let program = ZoomProgram::new(level, movement, increment); let element = IcedElement::new(program, Size::default(), loop_handle, theme); let mut size = element.minimum_size(); - size.w = (size.w + 32/*TODO: figure out why iced is calculating too little*/) - .min(output_geometry.size.w.round() as i32); + size.w = size.w.min(output_geometry.size.w.round() as i32); element.set_activate(true); element.resize(size); element.output_enter(output, Rectangle::new(Point::from((0, 0)), size)); @@ -196,9 +197,9 @@ impl OutputZoomState { &mut self, renderer: &mut R, output: &Output, - push: &mut dyn FnMut(MemoryRenderBufferRenderElement), + push: &mut dyn FnMut(IcedRenderElement), ) where - R: Renderer + ImportMem, + R: AsGlowRenderer + ImportMem, R::TextureId: Send + Clone + 'static, { let size = self.element.current_size().to_f64(); @@ -216,7 +217,11 @@ impl OutputZoomState { location, scale.fractional_scale().into(), 1.0, + self.element + .with_theme(|theme| theme.cosmic().radius_s()) + .map(|x| x.round() as u8), push, + None, ) } } @@ -390,12 +395,9 @@ impl ZoomState { None } - pub fn render( - renderer: &mut R, - output: &Output, - push: &mut dyn FnMut(MemoryRenderBufferRenderElement), - ) where - R: Renderer + ImportMem, + pub fn render(renderer: &mut R, output: &Output, push: &mut dyn FnMut(IcedRenderElement)) + where + R: AsGlowRenderer + ImportMem, R::TextureId: Send + Clone + 'static, { let output_state = output.user_data().get::>().unwrap(); @@ -515,7 +517,7 @@ impl Program for ZoomProgram { .padding(8) .class(theme::Container::custom(|theme| { let cosmic = theme.cosmic(); - let component = &cosmic.background(false).component; + let component = &cosmic.background(theme.transparent).component; iced_widget::container::Style { snap: true, icon_color: Some(component.on.into()), @@ -591,6 +593,8 @@ impl Program for ZoomProgram { let level = output_state_ref.level; std::mem::drop(output_state_ref); + let mut theme = state.common.theme.clone(); + theme.transparent = theme.cosmic().frosted_system_interface; let grab = MenuGrab::new( start_data, &seat, @@ -703,7 +707,7 @@ impl Program for ZoomProgram { ), Some(level.min(4.)), state.common.event_loop_handle.clone(), - state.common.theme.clone(), + theme, ); std::mem::drop(shell); @@ -756,6 +760,8 @@ impl Program for ZoomProgram { let level = output_state_ref.level; std::mem::drop(output_state_ref); + let mut theme = state.common.theme.clone(); + theme.transparent = theme.cosmic().frosted_system_interface; let grab = MenuGrab::new( start_data, &seat, @@ -788,7 +794,7 @@ impl Program for ZoomProgram { MenuAlignment::PREFER_CENTERED, Some(level.min(4.)), state.common.event_loop_handle.clone(), - state.common.theme.clone(), + theme, ); std::mem::drop(shell); From 77f57a656d1c03d412f135dd3e5fa647926d1b3d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 27 Apr 2026 18:57:48 -0400 Subject: [PATCH 36/56] fix: pad_rect typos --- src/wayland/handlers/corner_radius.rs | 4 ++-- src/wayland/protocols/corner_radius.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wayland/handlers/corner_radius.rs b/src/wayland/handlers/corner_radius.rs index 140b86ff..406cbf48 100644 --- a/src/wayland/handlers/corner_radius.rs +++ b/src/wayland/handlers/corner_radius.rs @@ -111,11 +111,11 @@ pub fn pad_rect( padding: &[i32; 4], ) -> Option> { rect.size.h = rect.size.h.checked_sub(padding[0])?; - rect.loc.x += padding[0]; + rect.loc.x += padding[3]; rect.size.w = rect.size.w.checked_sub(padding[1])?; rect.size.h = rect.size.h.checked_sub(padding[2])?; rect.size.w = rect.size.w.checked_sub(padding[3])?; - rect.loc.y += padding[3]; + rect.loc.y += padding[0]; Some(rect) } diff --git a/src/wayland/protocols/corner_radius.rs b/src/wayland/protocols/corner_radius.rs index f0edf343..be6f9ca0 100644 --- a/src/wayland/protocols/corner_radius.rs +++ b/src/wayland/protocols/corner_radius.rs @@ -646,9 +646,9 @@ fn pad_rect( padding: &Padding, ) -> Option> { rect.size.h = rect.size.h.checked_sub(padding.top)?; - rect.loc.x += padding.top; + rect.loc.x += padding.left; rect.size.w = rect.size.w.checked_sub(padding.left)?; - rect.loc.y += padding.left; + rect.loc.y += padding.top; rect.size.h = rect.size.h.checked_sub(padding.bottom)?; rect.size.w = rect.size.w.checked_sub(padding.right)?; Some(rect) From b890f7b0fa86d087ba2ae4d6cd64ec3a1f8b7cb9 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 27 Apr 2026 18:58:19 -0400 Subject: [PATCH 37/56] fix(corner_radius): loosen padding size constraint --- src/wayland/handlers/corner_radius.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/wayland/handlers/corner_radius.rs b/src/wayland/handlers/corner_radius.rs index 406cbf48..9618e77e 100644 --- a/src/wayland/handlers/corner_radius.rs +++ b/src/wayland/handlers/corner_radius.rs @@ -94,15 +94,14 @@ pub fn surface_corners(states: &SurfaceData, size: Size) -> Option pub fn surface_padding(states: &SurfaceData, size: Size) -> Option<[i32; 4]> { let mut guard = states.cached_state.get::(); - // guard against padding being too large - let half_min_dim = size.w.min(size.h) / 2; let padding = guard.current().0?; + // guard against padding being too large Some([ - padding.top.min(half_min_dim), - padding.right.min(half_min_dim), - padding.bottom.min(half_min_dim), - padding.left.min(half_min_dim), + padding.top.min(size.h / 2), + padding.right.min(size.w / 2), + padding.bottom.min(size.h / 2), + padding.left.min(size.w / 2), ]) } From 58938ef8ee60e320561535e6ec5e86089617c937 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 22 May 2026 16:09:53 +0200 Subject: [PATCH 38/56] render/blur: Fix transformed outputs --- src/backend/render/wayland/blur_effect.rs | 32 ++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index 47f5be88..aa30e474 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -482,23 +482,30 @@ fn blit_from_active_fb( to_texture: &mut GlesTexture, ) -> Result { if transform != Transform::Normal { + let dst = dst + .to_logical(1) + .to_buffer(1, transform, &frame.output_size().to_logical(1)); let tex_size = to_texture .size() .to_logical(1, Transform::Normal) .to_physical(1); let mut renderer = frame.renderer(); - let mut tmp_texture = renderer.as_mut().create_buffer( - Fourcc::Abgr8888, - dst.size.to_logical(1).to_buffer(1, Transform::Normal), - )?; + let mut tmp_texture = renderer + .as_mut() + .create_buffer(Fourcc::Abgr8888, dst.size)?; let mut fb_tmp = renderer.as_mut().bind(&mut tmp_texture)?; let mut fb = renderer.as_mut().bind(to_texture)?; std::mem::drop(renderer); + // TODO: Why does blit not take buffer coordinates? + let dst_phys = Rectangle::<_, Physical>::new( + Point::new(dst.loc.x, dst.loc.y), + dst.size.to_logical(1, Transform::Normal).to_physical(1), + ); let sync = frame.blit_to( &mut fb_tmp, - dst, - Rectangle::from_size(dst.size), + dst_phys, + Rectangle::from_size(dst_phys.size), TextureFilter::Linear, )?; std::mem::drop(fb_tmp); @@ -511,18 +518,13 @@ fn blit_from_active_fb( Frame::render_texture_from_to( &mut frame, &tmp_texture, - Rectangle::from_size( - dst.size - .to_logical(1) - .to_buffer(1, Transform::Normal) - .to_f64(), - ), + Rectangle::from_size(dst.size.to_f64()), src.to_logical(1., Transform::Normal, &src.size) .to_physical(1.) .to_i32_round(), - &[Rectangle::from_size(dst.size)], - &[Rectangle::from_size(dst.size)], - transform, + &[Rectangle::from_size(dst_phys.size)], + &[Rectangle::from_size(dst_phys.size)], + transform.invert(), 1.0, )?; std::mem::drop(tmp_texture); From 43d6145afd5e870a8789107462a645262bf6158b Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 22 May 2026 16:50:35 +0200 Subject: [PATCH 39/56] chore: fix warnings with no features --- src/input/mod.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index 69b5064c..1114d50b 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -35,14 +35,15 @@ use calloop::{ use cosmic_comp_config::{NumlockState, workspace::WorkspaceLayout}; use cosmic_settings_config::shortcuts; use cosmic_settings_config::shortcuts::action::{Direction, ResizeDirection}; +#[cfg(feature = "systemd")] +use smithay::backend::input::{Switch, SwitchState, SwitchToggleEvent}; use smithay::{ backend::input::{ AbsolutePositionEvent, Axis, AxisRelativeDirection, AxisSource, Device, DeviceCapability, GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _, InputBackend, InputEvent, KeyState, KeyboardKeyEvent, - PointerAxisEvent, ProximityState, Switch, SwitchState, SwitchToggleEvent, - TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent, - TabletToolTipState, TouchEvent, + PointerAxisEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent, + TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent, }, desktop::{PopupKeyboardGrab, WindowSurfaceType, utils::under_from_surface_tree}, input::{ @@ -74,7 +75,7 @@ use smithay::{ tablet_manager::{TabletDescriptor, TabletSeatTrait}, }, }; -use tracing::{error, trace, warn}; +use tracing::{error, trace}; use xkbcommon::xkb::{Keycode, Keysym}; use std::{ @@ -1599,6 +1600,7 @@ impl State { } } InputEvent::Special(_) => {} + #[allow(unused_variables)] InputEvent::SwitchToggle { event } => { #[cfg(feature = "logind")] if event.switch() == Some(Switch::Lid) && self.common.inhibit_lid_fd.is_some() { @@ -1620,7 +1622,7 @@ impl State { if let Err(err) = self.refresh_output_config() { if !closed { - warn!(?err, "Failed to re-enable internal connector"); + tracing::warn!(?err, "Failed to re-enable internal connector"); if let Some(output) = output { use cosmic_comp_config::output::comp::OutputState; From f424abbc298b37dc8bf99441c7f6e1877bc2983b Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 22 May 2026 17:15:16 +0200 Subject: [PATCH 40/56] render/blur: Fix div-by-zero in shaders --- src/backend/render/shaders/blur_downsample.frag | 6 +++++- src/backend/render/shaders/blur_upsample.frag | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/backend/render/shaders/blur_downsample.frag b/src/backend/render/shaders/blur_downsample.frag index 1fa24beb..1fff7522 100644 --- a/src/backend/render/shaders/blur_downsample.frag +++ b/src/backend/render/shaders/blur_downsample.frag @@ -22,5 +22,9 @@ void main() { sum += texture2D(tex, v_coords + vec2(half_pixel.x, -half_pixel.y) * offset); sum += texture2D(tex, v_coords - vec2(half_pixel.x, -half_pixel.y) * offset); - gl_FragColor = sum / sum.a; + if (sum.a == 0.0) { + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); + } else { + gl_FragColor = sum / sum.a; + } } diff --git a/src/backend/render/shaders/blur_upsample.frag b/src/backend/render/shaders/blur_upsample.frag index ac026d19..588fcb65 100644 --- a/src/backend/render/shaders/blur_upsample.frag +++ b/src/backend/render/shaders/blur_upsample.frag @@ -23,5 +23,9 @@ void main() { sum += texture2D(tex, v_coords + vec2(0.0, -half_pixel.y * 2.0) * offset); sum += texture2D(tex, v_coords + vec2(-half_pixel.x, -half_pixel.y) * offset) * 2.0; - gl_FragColor = sum / sum.a; + if (sum.a == 0.0) { + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); + } else { + gl_FragColor = sum / sum.a; + } } From e0b6784eb5608c72f94c6b52d1bba154e426bd0a Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 22 May 2026 18:25:05 +0200 Subject: [PATCH 41/56] render/blur: Wipe blur_texture before blit --- src/backend/render/wayland/blur_effect.rs | 30 ++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index aa30e474..8ceb1704 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -481,21 +481,32 @@ fn blit_from_active_fb( transform: Transform, to_texture: &mut GlesTexture, ) -> Result { + let tex_size = to_texture + .size() + .to_logical(1, Transform::Normal) + .to_physical(1); + let fb_size = frame.output_size(); + + let mut renderer = frame.renderer(); + let mut fb = renderer.as_mut().bind(to_texture)?; + let sync = { + let mut subframe = renderer + .as_mut() + .render(&mut fb, tex_size, Transform::Normal)?; + subframe.clear(Color32F::TRANSPARENT, &[Rectangle::from_size(tex_size)])?; + subframe.finish()? + }; + if transform != Transform::Normal { let dst = dst .to_logical(1) - .to_buffer(1, transform, &frame.output_size().to_logical(1)); - let tex_size = to_texture - .size() - .to_logical(1, Transform::Normal) - .to_physical(1); - let mut renderer = frame.renderer(); + .to_buffer(1, transform, &fb_size.to_logical(1)); let mut tmp_texture = renderer .as_mut() .create_buffer(Fourcc::Abgr8888, dst.size)?; let mut fb_tmp = renderer.as_mut().bind(&mut tmp_texture)?; - let mut fb = renderer.as_mut().bind(to_texture)?; std::mem::drop(renderer); + frame.wait(&sync)?; // TODO: Why does blit not take buffer coordinates? let dst_phys = Rectangle::<_, Physical>::new( @@ -508,6 +519,7 @@ fn blit_from_active_fb( Rectangle::from_size(dst_phys.size), TextureFilter::Linear, )?; + frame.wait(&sync)?; std::mem::drop(fb_tmp); let mut renderer = frame.renderer(); @@ -530,8 +542,8 @@ fn blit_from_active_fb( std::mem::drop(tmp_texture); frame.finish() } else { - let mut fb = frame.renderer().as_mut().bind(to_texture)?; - + std::mem::drop(renderer); + frame.wait(&sync)?; frame.blit_to( &mut fb, dst, From f6d3dafda7790023059237bc717c21a288d552df Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 26 May 2026 18:24:33 -0400 Subject: [PATCH 42/56] fix: use configured blur strength for lock surface and its subsurfaces --- src/backend/render/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index ac49af7d..2c26550a 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -826,7 +826,7 @@ where }); } Stage::SessionLock(lock_surface) => { - session_lock_elements(renderer, output, lock_surface, &mut |elem| { + session_lock_elements(renderer, output, lock_surface, blur_strength, &mut |elem| { elements.extend(crop_to_output(elem.into()).map(Into::into)) }) } @@ -1060,6 +1060,7 @@ fn session_lock_elements( renderer: &mut R, output: &Output, lock_surface: Option<&LockSurface>, + blur_strength: usize, push: &mut dyn FnMut(SurfaceRenderElement), ) where R: AsGlowRenderer, @@ -1077,7 +1078,7 @@ fn session_lock_elements( false, [0; 4], None, - 0, + blur_strength, FRAME_TIME_FILTER, push, None, From 361a2282c7d2849fc050d16b2ecc815fa31c28bb Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 10 Jun 2026 18:05:14 +0200 Subject: [PATCH 43/56] output: Add increasing output id --- src/shell/mod.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 1dfc528c..8e628985 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -16,6 +16,7 @@ use crate::{ element::CosmicStack, focus::FocusTarget, grabs::fullscreen_items, layout::tiling::PlaceholderType, }, + utils, wayland::{ handlers::data_device::{self, get_dnd_icon}, protocols::workspace::{State as WState, WorkspaceCapabilities}, @@ -1510,6 +1511,21 @@ impl Workspaces { #[derive(Debug)] pub struct InvalidWorkspaceIndex; +utils::id_gen!(next_output_id, OUTPUT_ID, OUTPUT_IDS); +pub struct OutputId(usize); + +impl OutputId { + pub fn namespace_for_workspace(&self, idx: usize) -> usize { + self.0 | (idx << 32) + } +} + +impl Drop for OutputId { + fn drop(&mut self) { + OUTPUT_IDS.lock().unwrap().remove(&self.0); + } +} + impl Common { pub fn add_output(&mut self, output: &Output) { let mut shell = self.shell.write(); @@ -1517,6 +1533,10 @@ impl Common { .workspaces .add_output(output, &mut self.workspace_state.update()); + output + .user_data() + .insert_if_missing_threadsafe(|| OutputId(next_output_id())); + if let Some(state) = shell.zoom_state.as_ref() { output.user_data().insert_if_missing_threadsafe(|| { Mutex::new(OutputZoomState::new( From 1f0456a5af114fe2fc33c130555a2a3e87e1fd33 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 10 Jun 2026 18:05:57 +0200 Subject: [PATCH 44/56] render: Include OutputId in namespaces --- src/backend/render/mod.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 2c26550a..472abb8e 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -27,7 +27,7 @@ use crate::{ }, config::ScreenFilter, shell::{ - CosmicMappedRenderElement, OverviewMode, SeatExt, Trigger, WorkspaceDelta, + CosmicMappedRenderElement, OutputId, OverviewMode, SeatExt, Trigger, WorkspaceDelta, WorkspaceRenderElement, element::CosmicMappedKey, focus::{FocusTarget, Stage, render_input_order, target::WindowGroup}, @@ -844,6 +844,12 @@ where }) .unwrap_or([0; 4]); + let namespace = output + .user_data() + .get::() + .map(|id| id.namespace_for_workspace(workspace_idx)) + .unwrap_or(workspace_idx); + push_render_elements_from_surface_tree( renderer, popup.wl_surface(), @@ -861,7 +867,7 @@ where FRAME_TIME_FILTER, &mut |elem| { elements.extend( - crop_to_output(NamespacedElement::new(elem, workspace_idx).into()) + crop_to_output(NamespacedElement::new(elem, namespace).into()) .map(Into::into), ) }, @@ -887,6 +893,12 @@ where }) .unwrap_or([0; 4]); + let namespace = output + .user_data() + .get::() + .map(|id| id.namespace_for_workspace(workspace_idx)) + .unwrap_or(workspace_idx); + push_render_elements_from_surface_tree( renderer, layer.wl_surface(), @@ -904,7 +916,7 @@ where FRAME_TIME_FILTER, &mut |elem| { elements.extend( - crop_to_output(NamespacedElement::new(elem, workspace_idx).into()) + crop_to_output(NamespacedElement::new(elem, namespace).into()) .map(Into::into), ) }, From c92b91221c1fcbe6e6ff707ce5d5f3561682c1b7 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 11 Jun 2026 13:17:11 +0200 Subject: [PATCH 45/56] render/element: Support converting gl-textures --- src/backend/render/element.rs | 36 ++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index 51d282c1..27179045 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -8,20 +8,27 @@ use crate::{ use smithay::backend::renderer::element::texture::TextureRenderElement; use smithay::{ backend::{ - allocator::dmabuf::Dmabuf, + allocator::{Fourcc, dmabuf::Dmabuf}, + drm::DrmDeviceFd, renderer::{ - Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, + Bind, Blit, ContextId, ExportMem, Frame as _, FrameContext, ImportAll, ImportMem, + Offscreen, Renderer, RendererSuper, element::{ Element, Id, Kind, RenderElement, UnderlyingStorage, utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, }, gles::{GlesError, GlesRenderbuffer, GlesTexture, element::TextureShaderElement}, glow::{GlowFrame, GlowRenderer}, + multigpu::{ + MultiTexture, + gbm::{GbmGlesBackend, GbmGlesDevice}, + }, utils::{CommitCounter, DamageSet, OpaqueRegions}, }, }, utils::{ - Buffer as BufferCoords, Logical, Physical, Point, Rectangle, Scale, user_data::UserDataMap, + Buffer as BufferCoords, Logical, Physical, Point, Rectangle, Scale, Size, + user_data::UserDataMap, }, }; @@ -396,6 +403,11 @@ pub trait AsGlowRenderer: fn glow_frame_mut<'a, 'frame, 'buffer>( frame: &'a mut Self::Frame<'frame, 'buffer>, ) -> &'a mut GlowFrame<'frame, 'buffer>; + fn tex_from_gl(context: &ContextId, texture: GlesTexture) -> Self::TextureId; + fn tex_to_gl( + context: &ContextId, + texture: &Self::TextureId, + ) -> Option; fn from_gles_error(err: GlesError) -> Self::Error; } @@ -416,6 +428,15 @@ impl AsGlowRenderer for GlowRenderer { ) -> &'a mut GlowFrame<'frame, 'buffer> { frame } + fn tex_from_gl(_context: &ContextId, texture: GlesTexture) -> Self::TextureId { + texture + } + fn tex_to_gl( + _context: &ContextId, + texture: &Self::TextureId, + ) -> Option { + Some(texture.clone()) + } fn from_gles_error(err: GlesError) -> Self::Error { err } @@ -438,6 +459,15 @@ impl AsGlowRenderer for GlMultiRenderer<'_> { ) -> &'b mut GlowFrame<'frame, 'buffer> { frame.as_mut() } + fn tex_from_gl(context: &ContextId, texture: GlesTexture) -> Self::TextureId { + MultiTexture::from_native_texture::>(context, texture).unwrap() + } + fn tex_to_gl( + context: &ContextId, + texture: &Self::TextureId, + ) -> Option { + texture.get::>(context) + } fn from_gles_error(err: GlesError) -> Self::Error { GlMultiError::Render(err) } From f6cb4519a0ef4124aeea1b41da4810f5a27ea8e3 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 11 Jun 2026 13:18:41 +0200 Subject: [PATCH 46/56] render/blur: Cache native texture type --- src/backend/render/cursor.rs | 2 +- src/backend/render/element.rs | 19 +++-- src/backend/render/wayland/blur_effect.rs | 76 +++++++++++-------- src/backend/render/wayland/mod.rs | 2 +- src/shell/element/mod.rs | 4 +- src/shell/element/stack.rs | 4 +- src/shell/element/window.rs | 4 +- src/shell/workspace.rs | 18 ++--- src/utils/iced/mod.rs | 2 +- src/utils/screenshot.rs | 2 +- .../handlers/image_copy_capture/render.rs | 2 +- 11 files changed, 74 insertions(+), 61 deletions(-) diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 0585e664..da01b270 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -175,7 +175,7 @@ fn load_icon(theme: &CursorTheme, shape: CursorIcon) -> Result, Error } render_elements! { - pub CursorRenderElement where R: ImportAll + ImportMem + AsGlowRenderer; + pub CursorRenderElement where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send; Static=MemoryRenderBufferRenderElement, Surface=SurfaceRenderElement, } diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index 27179045..706a542b 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -1,5 +1,8 @@ use crate::{ - backend::render::{GlMultiError, wayland::SurfaceRenderElement}, + backend::{ + kms::render::gles::GbmGlowBackend, + render::{GlMultiError, wayland::SurfaceRenderElement}, + }, shell::{CosmicMappedRenderElement, WorkspaceRenderElement}, utils::iced::IcedRenderElement, }; @@ -37,7 +40,7 @@ use super::{GlMultiRenderer, cursor::CursorRenderElement}; pub enum CosmicElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { Workspace( @@ -58,7 +61,7 @@ where impl Element for CosmicElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn id(&self) -> &Id { @@ -223,7 +226,7 @@ where impl RenderElement for CosmicElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn draw( @@ -339,7 +342,7 @@ impl From>>> for CosmicElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(elem: CropRenderElement>>) -> Self { @@ -354,7 +357,7 @@ where impl From> for CosmicElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(value: IcedRenderElement) -> Self { @@ -365,7 +368,7 @@ where impl From for CosmicElement where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(value: DamageElement) -> Self { @@ -377,7 +380,7 @@ where impl From> for CosmicElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(elem: TextureRenderElement) -> Self { diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index 8ceb1704..9b3145bc 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -9,7 +9,7 @@ use smithay::{ allocator::Fourcc, renderer::{ Bind, BlitFrame, Color32F, ContextId, Frame, FrameContext, ImportAll, Offscreen, - Renderer, Texture, TextureFilter, + Renderer, RendererSuper, Texture, TextureFilter, element::{Element, Id, Kind, RenderElement}, gles::{ GlesError, GlesFrame, GlesRenderer, GlesTexProgram, GlesTexture, Uniform, @@ -115,7 +115,7 @@ impl BlurShaders { } } -type BlurTexture = Mutex>; +type BlurTexture = Mutex>; #[derive(Debug)] pub struct BlurState { @@ -369,7 +369,10 @@ impl Element for BlurElement { } } -impl RenderElement for BlurElement { +impl RenderElement for BlurElement +where + R::TextureId: Send + 'static, +{ fn capture_framebuffer( &self, frame: &mut ::Frame<'_, '_>, @@ -377,41 +380,48 @@ impl RenderElement for BlurElement { dst: Rectangle, cache: &UserDataMap, ) -> Result<(), ::Error> { + let transform = frame.transformation(); + let tex_size = self.src.to_i32_round(); let glow_frame = ::glow_frame_mut(frame); let gles_frame = BorrowMut::>::borrow_mut(glow_frame); - let transform = gles_frame.transformation(); - let tex_size = self.src.to_i32_round(); + let mut renderer = gles_frame.renderer(); - let texture_ref = cache.get_or_insert_threadsafe(BlurTexture::default); + let texture_ref = cache.get_or_insert_threadsafe(BlurTexture::::default); let mut texture_entry = texture_ref.lock().unwrap(); - if texture_entry - .as_ref() - .is_some_and(|tex| tex.size() != tex_size) - { + if texture_entry.as_ref().is_some_and(|tex| { + tex.size() != tex_size + || R::tex_to_gl( + &renderer.as_ref().context_id(), + texture_entry.as_ref().unwrap(), + ) + .is_none() + }) { texture_entry.take(); } - - let mut renderer = gles_frame.renderer(); if texture_entry.is_none() { - *texture_entry = Some( - renderer - .as_mut() - .create_buffer(Fourcc::Abgr8888, tex_size) - .map_err(R::from_gles_error)?, - ); + let gl_texture = renderer + .as_mut() + .create_buffer(Fourcc::Abgr8888, tex_size) + .map_err(R::from_gles_error)?; + *texture_entry = Some(R::tex_from_gl(&renderer.as_ref().context_id(), gl_texture)); } - let texture = texture_entry.as_mut().unwrap(); + + let mut texture = R::tex_to_gl( + &renderer.as_ref().context_id(), + texture_entry.as_ref().unwrap(), + ) + .unwrap(); let mut off_texture = renderer .as_mut() .create_buffer(Fourcc::Abgr8888, tex_size) .map_err(R::from_gles_error)?; std::mem::drop(renderer); - let sync = blit_from_active_fb(gles_frame, src, dst, transform, texture) + let sync = blit_from_active_fb(gles_frame, src, dst, transform, &mut texture) .map_err(R::from_gles_error)?; gles_frame.wait(&sync).map_err(R::from_gles_error)?; - let mut textures = [texture, &mut off_texture]; + let mut textures = [&mut texture, &mut off_texture]; render_blur( gles_frame.renderer().as_mut(), &self.scaling_shaders, @@ -433,7 +443,6 @@ impl RenderElement for BlurElement { opaque_regions: &[Rectangle], cache: Option<&UserDataMap>, ) -> Result<(), R::Error> { - let glow_frame = ::glow_frame_mut(frame); let src_to_geo = self.geometry.size / self.src; let src_log = src .upscale(src_to_geo) @@ -451,26 +460,27 @@ impl RenderElement for BlurElement { .flat_map(|rect| damage.iter().flat_map(move |r| r.intersection(rect))) .collect::>(); let cache = cache.expect("Framebuffer element without cache?"); - let Some(texture) = cache.get::() else { + let Some(texture) = cache.get::>() else { return Err(R::from_gles_error(GlesError::BlitError)); }; let texture_ref = texture.lock().unwrap(); - BorrowMut::>::borrow_mut(glow_frame) - .render_texture_from_to( - texture_ref - .as_ref() - .ok_or(R::from_gles_error(GlesError::BlitError))?, + if let Some(tex) = texture_ref.as_ref() { + BorrowMut::::borrow_mut(::glow_frame_mut(frame)) + .override_default_tex_program(self.render_shader.clone(), self.uniforms.clone()); + frame.render_texture_from_to( + tex, src, dst, &damage, - opaque_regions, + &opaque_regions, Transform::Normal, 1.0, - Some(&self.render_shader), - &self.uniforms, - ) - .map_err(R::from_gles_error) + )?; + BorrowMut::::borrow_mut(::glow_frame_mut(frame)) + .clear_tex_program_override(); + } + Ok(()) } } diff --git a/src/backend/render/wayland/mod.rs b/src/backend/render/wayland/mod.rs index 9f07d08b..c2b7d4ec 100644 --- a/src/backend/render/wayland/mod.rs +++ b/src/backend/render/wayland/mod.rs @@ -20,7 +20,7 @@ pub mod blur_effect; pub mod clipped_surface; render_elements! { - pub SurfaceRenderElement where R: AsGlowRenderer + ImportAll; + pub SurfaceRenderElement where R: AsGlowRenderer + ImportAll, R::TextureId: Send; Blur=BlurElement, Clipped=ClippedSurfaceRenderElement, Wayland=WaylandSurfaceRenderElement, diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index f5691b29..14736719 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -1137,7 +1137,7 @@ where impl Element for CosmicMappedRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, { fn id(&self) -> &smithay::backend::renderer::element::Id { match self { @@ -1358,7 +1358,7 @@ where impl RenderElement for CosmicMappedRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, { fn draw( &self, diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 92d29b75..fabb87bd 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1927,7 +1927,7 @@ impl From> impl Element for CosmicStackRenderElement where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, { fn id(&self) -> &RendererId { match self { @@ -2036,7 +2036,7 @@ where impl RenderElement for CosmicStackRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, { fn draw( &self, diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 09c59f90..f6c9e1c4 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1275,7 +1275,7 @@ impl From> impl Element for CosmicWindowRenderElement where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, { fn id(&self) -> &RendererId { match self { @@ -1384,7 +1384,7 @@ where impl RenderElement for CosmicWindowRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, { fn draw( &self, diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index ea5dbad7..e771e502 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1973,7 +1973,7 @@ pub struct OutputNotMapped; pub enum WorkspaceRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, { OverrideRedirect(SurfaceRenderElement), LowerLayerShell(NamespacedElement>), @@ -1986,7 +1986,7 @@ where impl Element for WorkspaceRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, { fn id(&self) -> &smithay::backend::renderer::element::Id { match self { @@ -2117,7 +2117,7 @@ where impl RenderElement for WorkspaceRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, { fn draw( &self, @@ -2213,7 +2213,7 @@ where impl From>> for WorkspaceRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(elem: RescaleRenderElement>) -> Self { @@ -2224,7 +2224,7 @@ where impl From> for WorkspaceRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(elem: CosmicWindowRenderElement) -> Self { @@ -2235,7 +2235,7 @@ where impl From> for WorkspaceRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(elem: SurfaceRenderElement) -> Self { @@ -2246,7 +2246,7 @@ where impl From>> for WorkspaceRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(elem: NamespacedElement>) -> Self { @@ -2257,7 +2257,7 @@ where impl From> for WorkspaceRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(elem: CosmicMappedRenderElement) -> Self { @@ -2268,7 +2268,7 @@ where impl From> for WorkspaceRenderElement where R: AsGlowRenderer, - R::TextureId: 'static, + R::TextureId: Send + 'static, CosmicMappedRenderElement: RenderElement, { fn from(elem: TextureRenderElement) -> Self { diff --git a/src/utils/iced/mod.rs b/src/utils/iced/mod.rs index b03e1682..2bfe888d 100644 --- a/src/utils/iced/mod.rs +++ b/src/utils/iced/mod.rs @@ -1112,7 +1112,7 @@ impl IcedElement

{ } render_elements! { - pub IcedRenderElement where R: ImportMem + AsGlowRenderer; + pub IcedRenderElement where R: ImportMem + AsGlowRenderer, R::TextureId: Send; UI=MemoryRenderBufferRenderElement, Blur=BlurElement, } diff --git a/src/utils/screenshot.rs b/src/utils/screenshot.rs index de544b72..f1f1f5f4 100644 --- a/src/utils/screenshot.rs +++ b/src/utils/screenshot.rs @@ -23,7 +23,7 @@ pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) { fn render_window(renderer: &mut R, window: &CosmicSurface) -> anyhow::Result<()> where R: Renderer + ImportAll + Offscreen + ExportMem + AsGlowRenderer, - R::TextureId: Clone + 'static, + R::TextureId: Send + Clone + 'static, R::Error: Send + Sync + 'static, { let bbox = bbox_from_surface_tree(&window.wl_surface().unwrap(), (0, 0)); diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index 0b77cbf5..ae429b4d 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -502,7 +502,7 @@ pub fn render_workspace_to_buffer( } smithay::render_elements! { - pub WindowCaptureElement where R: ImportAll + ImportMem + AsGlowRenderer; + pub WindowCaptureElement where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send; WaylandElement=SurfaceRenderElement, CursorElement=RelocateRenderElement>, DamageElement=DamageElement, From 7fbeb8787f2f2df0509d547c652d583c20da096f Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 11 Jun 2026 15:46:58 +0200 Subject: [PATCH 47/56] examples: Fix new libcosmic build --- Cargo.lock | 1 + Cargo.toml | 1 + examples/customized-window-decorations.rs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ed27a859..b58cd62c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -806,6 +806,7 @@ dependencies = [ "futures-util", "i18n-embed", "i18n-embed-fl", + "iced_graphics", "iced_tiny_skia", "id_tree", "indexmap 2.13.0", diff --git a/Cargo.toml b/Cargo.toml index 7023d1d7..27f79056 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ i18n-embed = { version = "0.16", features = [ ] } i18n-embed-fl = "0.10" iced_tiny_skia = { git = "https://github.com/pop-os/libcosmic" } +iced_graphics = { git = "https://github.com/pop-os/libcosmic/", features = ["geometry"] } indexmap = "2.13" keyframe = "1.1.1" libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false } diff --git a/examples/customized-window-decorations.rs b/examples/customized-window-decorations.rs index 364221fe..6b3baf8f 100644 --- a/examples/customized-window-decorations.rs +++ b/examples/customized-window-decorations.rs @@ -23,7 +23,7 @@ struct Circle { color: Color, } -impl +impl canvas::Program for Circle { type State = (); From 0c805d9b30c02fc79d9586efac3d208135d7ec6a Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 11 Jun 2026 15:47:16 +0200 Subject: [PATCH 48/56] chore: cleanup new blur code --- src/backend/render/element.rs | 13 ++++--------- src/backend/render/wayland/blur_effect.rs | 6 +++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index 706a542b..e1259dc1 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -11,27 +11,22 @@ use crate::{ use smithay::backend::renderer::element::texture::TextureRenderElement; use smithay::{ backend::{ - allocator::{Fourcc, dmabuf::Dmabuf}, + allocator::dmabuf::Dmabuf, drm::DrmDeviceFd, renderer::{ - Bind, Blit, ContextId, ExportMem, Frame as _, FrameContext, ImportAll, ImportMem, - Offscreen, Renderer, RendererSuper, + Bind, Blit, ContextId, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, element::{ Element, Id, Kind, RenderElement, UnderlyingStorage, utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, }, gles::{GlesError, GlesRenderbuffer, GlesTexture, element::TextureShaderElement}, glow::{GlowFrame, GlowRenderer}, - multigpu::{ - MultiTexture, - gbm::{GbmGlesBackend, GbmGlesDevice}, - }, + multigpu::MultiTexture, utils::{CommitCounter, DamageSet, OpaqueRegions}, }, }, utils::{ - Buffer as BufferCoords, Logical, Physical, Point, Rectangle, Scale, Size, - user_data::UserDataMap, + Buffer as BufferCoords, Logical, Physical, Point, Rectangle, Scale, user_data::UserDataMap, }, }; diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index 9b3145bc..3cf8ea9e 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -9,7 +9,7 @@ use smithay::{ allocator::Fourcc, renderer::{ Bind, BlitFrame, Color32F, ContextId, Frame, FrameContext, ImportAll, Offscreen, - Renderer, RendererSuper, Texture, TextureFilter, + Renderer, Texture, TextureFilter, element::{Element, Id, Kind, RenderElement}, gles::{ GlesError, GlesFrame, GlesRenderer, GlesTexProgram, GlesTexture, Uniform, @@ -115,7 +115,7 @@ impl BlurShaders { } } -type BlurTexture = Mutex>; +type BlurTexture = Mutex>; #[derive(Debug)] pub struct BlurState { @@ -473,7 +473,7 @@ where src, dst, &damage, - &opaque_regions, + opaque_regions, Transform::Normal, 1.0, )?; From 4f8aed4149449430575419dd232b1e5856eda1fa Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 11 Jun 2026 16:41:04 +0200 Subject: [PATCH 49/56] blur: Fix invalid matrix inversions --- src/backend/render/wayland/blur_effect.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index 3cf8ea9e..243cd510 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -218,7 +218,7 @@ impl BlurElement { radii: [u8; 4], strength: usize, ) -> Result, R::Error> { - if strength == 0 { + if strength == 0 || geometry.size.w == 0. || geometry.size.h == 0. { return Ok(None); } @@ -237,12 +237,10 @@ impl BlurElement { }; let geo_translation = { let offset = geo.loc - extended_geo.loc; - Matrix3::from_translation(Vector2::new( + Matrix3::from_translation(-Vector2::new( (offset.x / extended_geo.size.w) as f32, (offset.y / extended_geo.size.h) as f32, )) - .invert() - .unwrap() }; let input_to_geo = geo_scale * geo_translation; From 1f48a5e86621d161c47b96565d504c3eff8d8e35 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 12 Jun 2026 18:18:34 +0200 Subject: [PATCH 50/56] shell/menu: Use new MenuColumn widget --- src/shell/grabs/menu/item.rs | 21 ++++++++++++++++++--- src/shell/grabs/menu/mod.rs | 29 +++++++++++++++++------------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/shell/grabs/menu/item.rs b/src/shell/grabs/menu/item.rs index 90614f6b..f254df72 100644 --- a/src/shell/grabs/menu/item.rs +++ b/src/shell/grabs/menu/item.rs @@ -1,6 +1,6 @@ use cosmic::{ iced::{ - Element, + Alignment, Element, core::{ Background, Border, Clipboard, Color, Event, Layout, Length, Rectangle, Renderer as IcedRenderer, Shell, Size, layout, mouse, overlay, @@ -75,11 +75,26 @@ where viewport: &Rectangle, ) { let widget_state = state.state.downcast_ref::(); - let styling = if widget_state.cursor_over { + let mut styling = if widget_state.cursor_over { theme.hovered(true, false, &self.styling) } else { theme.active(true, false, &self.styling) }; + if matches!(self.styling, cosmic::theme::Button::MenuItem) { + match theme.list_item_position { + Some((Alignment::Start, _)) => { + styling.border_radius = + styling.border_radius.bottom(theme.cosmic().radius_0()[3]); + } + Some((Alignment::End, _)) => { + styling.border_radius = styling.border_radius.top(theme.cosmic().radius_0()[0]); + } + Some((Alignment::Center, _)) => {} + None => { + styling.border_radius = theme.cosmic().radius_0().into(); + } + }; + } renderer.fill_quad( Quad { @@ -87,7 +102,7 @@ where bounds: layout.bounds(), border: Border { radius: styling.border_radius, - width: styling.border_width, + width: 0., color: styling.border_color, }, shadow: Default::default(), diff --git a/src/shell/grabs/menu/mod.rs b/src/shell/grabs/menu/mod.rs index efd66886..46639631 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -12,10 +12,10 @@ use cosmic::{ iced::{ Alignment, Background, core::{Border, Length, Rectangle as IcedRectangle, alignment::Horizontal}, - widget::{self as iced_widget, Column, Row, text::Style as TextStyle}, + widget::{self as iced_widget, Row, text::Style as TextStyle}, }, theme, - widget::{button, divider, icon::from_name, space, text}, + widget::{button, divider, icon::from_name, menu::menu_column::MenuColumn, space, text}, }; use smithay::{ backend::{ @@ -300,7 +300,7 @@ impl Program for ContextMenu { Rectangle::new( position + Point::from(( - bounds.width.ceil() as i32, + bounds.width.floor() as i32, bounds.y.ceil() as i32, )), min_size.as_global(), @@ -309,7 +309,7 @@ impl Program for ContextMenu { Rectangle::new( position + Point::from(( - bounds.width.ceil() as i32, + bounds.width.floor() as i32, bounds.y.ceil() as i32 + bounds.height.ceil() as i32 - min_size.h, )), @@ -317,14 +317,15 @@ impl Program for ContextMenu { ), // to the left -> down Rectangle::new( - position + Point::from((-min_size.w, bounds.y.ceil() as i32)), + position + + Point::from((-min_size.w + 1, bounds.y.ceil() as i32)), min_size.as_global(), ), // to the left -> up Rectangle::new( position + Point::from(( - -min_size.w, + -min_size.w + 1, bounds.y.ceil() as i32 + bounds.height.ceil() as i32 - min_size.h, )), @@ -390,9 +391,11 @@ impl Program for ContextMenu { _ => Length::Fill, }; - Column::with_children(self.items.iter().enumerate().map(|(idx, item)| { + MenuColumn::with_children(self.items.iter().enumerate().map(|(idx, item)| { match item { - Item::Separator => divider::horizontal::light().into(), + Item::Separator => divider::horizontal::light() + .class(theme::Rule::Default) + .into(), Item::Submenu { title, .. } => Row::with_children(vec![ space::horizontal().width(16).into(), text::body(title).width(mode).into(), @@ -561,8 +564,11 @@ impl PointerGrab for MenuGrab { PointerTarget::motion(&element.iced, &self.seat, state, &new_event); } } else { - elements.iter_mut().for_each(|element| { - if element.pointer_entered { + elements + .iter_mut() + .filter(|element| element.pointer_entered) + .skip(1) + .for_each(|element| { PointerTarget::leave( &element.iced, &self.seat, @@ -571,8 +577,7 @@ impl PointerGrab for MenuGrab { event.time, ); element.pointer_entered = false; - } - }) + }) } } handle.motion(state, None, event); From 3e17157ccf60c41d35904564151e667b6270da00 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 6 Jul 2026 16:22:18 +0200 Subject: [PATCH 51/56] chore: update smithay --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/wayland/protocols/drm.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b58cd62c..27e8287e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4813,7 +4813,7 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "smithay" version = "0.7.0" -source = "git+https://github.com/smithay/smithay.git?rev=8eb4076#8eb4076cad8705c56f5e3bac8577b83576b6ce76" +source = "git+https://github.com/smithay/smithay.git?rev=efeb597#efeb59768eb9a2d8dae9f71300a56aef06b0c1c4" dependencies = [ "aliasable", "appendlist", diff --git a/Cargo.toml b/Cargo.toml index 27f79056..b5ebbb06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,4 +144,4 @@ lto = "fat" cosmic-protocols = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" } [patch.crates-io] -smithay = { git = "https://github.com/smithay/smithay.git", rev = "8eb4076" } +smithay = { git = "https://github.com/smithay/smithay.git", rev = "efeb597" } diff --git a/src/wayland/protocols/drm.rs b/src/wayland/protocols/drm.rs index 87eeb0db..c86e5ff0 100644 --- a/src/wayland/protocols/drm.rs +++ b/src/wayland/protocols/drm.rs @@ -180,7 +180,7 @@ where Modifier::Invalid, DmabufFlags::empty(), ); - dma.add_plane(name, 0, offset0 as u32, stride0 as u32); + dma.add_plane(name, offset0 as u32, stride0 as u32); match dma.build() { Some(dmabuf) => { match state.dmabuf_imported(&data.dmabuf_global, dmabuf.clone()) { From 2971845d400371034ec8ab48b1f060294c1220dd Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 8 Jul 2026 14:53:10 +0200 Subject: [PATCH 52/56] render/blur: Fix rotated outputs having cut off blur regions --- src/backend/render/wayland/blur_effect.rs | 45 +++++++++++++---------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs index 243cd510..bb667af1 100644 --- a/src/backend/render/wayland/blur_effect.rs +++ b/src/backend/render/wayland/blur_effect.rs @@ -489,10 +489,8 @@ fn blit_from_active_fb( transform: Transform, to_texture: &mut GlesTexture, ) -> Result { - let tex_size = to_texture - .size() - .to_logical(1, Transform::Normal) - .to_physical(1); + let tex_size = to_texture.size(); + let tex_size_phys = tex_size.to_logical(1, Transform::Normal).to_physical(1); let fb_size = frame.output_size(); let mut renderer = frame.renderer(); @@ -500,27 +498,31 @@ fn blit_from_active_fb( let sync = { let mut subframe = renderer .as_mut() - .render(&mut fb, tex_size, Transform::Normal)?; - subframe.clear(Color32F::TRANSPARENT, &[Rectangle::from_size(tex_size)])?; + .render(&mut fb, tex_size_phys, Transform::Normal)?; + subframe.clear( + Color32F::TRANSPARENT, + &[Rectangle::from_size(tex_size_phys)], + )?; subframe.finish()? }; if transform != Transform::Normal { - let dst = dst + // We need to copy to a temporary texture to do an actual + // render pass with `render_texture_from_to` to do the rotation. + // dst is in screen space, but we just want to do a 1:1 copy in + // buffer space from dst of the current fb, so we need to undo any + // transforms for the blit. + let dst_phys = transform.transform_rect_in(dst, &fb_size); + let dst_buffer = dst_phys .to_logical(1) - .to_buffer(1, transform, &fb_size.to_logical(1)); + .to_buffer(1, Transform::Normal, &Size::default()); let mut tmp_texture = renderer .as_mut() - .create_buffer(Fourcc::Abgr8888, dst.size)?; + .create_buffer(Fourcc::Abgr8888, dst_buffer.size)?; let mut fb_tmp = renderer.as_mut().bind(&mut tmp_texture)?; std::mem::drop(renderer); frame.wait(&sync)?; - // TODO: Why does blit not take buffer coordinates? - let dst_phys = Rectangle::<_, Physical>::new( - Point::new(dst.loc.x, dst.loc.y), - dst.size.to_logical(1, Transform::Normal).to_physical(1), - ); let sync = frame.blit_to( &mut fb_tmp, dst_phys, @@ -530,20 +532,23 @@ fn blit_from_active_fb( frame.wait(&sync)?; std::mem::drop(fb_tmp); + // now we bind the target texture with `Transform::Normal` + // and render the temporary texture with inverse transform + // into src. let mut renderer = frame.renderer(); let mut frame = renderer .as_mut() - .render(&mut fb, tex_size, Transform::Normal)?; + .render(&mut fb, tex_size_phys, Transform::Normal)?; frame.wait(&sync)?; Frame::render_texture_from_to( &mut frame, &tmp_texture, - Rectangle::from_size(dst.size.to_f64()), - src.to_logical(1., Transform::Normal, &src.size) + Rectangle::from_size(dst_buffer.size.to_f64()), + src.to_logical(1., Transform::Normal, &Size::default()) .to_physical(1.) .to_i32_round(), - &[Rectangle::from_size(dst_phys.size)], - &[Rectangle::from_size(dst_phys.size)], + &[Rectangle::from_size(dst.size)], + &[Rectangle::from_size(dst.size)], transform.invert(), 1.0, )?; @@ -555,7 +560,7 @@ fn blit_from_active_fb( frame.blit_to( &mut fb, dst, - src.to_logical(1., Transform::Normal, &src.size) + src.to_logical(1., Transform::Normal, &Size::default()) .to_physical(1.) .to_i32_round(), TextureFilter::Linear, From 693d1ad775193deb95a33810b5eac59684ca2ac0 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 8 Jul 2026 15:23:00 +0200 Subject: [PATCH 53/56] shell/window: Fix radii for server-side decorations --- src/shell/element/window.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index f6c9e1c4..324299e0 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -549,6 +549,7 @@ impl CosmicWindow { } self.0.with_program(|p| { + let mut radii = radii; if has_ssd { radii[1] = 0; radii[3] = 0; @@ -575,6 +576,8 @@ impl CosmicWindow { }); if has_ssd { + radii[0] = 0; + radii[2] = 0; let ssd_loc = location + self .0 From 01c0cb020e9fce991254b1d1e6a6485be6046dde Mon Sep 17 00:00:00 2001 From: Tom Grushka Date: Tue, 7 Jul 2026 19:45:41 -0600 Subject: [PATCH 54/56] fix: a11y: magnifier: correct Centered/OnEdge focal-point math and refresh focal point at zoom-in - I used AI to assist in research of the focal point computation formulae for OnEdge and Centered modes. - I used the formulas found in [KDE's zoom magnifier](https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/zoom/zoom.cpp) and converted them to Rust and the COSMIC focal point system. - Remove unnecessary focal-point precomputation in `OutputZoomState::new`, because it is now updated on first zoom in anyway. - Do focal point computation in Local coordinates in update_focal_point to eliminate local-global conversion confusion/ issues. - ZoomMovement::OnEdge - single code path rewrite - old two-branch logic caused jumping and unpredictability at high zoom. Fixes [#2683](https://github.com/pop-os/cosmic-epoch/issues/2683), partially addresses [#2688](https://github.com/pop-os/cosmic-epoch/issues/2688). - ZoomMovement::Centered - translation/clamp/invert - prevents "blow up" of calculation at zoom level > 2.0 that caused cursor to disappear off bottom right of screen. Fixes [#1503](https://github.com/pop-os/cosmic-comp/issues/1503). - `is_level_change` - sets `previous_point` so that `animating_focal_point` interpolates instead of jumping. Still room for improvement (needs focal point recomputation per-frame during animation to prevent "boomerang" feel), but this is still an improvement over jumping. - In `trigger_zoom`, call `update_focal_point` when `level > 1.0` - overlaps proposal in [#1859](https://github.com/pop-os/cosmic-comp/pull/1859) + the level gate to prevent stale focal point on zoom in. Fixes [#2511](https://github.com/pop-os/cosmic-comp/issues/2511) and [#1883](https://github.com/pop-os/cosmic-epoch/issues/1883). Partially addresses [#1420](https://github.com/pop-os/cosmic-comp/issues/1420) (item 1 only). --- src/shell/mod.rs | 8 +++ src/shell/zoom.rs | 165 +++++++++++++++++++++------------------------- 2 files changed, 83 insertions(+), 90 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 8e628985..83845598 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -2490,6 +2490,14 @@ impl Shell { increment: zoom_config.increment, movement: zoom_config.view_moves, }); + + if level > 1.0 { + self.update_focal_point( + seat, + seat.get_pointer().unwrap().current_location().as_global(), + zoom_config.view_moves, + ); + } } pub fn update_focal_point( diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs index 11114e2d..1d5bbf1e 100644 --- a/src/shell/zoom.rs +++ b/src/shell/zoom.rs @@ -27,7 +27,7 @@ use smithay::{ }, }, output::Output, - utils::{IsAlive, Point, Rectangle, Serial, Size}, + utils::{FrameExtents, IsAlive, Point, Rectangle, Serial, Size}, }; use tracing::error; @@ -76,36 +76,8 @@ impl OutputZoomState { ) -> OutputZoomState { theme.transparent = theme.cosmic().frosted_system_interface; let cursor_position = seat.get_pointer().unwrap().current_location().as_global(); + let focal_point = cursor_position.to_local(output); let output_geometry = output.geometry().to_f64(); - let focal_point = if output_geometry.contains(cursor_position) { - match movement { - ZoomMovement::Continuously | ZoomMovement::OnEdge => { - cursor_position.to_local(output) - } - ZoomMovement::Centered => { - let mut zoomed_output_geometry = output.geometry().to_f64().downscale(level); - zoomed_output_geometry.loc = - cursor_position - zoomed_output_geometry.size.downscale(2.).to_point(); - - let mut focal_point = zoomed_output_geometry - .loc - .to_local(output) - .upscale(level) - .to_global(output); - focal_point.x = focal_point.x.clamp( - output_geometry.loc.x, - (output_geometry.loc.x + output_geometry.size.w).next_down(), - ); - focal_point.y = focal_point.y.clamp( - output_geometry.loc.y, - (output_geometry.loc.y + output_geometry.size.h).next_down(), - ); - focal_point.to_local(output) - } - } - } else { - (output_geometry.size.w / 2., output_geometry.size.h / 2.).into() - }; let program = ZoomProgram::new(level, movement, increment); let element = IcedElement::new(program, Size::default(), loop_handle, theme); @@ -269,86 +241,99 @@ impl ZoomState { &mut self, output: &Output, cursor_position: Point, - original_position: Point, + _original_position: Point, movement: ZoomMovement, ) { - let output_geometry = output.geometry().to_f64(); - let mut zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64(); + let output_geometry = output.geometry().to_f64().to_local(output); + let zoomed_output_geometry = output.zoomed_geometry().unwrap().to_f64().to_local(output); let output_state = output.user_data().get::>().unwrap(); let mut output_state_ref = output_state.lock().unwrap(); - // animate movement type changes - if self.movement != movement { + let level = output_state_ref.current_level(); + + let is_level_change = output_state_ref + .previous_level + .is_some_and(|prev| prev.0 != level); + + // animate level and movement type changes + if is_level_change || self.movement != movement { output_state_ref.previous_point = Some((output_state_ref.focal_point, Instant::now())); self.movement = movement; } let cursor_position = cursor_position.to_local(output); match movement { - ZoomMovement::Continuously => output_state_ref.focal_point = cursor_position, - ZoomMovement::OnEdge => { - if !zoomed_output_geometry - .overlaps_or_touches(Rectangle::new(original_position, Size::from((16., 16.)))) - { - zoomed_output_geometry.loc = cursor_position.to_global(output) - - zoomed_output_geometry.size.downscale(2.).to_point(); - let mut focal_point = zoomed_output_geometry - .loc - .to_local(output) - .upscale(output_state_ref.level) - .to_global(output); - focal_point.x = focal_point.x.clamp( - output_geometry.loc.x, - output_geometry.loc.x + output_geometry.size.w - 1., - ); - focal_point.y = focal_point.y.clamp( - output_geometry.loc.y, - output_geometry.loc.y + output_geometry.size.h - 1., - ); - output_state_ref.previous_point = - Some((output_state_ref.focal_point, Instant::now())); - output_state_ref.focal_point = focal_point.to_local(output); - } else if !zoomed_output_geometry.contains(cursor_position.to_global(output)) { - let mut diff = output_state_ref.focal_point.to_global(output) - + (cursor_position.to_global(output) - original_position) - .upscale(output_state_ref.level); - diff.x = diff.x.clamp( - output_geometry.loc.x, - (output_geometry.loc.x + output_geometry.size.w).next_down(), - ); - diff.y = diff.y.clamp( - output_geometry.loc.y, - (output_geometry.loc.y + output_geometry.size.h).next_down(), - ); - diff -= output_state_ref.focal_point.to_global(output); - - output_state_ref.focal_point += diff.as_logical().as_local(); - } + ZoomMovement::Continuously => { + // Focal point is the cursor position + output_state_ref.focal_point = cursor_position; } - ZoomMovement::Centered => { - zoomed_output_geometry.loc = cursor_position.to_global(output) - - zoomed_output_geometry.size.downscale(2.).to_point(); + ZoomMovement::OnEdge => { + if is_level_change { + output_state_ref.focal_point = cursor_position; + return; + } - let mut focal_point = zoomed_output_geometry - .loc - .to_local(output) - .upscale( - (output_geometry.size.w - / (output_geometry.size.w - zoomed_output_geometry.size.w) - .max(f64::EPSILON)) - .max(1.), - ) - .to_global(output); + // Compute small margin relative to zoomed output to keep cursor within + let margin_size = zoomed_output_geometry.size.h * 0.02; + let margins = FrameExtents::new(margin_size, margin_size, margin_size, margin_size); + let inner_rect = zoomed_output_geometry - margins; + + if inner_rect.contains(cursor_position) { + // Do not move if cursor within margins + return; + } + + // Compute dx and dy to move the zoomed output based on cursor distance outside margin(s) + let dx = if cursor_position.x < inner_rect.loc.x { + cursor_position.x - inner_rect.loc.x + } else if cursor_position.x > inner_rect.loc.x + inner_rect.size.w { + cursor_position.x - (inner_rect.loc.x + inner_rect.size.w) + } else { + 0.0 + }; + let dy = if cursor_position.y < inner_rect.loc.y { + cursor_position.y - inner_rect.loc.y + } else if cursor_position.y > inner_rect.loc.y + inner_rect.size.h { + cursor_position.y - (inner_rect.loc.y + inner_rect.size.h) + } else { + 0.0 + }; + + let mut focal_point = output_state_ref.focal_point + Point::new(dx, dy); + + // Clamp the final focal point to output geometry focal_point.x = focal_point.x.clamp( output_geometry.loc.x, - output_geometry.loc.x + output_geometry.size.w - 1., + output_geometry.loc.x + output_geometry.size.w - 1.0, ); focal_point.y = focal_point.y.clamp( output_geometry.loc.y, - output_geometry.loc.y + output_geometry.size.h - 1., + output_geometry.loc.y + output_geometry.size.h - 1.0, ); - output_state_ref.focal_point = focal_point.to_local(output); + + output_state_ref.focal_point = focal_point; + } + ZoomMovement::Centered => { + let center = (output_geometry.size / 2.).to_point(); + + if level <= 1.0 + f64::EPSILON { + // Without this break, focal point will jump to (0, 0) on zoom out + output_state_ref.focal_point = center; + return; + } + + // Compute translation to keep cursor at center of screen + let mut tx = center.x - cursor_position.x * level; + let mut ty = center.y - cursor_position.y * level; + + // Clamp translation to keep viewport within screen bounds + tx = tx.clamp(output_geometry.size.w * (1.0 - level), 0.0); + ty = ty.clamp(output_geometry.size.h * (1.0 - level), 0.0); + + // Convert translation back to focal point: T = F * (1 - level) + output_state_ref.focal_point = + Point::from((tx / (1.0 - level), ty / (1.0 - level))); } } } From 4d14f5568fcfda61c1c9aa01d4a305d0de89cfb8 Mon Sep 17 00:00:00 2001 From: Tom Grushka Date: Tue, 7 Jul 2026 20:03:28 -0600 Subject: [PATCH 55/56] improve comments --- src/shell/zoom.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs index 1d5bbf1e..a8da3561 100644 --- a/src/shell/zoom.rs +++ b/src/shell/zoom.rs @@ -270,11 +270,13 @@ impl ZoomState { } ZoomMovement::OnEdge => { if is_level_change { + // Ensure the cursor doesn't disappear off screen output_state_ref.focal_point = cursor_position; return; } // Compute small margin relative to zoomed output to keep cursor within + // (can be user-configurable in the future) let margin_size = zoomed_output_geometry.size.h * 0.02; let margins = FrameExtents::new(margin_size, margin_size, margin_size, margin_size); let inner_rect = zoomed_output_geometry - margins; @@ -318,7 +320,7 @@ impl ZoomState { let center = (output_geometry.size / 2.).to_point(); if level <= 1.0 + f64::EPSILON { - // Without this break, focal point will jump to (0, 0) on zoom out + // Prevent focal point jumping to top-left corner (0, 0) on zoom out output_state_ref.focal_point = center; return; } From 0596cce40d93df7518738ae9f8c631fa413ac4a9 Mon Sep 17 00:00:00 2001 From: Tom Grushka Date: Wed, 8 Jul 2026 14:43:32 -0600 Subject: [PATCH 56/56] remove redundant level condition; simplify level early return on ZoomMovement::Centered --- src/shell/mod.rs | 12 +++++------- src/shell/zoom.rs | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 83845598..326b064b 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -2491,13 +2491,11 @@ impl Shell { movement: zoom_config.view_moves, }); - if level > 1.0 { - self.update_focal_point( - seat, - seat.get_pointer().unwrap().current_location().as_global(), - zoom_config.view_moves, - ); - } + self.update_focal_point( + seat, + seat.get_pointer().unwrap().current_location().as_global(), + zoom_config.view_moves, + ); } pub fn update_focal_point( diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs index a8da3561..ef81053f 100644 --- a/src/shell/zoom.rs +++ b/src/shell/zoom.rs @@ -319,7 +319,7 @@ impl ZoomState { ZoomMovement::Centered => { let center = (output_geometry.size / 2.).to_point(); - if level <= 1.0 + f64::EPSILON { + if level == 1.0 { // Prevent focal point jumping to top-left corner (0, 0) on zoom out output_state_ref.focal_point = center; return;