diff --git a/Cargo.lock b/Cargo.lock index cf167e86..5edc17e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,13 +67,11 @@ source = "git+https://github.com/wash2/accesskit?tag=cosmic-0.14#f0599eed5f18111 dependencies = [ "accesskit", "accesskit_atspi_common", - "async-channel 2.5.0", - "async-executor", - "async-task", "atspi", "futures-lite", - "futures-util", "serde", + "tokio", + "tokio-stream", "zbus", ] @@ -272,8 +270,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" dependencies = [ - "async-fs", - "async-net", "enumflags2", "futures-channel", "futures-util", @@ -281,6 +277,7 @@ dependencies = [ "raw-window-handle", "serde", "serde_repr", + "tokio", "url", "zbus", ] @@ -291,14 +288,13 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33a3c86f3fd70c0ffa500ed189abfa90b5a52398a45d5dc372fcc38ebeb7a645" dependencies = [ - "async-fs", - "async-net", "enumflags2", "futures-channel", "futures-util", "rand 0.9.2", "serde", "serde_repr", + "tokio", "url", "wayland-backend", "wayland-client", @@ -312,23 +308,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "futures-core", "pin-project-lite", ] -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - [[package]] name = "async-channel" version = "2.5.0" @@ -355,32 +340,6 @@ dependencies = [ "slab", ] -[[package]] -name = "async-fs" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" -dependencies = [ - "async-lock", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - [[package]] name = "async-io" version = "2.6.0" @@ -405,36 +364,25 @@ version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] -[[package]] -name = "async-net" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" -dependencies = [ - "async-io", - "blocking", - "futures-lite", -] - [[package]] name = "async-process" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ - "async-channel 2.5.0", + "async-channel", "async-io", "async-lock", "async-signal", "async-task", "blocking", "cfg-if", - "event-listener 5.4.1", + "event-listener", "futures-lite", "rustix 1.1.4", ] @@ -468,32 +416,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - [[package]] name = "async-task" version = "4.7.1" @@ -572,9 +494,9 @@ dependencies = [ [[package]] name = "auto_enums" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65398a2893f41bce5c9259f6e1a4f03fbae40637c1bdc755b4f387f48c613b03" +checksum = "2e4487600931c9a89f8db7ffbdf3fbdd45bb7bd85e26861f659a463cd0dff966" dependencies = [ "derive_utils", "proc-macro2", @@ -693,7 +615,7 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ - "async-channel 2.5.0", + "async-channel", "async-task", "futures-io", "futures-lite", @@ -773,20 +695,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" @@ -795,31 +703,20 @@ checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" dependencies = [ "async-task", "bitflags 2.11.0", + "futures-core", "polling", "rustix 1.1.4", "slab", "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", @@ -969,7 +866,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ "serde", "termcolor", - "unicode-width 0.2.2", + "unicode-width 0.1.14", ] [[package]] @@ -1080,7 +977,7 @@ dependencies = [ "bitflags 2.11.0", "cosmic-protocols", "libc", - "smithay-client-toolkit 0.20.0", + "smithay-client-toolkit", "wayland-client", "wayland-protocols", ] @@ -1091,7 +988,7 @@ version = "1.0.0" dependencies = [ "anyhow", "bitflags 2.11.0", - "calloop 0.14.4", + "calloop", "cgmath", "clap_lex", "cosmic-comp-config", @@ -1128,11 +1025,11 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", - "smallvec", "smithay", "smithay-egui", "thiserror 2.0.18", "tiny-skia", + "tokio", "tracing", "tracing-journald", "tracing-subscriber", @@ -1163,9 +1060,8 @@ dependencies = [ name = "cosmic-config" version = "1.0.0" dependencies = [ - "async-std", "atomicwrites", - "calloop 0.14.4", + "calloop", "cosmic-config-derive", "cosmic-settings-daemon", "dirs", @@ -1175,6 +1071,7 @@ dependencies = [ "notify", "ron 0.12.0", "serde", + "tokio", "tracing", "xdg", "zbus", @@ -1264,7 +1161,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", @@ -1487,12 +1384,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - [[package]] name = "dispatch2" version = "0.3.1" @@ -1532,7 +1423,7 @@ dependencies = [ "bitflags 2.11.0", "mime 0.1.0", "raw-window-handle", - "smithay-client-toolkit 0.20.0", + "smithay-client-toolkit", "smithay-clipboard", ] @@ -1804,12 +1695,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "event-listener" version = "5.4.1" @@ -1827,7 +1712,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener", "pin-project-lite", ] @@ -2275,18 +2160,6 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "glow" version = "0.16.0" @@ -2319,7 +2192,7 @@ dependencies = [ "log", "presser", "thiserror 2.0.18", - "windows 0.61.3", + "windows 0.62.2", ] [[package]] @@ -2610,7 +2483,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 2.1.1", "serde", - "smol_str 0.3.5", + "smol_str", "thiserror 2.0.18", "unicode-segmentation", "web-time", @@ -2634,6 +2507,7 @@ dependencies = [ "iced_core", "log", "rustc-hash 2.1.1", + "tokio", "wasm-bindgen-futures", "wasmtimer", ] @@ -2776,7 +2650,7 @@ dependencies = [ "winapi", "window_clipboard", "winit 0.31.0-beta.2", - "winit-core", + "winit-core 0.31.0-beta.2", "xkbcommon 0.7.0", "xkbcommon-dl", "xkeysym", @@ -3238,15 +3112,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -3261,9 +3126,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libcosmic-yoda" @@ -3271,7 +3136,6 @@ version = "0.1.0-yoda.2" dependencies = [ "apply", "ashpd 0.12.3", - "async-std", "auto_enums", "cosmic-client-toolkit", "cosmic-config", @@ -3305,6 +3169,7 @@ dependencies = [ "slotmap", "taffy", "thiserror 2.0.18", + "tokio", "tracing", "unicode-segmentation", "url", @@ -3363,13 +3228,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ "bitflags 2.11.0", "libc", - "redox_syscall 0.7.2", + "plain", + "redox_syscall 0.9.0", ] [[package]] @@ -3479,9 +3345,6 @@ name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -dependencies = [ - "value-bag", -] [[package]] name = "log-panics" @@ -3693,9 +3556,9 @@ checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" [[package]] name = "mio" -version = "1.1.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -4015,30 +3878,6 @@ dependencies = [ "objc2-foundation 0.3.2", ] -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-core-data" version = "0.2.2" @@ -4086,18 +3925,6 @@ dependencies = [ "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 0.5.1", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-core-video" version = "0.3.2" @@ -4123,7 +3950,6 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.11.0", "block2 0.5.1", - "dispatch", "libc", "objc2 0.5.2", ] @@ -4140,18 +3966,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-metal" version = "0.2.2" @@ -4177,37 +3991,6 @@ dependencies = [ "objc2-metal", ] -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - [[package]] name = "objc2-ui-kit" version = "0.3.2" @@ -4220,30 +4003,6 @@ dependencies = [ "objc2-foundation 0.3.2", ] -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[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 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc_id" version = "0.1.1" @@ -4564,6 +4323,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "png" version = "0.17.16" @@ -4753,16 +4518,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "quick-xml" version = "0.39.2" @@ -4770,6 +4525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", + "serde", ] [[package]] @@ -4910,23 +4666,14 @@ dependencies = [ [[package]] name = "redox_event" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3514da49aa6ca4cff5088a1b382ef3a0d9149964d29bb49ebd03fd66cc575a18" +checksum = "c5018d583d6d2f5499352aea8d177e9067d1eb03ab17c78169d5ba7a30001b15" dependencies = [ "bitflags 2.11.0", "libredox", ] -[[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" @@ -4938,9 +4685,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d94dd2f7cd932d4dc02cc8b2b50dfd38bd079a4e5d79198b99743d7fcf9a4b4" +checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ "bitflags 2.11.0", ] @@ -5005,6 +4752,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" @@ -5279,7 +5038,7 @@ dependencies = [ "ab_glyph", "log", "memmap2 0.9.10", - "smithay-client-toolkit 0.20.0", + "smithay-client-toolkit", "tiny-skia", ] @@ -5497,14 +5256,14 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay" version = "0.7.0" -source = "git+https://github.com/smithay/smithay.git?rev=774f2ab#774f2ab010e080a2f79176d0e12eb63d5e09d680" +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", @@ -5526,6 +5285,7 @@ dependencies = [ "pkg-config", "profiling", "rand 0.9.2", + "reis", "rustix 1.1.4", "scopeguard", "sha2", @@ -5542,36 +5302,11 @@ dependencies = [ "wayland-protocols-misc", "wayland-protocols-wlr", "wayland-server", - "winit 0.30.12", + "winit 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", "x11rb", "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 0.9.10", - "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" @@ -5580,8 +5315,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", @@ -5608,7 +5343,7 @@ version = "0.8.0" dependencies = [ "libc", "raw-window-handle", - "smithay-client-toolkit 0.20.0", + "smithay-client-toolkit", "wayland-backend", ] @@ -5629,15 +5364,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" @@ -5654,6 +5380,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27207bb65232eda1f588cf46db2fee75c0808d557f6b3cf19a75f5d6d7c94df1" +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "softbuffer" version = "0.4.1" @@ -5990,6 +5726,45 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.5.11" @@ -6275,9 +6050,9 @@ checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-vo" @@ -6416,12 +6191,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "value-bag" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" - [[package]] name = "version-compare" version = "0.2.1" @@ -6707,7 +6476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" dependencies = [ "proc-macro2", - "quick-xml 0.39.2", + "quick-xml", "quote", ] @@ -7452,57 +7221,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winit" -version = "0.30.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" -dependencies = [ - "ahash", - "android-activity", - "atomic-waker", - "bitflags 2.11.0", - "block2 0.5.1", - "bytemuck", - "calloop 0.13.0", - "cfg_aliases", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics", - "cursor-icon", - "dpi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys", - "libc", - "memmap2 0.9.10", - "ndk", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit 0.2.2", - "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", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] - [[package]] name = "winit" version = "0.31.0-beta.2" @@ -7514,17 +7232,44 @@ dependencies = [ "libc", "raw-window-handle", "rustix 1.1.4", - "smol_str 0.3.5", + "smol_str", "tracing", - "winit-android", - "winit-appkit", - "winit-common", - "winit-core", - "winit-orbital", - "winit-uikit", - "winit-wayland", - "winit-web", - "winit-win32", + "winit-android 0.31.0-beta.2", + "winit-appkit 0.31.0-beta.2", + "winit-common 0.31.0-beta.2", + "winit-core 0.31.0-beta.2", + "winit-orbital 0.31.0-beta.2", + "winit-uikit 0.31.0-beta.2", + "winit-wayland 0.31.0-beta.2", + "winit-web 0.31.0-beta.2", + "winit-win32 0.31.0-beta.2", +] + +[[package]] +name = "winit" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2879d2854d1a43e48f67322d4bd097afcb6eb8f8f775c8de0260a71aea1df1aa" +dependencies = [ + "bitflags 2.11.0", + "cfg_aliases", + "cursor-icon", + "dpi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "raw-window-handle", + "rustix 1.1.4", + "smol_str", + "tracing", + "winit-android 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-appkit 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-common 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-orbital 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-uikit 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-wayland 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-web 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-win32 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-x11", ] [[package]] @@ -7536,9 +7281,25 @@ dependencies = [ "dpi 0.1.2", "ndk", "raw-window-handle", - "smol_str 0.3.5", + "smol_str", "tracing", - "winit-core", + "winit-core 0.31.0-beta.2", +] + +[[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 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ndk", + "raw-window-handle", + "smol_str", + "tracing", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -7556,10 +7317,33 @@ dependencies = [ "objc2-core-video", "objc2-foundation 0.3.2", "raw-window-handle", - "smol_str 0.3.5", + "smol_str", "tracing", - "winit-common", - "winit-core", + "winit-common 0.31.0-beta.2", + "winit-core 0.31.0-beta.2", +] + +[[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 0.6.2", + "dispatch2", + "dpi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-video", + "objc2-foundation 0.3.2", + "raw-window-handle", + "smol_str", + "tracing", + "winit-common 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -7569,9 +7353,25 @@ dependencies = [ "memmap2 0.9.10", "objc2 0.6.4", "objc2-core-foundation", - "smol_str 0.3.5", + "smol_str", "tracing", - "winit-core", + "winit-core 0.31.0-beta.2", + "xkbcommon-dl", +] + +[[package]] +name = "winit-common" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45375fbac4cbb77260d83a30b1f9d8105880dbac99a9ae97f56656694680ff69" +dependencies = [ + "memmap2 0.9.10", + "objc2 0.6.4", + "objc2-core-foundation", + "smol_str", + "tracing", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "x11-dl", "xkbcommon-dl", ] @@ -7584,7 +7384,22 @@ dependencies = [ "dpi 0.1.2", "keyboard-types", "raw-window-handle", - "smol_str 0.3.5", + "smol_str", + "web-time", +] + +[[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 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "keyboard-types", + "raw-window-handle", + "smol_str", "web-time", ] @@ -7598,9 +7413,25 @@ dependencies = [ "orbclient", "raw-window-handle", "redox_event", - "smol_str 0.3.5", + "smol_str", "tracing", - "winit-core", + "winit-core 0.31.0-beta.2", +] + +[[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 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "orbclient", + "raw-window-handle", + "redox_syscall 0.5.18", + "smol_str", + "tracing", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -7614,12 +7445,33 @@ dependencies = [ "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", - "objc2-ui-kit 0.3.2", + "objc2-ui-kit", "raw-window-handle", - "smol_str 0.3.5", + "smol_str", "tracing", - "winit-common", - "winit-core", + "winit-common 0.31.0-beta.2", + "winit-core 0.31.0-beta.2", +] + +[[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 0.6.2", + "dispatch2", + "dpi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "raw-window-handle", + "smol_str", + "tracing", + "winit-common 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -7628,7 +7480,7 @@ version = "0.31.0-beta.2" dependencies = [ "ahash", "bitflags 2.11.0", - "calloop 0.14.4", + "calloop", "cursor-icon", "dpi 0.1.2", "libc", @@ -7636,15 +7488,41 @@ dependencies = [ "raw-window-handle", "rustix 1.1.4", "sctk-adwaita", - "smithay-client-toolkit 0.20.0", - "smol_str 0.3.5", + "smithay-client-toolkit", + "smol_str", "tracing", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-protocols-plasma", - "winit-common", - "winit-core", + "winit-common 0.31.0-beta.2", + "winit-core 0.31.0-beta.2", +] + +[[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 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "memmap2 0.9.10", + "raw-window-handle", + "rustix 1.1.4", + "smithay-client-toolkit", + "smol_str", + "tracing", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "winit-common 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -7659,13 +7537,36 @@ dependencies = [ "js-sys", "pin-project", "raw-window-handle", - "smol_str 0.3.5", + "smol_str", "tracing", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "web-time", - "winit-core", + "winit-core 0.31.0-beta.2", +] + +[[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 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys", + "pin-project", + "raw-window-handle", + "smol_str", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -7676,11 +7577,52 @@ dependencies = [ "cursor-icon", "dpi 0.1.2", "raw-window-handle", - "smol_str 0.3.5", + "smol_str", "tracing", "unicode-segmentation", "windows-sys 0.59.0", - "winit-core", + "winit-core 0.31.0-beta.2", +] + +[[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 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-window-handle", + "smol_str", + "tracing", + "unicode-segmentation", + "windows-sys 0.59.0", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[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 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "percent-encoding", + "raw-window-handle", + "rustix 1.1.4", + "smol_str", + "tracing", + "winit-common 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winit-core 0.31.0-beta.2 (registry+https://github.com/rust-lang/crates.io-index)", + "x11-dl", + "x11rb", + "xkbcommon-dl", ] [[package]] @@ -7828,6 +7770,7 @@ dependencies = [ "once_cell", "rustix 1.1.4", "x11rb-protocol", + "xcursor", ] [[package]] @@ -7962,9 +7905,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.15.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" dependencies = [ "async-broadcast", "async-executor", @@ -7976,7 +7919,7 @@ dependencies = [ "async-trait", "blocking", "enumflags2", - "event-listener 5.4.1", + "event-listener", "futures-core", "futures-lite", "hex", @@ -7985,6 +7928,7 @@ dependencies = [ "rustix 1.1.4", "serde", "serde_repr", + "tokio", "tracing", "uds_windows", "uuid", @@ -8021,9 +7965,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.15.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8047,11 +7991,11 @@ dependencies = [ [[package]] name = "zbus_xml" -version = "5.1.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "441a0064125265655bccc3a6af6bef56814d9277ac83fce48b1cd7e160b80eac" +checksum = "a8067892e940ed1727dea64690378601603b31d62dfde019a5335fbb7c0e0ed9" dependencies = [ - "quick-xml 0.38.4", + "quick-xml", "serde", "zbus_names", "zvariant", @@ -8176,9 +8120,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" dependencies = [ "endi", "enumflags2", @@ -8191,9 +8135,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.11.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8204,9 +8148,9 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 55757aa5..277655e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,12 @@ members = ["cosmic-comp-config"] [dependencies] anyhow = { version = "1.0.102", features = ["backtrace"] } bitflags = "2.11.0" -calloop = { version = "0.14.4", features = ["executor"] } +calloop = { version = "0.14.4", features = ["executor", "stream"] } cosmic-comp-config = { path = "cosmic-comp-config", features = [ "libdisplay-info", "output", ] } cosmic-config = { path = "../libcosmic/cosmic-config", features = [ - "async-std", "calloop", "macro", ] } @@ -44,7 +43,7 @@ iced_tiny_skia = { path = "../libcosmic/iced/tiny_skia" } indexmap = "2.13" keyframe = "1.1.1" cosmic = { package = "libcosmic-yoda", path = "../libcosmic", default-features = false, features = [ - "async-std", + "tokio", "wayland", "multi-window", ] } @@ -78,6 +77,7 @@ xdg = "^3.0" xdg-user = "0.2.1" xkbcommon = "0.9" zbus = "5.14.0" +tokio = { version = "1", features = ["rt-multi-thread"] } profiling = { version = "1.0" } rustix = { version = "1.1.4", features = ["process"] } rand = "0.10" @@ -85,10 +85,9 @@ rand = "0.10" clap_lex = "1.0" parking_lot = "0.12.5" logind-zbus = { version = "5.3.2", optional = true } -futures-executor = { version = "0.3.32", features = ["thread-pool"] } +futures-executor = { version = "0.3.32" } futures-util = "0.3.32" cgmath = "0.18.0" -smallvec = "1.15.1" [dependencies.id_tree] branch = "feature/copy_clone" @@ -123,7 +122,8 @@ optional = true [features] debug = ["egui", "egui_plot", "smithay-egui", "anyhow/backtrace"] default = ["systemd"] -systemd = ["libsystemd", "logind-zbus"] +logind = ["logind-zbus"] +systemd = ["libsystemd", "logind"] profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"] profile-with-tracy-gpu = ["profile-with-tracy", "smithay/tracy_gpu_profiling"] @@ -175,4 +175,4 @@ dnd = { path = "../window_clipboard/dnd" } mime = { path = "../window_clipboard/mime" } [patch.crates-io] -smithay = { git = "https://github.com/smithay/smithay.git", rev = "774f2ab" } +smithay = { git = "https://github.com/smithay/smithay.git", rev = "8eb4076" } diff --git a/Makefile b/Makefile index cfe59669..16f59ee6 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ vendor: mkdir -p .cargo cargo vendor | head -n -1 > .cargo/config echo 'directory = "vendor"' >> .cargo/config + [ -n "$(SOURCE_GIT_HASH)" ] && printf '\n[env]\nGIT_HASH = "%s"\n' "$(SOURCE_GIT_HASH)" >> .cargo/config || true tar pcf vendor.tar vendor rm -rf vendor diff --git a/cosmic-comp-config/src/lib.rs b/cosmic-comp-config/src/lib.rs index 8c4d9588..e5e73883 100644 --- a/cosmic-comp-config/src/lib.rs +++ b/cosmic-comp-config/src/lib.rs @@ -101,6 +101,7 @@ pub struct CosmicCompConfig { pub appearance_settings: AppearanceConfig, /// Hide the cursor after this many seconds of pointer inactivity (None disables) pub cursor_hide_timeout: Option, + pub activation_policy: ActivationPolicy, } impl Default for CosmicCompConfig { @@ -138,6 +139,7 @@ impl Default for CosmicCompConfig { accessibility_zoom: ZoomConfig::default(), appearance_settings: AppearanceConfig::default(), cursor_hide_timeout: None, + activation_policy: ActivationPolicy::default(), } } } @@ -227,6 +229,14 @@ pub enum EavesdroppingKeyboardMode { All, } +#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialEq, Eq)] +pub enum ActivationPolicy { + #[default] + Focus, + FocusIfActiveWorkspace, + Urgent, +} + #[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum XwaylandDescaling { diff --git a/cosmic-comp-config/src/workspace.rs b/cosmic-comp-config/src/workspace.rs index 08c3dc6c..62676264 100644 --- a/cosmic-comp-config/src/workspace.rs +++ b/cosmic-comp-config/src/workspace.rs @@ -63,5 +63,5 @@ pub struct PinnedWorkspace { pub output: OutputMatch, pub tiling_enabled: bool, pub id: Option, - // TODO: name + pub name: Option, } diff --git a/data/tiling-exceptions.ron b/data/tiling-exceptions.ron index 14976eec..2065c489 100644 --- a/data/tiling-exceptions.ron +++ b/data/tiling-exceptions.ron @@ -48,5 +48,6 @@ (appid: "zoom", titles: [".*"]), (appid: "Tor Browser", titles: [".*"]), (appid: "^.*?action=join.*$", titles: [".*"]), - + (appid: "^(slack|com.slack.Slack)", titles: ["^.*?(Huddle Preview).*"]), + (appid: "^(thunderbird|org.mozilla.thunderbird)(-esr|_esr)*", titles: ["^(Write:).*"]), ] 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 6144e4f0..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,28 +73,10 @@ 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()?; - 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,76 @@ 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, &cleanup)?; + + Ok(map) +} + +/// Disables the given CRTCs and detaches their connectors/planes. +pub fn disable_crtcs(device: &mut DrmDevice, crtcs: &[crtc::Handle]) -> Result<()> { + if crtcs.is_empty() { + return Ok(()); + } + + let res_handles = device.resource_handles()?; + + if device.is_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 3a0d8c8c..13832da8 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -374,6 +374,7 @@ impl State { for device in backend.drm_devices.values_mut() { if let Err(err) = device.drm.lock().activate(true) { error!(?err, "Failed to resume drm device"); + continue; } if let Some(lease_state) = device.inner.leasing_global.as_mut() { lease_state.resume::(); @@ -851,11 +852,23 @@ 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 + } + }); + + if let Err(err) = + drm_helpers::disable_crtcs(device.drm.device_mut(), &disabled_crtcs) + { + warn!("Failed to disable crtcs for disabled outputs: {err}"); } } diff --git a/src/backend/kms/render/gles.rs b/src/backend/kms/render/gles.rs index 5d79b1ee..4a75c0ab 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,29 @@ 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("TGL") + || 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 +177,7 @@ impl GraphicsApi for GbmGlowBackend { pub struct GbmGlowDevice { node: DrmNode, renderer: GlowRenderer, + intel_export_quirk: bool, allocator: Box>, } @@ -180,9 +208,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, diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 6b0d42b4..62ba7c1f 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -1024,14 +1024,18 @@ impl SurfaceThreadState { let animations_going = shell.animations_going(); let output = self.mirroring.as_ref().unwrap_or(&self.output); if let Some((_, workspace)) = shell.workspaces.active(output) { - if let Some(fullscreen_surface) = workspace.get_fullscreen() { + let seat = shell.seats.last_active(); + if let Some(fullscreen_surface) = workspace.get_fullscreen(seat) { const _30_FPS: Duration = Duration::from_nanos(1_000_000_000 / 30); ( true, - fullscreen_surface.wl_surface().is_some_and(|surface| { - recursive_frame_time_estimation(&self.clock, &surface) - .is_some_and(|dur| dur <= _30_FPS) - }), + fullscreen_surface + .surface + .wl_surface() + .is_some_and(|surface| { + recursive_frame_time_estimation(&self.clock, &surface) + .is_some_and(|dur| dur <= _30_FPS) + }), animations_going, ) } else { @@ -1490,18 +1494,21 @@ fn render_node_for_output( let Some(workspace) = shell.active_space(output) else { return *target_node; }; - let nodes = workspace - .get_fullscreen() - .map(|w| vec![w.clone()]) - .unwrap_or_else(|| { - workspace - .mapped() - .map(|mapped| mapped.active_window()) - .collect::>() - }) - .into_iter() - .flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s))) - .collect::>(); + let fullscreens: Vec<_> = workspace + .get_fullscreen_surfaces() + .map(|f| f.surface.clone()) + .collect(); + let nodes = if !fullscreens.is_empty() { + fullscreens + } else { + workspace + .mapped() + .map(|mapped| mapped.active_window()) + .collect::>() + } + .into_iter() + .flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s))) + .collect::>(); if nodes.contains(target_node) || nodes.is_empty() { *target_node 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/config/mod.rs b/src/config/mod.rs index d75eed4e..1d9b9c92 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -45,8 +45,8 @@ mod types; use cosmic::config::CosmicTk; pub use cosmic_comp_config::EdidProduct; use cosmic_comp_config::{ - AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig, XwaylandDescaling, - XwaylandEavesdropping, ZoomConfig, + ActivationPolicy, AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig, + XwaylandDescaling, XwaylandEavesdropping, ZoomConfig, input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride}, output::comp::{ OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs, @@ -966,6 +966,12 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut } } } + "activation_policy" => { + let new = get_config::(&config, "activation_policy"); + if new != state.common.config.cosmic_conf.activation_policy { + state.common.config.cosmic_conf.activation_policy = new; + } + } _ => {} } } diff --git a/src/dbus/a11y_keyboard_monitor.rs b/src/dbus/a11y_keyboard_monitor.rs index c40be081..cf66a54b 100644 --- a/src/dbus/a11y_keyboard_monitor.rs +++ b/src/dbus/a11y_keyboard_monitor.rs @@ -1,13 +1,12 @@ // https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.freedesktop.a11y.xml -use futures_executor::ThreadPool; use smithay::{ backend::input::KeyState, input::keyboard::{KeysymHandle, ModifiersState}, }; use std::{ collections::{HashMap, HashSet}, - sync::{Arc, Mutex, OnceLock}, + sync::{Arc, Mutex}, }; use tracing::debug; use xkbcommon::xkb::Keysym; @@ -72,40 +71,37 @@ impl Clients { #[derive(Debug)] pub struct A11yKeyboardMonitorState { - executor: ThreadPool, + executor: calloop::futures::Scheduler<()>, clients: Arc>, active_virtual_mods: HashSet, - conn: Arc>, - name_owners: Arc>, + conn: zbus::Connection, + name_owners: NameOwners, } impl A11yKeyboardMonitorState { - pub fn new(executor: &ThreadPool) -> Self { + pub async fn new( + conn: &zbus::Connection, + name_owners: &NameOwners, + executor: &calloop::futures::Scheduler<()>, + ) -> zbus::Result { let clients = Arc::new(Mutex::new(Clients::default())); - let clients_clone = clients.clone(); - let conn_cell = Arc::new(OnceLock::new()); - let conn_cell_clone = conn_cell.clone(); - let name_owners_cell = Arc::new(OnceLock::new()); - let name_owners_cell_clone = name_owners_cell.clone(); - let executor_clone = executor.clone(); - executor.spawn_ok(async move { - match serve(clients_clone, &executor_clone).await { - Ok((conn, name_owners)) => { - conn_cell_clone.set(conn).unwrap(); - name_owners_cell_clone.set(name_owners).unwrap(); - } - Err(err) => { - tracing::error!("Failed to serve `org.freedesktop.a11y.Manager`: {err}"); - } - } - }); - Self { + + let keyboard_monitor = KeyboardMonitor { + clients: clients.clone(), + name_owners: name_owners.clone(), + }; + conn.object_server() + .at("/org/freedesktop/a11y/Manager", keyboard_monitor) + .await?; + conn.request_name("org.freedesktop.a11y.Manager").await?; + + Ok(Self { executor: executor.clone(), clients, active_virtual_mods: HashSet::new(), - conn: conn_cell, - name_owners: name_owners_cell, - } + conn: conn.clone(), + name_owners: name_owners.clone(), + }) } pub fn has_virtual_mod(&self, keysym: Keysym) -> bool { @@ -154,10 +150,6 @@ impl A11yKeyboardMonitorState { } pub fn key_event(&self, modifiers: &ModifiersState, keysym: &KeysymHandle, state: KeyState) { - let Some(conn) = self.conn.get() else { - return; - }; - let clients = self.clients.lock().unwrap(); for (unique_name, client) in clients.0.iter() { if !client.watched && !self.has_key_grab(modifiers, keysym.modified_sym()) { @@ -165,7 +157,7 @@ impl A11yKeyboardMonitorState { } let mut signal_context = - SignalEmitter::new(conn, "/org/freedesktop/a11y/Manager").unwrap(); + SignalEmitter::new(&self.conn, "/org/freedesktop/a11y/Manager").unwrap(); // Instead of sending signal to all clients, send only to authorized // clients with registed watches. signal_context = signal_context.set_destination(unique_name.clone().into()); @@ -186,7 +178,7 @@ impl A11yKeyboardMonitorState { unichar, keysym.raw_code().raw() as u16, ); - self.executor.spawn_ok(async { + let _ = self.executor.schedule(async { let _ = future.await; }); } @@ -195,13 +187,11 @@ impl A11yKeyboardMonitorState { pub fn refresh(&mut self) { // Remove clients and associated grabs when unique names are no longer // present on bus, or no longer hold approved name on bus. - if let Some(name_owners) = self.name_owners.get() { - self.clients - .lock() - .unwrap() - .0 - .retain(|k, _| name_owners.check_owner_no_poll(k, ALLOWED_NAMES)) - } + self.clients + .lock() + .unwrap() + .0 + .retain(|k, _| self.name_owners.check_owner_no_poll(k, ALLOWED_NAMES)) } } @@ -311,20 +301,3 @@ impl KeyboardMonitor { keycode: u16, ) -> zbus::Result<()>; } - -async fn serve( - clients: Arc>, - executor: &ThreadPool, -) -> zbus::Result<(zbus::Connection, NameOwners)> { - let conn = zbus::Connection::session().await?; - let name_owners = NameOwners::new(&conn, executor).await?; - let keyboard_monitor = KeyboardMonitor { - clients, - name_owners: name_owners.clone(), - }; - conn.object_server() - .at("/org/freedesktop/a11y/Manager", keyboard_monitor) - .await?; - conn.request_name("org.freedesktop.a11y.Manager").await?; - Ok((conn, name_owners)) -} diff --git a/src/dbus/logind.rs b/src/dbus/logind.rs index b0c59170..53e3b2c0 100644 --- a/src/dbus/logind.rs +++ b/src/dbus/logind.rs @@ -1,24 +1,34 @@ use std::os::fd::OwnedFd; use anyhow::{Context, Result}; -use logind_zbus::manager::{InhibitType::HandleLidSwitch, ManagerProxyBlocking}; -use zbus::blocking::Connection; +use logind_zbus::manager::{InhibitType::HandleLidSwitch, ManagerProxy}; -pub fn inhibit_lid() -> Result { - let conn = Connection::system()?; - let proxy = ManagerProxyBlocking::new(&conn)?; - let fd = proxy.inhibit( - HandleLidSwitch, - "cosmic-comp", - "External output connected", - "block", - )?; +use crate::state::Common; + +pub fn inhibit_lid(common: &Common) -> Result { + let fd = futures_executor::block_on(async { + let conn = common.dbus_state.system_conn().await?; + let manager = ManagerProxy::new(conn).await?; + manager + .inhibit( + HandleLidSwitch, + "cosmic-comp", + "External output connected", + "block", + ) + .await + })?; Ok(fd.into()) } -pub fn lid_closed() -> Result { - let conn = Connection::system()?; - let proxy = ManagerProxyBlocking::new(&conn)?; - proxy.lid_closed().context("Failed to talk to logind") +pub fn lid_closed(common: &Common) -> Result { + futures_executor::block_on(async { + let conn = common.dbus_state.system_conn().await?; + let manager = ManagerProxy::new(conn).await?; + manager + .lid_closed() + .await + .context("Failed to talk to logind") + }) } diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs index dcb7f257..5b9754c3 100644 --- a/src/dbus/mod.rs +++ b/src/dbus/mod.rs @@ -3,103 +3,168 @@ use crate::{ utils::prelude::OutputExt, }; use anyhow::{Context, Result}; -use calloop::{InsertError, LoopHandle, RegistrationToken}; +use calloop::{InsertError, LoopHandle, stream::StreamSource}; use cosmic_comp_config::output::comp::OutputState; -use futures_executor::{ThreadPool, block_on}; -use futures_util::stream::StreamExt; -use std::collections::HashMap; +use std::{ + cell::{RefCell, RefMut}, + collections::HashMap, + rc::Rc, +}; use tracing::{error, warn}; -use zbus::blocking::{Connection, fdo::DBusProxy}; pub mod a11y_keyboard_monitor; -#[cfg(feature = "systemd")] +use a11y_keyboard_monitor::A11yKeyboardMonitorState; +#[cfg(feature = "logind")] pub mod logind; mod name_owners; mod power; -pub fn init( - evlh: &LoopHandle<'static, State>, - executor: &ThreadPool, -) -> Result> { - let mut tokens = Vec::new(); +#[derive(Clone, Debug)] +pub struct DBusState(Rc); - match block_on(power::init()) { +#[derive(Debug)] +struct DBusStateInner { + evlh: LoopHandle<'static, State>, + executor: calloop::futures::Scheduler<()>, + session_conn: zbus::Result, + system_conn: zbus::Result, + a11y_keyboard_monitor: RefCell>, +} + +impl DBusState { + pub fn init(evlh: &LoopHandle<'static, State>) -> Self { + let (source, executor) = calloop::futures::executor().unwrap(); + let session_conn = futures_executor::block_on(zbus::Connection::session()); + let system_conn = futures_executor::block_on(zbus::Connection::system()); + let state = Self(Rc::new(DBusStateInner { + evlh: evlh.clone(), + executor, + session_conn, + system_conn, + a11y_keyboard_monitor: RefCell::new(None), + })); + evlh.insert_source(source, |_, _, _| {}).unwrap(); + let state_clone = state.clone(); + state.spawn(async move { + if let Err(err) = init_session(&state_clone).await { + tracing::error!("Failed to initialize session DBus connection: {}", err); + } + }); + let state_clone = state.clone(); + state.spawn(async move { + if let Err(err) = init_system(&state_clone).await { + tracing::error!("Failed to initialize system DBus connection: {}", err); + } + }); + state + } + + pub fn a11y_keyboard_monitor( + &self, + ) -> Option> { + RefMut::filter_map(self.0.a11y_keyboard_monitor.borrow_mut(), |x| x.as_mut()).ok() + } + + // TODO Lazy async init when we don't have anything blocking main thread + async fn session_conn(&self) -> zbus::Result<&zbus::Connection> { + self.0.session_conn.as_ref().map_err(|err| err.clone()) + } + + async fn system_conn(&self) -> zbus::Result<&zbus::Connection> { + self.0.system_conn.as_ref().map_err(|err| err.clone()) + } + + fn spawn(&self, fut: impl Future + 'static) { + let _ = self.0.executor.schedule(fut); + } +} + +async fn init_session(state: &DBusState) -> zbus::Result<()> { + let conn = state.session_conn().await?; + let name_owners = name_owners::NameOwners::new(conn, &state.0.executor).await?; + let a11y_keyboard_monitor_state = + A11yKeyboardMonitorState::new(conn, &name_owners, &state.0.executor).await?; + *state.0.a11y_keyboard_monitor.borrow_mut() = Some(a11y_keyboard_monitor_state); + Ok(()) +} + +async fn init_system(state: &DBusState) -> zbus::Result<()> { + let conn = state.system_conn().await?.clone(); + let evlh = state.0.evlh.clone(); + state.spawn(async move { + if let Err(err) = power_hot_plug_task(conn, evlh).await { + tracing::warn!(?err, "Failed to initialize dbus handlers"); + } + }); + Ok(()) +} + +async fn power_hot_plug_task( + conn: zbus::Connection, + evlh: LoopHandle<'static, State>, +) -> Result<()> { + match power::init(&conn).await { Ok(power_daemon) => { - let (tx, rx) = calloop::channel::channel(); - - let token = evlh - .insert_source(rx, |event, _, state| match event { - calloop::channel::Event::Msg(_) => { - let nodes = match &mut state.backend { - BackendData::Kms(kms) => { - kms.drm_devices.keys().cloned().collect::>() - } - _ => Vec::new(), - }; - let mut added = Vec::new(); - for node in nodes { - match state.device_changed(node.dev_id()) { - Ok(outputs) => added.extend(outputs), - Err(err) => { - tracing::error!(?err, "Failed to update drm device {}.", node) - } - } + if let Ok(stream) = power_daemon.receive_hot_plug_detect().await { + let source = StreamSource::new(stream).unwrap(); + evlh.insert_source(source, |_, _, state| { + let nodes = match &mut state.backend { + BackendData::Kms(kms) => { + kms.drm_devices.keys().cloned().collect::>() } - if let Err(err) = state.refresh_output_config() { - warn!("Unable to load output config: {}", err); - if !added.is_empty() { - for output in added { - output.config_mut().enabled = OutputState::Disabled; - } - if let Err(err) = state.refresh_output_config() { - error!("Unrecoverable config error: {}", err); - } + _ => Vec::new(), + }; + let mut added = Vec::new(); + for node in nodes { + match state.device_changed(node.dev_id()) { + Ok(outputs) => added.extend(outputs), + Err(err) => { + tracing::error!(?err, "Failed to update drm device {}.", node) + } + } + } + if let Err(err) = state.refresh_output_config() { + warn!("Unable to load output config: {}", err); + if !added.is_empty() { + for output in added { + output.config_mut().enabled = OutputState::Disabled; + } + if let Err(err) = state.refresh_output_config() { + error!("Unrecoverable config error: {}", err); } } } - calloop::channel::Event::Closed => (), }) .map_err(|InsertError { error, .. }| error) .with_context(|| "Failed to add channel to event_loop")?; - - // start helper thread - executor.spawn_ok(async move { - if let Ok(mut msg_iter) = power_daemon.receive_hot_plug_detect().await { - while let Some(msg) = msg_iter.next().await { - if tx.send(msg).is_err() { - break; - } - } - } - }); - - tokens.push(token); + } } Err(err) => { tracing::info!(?err, "Failed to connect to com.system76.PowerDaemon"); } }; - - Ok(tokens) + Ok(()) } /// Updated the D-Bus activation environment with `WAYLAND_DISPLAY` and /// `DISPLAY` variables. pub fn ready(common: &Common) -> Result<()> { - let conn = Connection::session()?; - let proxy = DBusProxy::new(&conn)?; - - proxy.update_activation_environment(HashMap::from([ - ("WAYLAND_DISPLAY", common.socket.to_str().unwrap()), - ( - "DISPLAY", - &common - .xwayland_state - .as_ref() - .map(|s| format!(":{}", s.display)) - .unwrap_or_default(), - ), - ]))?; + futures_executor::block_on(async { + let conn = common.dbus_state.session_conn().await?; + let dbus = zbus::fdo::DBusProxy::new(conn).await?; + dbus.update_activation_environment(HashMap::from([ + ("WAYLAND_DISPLAY", common.socket.to_str().unwrap()), + ( + "DISPLAY", + &common + .xwayland_state + .as_ref() + .map(|s| format!(":{}", s.display)) + .unwrap_or_default(), + ), + ])) + .await + })?; Ok(()) } diff --git a/src/dbus/name_owners.rs b/src/dbus/name_owners.rs index fe3591b5..a2715c73 100644 --- a/src/dbus/name_owners.rs +++ b/src/dbus/name_owners.rs @@ -2,9 +2,10 @@ //! //! Compare to Mutter's `MetaDbusAccessChecker` -use futures_executor::ThreadPool; -use futures_util::stream::FusedStream; -use futures_util::{StreamExt, stream::FuturesUnordered}; +use futures_util::{ + StreamExt, + stream::{FusedStream, FuturesUnordered}, +}; use std::{ collections::{HashMap, HashSet}, future::{Future, poll_fn}, @@ -94,7 +95,10 @@ fn update_task(inner: Weak>) -> impl Future { pub struct NameOwners(Arc>); impl NameOwners { - pub async fn new(connection: &zbus::Connection, executor: &ThreadPool) -> zbus::Result { + pub async fn new( + connection: &zbus::Connection, + executor: &calloop::futures::Scheduler<()>, + ) -> zbus::Result { let dbus = fdo::DBusProxy::new(connection).await?; let stream = dbus.receive_name_owner_changed().await?; @@ -126,7 +130,7 @@ impl NameOwners { })); if enforce { - executor.spawn_ok(update_task(Arc::downgrade(&inner))); + let _ = executor.schedule(update_task(Arc::downgrade(&inner))); } Ok(NameOwners(inner)) diff --git a/src/dbus/power.rs b/src/dbus/power.rs index 346b8107..a20e5669 100644 --- a/src/dbus/power.rs +++ b/src/dbus/power.rs @@ -79,9 +79,8 @@ pub trait PowerDaemon { fn power_profile_switch(&self, profile: &str) -> zbus::Result<()>; } -pub async fn init() -> anyhow::Result> { - let conn = Connection::system().await?; - let proxy = PowerDaemonProxy::new(&conn).await?; +pub async fn init(conn: &Connection) -> anyhow::Result> { + let proxy = PowerDaemonProxy::new(conn).await?; proxy.0.introspect().await?; Ok(proxy) } diff --git a/src/input/mod.rs b/src/input/mod.rs index 548a60ab..da3014ae 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -52,16 +52,21 @@ use smithay::{ AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent, - PointerGrab, RelativeMotionEvent, + PointerGrab, PointerHandle, RelativeMotionEvent, }, touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent}, }, output::Output, reexports::{ - input::Device as InputDevice, wayland_server::protocol::wl_shm::Format as ShmFormat, + input::Device as InputDevice, + wayland_server::{ + Resource as _, + protocol::{wl_shm::Format as ShmFormat, wl_surface::WlSurface}, + }, }, - utils::{Point, Rectangle, SERIAL_COUNTER, Serial}, + utils::{Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size}, wayland::{ + compositor::CompositorHandler, image_copy_capture::{BufferConstraints, CursorSessionRef}, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, pointer_constraints::{PointerConstraint, with_pointer_constraint}, @@ -349,27 +354,6 @@ impl State { _ => {} }); } - let original_position = position; - position += event.delta().as_global(); - - let output = shell - .outputs() - .find(|output| output.geometry().to_f64().contains(position)) - .cloned() - .unwrap_or(current_output.clone()); - - let output_geometry = output.geometry(); - position.x = position.x.clamp( - output_geometry.loc.x as f64, - (output_geometry.loc.x + output_geometry.size.w - 1) as f64, - ); - position.y = position.y.clamp( - output_geometry.loc.y as f64, - (output_geometry.loc.y + output_geometry.size.h - 1) as f64, - ); - - let new_under = State::surface_under(position, &output, &shell) - .map(|(target, pos)| (target, pos.as_logical())); std::mem::drop(shell); ptr.relative_motion( @@ -387,6 +371,25 @@ impl State { return; } + let original_position = position; + position += event.delta().as_global(); + let shell = self.common.shell.read(); + let output = shell + .outputs() + .find(|output| output.geometry().to_f64().contains(position)) + .cloned() + .unwrap_or(current_output.clone()); + drop(shell); + let output_geometry = output.geometry(); + position.x = position.x.clamp( + output_geometry.loc.x as f64, + (output_geometry.loc.x + output_geometry.size.w - 1) as f64, + ); + position.y = position.y.clamp( + output_geometry.loc.y as f64, + (output_geometry.loc.y + output_geometry.size.h - 1) as f64, + ); + if ptr.is_grabbed() { if seat .user_data() @@ -494,50 +497,90 @@ impl State { } } - // If confined, don't move pointer if it would go outside surface or region - if pointer_confined && let Some((surface, surface_loc)) = &under { - if new_under.as_ref().and_then(|(under, _)| under.wl_surface()) - != surface.wl_surface() - { - ptr.frame(self); - return; - } - match surface { - PointerFocusTarget::WlSurface { surface, .. } => { - if under_from_surface_tree( - surface, - position.as_logical() - surface_loc.to_f64(), - (0, 0), - WindowSurfaceType::ALL, - ) - .is_none() - { - ptr.frame(self); - return; - } + // If confined, help user to update valid coordinates can improve user experience + let shell = self.common.shell.read(); + let new_under = if pointer_confined && let Some((surface, surface_loc)) = &under + { + let is_legal = |pos: Point, shell: &Shell| { + let new_under = State::surface_under(pos, &output, shell) + .map(|(target, pos)| (target, pos.as_logical())); + + // TODO: We might need a solution that allows constraints to bypass the surface without affecting the constraints themselves + if new_under.as_ref().and_then(|(under, _)| under.wl_surface()) + != surface.wl_surface() + { + return (false, None); } - PointerFocusTarget::X11Surface { surface, .. } => { - if surface - .surface_under( + + match surface { + PointerFocusTarget::WlSurface { surface, .. } => { + if under_from_surface_tree( + surface, position.as_logical() - surface_loc.to_f64(), (0, 0), WindowSurfaceType::ALL, ) .is_none() - { - ptr.frame(self); - return; + { + return (false, None); + } + } + PointerFocusTarget::X11Surface { surface, .. } => { + if surface + .surface_under( + position.as_logical() - surface_loc.to_f64(), + (0, 0), + WindowSurfaceType::ALL, + ) + .is_none() + { + return (false, None); + } + } + _ => {} + } + + if let Some(region) = &confine_region + && !region + .contains((pos.as_logical() - *surface_loc).to_i32_round()) + { + return (false, None); + } + (true, new_under) + }; + + match is_legal(position, &shell) { + (true, under) => under, + _ => { + let y_only_pos = Point::new(original_position.x, position.y); + let x_only_pos = Point::new(position.x, original_position.y); + match is_legal(y_only_pos, &shell) { + (true, under) => { + position = y_only_pos; + under + } + _ => match is_legal(x_only_pos, &shell) { + (true, under) => { + position = x_only_pos; + under + } + _ => { + position = original_position; + None + } + }, } } - _ => {} - } - if let Some(region) = confine_region - && !region - .contains((position.as_logical() - *surface_loc).to_i32_round()) - { - ptr.frame(self); - return; } + } else { + State::surface_under(position, &output, &shell) + .map(|(target, pos)| (target, pos.as_logical())) + }; + + drop(shell); + if pointer_confined && new_under.is_none() { + ptr.frame(self); + return; } let serial = SERIAL_COUNTER.next_serial(); @@ -552,24 +595,32 @@ impl State { ); ptr.frame(self); - // If pointer is now in a constraint region, activate it + // If pointer is now in a constraint region and window is in focused, activate it if let Some((under, surface_location)) = new_under .and_then(|(target, loc)| Some((target.wl_surface()?.into_owned(), loc))) { - with_pointer_constraint(&under, &ptr, |constraint| match constraint { - Some(constraint) if !constraint.is_active() => { - let region = match &*constraint { - PointerConstraint::Locked(locked) => locked.region(), - PointerConstraint::Confined(confined) => confined.region(), - }; - let point = - (ptr.current_location() - surface_location).to_i32_round(); - if region.is_none_or(|region| region.contains(point)) { - constraint.activate(); + let shell = self.common.shell.read(); + let is_focused = seat + .get_keyboard() + .and_then(|k| k.current_focus()) + .is_some_and(|f| f.has_surface(&shell, &under)); + + if is_focused { + with_pointer_constraint(&under, &ptr, |constraint| match constraint { + Some(constraint) if !constraint.is_active() => { + let region = match &*constraint { + PointerConstraint::Locked(locked) => locked.region(), + PointerConstraint::Confined(confined) => confined.region(), + }; + let point = + (ptr.current_location() - surface_location).to_i32_round(); + if region.is_none_or(|region| region.contains(point)) { + constraint.activate(); + } } - } - _ => {} - }); + _ => {} + }); + } } let mut shell = self.common.shell.write(); @@ -589,11 +640,13 @@ impl State { for session in cursor_sessions_for_output(&shell, &output) { if let Some((geometry, offset)) = seat.cursor_geometry( - position.as_logical().to_buffer( - output.current_scale().fractional_scale(), - output.current_transform(), - &output_geometry.size.to_f64().as_logical(), - ), + (position - output_geometry.loc.to_f64()) + .as_logical() + .to_buffer( + output.current_scale().fractional_scale(), + output.current_transform(), + &output_geometry.size.to_f64().as_logical(), + ), self.common.clock.now(), ) { if session @@ -601,8 +654,13 @@ impl State { .map(|constraint| constraint.size != geometry.size) .unwrap_or(true) { + let mut cursor_size = geometry.size; + // Client shouldn't try to allocate 0x0 buffer + if cursor_size == Size::new(0, 0) { + cursor_size = Size::new(1, 1); + } session.update_constraints(BufferConstraints { - size: geometry.size, + size: cursor_size, shm: vec![ShmFormat::Argb8888], dma: None, }); @@ -625,11 +683,11 @@ impl State { self.common.idle_notifier_state.notify_activity(&seat); notify_cursor_activity(self, &seat); let output = seat.active_output(); - let geometry = output.geometry(); - let position = geometry.loc.to_f64() + let output_geometry = output.geometry(); + let position = output_geometry.loc.to_f64() + smithay::backend::input::AbsolutePositionEvent::position_transformed( &event, - geometry.size.as_logical(), + output_geometry.size.as_logical(), ) .as_global(); let serial = SERIAL_COUNTER.next_serial(); @@ -651,11 +709,13 @@ impl State { let shell = self.common.shell.read(); for session in cursor_sessions_for_output(&shell, &output) { if let Some((geometry, offset)) = seat.cursor_geometry( - position.as_logical().to_buffer( - output.current_scale().fractional_scale(), - output.current_transform(), - &geometry.size.to_f64().as_logical(), - ), + (position - output_geometry.loc.to_f64()) + .as_logical() + .to_buffer( + output.current_scale().fractional_scale(), + output.current_transform(), + &output_geometry.size.to_f64().as_logical(), + ), self.common.clock.now(), ) { if session @@ -663,8 +723,13 @@ impl State { .map(|constraint| constraint.size != geometry.size) .unwrap_or(true) { + let mut cursor_size = geometry.size; + // Client shouldn't try to allocate 0x0 buffer + if cursor_size == Size::new(0, 0) { + cursor_size = Size::new(1, 1); + } session.update_constraints(BufferConstraints { - size: geometry.size, + size: cursor_size, shm: vec![ShmFormat::Argb8888], dma: None, }); @@ -1535,7 +1600,7 @@ impl State { } InputEvent::Special(_) => {} InputEvent::SwitchToggle { event } => { - #[cfg(feature = "systemd")] + #[cfg(feature = "logind")] if event.switch() == Some(Switch::Lid) && self.common.inhibit_lid_fd.is_some() { let backend = self.backend.lock(); let output = backend @@ -1617,9 +1682,9 @@ impl State { .unwrap_or(false) }); - self.common - .a11y_keyboard_monitor_state - .key_event(modifiers, &handle, event.state()); + if let Some(a11y_keyboard_monitor) = self.common.dbus_state.a11y_keyboard_monitor() { + a11y_keyboard_monitor.key_event(modifiers, &handle, event.state()); + } // Leave move overview mode, if any modifier was released if let Some(Trigger::KeyboardMove(action_modifiers)) = @@ -1779,61 +1844,54 @@ impl State { ))); } - if event.state() == KeyState::Released { - let removed = self - .common - .a11y_keyboard_monitor_state - .remove_active_virtual_mod(handle.modified_sym()); - // If `Caps_Lock` is a virtual modifier, and is in locked state, clear it - if removed - && handle.modified_sym() == Keysym::Caps_Lock - && (modifiers.serialized.locked & 2) != 0 + if let Some(mut a11y_keyboard_monitor) = self.common.dbus_state.a11y_keyboard_monitor() { + if event.state() == KeyState::Released { + let removed = + a11y_keyboard_monitor.remove_active_virtual_mod(handle.modified_sym()); + // If `Caps_Lock` is a virtual modifier, and is in locked state, clear it + if removed + && handle.modified_sym() == Keysym::Caps_Lock + && (modifiers.serialized.locked & 2) != 0 + { + let seat = seat.clone(); + let key_code = event.key_code(); + self.common.event_loop_handle.insert_idle(move |state| { + if let Some(keyboard) = seat.get_keyboard() { + let serial = SERIAL_COUNTER.next_serial(); + let time = state.common.clock.now().as_millis(); + keyboard.input( + state, + key_code, + KeyState::Pressed, + serial, + time, + |_, _, _| FilterResult::<()>::Forward, + ); + let serial = SERIAL_COUNTER.next_serial(); + keyboard.input( + state, + key_code, + KeyState::Released, + serial, + time, + |_, _, _| FilterResult::<()>::Forward, + ); + } + }); + } + } else if event.state() == KeyState::Pressed + && a11y_keyboard_monitor.has_virtual_mod(handle.modified_sym()) { - let seat = seat.clone(); - let key_code = event.key_code(); - self.common.event_loop_handle.insert_idle(move |state| { - if let Some(keyboard) = seat.get_keyboard() { - let serial = SERIAL_COUNTER.next_serial(); - let time = state.common.clock.now().as_millis(); - keyboard.input( - state, - key_code, - KeyState::Pressed, - serial, - time, - |_, _, _| FilterResult::<()>::Forward, - ); - let serial = SERIAL_COUNTER.next_serial(); - keyboard.input( - state, - key_code, - KeyState::Released, - serial, - time, - |_, _, _| FilterResult::<()>::Forward, - ); - } - }); + a11y_keyboard_monitor.add_active_virtual_mod(handle.modified_sym()); + + tracing::debug!( + "active virtual mods: {:?}", + a11y_keyboard_monitor.active_virtual_mods() + ); + seat.supressed_keys().add(&handle, None); + + return FilterResult::Intercept(None); } - } else if event.state() == KeyState::Pressed - && self - .common - .a11y_keyboard_monitor_state - .has_virtual_mod(handle.modified_sym()) - { - self.common - .a11y_keyboard_monitor_state - .add_active_virtual_mod(handle.modified_sym()); - - tracing::debug!( - "active virtual mods: {:?}", - self.common - .a11y_keyboard_monitor_state - .active_virtual_mods() - ); - seat.supressed_keys().add(&handle, None); - - return FilterResult::Intercept(None); } // Skip released events for initially surpressed keys @@ -1860,12 +1918,10 @@ impl State { return FilterResult::Intercept(None); } - if event.state() == KeyState::Pressed - && (self.common.a11y_keyboard_monitor_state.has_keyboard_grab() - || self - .common - .a11y_keyboard_monitor_state - .has_key_grab(modifiers, handle.modified_sym())) + if let Some(a11y_keyboard_monitor) = self.common.dbus_state.a11y_keyboard_monitor() + && event.state() == KeyState::Pressed + && (a11y_keyboard_monitor.has_keyboard_grab() + || a11y_keyboard_monitor.has_key_grab(modifiers, handle.modified_sym())) { let modifiers_queue = seat.modifiers_shortcut_queue(); modifiers_queue.clear(); @@ -2117,14 +2173,14 @@ impl State { } Stage::StickyPopups(layout) => { if let Some(element) = - layout.popup_element_under(global_pos.to_local(output)) + layout.popup_element_under(global_pos.to_local(output), seat) { return ControlFlow::Break(Ok(Some(element))); } } Stage::Sticky(layout) => { if let Some(element) = - layout.toplevel_element_under(global_pos.to_local(output)) + layout.toplevel_element_under(global_pos.to_local(output), seat) { return ControlFlow::Break(Ok(Some(element))); } @@ -2279,7 +2335,7 @@ impl State { } Stage::StickyPopups(floating_layer) => { if let Some(under) = floating_layer - .popup_surface_under(relative_pos) + .popup_surface_under(relative_pos, seat) .map(|(target, point)| (target, point.to_global(output))) { return ControlFlow::Break(Ok(Some(under))); @@ -2287,7 +2343,7 @@ impl State { } Stage::Sticky(floating_layer) => { if let Some(under) = floating_layer - .toplevel_surface_under(relative_pos) + .toplevel_surface_under(relative_pos, seat) .map(|(target, point)| (target, point.to_global(output))) { return ControlFlow::Break(Ok(Some(under))); @@ -2317,8 +2373,113 @@ impl State { .ok() .flatten() } + + pub fn apply_cursor_hint( + &mut self, + surface: &WlSurface, + pointer: &PointerHandle, + mut location: Point, + ) { + let Some(client) = surface.client() else { + return; + }; + location = location.downscale(self.client_compositor_state(&client).client_scale()); + + let point_and_output = { + let shell = self.common.shell.read(); + let found = shell.workspaces.sets.iter().find_map(|(out, set)| { + set.surface_geometry_offset_from_toplevel(surface) + .map(|(geometry, surface_offset)| (out, geometry, surface_offset)) + }); + + if let Some((output, geometry, surface_offset)) = found { + let pos_in_element = location + surface_offset.to_f64(); + let window_size = geometry.size.to_f64(); + + let is_legal = |p: Point| { + let in_window = + p.x >= 0.0 && p.y >= 0.0 && p.x < window_size.w && p.y < window_size.h; + if !in_window { + return false; + } + + with_pointer_constraint(surface, pointer, |constraint| { + if let Some(constraint) = constraint + && let Some(region) = constraint.region() + { + let point_in_surface = (p - surface_offset.to_f64()).to_i32_round(); + return region.contains(point_in_surface); + } + true + }) + }; + + let workspace_origin = output.geometry().loc.to_f64(); + let origin = geometry.loc.to_f64(); + + if is_legal(pos_in_element) { + let x = workspace_origin.x + origin.x + pos_in_element.x; + let y = workspace_origin.y + origin.y + pos_in_element.y; + Some((Point::new(x, y), output.clone())) + } else { + None + } + } else { + None + } + }; + + if let Some((point, output)) = point_and_output { + let original_position = pointer.current_location(); + pointer.set_location(point); + + let mut shell = self.common.shell.write(); + shell.update_pointer_position(point.as_global().to_local(&output), &output); + + let seat = shell + .seats + .iter() + .find(|s| s.get_pointer().as_ref() == Some(pointer)) + .cloned(); + + if let Some(seat) = seat { + shell.update_focal_point( + &seat, + original_position.as_global(), + self.common.config.cosmic_conf.accessibility_zoom.view_moves, + ); + + let output_geometry = output.geometry(); + for session in cursor_sessions_for_output(&shell, &output) { + if let Some((geometry, offset)) = seat.cursor_geometry( + point.to_buffer( + output.current_scale().fractional_scale(), + output.current_transform(), + &output_geometry.size.to_f64().as_logical(), + ), + self.common.clock.now(), + ) { + if session + .current_constraints() + .map(|constraint| constraint.size != geometry.size) + .unwrap_or(true) + { + session.update_constraints(BufferConstraints { + size: geometry.size, + shm: vec![ShmFormat::Argb8888], + dma: None, + }); + } + session.set_cursor_hotspot(offset); + session.set_cursor_pos(Some(geometry.loc)); + } + } + } + } + } } +// Output and workspace sessions for the given output fn cursor_sessions_for_output<'a>( shell: &'a Shell, output: &'a Output, @@ -2327,16 +2488,9 @@ fn cursor_sessions_for_output<'a>( .active_space(output) .into_iter() .flat_map(|workspace| { - let maybe_fullscreen = workspace.get_fullscreen(); workspace .cursor_sessions() .into_iter() - .chain( - maybe_fullscreen - .map(|w| w.cursor_sessions()) - .into_iter() - .flatten(), - ) .chain(output.cursor_sessions()) }) } diff --git a/src/main.rs b/src/main.rs index 7ae05991..ffa37236 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-only fn main() { + // libcosmic-yoda enables zbus' `tokio` feature, so zbus 5.14 expects an + // ambient Tokio runtime via Handle::current(). cosmic-comp's loop is not + // async, so hold a runtime guard for the lifetime of run(). + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("failed to build tokio runtime"); + let _guard = runtime.enter(); + if let Err(err) = cosmic_comp::run(Default::default()) { tracing::error!("Error occured in main(): {}", err); std::process::exit(1); diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 4696aa2e..f58259bf 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -254,6 +254,14 @@ impl CosmicMapped { .any(|(w, _)| w.has_surface(surface, surface_type)) } + pub fn surface_offset(&self, surface: &WlSurface) -> Option> { + self.windows().find_map(|(window, window_offset)| { + window + .surface_offset(surface) + .map(|offset| window_offset + offset) + }) + } + /// Give the pointer target under a relative offset into this element. /// /// Returns Target + Offset relative to the target @@ -261,10 +269,13 @@ impl CosmicMapped { &self, relative_pos: Point, surface_type: WindowSurfaceType, + seat: &Seat, ) -> Option<(PointerFocusTarget, Point)> { match &self.element { CosmicMappedInternal::Stack(stack) => stack.focus_under(relative_pos, surface_type), - CosmicMappedInternal::Window(window) => window.focus_under(relative_pos, surface_type), + CosmicMappedInternal::Window(window) => { + window.focus_under(relative_pos, surface_type, Some(seat)) + } _ => unreachable!(), } } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 7d53bfcc..058719e3 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), @@ -249,6 +251,10 @@ impl CosmicStack { window.set_tiled(false); p.active.fetch_min(windows.len() - 1, Ordering::SeqCst); + p.previous_index + .lock() + .unwrap() + .take_if(|(_, idx)| *idx >= windows.len()); }); self.0 .resize(Size::from((self.active().geometry().size.w, TAB_HEIGHT))); @@ -276,6 +282,10 @@ impl CosmicStack { window.set_tiled(false); p.active.fetch_min(windows.len() - 1, Ordering::SeqCst); + p.previous_index + .lock() + .unwrap() + .take_if(|(_, idx)| *idx >= windows.len()); Some(window) }); @@ -1807,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/surface.rs b/src/shell/element/surface.rs index 1a1e214b..8495fe30 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -45,7 +45,10 @@ use smithay::{ IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, user_data::UserDataMap, }, wayland::{ - compositor::{SurfaceData, TraversalAction, with_states, with_surface_tree_downward}, + compositor::{ + SubsurfaceCachedState, SurfaceData, TraversalAction, get_parent, with_states, + with_surface_tree_downward, + }, seat::WaylandFocus, shell::xdg::{ SurfaceCachedState, ToplevelCachedState, ToplevelSurface, XdgToplevelSurfaceData, @@ -235,7 +238,8 @@ impl CosmicSurface { toplevel.with_pending_state(|state| state.size = Some(geo.size.as_logical())) } WindowSurface::X11(surface) => { - let _ = surface.configure_with_sync(geo.as_logical(), None); + let _ = + surface.configure_with_sync(geo.as_logical() + surface.frame_extents(), None); } } } @@ -342,7 +346,7 @@ impl CosmicSurface { state.is_some_and(|state| state.states.contains(ToplevelState::Resizing)) })) } - WindowSurface::X11(surface) => surface.pending_geometry().map(|_| true), + WindowSurface::X11(surface) => surface.pending_configure().map(|_| true), } } @@ -588,7 +592,7 @@ impl CosmicSurface { } }) } - WindowSurface::X11(surface) => surface.pending_geometry().is_none(), + WindowSurface::X11(surface) => surface.pending_configure().is_none(), } } @@ -652,6 +656,54 @@ impl CosmicSurface { } } + pub fn surface_offset(&self, surface: &WlSurface) -> Option> { + match self.0.underlying_surface() { + WindowSurface::Wayland(toplevel) => { + Self::surface_tree_offset(toplevel.wl_surface(), surface) + } + WindowSurface::X11(surface_x11) => { + if surface_x11.wl_surface().as_ref() == Some(surface) { + Some(Point::default()) + } else { + None + } + } + } + } + + pub fn surface_tree_offset( + root: &WlSurface, + surface: &WlSurface, + ) -> Option> { + let mut offset = Point::::default(); + let mut parent = surface.clone(); + loop { + if parent == *root { + return Some(offset); + } else if let Some(s) = get_parent(&parent) { + offset += with_states(&parent, |states| { + states + .cached_state + .get::() + .current() + .location + }); + parent = s; + } else { + // `parent` is now root of subsurface tree; `surface` is not a subsurface child of `root` + break; + } + } + + for (popup, popup_offset) in PopupManager::popups_for_surface(root) { + if let Some(offset) = Self::surface_tree_offset(popup.wl_surface(), surface) { + return Some(popup_offset + offset); + } + } + + None + } + pub fn focus_under( &self, relative_pos: Point, diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 562f6988..ea20bb6a 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, @@ -57,7 +57,7 @@ use smithay::{ Buffer, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, Transform, user_data::UserDataMap, }, - wayland::seat::WaylandFocus, + wayland::{pointer_constraints::with_pointer_constraint, seat::WaylandFocus}, }; use std::{ borrow::Cow, @@ -274,12 +274,25 @@ impl CosmicWindow { &self, mut relative_pos: Point, surface_type: WindowSurfaceType, + seat: Option<&Seat>, ) -> Option<(PointerFocusTarget, Point)> { + let has_constraint = if let Some(seat) = seat + && let Some(pointer) = seat.get_pointer() + && let Some(surface) = self.wl_surface() + && with_pointer_constraint(&surface, &pointer, |constraint| { + constraint.is_some_and(|c| c.is_active()) + }) { + true + } else { + false + }; + self.0.with_program(|p| { let mut offset = Point::from((0., 0.)); let mut window_ui = None; let has_ssd = p.has_ssd(false); - if (has_ssd || p.has_tiled_state()) + + if (!has_constraint && (has_ssd || p.has_tiled_state())) && surface_type.contains(WindowSurfaceType::TOPLEVEL) { let geo = p.window.geometry(); @@ -877,7 +890,8 @@ impl SpaceElement for CosmicWindow { }) } fn is_in_input_region(&self, point: &Point) -> bool { - self.focus_under(*point, WindowSurfaceType::ALL).is_some() + self.focus_under(*point, WindowSurfaceType::ALL, None) + .is_some() } fn set_activate(&self, activated: bool) { if self @@ -1176,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/mod.rs b/src/shell/focus/mod.rs index 58301072..9374230a 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -1,6 +1,6 @@ use crate::{ shell::{CosmicSurface, MinimizedWindow, Shell, Trigger, element::CosmicMapped}, - state::Common, + state::{Common, State}, utils::prelude::*, wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData}, }; @@ -12,6 +12,7 @@ use smithay::{ reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface}, utils::{IsAlive, Point, SERIAL_COUNTER, Serial}, wayland::{ + pointer_constraints::with_pointer_constraint, seat::WaylandFocus, selection::{data_device::set_data_device_focus, primary_selection::set_primary_focus}, shell::wlr_layer::{KeyboardInteractivity, Layer}, @@ -287,22 +288,18 @@ impl Shell { } let workspace = &mut set.workspaces[set.active]; - if let Some(fullscreen) = workspace.get_fullscreen() { - if self.seats.iter().any(|seat| { + for fs in workspace.get_fullscreen_surfaces() { + let is_focused = self.seats.iter().any(|seat| { if let Some(KeyboardFocusTarget::Fullscreen(s)) = seat.get_keyboard().unwrap().current_focus() { - &s == fullscreen + s == fs.surface } else { false } - }) { - fullscreen.set_activated(true); - fullscreen.send_configure(); - } else { - fullscreen.set_activated(false); - fullscreen.send_configure(); - } + }); + fs.surface.set_activated(is_focused); + fs.surface.send_configure(); } for focused in focused_windows.iter() { raise_with_children(&mut workspace.floating_layer, focused); @@ -347,6 +344,20 @@ fn update_focus_state( ) { // update keyboard focus if let Some(keyboard) = seat.get_keyboard() { + // remove constraint when target changed + let old_focus = keyboard.current_focus(); + if let Some(old_target) = old_focus + && target != Some(&old_target) + && let Some(surface) = old_target.wl_surface() + && let Some(pointer) = seat.get_pointer() + { + with_pointer_constraint(&surface, &pointer, |constraint| { + if let Some(constraint) = constraint { + constraint.deactivate(); + } + }); + } + if should_update_cursor && state.common.config.cosmic_conf.cursor_follows_focus && target.is_some() @@ -646,7 +657,9 @@ fn focus_target_is_valid( .has_node(&node), KeyboardFocusTarget::Fullscreen(window) => { let workspace = shell.active_space(output).unwrap(); - workspace.get_fullscreen().is_some_and(|w| w == &window) + workspace + .get_fullscreen_surfaces() + .any(|f| f.surface == window) } KeyboardFocusTarget::Popup(_) => true, KeyboardFocusTarget::LockSurface(_) => false, @@ -699,9 +712,9 @@ fn update_focus_target( .map(KeyboardFocusTarget::Element) .or_else(|| { workspace - .get_fullscreen() + .get_fullscreen(seat) .cloned() - .map(KeyboardFocusTarget::Fullscreen) + .map(|fs| KeyboardFocusTarget::Fullscreen(fs.surface)) }) }) } diff --git a/src/shell/focus/order.rs b/src/shell/focus/order.rs index 6a2488fe..d02b310e 100644 --- a/src/shell/focus/order.rs +++ b/src/shell/focus/order.rs @@ -112,8 +112,8 @@ fn render_input_order_internal( let output_size = output.geometry().size; // this is more hacky than I would like.. - let fullscreen = workspace.fullscreen.as_ref().filter(|f| !f.is_animating()); let seat = shell.seats.last_active(); + let fullscreen = workspace.get_fullscreen(seat); let is_active_workspace = seat.focused_output().is_some_and(|output| { shell .active_space(&output) @@ -144,7 +144,7 @@ fn render_input_order_internal( let Some(workspace) = shell.workspaces.space_for_handle(previous) else { return ControlFlow::Break(Err(OutputNoMode)); }; - let has_fullscreen = workspace.fullscreen.is_some(); + let has_fullscreen = workspace.get_fullscreen(seat).is_some(); let (forward, percentage) = match start { WorkspaceDelta::Shortcut(st) => ( @@ -220,20 +220,18 @@ fn render_input_order_internal( .rev() .filter(|or| { (*or) - .geometry() + .last_configure() .as_global() .intersection(output.geometry()) .is_some() }) - .map(|or| (or, or.geometry().loc.as_global())) + .map(|or| (or, or.last_configure().loc.as_global())) { callback(Stage::OverrideRedirect { surface, location })?; } // sticky window popups - if !has_focused_fullscreen { - callback(Stage::StickyPopups(&set.sticky_layer))?; - } + callback(Stage::StickyPopups(&set.sticky_layer))?; } if element_filter != ElementFilter::LayerShellOnly { @@ -313,11 +311,11 @@ fn render_input_order_internal( for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) { callback(Stage::LayerSurface { layer, location })?; } + } - // sticky windows - if element_filter != ElementFilter::LayerShellOnly { - callback(Stage::Sticky(&set.sticky_layer))?; - } + // sticky windows + if element_filter != ElementFilter::LayerShellOnly { + callback(Stage::Sticky(&set.sticky_layer))?; } if element_filter != ElementFilter::LayerShellOnly { diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index b177f00f..fff77282 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -29,15 +29,18 @@ 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::{ Client, DisplayHandle, Resource, backend::ObjectId, protocol::wl_surface::WlSurface, }, utils::{IsAlive, Logical, Point, Serial, Transform}, - wayland::{seat::WaylandFocus, selection::data_device::WlOfferData, session_lock::LockSurface}, + wayland::{ + compositor::with_states, seat::WaylandFocus, selection::data_device::WlOfferData, + session_lock::LockSurface, shell::xdg::SurfaceCachedState, + }, xwayland::{ X11Surface, xwm::{XwmId, XwmOfferData}, @@ -225,6 +228,63 @@ impl PointerFocusTarget { _ => false, } } + + // Update image copy cursor position/hotspot for enter/motion event + fn update_image_copy_cursor_position( + &self, + seat: &Seat, + data: &mut State, + event: &PointerMotionEvent, + ) { + let Some(toplevel) = self.toplevel(&data.common.shell.read()) else { + return; + }; + let cursor_sessions = toplevel.cursor_sessions(); + if cursor_sessions.is_empty() { + return; + } + + let cursor_pos = if let Some(wl_surface) = self.wl_surface() { + let surface_offset = toplevel + .surface_offset(&wl_surface) + .unwrap_or(Point::from((0, 0))) + .to_f64(); + let geometry_loc = toplevel + .wl_surface() + .and_then(|s| { + with_states(&s, |states| { + states + .cached_state + .get::() + .current() + .geometry + .map(|g| g.loc.to_f64()) + }) + }) + .unwrap_or_default(); + Some( + (event.location - geometry_loc + surface_offset) + .to_buffer(1.0, Transform::Normal, &toplevel.geometry().size.to_f64()) + .to_i32_round(), + ) + } else { + // If cursor is in SSD, instead of a `wl_surface`, it is outside the captured bounds + None + }; + + let cursor_hotspot = if let Some((_, hotspot)) = + seat.cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into()) + { + hotspot + } else { + Point::from((0, 0)) + }; + + for session in cursor_sessions { + session.set_cursor_pos(cursor_pos); + session.set_cursor_hotspot(cursor_hotspot); + } + } } impl KeyboardFocusTarget { @@ -281,6 +341,18 @@ impl KeyboardFocusTarget { false } } + + pub fn has_surface(&self, shell: &Shell, surface: &WlSurface) -> bool { + if let Some(fe) = shell.focused_element(self) { + fe.has_surface(surface, WindowSurfaceType::ALL) + } else if let KeyboardFocusTarget::Fullscreen(s) = self { + s.has_surface(surface, WindowSurfaceType::ALL) + } else if let Some(root) = WaylandFocus::wl_surface(self) { + CosmicSurface::surface_tree_offset(&root, surface).is_some() + } else { + false + } + } } #[derive(Debug, Clone)] @@ -325,47 +397,11 @@ impl IsAlive for KeyboardFocusTarget { impl PointerTarget for PointerFocusTarget { fn enter(&self, seat: &Seat, data: &mut State, event: &PointerMotionEvent) { - let toplevel = self.toplevel(&data.common.shell.read()); - if let Some(element) = toplevel { - for session in element.cursor_sessions() { - session.set_cursor_pos(Some( - event - .location - .to_buffer(1.0, Transform::Normal, &element.geometry().size.to_f64()) - .to_i32_round(), - )); - if let Some((_, hotspot)) = seat - .cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into()) - { - session.set_cursor_hotspot(hotspot); - } else { - session.set_cursor_hotspot((0, 0)); - } - } - } - + self.update_image_copy_cursor_position(seat, data, event); self.inner_pointer_target().enter(seat, data, event); } fn motion(&self, seat: &Seat, data: &mut State, event: &PointerMotionEvent) { - let toplevel = self.toplevel(&data.common.shell.read()); - if let Some(element) = toplevel { - for session in element.cursor_sessions() { - session.set_cursor_pos(Some( - event - .location - .to_buffer(1.0, Transform::Normal, &element.geometry().size.to_f64()) - .to_i32_round(), - )); - if let Some((_, hotspot)) = seat - .cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into()) - { - session.set_cursor_hotspot(hotspot); - } else { - session.set_cursor_hotspot((0, 0)); - } - } - } - + self.update_image_copy_cursor_position(seat, data, event); self.inner_pointer_target().motion(seat, data, event); } fn relative_motion(&self, seat: &Seat, data: &mut State, event: &RelativeMotionEvent) { @@ -462,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 ed00821b..671e004a 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -720,7 +720,6 @@ impl TouchGrab for MenuGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &DownEvent, - seq: Serial, ) { { let mut guard = self.elements.lock().unwrap(); @@ -755,21 +754,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| { @@ -777,7 +770,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); @@ -789,7 +782,6 @@ impl TouchGrab for MenuGrab { handle: &mut TouchInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &TouchMotionEvent, - seq: Serial, ) { { let elements = self.elements.lock().unwrap(); @@ -798,24 +790,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( @@ -823,9 +815,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( @@ -833,9 +824,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 ad5915f9..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 { @@ -953,6 +948,7 @@ impl Drop for MoveGrab { if let Some((target, offset)) = mapped.focus_under( current_location - position.as_logical().to_f64(), WindowSurfaceType::ALL, + &seat, ) { pointer.motion( state, diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs index 04363207..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 { @@ -543,7 +533,7 @@ impl ResizeSurfaceGrab { ); } WindowSurface::X11(surface) => { - let mut geometry = surface.geometry(); + let mut geometry = surface.last_configure(); geometry.loc += (location - new_location).as_logical(); let _ = surface.configure(geometry); } diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index e399f356..e3829e58 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -295,6 +295,7 @@ impl FloatingLayout { for mapped in self .space .elements() + .filter(|w| w.alive()) .cloned() .collect::>() .into_iter() @@ -309,7 +310,10 @@ impl FloatingLayout { None, ); } else { - let geometry = self.space.element_geometry(&mapped).unwrap().to_f64(); + let Some(geometry) = self.space.element_geometry(&mapped) else { + continue; + }; + let geometry = geometry.to_f64(); let new_loc = ( ((geometry.loc.x - old_output_geometry.loc.x).max(0.) / old_output_geometry.size.w @@ -619,6 +623,10 @@ impl FloatingLayout { from: Rectangle, position: Point, ) { + if !mapped.alive() { + return; + } + let output = self.space.outputs().next().unwrap().clone(); let layers = layer_map_for_output(&output); let geometry = layers.non_exclusive_zone().as_local(); @@ -731,7 +739,11 @@ impl FloatingLayout { self.space.element_geometry(elem).map(RectExt::as_local) } - pub fn popup_element_under(&self, location: Point) -> Option { + pub fn popup_element_under( + &self, + location: Point, + seat: &Seat, + ) -> Option { self.space .elements() .rev() @@ -752,6 +764,7 @@ impl FloatingLayout { if e.focus_under( point.as_logical(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + seat, ) .is_some() { @@ -765,6 +778,7 @@ impl FloatingLayout { pub fn toplevel_element_under( &self, location: Point, + seat: &Seat, ) -> Option { self.space .elements() @@ -786,6 +800,7 @@ impl FloatingLayout { if e.focus_under( point.as_logical(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + seat, ) .is_some() { @@ -799,6 +814,7 @@ impl FloatingLayout { pub fn popup_surface_under( &self, location: Point, + seat: &Seat, ) -> Option<(PointerFocusTarget, Point)> { self.space .elements() @@ -820,6 +836,7 @@ impl FloatingLayout { e.focus_under( point.as_logical(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + seat, ) .map(|(surface, surface_offset)| { (surface, render_location + surface_offset.as_local()) @@ -830,6 +847,7 @@ impl FloatingLayout { pub fn toplevel_surface_under( &self, location: Point, + seat: &Seat, ) -> Option<(PointerFocusTarget, Point)> { self.space .elements() @@ -851,6 +869,7 @@ impl FloatingLayout { e.focus_under( point.as_logical(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + seat, ) .map(|(surface, surface_offset)| { (surface, render_location + surface_offset.as_local()) 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/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 22d592bf..e7a7f0f1 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -3145,6 +3145,7 @@ impl TilingLayout { pub fn popup_element_under( &self, location_f64: Point, + seat: &Seat, ) -> Option { let location = location_f64.to_i32_round(); @@ -3157,6 +3158,7 @@ impl TilingLayout { .focus_under( (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + seat, ) .is_some() { @@ -3170,6 +3172,7 @@ impl TilingLayout { pub fn toplevel_element_under( &self, location_f64: Point, + seat: &Seat, ) -> Option { let location = location_f64.to_i32_round(); @@ -3182,6 +3185,7 @@ impl TilingLayout { .focus_under( (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + seat, ) .is_some() { @@ -3196,6 +3200,7 @@ impl TilingLayout { &self, location_f64: Point, overview: OverviewMode, + seat: &Seat, ) -> Option<(PointerFocusTarget, Point)> { let location = location_f64.to_i32_round(); @@ -3210,6 +3215,7 @@ impl TilingLayout { if let Some((target, surface_offset)) = mapped.focus_under( (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + seat, ) { return Some(( target, @@ -3227,6 +3233,7 @@ impl TilingLayout { &self, location_f64: Point, overview: OverviewMode, + seat: &Seat, ) -> Option<(PointerFocusTarget, Point)> { let tree = &self.queue.trees.back().unwrap().0; let root = tree.root_node_id()?; @@ -3243,6 +3250,7 @@ impl TilingLayout { if let Some((target, surface_offset)) = mapped.focus_under( (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + seat, ) { return Some(( target, @@ -3293,6 +3301,7 @@ impl TilingLayout { .focus_under( test_point, WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + seat, ) .map(|(surface, surface_offset)| { ( diff --git a/src/shell/mod.rs b/src/shell/mod.rs index e1a48567..89309e88 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -50,10 +50,13 @@ use smithay::{ }, utils::{IsAlive, Logical, Point, Rectangle, Serial, Size}, wayland::{ - compositor::{SurfaceAttributes, with_states}, + compositor::{SurfaceAttributes, get_parent, with_states}, seat::WaylandFocus, session_lock::LockSurface, - shell::wlr_layer::{KeyboardInteractivity, Layer, LayerSurfaceCachedState}, + shell::{ + wlr_layer::{KeyboardInteractivity, Layer, LayerSurfaceCachedState}, + xdg::{XDG_POPUP_ROLE, XdgPopupSurfaceData}, + }, xdg_activation::XdgActivationState, xwayland_keyboard_grab::XWaylandKeyboardGrab, }, @@ -436,6 +439,11 @@ fn create_workspace_from_pinned( | WorkspaceCapabilities::Pin | WorkspaceCapabilities::Move, ); + + if let Some(ref name) = pinned.name { + state.set_workspace_name(&workspace_handle, name); + } + Workspace::from_pinned( pinned, workspace_handle, @@ -616,6 +624,7 @@ impl WorkspaceSet { state, self.workspaces.len() as u8 + 1, &workspace.handle, + workspace.name.as_deref(), // this method is only used by code paths related to dynamic workspaces, so this should be fine ); self.workspaces.push(workspace); @@ -677,7 +686,12 @@ impl WorkspaceSet { fn update_workspace_idxs(&self, state: &mut WorkspaceUpdateGuard<'_, State>) { for (i, workspace) in self.workspaces.iter().enumerate() { - workspace_set_idx(state, i as u8 + 1, &workspace.handle); + workspace_set_idx( + state, + i as u8 + 1, + &workspace.handle, + workspace.name.as_deref(), + ); } } @@ -728,6 +742,78 @@ impl WorkspaceSet { self.post_remove_workspace(workspace_state, &previous_active_handle); prefers } + + pub fn surface_geometry_offset_from_toplevel( + &self, + surface: &WlSurface, + ) -> Option<(Rectangle, Point)> { + let mut root = surface.clone(); + + while let Some(parent) = get_parent(&root) { + root = parent; + } + + while smithay::wayland::compositor::get_role(&root) == Some(XDG_POPUP_ROLE) { + let parent = with_states(&root, |states| { + states + .data_map + .get::() + .and_then(|m| m.lock().unwrap().parent.as_ref().cloned()) + }); + if let Some(parent) = parent { + root = parent; + } else { + break; + } + } + + self.sticky_layer + .mapped() + .find(|w| { + w.windows() + .any(|(w, _)| w.wl_surface().as_deref() == Some(&root)) + }) + .and_then(|w| { + self.sticky_layer + .element_geometry(w) + .zip(w.surface_offset(surface)) + }) + .or_else(|| { + self.workspaces.iter().find_map(|workspace| { + workspace + .get_fullscreen_surfaces() + .find_map(|fs| { + (fs.surface.wl_surface().as_deref() == Some(&root)) + .then(|| { + fs.surface.surface_offset(surface).map(|offset| { + (workspace.fullscreen_geometry_for(fs), offset) + }) + }) + .flatten() + }) + .or_else(|| { + workspace.mapped().find_map(|w| { + w.windows() + .any(|(w, _)| w.wl_surface().as_deref() == Some(&root)) + .then(|| { + workspace.element_geometry(w).zip(w.surface_offset(surface)) + }) + .flatten() + }) + }) + }) + }) + .or_else(|| { + layer_map_for_output(&self.output).layers().find_map(|l| { + (l.wl_surface() == &root) + .then(|| { + CosmicSurface::surface_tree_offset(l.wl_surface(), surface) + .map(|offset| (l.geometry().as_local(), offset)) + }) + .flatten() + }) + }) + } } #[derive(Debug)] @@ -1502,7 +1588,9 @@ impl Common { self.popups.cleanup(); self.toplevel_info_state.refresh(&self.workspace_state); self.refresh_idle_inhibit(); - self.a11y_keyboard_monitor_state.refresh(); + if let Some(mut a11y_keyboard_monitor) = self.dbus_state.a11y_keyboard_monitor() { + a11y_keyboard_monitor.refresh(); + } self.image_copy_capture_state.cleanup(); } @@ -1562,12 +1650,13 @@ impl Common { if let Some(mapped) = shell.element_for_surface(surface) { mapped.on_commit(surface); } - if let Some(surface) = shell + if let Some(fs) = shell .workspaces .spaces() - .find_map(|w| w.get_fullscreen().filter(|s| *s == surface)) + .flat_map(|w| w.get_fullscreen_surfaces()) + .find(|f| f.surface == *surface) { - surface.on_commit() + fs.surface.on_commit() }; } self.popups.commit(surface); @@ -1828,7 +1917,9 @@ impl Shell { .outputs() .find(|output| { let workspace = self.active_space(output).unwrap(); - workspace.get_fullscreen() == Some(&elem) + workspace + .get_fullscreen_surfaces() + .any(|f| f.surface == elem) }) .cloned(), KeyboardFocusTarget::Group(WindowGroup { node, .. }) => self @@ -1961,8 +2052,8 @@ impl Shell { let workspace = self.active_space(o).unwrap(); workspace - .get_fullscreen() - .is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL)) + .get_fullscreen_surfaces() + .any(|f| f.surface.has_surface(surface, WindowSurfaceType::ALL)) || workspace .mapped() .any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) @@ -2017,8 +2108,8 @@ impl Shell { .workspaces .spaces() .find(|w| { - w.get_fullscreen() - .is_some_and(|s| s.has_surface(surface, WindowSurfaceType::ALL)) + w.get_fullscreen_surfaces() + .any(|f| f.surface.has_surface(surface, WindowSurfaceType::ALL)) || w.mapped() .any(|e| e.has_surface(surface, WindowSurfaceType::ALL)) || w.minimized_windows.iter().any(|m| { @@ -2065,7 +2156,7 @@ impl Shell { .mapped() .any(|m| m.windows().any(|(s, _)| &s == surface)) || set.workspaces.iter().any(|w| { - w.get_fullscreen().is_some_and(|s| s == surface) + w.get_fullscreen_surfaces().any(|f| &f.surface == surface) || w.minimized_windows .iter() .any(|m| m.windows().any(|s| &s == surface)) @@ -2754,12 +2845,7 @@ impl Shell { let floating_exception = layout::has_floating_exception(&self.tiling_exceptions, &window); if should_be_fullscreen { - if let Some((surface, state, _)) = workspace.map_fullscreen(&window, &seat, None, None) - { - toplevel_leave_output(&surface, &workspace.output); - toplevel_leave_workspace(&surface, &workspace.handle); - self.remap_unfullscreened_window(surface, state, loop_handle); - } + workspace.map_fullscreen(&window, &seat, None, None); if was_activated { workspace_state.add_workspace_state(&workspace_handle, WState::Urgent); } @@ -3130,9 +3216,12 @@ impl Shell { let from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above let is_minimized = window.is_minimized(); - let is_fullscreen = from_workspace.get_fullscreen().is_some_and(|f| f == window); + let is_fullscreen = from_workspace + .get_fullscreen_surfaces() + .any(|f| &f.surface == window); let mut window_state = if is_fullscreen { - let (_, previous_state, previous_geometry) = from_workspace.take_fullscreen().unwrap(); + let (_, previous_state, previous_geometry) = + from_workspace.take_fullscreen(window).unwrap(); WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map( |(previous_state, previous_geometry)| FullscreenRestoreData { previous_state, @@ -3288,14 +3377,12 @@ impl Shell { ); mapped.into() } else if let WorkspaceRestoreData::Fullscreen(previous) = window_state { - if let Some((old_surface, previous_state, _)) = to_workspace.map_fullscreen( + to_workspace.map_fullscreen( window, None, previous.clone().map(|p| p.previous_state), previous.map(|p| p.previous_geometry), - ) { - self.remap_unfullscreened_window(old_surface, previous_state, evlh); - } + ); window.clone().into() } else { unreachable!() // TODO: sticky @@ -3513,8 +3600,12 @@ impl Shell { } else if let Some((workspace, output)) = self.workspace_for_surface(surface) { let workspace = self.workspaces.space_for_handle(&workspace).unwrap(); - if let Some(window) = workspace.get_fullscreen().filter(|s| *s == surface) { - let global_position = (workspace.fullscreen_geometry().unwrap().loc + if let Some(fs) = workspace + .get_fullscreen_surfaces() + .find(|f| &f.surface == surface) + { + let window = &fs.surface; + let global_position = (workspace.fullscreen_geometry_for(fs).loc + location.as_local()) .to_global(&output); @@ -3607,10 +3698,14 @@ impl Shell { let maybe_fullscreen_workspace = self .workspaces .spaces_mut() - .find(|w| w.get_fullscreen().is_some_and(|s| s == surface)); + .find(|w| w.get_fullscreen_surfaces().any(|f| &f.surface == surface)); if let Some(workspace) = maybe_fullscreen_workspace { - element_geo = Some(workspace.fullscreen_geometry().unwrap()); - let (surface, state, _) = workspace.remove_fullscreen().unwrap(); + let fs = workspace + .get_fullscreen_surfaces() + .find(|f| &f.surface == surface) + .unwrap(); + element_geo = Some(workspace.fullscreen_geometry_for(fs)); + let (surface, state, _) = workspace.remove_fullscreen_surface(surface).unwrap(); self.remap_unfullscreened_window(surface, state, evlh); }; @@ -3635,7 +3730,11 @@ impl Shell { if old_mapped.is_maximized(false) { new_mapped.set_maximized(false); } - start_data.set_focus(new_mapped.focus_under((0., 0.).into(), WindowSurfaceType::ALL)); + start_data.set_focus(new_mapped.focus_under( + (0., 0.).into(), + WindowSurfaceType::ALL, + seat, + )); new_mapped } else { old_mapped.clone() @@ -3819,19 +3918,15 @@ impl Shell { /// Get the window geometry of a keyboard focus target pub fn focused_geometry(&self, target: &KeyboardFocusTarget) -> Option> { match target { - KeyboardFocusTarget::Fullscreen(surface) => { - if let Some(workspace) = surface - .wl_surface() - .and_then(|s| self.workspace_for_surface(&s)) - .and_then(|(handle, _)| self.workspaces.space_for_handle(&handle)) - { + KeyboardFocusTarget::Fullscreen(surface) => surface + .wl_surface() + .and_then(|s| self.workspace_for_surface(&s)) + .and_then(|(handle, _)| self.workspaces.space_for_handle(&handle)) + .map(|workspace| { workspace - .fullscreen_geometry() - .map(|f| f.to_global(workspace.output())) - } else { - None - } - } + .fullscreen_geometry_for_surface(surface) + .to_global(workspace.output()) + }), _ => { if let Some(element) = self.focused_element(target) { self.element_geometry(&element) @@ -4106,7 +4201,7 @@ impl Shell { let element_offset = (new_loc - geometry.loc).as_logical(); let focus = mapped - .focus_under(element_offset.to_f64(), WindowSurfaceType::ALL) + .focus_under(element_offset.to_f64(), WindowSurfaceType::ALL, seat) .map(|(target, surface_offset)| (target, (surface_offset + element_offset.to_f64()))); start_data.set_location(new_loc.as_logical().to_f64()); start_data.set_focus(focus.clone()); @@ -4183,9 +4278,8 @@ impl Shell { self.workspaces.sets.values_mut().find_map(|set| { set.workspaces.iter_mut().find_map(|workspace| { let window = workspace - .get_fullscreen() - .cloned() - .into_iter() + .get_fullscreen_surfaces() + .map(|f| f.surface.clone()) .chain(workspace.mapped().map(|m| m.active_window())) .find(|s| s == surface); window.map(|s| (workspace, s)) @@ -4685,15 +4779,16 @@ impl Shell { &mut self, surface: &S, output: Output, - loop_handle: &LoopHandle<'static, State>, + _loop_handle: &LoopHandle<'static, State>, ) -> Option where CosmicSurface: PartialEq, { let mapped = self.element_for_surface(surface).cloned()?; + let seat = self.seats.last_active().clone(); let window; - let old_fullscreen = if let Some((old_output, set)) = self + if let Some((old_output, set)) = self .workspaces .sets .iter_mut() @@ -4740,7 +4835,7 @@ impl Shell { let workspace = self.active_space_mut(&output).unwrap(); workspace.map_fullscreen( &window, - None, + &seat, Some(FullscreenRestoreState::Sticky { output: old_output, state: FloatingRestoreData { @@ -4751,7 +4846,7 @@ impl Shell { }, }), Some(from), - ) + ); } else if let Some(workspace) = self.space_for_mut(&mapped) { if mapped.is_minimized() { // TODO: Rewrite the `MinimizedWindow` to restore to fullscreen @@ -4777,7 +4872,7 @@ impl Shell { workspace.map_fullscreen( &window, - None, + &seat, match state { WorkspaceRestoreData::Floating(floating_state) => { floating_state.map(|state| FullscreenRestoreState::Floating { @@ -4794,15 +4889,11 @@ impl Shell { WorkspaceRestoreData::Fullscreen(_) => unreachable!(), }, Some(from), - ) + ); } else { return None; }; - if let Some((old_fullscreen, restore, _)) = old_fullscreen { - self.remap_unfullscreened_window(old_fullscreen, restore, loop_handle); - } - Some(KeyboardFocusTarget::Fullscreen(window)) } @@ -4817,11 +4908,12 @@ impl Shell { let maybe_workspace = self.workspaces.iter_mut().find_map(|(_, s)| { s.workspaces .iter_mut() - .find(|w| w.get_fullscreen().is_some_and(|f| f == surface)) + .find(|w| w.get_fullscreen_surfaces().any(|f| &f.surface == surface)) }); if let Some(workspace) = maybe_workspace { - let (old_fullscreen, restore, _) = workspace.remove_fullscreen().unwrap(); + let (old_fullscreen, restore, _) = + workspace.remove_fullscreen_surface(surface).unwrap(); toplevel_leave_output(&old_fullscreen, &workspace.output); toplevel_leave_workspace(&old_fullscreen, &workspace.handle); @@ -4944,8 +5036,9 @@ fn workspace_set_idx( state: &mut WorkspaceUpdateGuard<'_, State>, idx: u8, handle: &WorkspaceHandle, + name: Option<&str>, ) { - state.set_workspace_name(handle, format!("{}", idx)); + state.set_workspace_name(handle, name.unwrap_or(&format!("{}", idx))); state.set_workspace_coordinates(handle, &[idx as u32]); } diff --git a/src/shell/seats.rs b/src/shell/seats.rs index a4f14364..5273e97f 100644 --- a/src/shell/seats.rs +++ b/src/shell/seats.rs @@ -17,8 +17,11 @@ use smithay::{ pointer::{CursorImageAttributes, CursorImageStatus}, }, output::Output, - reexports::{input::Device as InputDevice, wayland_server::DisplayHandle}, - utils::{Buffer, IsAlive, Monotonic, Point, Rectangle, Serial, Time, Transform}, + reexports::{ + input::Device as InputDevice, + wayland_server::{DisplayHandle, protocol::wl_surface::WlSurface}, + }, + utils::{Buffer, IsAlive, Logical, Monotonic, Point, Rectangle, Serial, Time, Transform}, wayland::compositor::with_states, }; use tracing::warn; @@ -178,6 +181,9 @@ struct ActiveOutput(pub Mutex); /// The output which currently has keyboard focus struct FocusedOutput(pub Mutex>); +#[derive(Default)] +pub struct PointerConstraintHint(pub Mutex)>>); + #[derive(Default)] pub struct LastModifierChange(pub Mutex>); @@ -201,6 +207,7 @@ pub fn create_seat( userdata.insert_if_missing_threadsafe(CursorState::default); userdata.insert_if_missing_threadsafe(|| ActiveOutput(Mutex::new(output.clone()))); userdata.insert_if_missing_threadsafe(|| FocusedOutput(Mutex::new(None))); + userdata.insert_if_missing_threadsafe(PointerConstraintHint::default); userdata.insert_if_missing_threadsafe(|| Mutex::new(CursorImageStatus::default_named())); // A lot of clients bind keyboard and pointer unconditionally once on launch.. @@ -252,6 +259,8 @@ pub trait SeatExt { fn supressed_buttons(&self) -> &SupressedButtons; fn modifiers_shortcut_queue(&self) -> &ModifiersShortcutQueue; fn last_modifier_change(&self) -> Option; + fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point)>; + fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point)>); fn cursor_geometry( &self, @@ -337,6 +346,23 @@ impl SeatExt for Seat { .unwrap() } + fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point)> { + let lock = self.user_data().get::().unwrap(); + let mut hint = lock.0.lock().unwrap(); + // Check if alive + if let Some((ref surface, _)) = *hint + && !surface.alive() + { + *hint = None; + } + hint.clone() + } + + fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point)>) { + let lock = self.user_data().get::().unwrap(); + *lock.0.lock().unwrap() = hint; + } + fn cursor_geometry( &self, loc: impl Into>, diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index a79b2acb..2b4db98c 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -108,9 +108,10 @@ pub struct Workspace { pub floating_layer: FloatingLayout, pub minimized_windows: Vec, pub tiling_enabled: bool, - pub fullscreen: Option, + pub fullscreen_surfaces: Vec, pub pinned: bool, pub id: Option, + pub name: Option, pub handle: WorkspaceHandle, pub focus_stack: FocusStacks, @@ -383,9 +384,10 @@ impl Workspace { floating_layer, tiling_enabled, minimized_windows: Vec::new(), - fullscreen: None, + fullscreen_surfaces: Vec::new(), pinned: false, id: None, + name: None, handle, focus_stack: FocusStacks::default(), image_copy: ImageCopySessions::default(), @@ -416,9 +418,10 @@ impl Workspace { floating_layer, tiling_enabled: pinned.tiling_enabled, minimized_windows: Vec::new(), - fullscreen: None, + fullscreen_surfaces: Vec::new(), pinned: true, id: pinned.id.clone(), + name: pinned.name.clone(), handle, focus_stack: FocusStacks::default(), image_copy: ImageCopySessions::default(), @@ -436,9 +439,9 @@ impl Workspace { } pub fn to_pinned(&self) -> Option { - debug_assert!(self.id.is_some()); let output = self.explicit_output().clone(); if self.pinned { + debug_assert!(self.id.is_some()); Some(PinnedWorkspace { output: cosmic_comp_config::workspace::OutputMatch { name: output.name, @@ -446,6 +449,7 @@ impl Workspace { }, tiling_enabled: self.tiling_enabled, id: self.id.clone(), + name: self.name.clone(), }) } else { None @@ -454,7 +458,10 @@ impl Workspace { #[profiling::function] pub fn refresh(&mut self) { - self.fullscreen.take_if(|w| !w.alive()); + // seems it removes dead windows + // self.fullscreen.take_if(|w| !w.alive()); + self.fullscreen_surfaces.retain(|w| w.alive()); + self.floating_layer.refresh(); self.tiling_layer.refresh(); } @@ -478,14 +485,15 @@ impl Workspace { self.is_empty() && !self.has_activation_token(xdg_activation_state) && !self.pinned } + /// cleans up any window that is not alive anymore pub fn refresh_focus_stack(&mut self) { for (seat, stack) in self.focus_stack.0.iter_mut() { - let fullscreen = self - .fullscreen - .as_ref() - .filter(|f| f.alive()) - .filter(|f| f.ended_at.is_none()) - .map(|f| &f.surface); + let fullscreen_surfaces: Vec<&CosmicSurface> = self + .fullscreen_surfaces + .iter() + .filter(|f| f.alive() && f.ended_at.is_none()) + .map(|f| &f.surface) + .collect(); // Move grab is treated as focused, so don't change focus to a // window while grab exists. @@ -506,7 +514,7 @@ impl Workspace { .chain(move_mapped.iter()) }; stack.retain(|w| match w { - FocusTarget::Fullscreen(s) => fullscreen.is_some_and(|f| f == s), + FocusTarget::Fullscreen(s) => fullscreen_surfaces.contains(&s), FocusTarget::Window(w) => mapped().any(|m| w == m), }); } @@ -515,15 +523,12 @@ impl Workspace { pub fn animations_going(&self) -> bool { self.tiling_layer.animations_going() || self.floating_layer.animations_going() - || self - .fullscreen - .as_ref() - .is_some_and(|f| f.start_at.is_some() || f.ended_at.is_some()) + || self.fullscreen_surfaces.iter().any(|f| f.is_animating()) || self.dirty.swap(false, Ordering::SeqCst) } pub fn update_animations(&mut self) -> HashMap { - if let Some(f) = self.fullscreen.as_mut() { + for f in self.fullscreen_surfaces.iter_mut() { if let Some(start) = f.start_at.as_ref() { let duration_since = Instant::now().duration_since(*start); if duration_since > FULLSCREEN_ANIMATION_DURATION { @@ -531,16 +536,18 @@ impl Workspace { self.dirty.store(true, Ordering::SeqCst); } } - - if let Some(end) = f.ended_at { - let duration_since = Instant::now().duration_since(end); - if duration_since >= FULLSCREEN_ANIMATION_DURATION { - let _ = self.fullscreen.take(); - self.dirty.store(true, Ordering::SeqCst); - } - } } + self.fullscreen_surfaces.retain(|f| { + if let Some(end) = f.ended_at + && Instant::now().duration_since(end) >= FULLSCREEN_ANIMATION_DURATION + { + self.dirty.store(true, Ordering::SeqCst); + return false; + } + true + }); + let clients = self.tiling_layer.update_animation_state(); self.floating_layer.update_animation_state(); clients @@ -574,7 +581,11 @@ impl Workspace { toplevel_enter_output(&surface, output); } } - if let Some(f) = self.fullscreen.as_ref().filter(|f| f.ended_at.is_none()) { + for f in self + .fullscreen_surfaces + .iter() + .filter(|f| f.ended_at.is_none()) + { toplevel_leave_output(&f.surface, &self.output); toplevel_enter_output(&f.surface, output); } @@ -665,12 +676,13 @@ impl Workspace { where CosmicSurface: PartialEq, { - if self - .fullscreen - .as_ref() - .is_some_and(|f| f.ended_at.is_none() && &f.surface == surface) + if let Some(idx) = self + .fullscreen_surfaces + .iter() + .position(|f| f.ended_at.is_none() && &f.surface == surface) { - let (surface, previous_state, previous_geometry) = self.remove_fullscreen().unwrap(); + let (surface, previous_state, previous_geometry) = + self.remove_fullscreen_at(idx).unwrap(); return Some(( surface, WorkspaceRestoreData::Fullscreen(previous_state.zip(previous_geometry).map( @@ -723,24 +735,28 @@ impl Workspace { Some((mapped.active_window(), layer)) } - pub fn fullscreen_geometry(&self) -> Option> { - self.fullscreen.as_ref().map(|fullscreen| { - let bbox = fullscreen.surface.bbox().as_local(); + pub fn fullscreen_geometry_for_surface( + &self, + surface: &CosmicSurface, + ) -> Rectangle { + let bbox = surface.bbox().as_local(); - let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local()); - if bbox != full_geo { - if bbox.size.w < full_geo.size.w { - full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2; - full_geo.size.w = bbox.size.w; - } - if bbox.size.h < full_geo.size.h { - full_geo.loc.y += (full_geo.size.h - bbox.size.h) / 2; - full_geo.size.h = bbox.size.h; - } + let mut full_geo = Rectangle::from_size(self.output.geometry().size.as_local()); + if bbox != full_geo { + if bbox.size.w < full_geo.size.w { + full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2; + full_geo.size.w = bbox.size.w; } + if bbox.size.h < full_geo.size.h { + full_geo.loc.y += (full_geo.size.h - bbox.size.h) / 2; + full_geo.size.h = bbox.size.h; + } + } - full_geo - }) + full_geo + } + pub fn fullscreen_geometry_for(&self, fullscreen: &FullscreenSurface) -> Rectangle { + self.fullscreen_geometry_for_surface(&fullscreen.surface) } pub fn element_for_surface(&self, surface: &S) -> Option<&CosmicMapped> @@ -780,24 +796,23 @@ impl Workspace { let stack = self.focus_stack.get(seat); let last_focused = stack.last(); - if let Some(fullscreen) = self.fullscreen.as_ref() - && last_focused.is_some_and( - |t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface), - ) - && !fullscreen.is_animating() - { - let geometry = self.fullscreen_geometry().unwrap(); + if let Some(fullscreen) = self.fullscreen_surfaces.iter().find(|f| { + !f.is_animating() + && last_focused + .is_some_and(|t| matches!(t, FocusTarget::Fullscreen(s) if s == &f.surface)) + }) { + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen_element_under(fullscreen, geometry); } self.floating_layer - .popup_element_under(location) - .or_else(|| self.tiling_layer.popup_element_under(location)) + .popup_element_under(location, seat) + .or_else(|| self.tiling_layer.popup_element_under(location, seat)) .or_else(|| { if last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_))) - && let Some(fullscreen) = self.fullscreen.as_ref() + && let Some(fullscreen) = self.get_fullscreen(seat) { - let geometry = self.fullscreen_geometry().unwrap(); + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen_element_under(fullscreen, geometry); } None @@ -830,24 +845,23 @@ impl Workspace { let stack = self.focus_stack.get(seat); let last_focused = stack.last(); - if let Some(fullscreen) = self.fullscreen.as_ref() - && last_focused.is_some_and( - |t| matches!(t, FocusTarget::Fullscreen(f) if f == &fullscreen.surface), - ) - && !fullscreen.is_animating() - { - let geometry = self.fullscreen_geometry().unwrap(); + if let Some(fullscreen) = self.fullscreen_surfaces.iter().find(|fs| { + !fs.is_animating() + && last_focused + .is_some_and(|t| matches!(t, FocusTarget::Fullscreen(f) if f == &fs.surface)) + }) { + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen_element_under(fullscreen, geometry); } self.floating_layer - .toplevel_element_under(location) - .or_else(|| self.tiling_layer.toplevel_element_under(location)) + .toplevel_element_under(location, seat) + .or_else(|| self.tiling_layer.toplevel_element_under(location, seat)) .or_else(|| { if last_focused.is_none_or(|t| !matches!(t, FocusTarget::Fullscreen(_))) - && let Some(fullscreen) = self.fullscreen.as_ref() + && let Some(fullscreen) = self.get_fullscreen(seat) { - let geometry = self.fullscreen_geometry().unwrap(); + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen_element_under(fullscreen, geometry); } None @@ -867,7 +881,7 @@ impl Workspace { let check_fullscreen = |fullscreen: &FullscreenSurface| { if !fullscreen.is_animating() { - let geometry = self.fullscreen_geometry().unwrap(); + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen .surface .0 @@ -891,18 +905,16 @@ impl Workspace { let stack = self.focus_stack.get(seat); let last_focused = stack.last(); - self.fullscreen - .as_ref() - .filter(|f| last_focused.is_some_and(|t| t == &f.surface)) + self.fullscreen_surfaces + .iter() + .find(|f| last_focused.is_some_and(|t| t == &f.surface)) .and_then(check_fullscreen) - .or_else(|| self.floating_layer.popup_surface_under(location)) - .or_else(|| self.tiling_layer.popup_surface_under(location, overview)) + .or_else(|| self.floating_layer.popup_surface_under(location, seat)) .or_else(|| { - self.fullscreen - .as_ref() - .filter(|f| last_focused.is_none_or(|t| t != &f.surface)) - .and_then(check_fullscreen) + self.tiling_layer + .popup_surface_under(location, overview, seat) }) + .or_else(|| self.get_fullscreen(seat).and_then(check_fullscreen)) .map(|(m, p)| (m, p.to_global(&self.output))) } @@ -919,7 +931,7 @@ impl Workspace { let check_fullscreen = |fullscreen: &FullscreenSurface| { if !fullscreen.is_animating() { - let geometry = self.fullscreen_geometry().unwrap(); + let geometry = self.fullscreen_geometry_for(fullscreen); return fullscreen .surface .focus_under( @@ -937,18 +949,16 @@ impl Workspace { let stack = self.focus_stack.get(seat); let last_focused = stack.last(); - self.fullscreen - .as_ref() - .filter(|f| last_focused.is_some_and(|t| t == &f.surface)) + self.fullscreen_surfaces + .iter() + .find(|f| last_focused.is_some_and(|t| t == &f.surface)) .and_then(check_fullscreen) - .or_else(|| self.floating_layer.toplevel_surface_under(location)) - .or_else(|| self.tiling_layer.toplevel_surface_under(location, overview)) + .or_else(|| self.floating_layer.toplevel_surface_under(location, seat)) .or_else(|| { - self.fullscreen - .as_ref() - .filter(|f| last_focused.is_none_or(|t| t != &f.surface)) - .and_then(check_fullscreen) + self.tiling_layer + .toplevel_surface_under(location, overview, seat) }) + .or_else(|| self.get_fullscreen(seat).and_then(check_fullscreen)) .map(|(m, p)| (m, p.to_global(&self.output))) } @@ -1020,10 +1030,14 @@ impl Workspace { where CosmicSurface: PartialEq, { - if self.get_fullscreen().is_some_and(|s| s == surface) { - let fullscreen_state = self.fullscreen.clone().unwrap(); + if let Some(idx) = self + .fullscreen_surfaces + .iter() + .position(|f| f.ended_at.is_none() && &f.surface == surface) + { + let fullscreen_state = self.fullscreen_surfaces.get(idx)?.clone(); { - let f = self.fullscreen.as_mut().unwrap(); + let f = self.fullscreen_surfaces.get_mut(idx)?; f.previous_geometry = Some(to); f.ended_at = Some( Instant::now() @@ -1127,16 +1141,20 @@ impl Workspace { )> { match window { MinimizedWindow::Fullscreen { previous, surface } => { - let old_fullscreen = self.remove_fullscreen(); surface.set_minimized(false); - self.fullscreen = Some(FullscreenSurface { + // focus it so it's the top fullscreen window + self.focus_stack + .get_mut(seat) + .append(FocusTarget::Fullscreen(surface.clone())); + self.fullscreen_surfaces.push(FullscreenSurface { surface, previous_state: previous.clone().map(|p| p.previous_state), previous_geometry: previous.map(|p| p.previous_geometry), start_at: None, ended_at: None, }); - old_fullscreen + self.dirty.store(true, Ordering::SeqCst); + None } MinimizedWindow::Floating { window, previous } => { let current_output_size = self.output.geometry().size.as_logical(); @@ -1224,13 +1242,7 @@ impl Workspace { seat: impl Into>>, restore: Option, previous_geometry: Option>, - ) -> Option<( - CosmicSurface, - Option, - Option>, - )> { - let res = self.remove_fullscreen(); - + ) { window.set_fullscreen(true); window.set_geometry(self.output.geometry(), 0); window.send_configure(); @@ -1243,96 +1255,137 @@ impl Workspace { self.focus_stack.get_mut(seat).append(window.clone()); } - self.fullscreen = Some(FullscreenSurface { + self.dirty.store(true, Ordering::SeqCst); + self.fullscreen_surfaces.push(FullscreenSurface { surface: window.clone(), previous_state: restore, previous_geometry, start_at: Some(Instant::now()), ended_at: None, }); - - res } #[must_use] - pub fn take_fullscreen( + pub fn take_fullscreen( &mut self, + surface: &S, + ) -> Option<( + CosmicSurface, + Option, + Option>, + )> + where + CosmicSurface: PartialEq, + { + let idx = self + .fullscreen_surfaces + .iter() + .position(|f| f.ended_at.is_none() && &f.surface == surface)?; + let fs = self.fullscreen_surfaces.remove(idx); + + for focus_stack in self.focus_stack.0.values_mut() { + focus_stack.retain(|t| t != &fs.surface); + } + + Some((fs.surface, fs.previous_state, fs.previous_geometry)) + } + + #[must_use] + pub fn remove_fullscreen_at( + &mut self, + idx: usize, ) -> Option<( CosmicSurface, Option, Option>, )> { - let surface = self.fullscreen.take_if(|s| s.ended_at.is_none())?; + // if it doesn't exist we move on. + let surface = self.fullscreen_surfaces.get_mut(idx)?; + // if already being removed, do nothing + if surface.ended_at.is_some() { + return None; + } + + if surface.surface.alive() { + surface.surface.output_leave(&self.output); + surface.surface.set_fullscreen(false); + if let Some(previous_geometry) = surface.previous_geometry.as_ref() { + surface + .surface + .set_geometry(previous_geometry.to_global(&self.output), 0); + } + surface.surface.send_configure(); + } for focus_stack in self.focus_stack.0.values_mut() { focus_stack.retain(|t| t != &surface.surface); } + surface.ended_at = Some( + Instant::now() + - (FULLSCREEN_ANIMATION_DURATION + - surface + .start_at + .take() + .map(|earlier| { + Instant::now() + .duration_since(earlier) + .min(FULLSCREEN_ANIMATION_DURATION) + }) + .unwrap_or(FULLSCREEN_ANIMATION_DURATION)), + ); + Some(( - surface.surface, - surface.previous_state, + surface.surface.clone(), + surface.previous_state.clone(), surface.previous_geometry, )) } #[must_use] - pub fn remove_fullscreen( + pub fn remove_fullscreen_surface( &mut self, + surface: &S, ) -> Option<( CosmicSurface, Option, Option>, - )> { - if let Some(surface) = self.fullscreen.as_mut() { - if surface.ended_at.is_some() { - return None; - } - - if surface.surface.alive() { - surface.surface.output_leave(&self.output); - surface.surface.set_fullscreen(false); - if let Some(previous_geometry) = surface.previous_geometry.as_ref() { - surface - .surface - .set_geometry(previous_geometry.to_global(&self.output), 0); - } - surface.surface.send_configure(); - } - - for focus_stack in self.focus_stack.0.values_mut() { - focus_stack.retain(|t| t != &surface.surface); - } - - surface.ended_at = Some( - Instant::now() - - (FULLSCREEN_ANIMATION_DURATION - - surface - .start_at - .take() - .map(|earlier| { - Instant::now() - .duration_since(earlier) - .min(FULLSCREEN_ANIMATION_DURATION) - }) - .unwrap_or(FULLSCREEN_ANIMATION_DURATION)), - ); - - Some(( - surface.surface.clone(), - surface.previous_state.clone(), - surface.previous_geometry, - )) - } else { - None - } + )> + where + CosmicSurface: PartialEq, + { + let idx = self + .fullscreen_surfaces + .iter() + .position(|f| f.ended_at.is_none() && &f.surface == surface)?; + self.remove_fullscreen_at(idx) } - pub fn get_fullscreen(&self) -> Option<&CosmicSurface> { - self.fullscreen - .as_ref() - .filter(|f| f.alive()) - .filter(|f| f.ended_at.is_none()) - .map(|f| &f.surface) + pub fn get_fullscreen(&self, seat: &Seat) -> Option<&FullscreenSurface> { + let stack = self.focus_stack.get(seat); + stack + .iter() + .find_map(|t| { + if let FocusTarget::Fullscreen(s) = t { + self.fullscreen_surfaces + .iter() + .find(|f| f.alive() && f.ended_at.is_none() && &f.surface == s) + } else { + None + } + }) + .or_else(|| { + self.fullscreen_surfaces + .iter() + .rev() + .find(|f| f.alive() && f.ended_at.is_none()) + }) + } + + pub fn get_fullscreen_surfaces(&self) -> impl Iterator { + self.fullscreen_surfaces + .iter() + .filter(|f| f.alive() && f.ended_at.is_none()) } pub fn resize( @@ -1459,14 +1512,18 @@ impl Workspace { self.floating_layer.mapped().count() + self.tiling_layer.mapped().count() + self.minimized_windows.len() - + if self.fullscreen.is_some() { 1 } else { 0 } + + self + .fullscreen_surfaces + .iter() + .filter(|f| f.ended_at.is_none()) + .count() } pub fn is_empty(&self) -> bool { self.floating_layer.mapped().next().is_none() && self.tiling_layer.mapped().next().is_none() && self.minimized_windows.is_empty() - && self.fullscreen.is_none() + && self.fullscreen_surfaces.is_empty() } pub fn is_floating(&self, surface: &S) -> bool @@ -1573,8 +1630,11 @@ impl Workspace { }; let focused = self.focus_stack.get(last_active_seat).last().cloned(); - let mut fullscreen_elements = if let Some(fullscreen) = self.fullscreen.as_ref() { - let fullscreen_geo = self.fullscreen_geometry().unwrap(); + 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() @@ -1644,20 +1704,35 @@ impl Workspace { .into_iter() .map(animation_rescale) .collect::>() - } else { - Vec::new() }; + 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)); + } + // 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)); + }; + } + if matches!(focused, Some(FocusTarget::Fullscreen(_))) { elements.append(&mut fullscreen_elements); } + let any_fullscreen_animating = self + .fullscreen_surfaces + .iter() + .any(|f| f.start_at.is_some() || f.ended_at.is_some()); if !matches!(focused, Some(FocusTarget::Fullscreen(_))) + || any_fullscreen_animating || self - .fullscreen - .as_ref() - .map(|f| f.start_at.is_some() || f.ended_at.is_some()) - .unwrap_or(true) + .fullscreen_surfaces + .iter() + .all(|f| !f.alive() || f.ended_at.is_some()) { // floating surfaces let alpha = match &overview.0 { @@ -1682,13 +1757,17 @@ impl Workspace { self.floating_layer .render::( renderer, - focused.as_ref().and_then(|target| { - if let FocusTarget::Window(mapped) = target { - Some(mapped) - } else { - None - } - }), + 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, @@ -1774,8 +1853,12 @@ impl Workspace { layer_map.non_exclusive_zone().as_local() }; - if let Some(fullscreen) = self.fullscreen.as_ref() { - let fullscreen_geo = self.fullscreen_geometry().unwrap(); + // Render popups for the top (most recently focused) fullscreen + let focus_stack = self.focus_stack.get(last_active_seat); + let top_fullscreen = self.get_fullscreen(last_active_seat); + + if let Some(fullscreen) = top_fullscreen { + let fullscreen_geo = self.fullscreen_geometry_for(fullscreen); let previous_geo = fullscreen .previous_geometry .as_ref() @@ -1832,13 +1915,16 @@ impl Workspace { ); } - let focus_stack = self.focus_stack.get(last_active_seat); + let any_fullscreen_animating = self + .fullscreen_surfaces + .iter() + .any(|f| f.start_at.is_some() || f.ended_at.is_some()); if !matches!(focus_stack.last(), Some(FocusTarget::Fullscreen(_))) + || any_fullscreen_animating || self - .fullscreen - .as_ref() - .map(|f| f.start_at.is_some() || f.ended_at.is_some()) - .unwrap_or(true) + .fullscreen_surfaces + .iter() + .all(|f| !f.alive() || f.ended_at.is_some()) { // floating surfaces let alpha = match &overview.0 { diff --git a/src/shell/zoom.rs b/src/shell/zoom.rs index 24d86763..f72ad8e8 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, @@ -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); } } } @@ -1036,58 +1029,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/state.rs b/src/state.rs index 88298a94..18de36ba 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,7 +8,7 @@ use crate::{ x11::X11State, }, config::{CompOutputConfig, Config, ScreenFilter}, - dbus::a11y_keyboard_monitor::A11yKeyboardMonitorState, + dbus::DBusState, input::{PointerFocusState, gestures::GestureState}, shell::{CosmicSurface, SeatExt, Shell, grabs::SeatMoveGrabState}, utils::prelude::OutputExt, @@ -32,7 +32,6 @@ use crate::{ use anyhow::Context; use calloop::RegistrationToken; use cosmic_comp_config::output::comp::{OutputConfig, OutputState}; -use futures_executor::ThreadPool; use i18n_embed::{ DesktopLanguageRequester, fluent::{FluentLanguageLoader, fluent_language_loader}, @@ -87,6 +86,7 @@ use smithay::{ output::OutputManagerState, pointer_constraints::PointerConstraintsState, pointer_gestures::PointerGesturesState, + pointer_warp::PointerWarpManager, presentation::PresentationState, seat::WaylandFocus, security_context::{SecurityContext, SecurityContextState}, @@ -117,7 +117,7 @@ use smithay::{ }; use tracing::warn; -#[cfg(feature = "systemd")] +#[cfg(feature = "logind")] use std::os::fd::OwnedFd; use std::{ @@ -234,7 +234,6 @@ pub struct Common { pub display_handle: DisplayHandle, pub event_loop_handle: LoopHandle<'static, State>, pub event_loop_signal: LoopSignal, - pub async_executor: ThreadPool, pub popups: PopupManager, pub shell: Arc>, @@ -279,7 +278,7 @@ pub struct Common { pub xdg_decoration_state: XdgDecorationState, pub overlap_notify_state: OverlapNotifyState, pub a11y_state: A11yState, - pub a11y_keyboard_monitor_state: A11yKeyboardMonitorState, + pub dbus_state: DBusState, // shell-related wayland state pub xdg_shell_state: XdgShellState, @@ -294,7 +293,7 @@ pub struct Common { pub xwayland_shell_state: XWaylandShellState, pub pointer_focus_state: Option, - #[cfg(feature = "systemd")] + #[cfg(feature = "logind")] pub inhibit_lid_fd: Option, pub with_xwayland: bool, @@ -673,6 +672,7 @@ impl State { XWaylandKeyboardGrabState::new::(dh); let xwayland_shell_state = XWaylandShellState::new::(dh); PointerConstraintsState::new::(dh); + PointerWarpManager::new::(dh); PointerGesturesState::new::(dh); TabletManagerState::new::(dh); SecurityContextState::new::(dh, client_has_no_security_context); @@ -727,15 +727,9 @@ impl State { ); let workspace_state = WorkspaceState::new(dh, client_not_sandboxed); - let async_executor = ThreadPool::builder().pool_size(1).create().unwrap(); - - if let Err(err) = crate::dbus::init(&handle, &async_executor) { - tracing::warn!(?err, "Failed to initialize dbus handlers"); - } - let a11y_state = A11yState::new::(dh, client_not_sandboxed); - let a11y_keyboard_monitor_state = A11yKeyboardMonitorState::new(&async_executor); + let dbus_state = DBusState::init(&handle); State { common: Common { @@ -744,7 +738,6 @@ impl State { display_handle: dh.clone(), event_loop_handle: handle, event_loop_signal: signal, - async_executor, popups: PopupManager::default(), shell, @@ -794,13 +787,13 @@ impl State { xdg_foreign_state, workspace_state, a11y_state, - a11y_keyboard_monitor_state, xwayland_scale: None, xwayland_state: None, xwayland_shell_state, pointer_focus_state: None, + dbus_state, - #[cfg(feature = "systemd")] + #[cfg(feature = "logind")] inhibit_lid_fd: None, with_xwayland, @@ -825,7 +818,7 @@ impl State { } fn update_inhibitor_locks(&mut self) { - #[cfg(feature = "systemd")] + #[cfg(feature = "logind")] { use smithay::backend::session::Session; use tracing::{debug, error, warn}; @@ -841,7 +834,7 @@ impl State { if should_handle_lid { if self.common.inhibit_lid_fd.is_none() { - match crate::dbus::logind::inhibit_lid() { + match crate::dbus::logind::inhibit_lid(&self.common) { Ok(fd) => { debug!("Inhibiting lid switch"); self.common.inhibit_lid_fd = Some(fd); @@ -852,7 +845,8 @@ impl State { .iter() .find(|o| o.is_internal()) .cloned(); - let closed = crate::dbus::logind::lid_closed().unwrap_or(false); + let closed = + crate::dbus::logind::lid_closed(&self.common).unwrap_or(false); if closed { backend.disable_internal_output( @@ -996,8 +990,8 @@ impl Common { // normal windows for space in shell.workspaces.spaces() { - if let Some(window) = space.get_fullscreen() { - window.with_surfaces(processor); + if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) { + fs.surface.with_surfaces(processor); } space.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { @@ -1162,15 +1156,16 @@ impl Common { }); if let Some(active) = shell.active_space(output) { - if let Some(window) = active.get_fullscreen() - && let Some(feedback) = window + if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) + && let Some(feedback) = fs + .surface .wl_surface() .and_then(|wl_surface| { advertised_node_for_surface(&wl_surface, &self.display_handle) }) .and_then(&mut dmabuf_feedback) { - window.send_dmabuf_feedback( + fs.surface.send_dmabuf_feedback( output, &feedback, render_element_states, @@ -1356,8 +1351,9 @@ impl Common { }); if let Some(active) = shell.active_space(output) { - if let Some(window) = active.get_fullscreen() { - window.send_frame(output, time, throttle(window), should_send); + if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) { + fs.surface + .send_frame(output, time, throttle(&fs.surface), should_send); } active.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { @@ -1377,9 +1373,9 @@ impl Common { .spaces_for_output(output) .filter(|w| w.handle != active.handle) { - if let Some(window) = space.get_fullscreen() { - let throttle = min(throttle(space), throttle(window)); - window.send_frame(output, time, throttle, |_, _| None); + if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) { + let throttle = min(throttle(space), throttle(&fs.surface)); + fs.surface.send_frame(output, time, throttle, |_, _| None); } space.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { 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/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) + }) } diff --git a/src/wayland/handlers/dmabuf.rs b/src/wayland/handlers/dmabuf.rs index 093f88da..4d9732bb 100644 --- a/src/wayland/handlers/dmabuf.rs +++ b/src/wayland/handlers/dmabuf.rs @@ -51,9 +51,11 @@ impl DmabufHandler for State { let is_fullscreen = shell .workspaces .space_for_handle(&handle)? - .fullscreen - .as_ref() - .is_some_and(|f| f.surface.has_surface(surface, WindowSurfaceType::all())); + .fullscreen_surfaces + .iter() + .any(|f| { + f.ended_at.is_none() && f.surface.has_surface(surface, WindowSurfaceType::all()) + }); let node = kms .drm_devices diff --git a/src/wayland/handlers/image_copy_capture/render.rs b/src/wayland/handlers/image_copy_capture/render.rs index 28ccd1cb..21370f31 100644 --- a/src/wayland/handlers/image_copy_capture/render.rs +++ b/src/wayland/handlers/image_copy_capture/render.rs @@ -753,11 +753,15 @@ pub fn render_cursor_to_buffer( seat: &Seat, ) { let buffer = frame.buffer(); - let cursor_size = seat + let mut cursor_size = seat .cursor_geometry((0.0, 0.0), state.common.clock.now()) .map(|(geo, _hotspot)| geo.size) .unwrap_or_else(|| Size::from((64, 64))); let buffer_size = buffer_dimensions(&buffer).unwrap(); + // Client shouldn't try to allocate 0x0 buffer + if cursor_size == Size::new(0, 0) { + cursor_size = Size::new(1, 1); + } if buffer_size != cursor_size { let constraints = BufferConstraints { size: cursor_size, diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 8f6547cf..34d063d0 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -25,6 +25,7 @@ pub mod output_configuration; pub mod output_power; pub mod overlap_notify; pub mod pointer_constraints; +pub mod pointer_warp; pub mod primary_selection; pub mod seat; pub mod security_context; diff --git a/src/wayland/handlers/pointer_constraints.rs b/src/wayland/handlers/pointer_constraints.rs index 71e4a4d1..e17c1961 100644 --- a/src/wayland/handlers/pointer_constraints.rs +++ b/src/wayland/handlers/pointer_constraints.rs @@ -1,35 +1,123 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::state::State; +use crate::{shell::CosmicSurface, state::State, utils::prelude::*}; use smithay::{ input::pointer::PointerHandle, reexports::wayland_server::protocol::wl_surface::WlSurface, utils::{Logical, Point}, - wayland::{ - pointer_constraints::{PointerConstraintsHandler, with_pointer_constraint}, - seat::WaylandFocus, - }, + wayland::{pointer_constraints::PointerConstraintsHandler, seat::WaylandFocus}, }; +pub use smithay::wayland::pointer_constraints::{PointerConstraintRef, with_pointer_constraint}; + impl PointerConstraintsHandler for State { fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle) { - // XXX region - if pointer - .current_focus() - .is_some_and(|x| x.wl_surface().as_deref() == Some(surface)) - { + let seat = self + .common + .shell + .read() + .seats + .iter() + .find(|s| s.get_pointer().as_ref() == Some(pointer)) + .cloned(); + + let (is_under, is_focused, surface_location) = if let Some(seat) = seat { + seat.set_pointer_constraint_hint(None); + let current_output = seat.active_output(); + let position = seat.get_pointer().unwrap().current_location().as_global(); + let shell = self.common.shell.read(); + let under = State::surface_under(position, ¤t_output, &shell); + let mut surface_location = None; + + let is_under = if let Some((target, target_loc)) = under + && let Some(under_surface) = target.wl_surface() + { + if *under_surface == *surface { + surface_location = Some(target_loc); + true + } else { + CosmicSurface::surface_tree_offset(surface, &under_surface).is_some_and( + |offset| { + surface_location = Some(target_loc - offset.to_f64().as_global()); + true + }, + ) + } + } else { + false + }; + + let is_focused = seat + .get_keyboard() + .and_then(|k| k.current_focus()) + .is_some_and(|f| f.has_surface(&shell, surface)); + + (is_under, is_focused, surface_location) + } else { + (false, false, None) + }; + + if is_focused && is_under { with_pointer_constraint(surface, pointer, |constraint| { - constraint.unwrap().activate(); + if let Some(constraint) = constraint { + if let Some(region) = constraint.region() { + if let Some(surface_location) = surface_location + && let position = pointer.current_location() + && let point = (position - surface_location.as_logical()).to_i32_round() + && region.contains(point) + { + constraint.activate(); + } + } else { + constraint.activate(); + } + } }); } } + fn remove_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle) { + if with_pointer_constraint(surface, pointer, |constraint| constraint.is_none()) { + let seat = self + .common + .shell + .read() + .seats + .iter() + .find(|s| s.get_pointer().as_ref() == Some(pointer)) + .cloned(); + + if let Some(seat) = seat + && let Some((hint_surface, hint_location)) = seat.pointer_constraint_hint() + && hint_surface == *surface + { + self.apply_cursor_hint(surface, pointer, hint_location); + seat.set_pointer_constraint_hint(None); + } + } + } + fn cursor_position_hint( &mut self, - _surface: &WlSurface, - _pointer: &PointerHandle, - _location: Point, + surface: &WlSurface, + pointer: &PointerHandle, + location: Point, ) { - // TODO + if with_pointer_constraint(surface, pointer, |constraint| { + constraint.is_some_and(|c| c.is_active()) + }) { + let seat = self + .common + .shell + .read() + .seats + .iter() + .find(|s| s.get_pointer().as_ref() == Some(pointer)) + .cloned(); + + if let Some(seat) = seat { + seat.set_pointer_constraint_hint(Some((surface.clone(), location))); + } + } } } diff --git a/src/wayland/handlers/pointer_warp.rs b/src/wayland/handlers/pointer_warp.rs new file mode 100644 index 00000000..971ab583 --- /dev/null +++ b/src/wayland/handlers/pointer_warp.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use smithay::{ + input::pointer::PointerHandle, + reexports::wayland_server::protocol::{wl_pointer::WlPointer, wl_surface::WlSurface}, + utils::{Logical, Point, Serial}, + wayland::pointer_warp::PointerWarpHandler, +}; + +impl PointerWarpHandler for State { + fn warp_pointer( + &mut self, + surface: WlSurface, + pointer: WlPointer, + pos: Point, + serial: Serial, + ) { + let Some(resource_handle) = PointerHandle::::from_resource(&pointer) else { + return; + }; + + let shell = self.common.shell.read(); + + let pointer_handle = shell.seats.iter().find_map(|seat| { + if let Some(pointer_handle) = seat.get_pointer() + && resource_handle == pointer_handle + && pointer_handle.last_enter() == Some(serial) + && let Some(keyboard) = seat.get_keyboard() + && let Some(keyboard_focus) = keyboard.current_focus() + && keyboard_focus.has_surface(&shell, &surface) + { + return Some(pointer_handle); + } + None + }); + + drop(shell); + + if let Some(pointer_handle) = pointer_handle { + self.apply_cursor_hint(&surface, &pointer_handle, pos); + } + } +} diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs index 01410bb5..3da39952 100644 --- a/src/wayland/handlers/toplevel_management.rs +++ b/src/wayland/handlers/toplevel_management.rs @@ -42,7 +42,7 @@ impl ToplevelManagementHandler for State { .spaces_for_output(output) .enumerate() .find(|(_, w)| { - w.get_fullscreen().is_some_and(|f| f == window) + w.get_fullscreen_surfaces().any(|f| &f.surface == window) || w.mapped() .flat_map(|m| m.windows().map(|(s, _)| s)) .any(|w| &w == window) diff --git a/src/wayland/handlers/xdg_activation.rs b/src/wayland/handlers/xdg_activation.rs index b9674e38..ad8ce0d0 100644 --- a/src/wayland/handlers/xdg_activation.rs +++ b/src/wayland/handlers/xdg_activation.rs @@ -5,6 +5,7 @@ use crate::{ state::State, wayland::protocols::workspace::{State as WState, WorkspaceHandle}, }; +use cosmic_comp_config::ActivationPolicy; use smithay::{ input::Seat, reexports::wayland_server::protocol::wl_surface::WlSurface, @@ -20,6 +21,10 @@ pub enum ActivationContext { Workspace(WorkspaceHandle), } +// It may happen that we get activation-requests, while all outputs are disabled. +// In these cases we won't be able to determine workspaces for windows and thus +// need to handle the corresponding code paths defensively. + impl XdgActivationHandler for State { fn activation_state(&mut self) -> &mut XdgActivationState { &mut self.common.xdg_activation_state @@ -51,7 +56,12 @@ impl XdgActivationHandler for State { }); let output = seat.active_output(); let mut shell = self.common.shell.write(); - let workspace = shell.active_space_mut(&output).unwrap(); + let Some(workspace) = shell.active_space_mut(&output) else { + debug!(?token, "created urgent token for privileged client"); + data.user_data + .insert_if_missing(move || ActivationContext::UrgentOnly); + return true; + }; let handle = workspace.handle; data.user_data .insert_if_missing(move || ActivationContext::Workspace(handle)); @@ -85,7 +95,11 @@ impl XdgActivationHandler for State { if valid { let output = seat.active_output(); let mut shell = self.common.shell.write(); - let workspace = shell.active_space_mut(&output).unwrap(); + let Some(workspace) = shell.active_space_mut(&output) else { + data.user_data + .insert_if_missing(|| ActivationContext::UrgentOnly); + return true; + }; let handle = workspace.handle; data.user_data .insert_if_missing(move || ActivationContext::Workspace(handle)); @@ -115,10 +129,44 @@ impl XdgActivationHandler for State { } } ActivationContext::Workspace(_) => { - self.activate_surface( - &surface, - Some((ActivationKey::Wayland(surface.clone()), *context)), - ); + match self.common.config.cosmic_conf.activation_policy { + ActivationPolicy::Focus => { + self.activate_surface( + &surface, + Some((ActivationKey::Wayland(surface.clone()), *context)), + ); + } + ActivationPolicy::FocusIfActiveWorkspace => { + let shell = self.common.shell.write(); + + let Some((target_workspace, _)) = shell.workspace_for_surface(&surface) + else { + return; + }; + + let seat = shell.seats.last_active().clone(); + let current_output = seat.active_output(); + let current_workspace = shell.active_space(¤t_output).unwrap().handle; + + if target_workspace == current_workspace { + std::mem::drop(shell); + self.activate_surface( + &surface, + Some((ActivationKey::Wayland(surface.clone()), *context)), + ); + } else { + let mut workspace_guard = self.common.workspace_state.update(); + workspace_guard.add_workspace_state(&target_workspace, WState::Urgent); + } + } + ActivationPolicy::Urgent => { + let shell = self.common.shell.write(); + if let Some((workspace, _output)) = shell.workspace_for_surface(&surface) { + let mut workspace_guard = self.common.workspace_state.update(); + workspace_guard.add_workspace_state(&workspace, WState::Urgent); + } + } + } } } } @@ -219,8 +267,8 @@ impl State { .workspaces .space_for_handle(&workspace) .unwrap() - .get_fullscreen() - .cloned() + .get_fullscreen(&seat) + .map(|f| f.surface.clone()) .map(KeyboardFocusTarget::Fullscreen) else { return; @@ -232,8 +280,8 @@ impl State { if let Some(surface) = shell .workspaces .space_for_handle(&workspace) - .and_then(|w| w.get_fullscreen()) - .cloned() + .and_then(|w| w.get_fullscreen(&seat)) + .map(|f| f.surface.clone()) { shell.append_focus_stack(surface, &seat) } diff --git a/src/wayland/handlers/xdg_shell/popup.rs b/src/wayland/handlers/xdg_shell/popup.rs index 965e8908..a3ee2e31 100644 --- a/src/wayland/handlers/xdg_shell/popup.rs +++ b/src/wayland/handlers/xdg_shell/popup.rs @@ -68,7 +68,7 @@ impl Shell { unconstrain_xdg_popup(surface, window_loc, output.geometry()); } } else if let Some(output) = self.workspaces.spaces().find_map(|w| { - w.fullscreen.as_ref().and_then(|f| { + w.fullscreen_surfaces.iter().find_map(|f| { (f.surface.wl_surface().as_deref() == Some(&parent)).then_some(w.output()) }) }) { diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 4643aca6..2dc76347 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -495,35 +495,18 @@ where .unwrap() .lock() .unwrap(); - let foreign_toplevel_handle = state.foreign_handle.as_ref(); let mut changed = false; - if handle_state.title != window.title() { - handle_state.title = window.title(); - if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE { - instance.title(handle_state.title.clone()); - } - if let Some(handle) = foreign_toplevel_handle { - handle.send_title(&handle_state.title); - } - changed = true; - } - if handle_state.app_id != window.app_id() { - handle_state.app_id = window.app_id(); - if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE { - instance.app_id(handle_state.app_id.clone()); - } - if let Some(handle) = foreign_toplevel_handle { - handle.send_app_id(&handle_state.app_id); - } - changed = true; - } + let new_title = (handle_state.title != window.title()).then(|| window.title()); + let new_app_id = (handle_state.app_id != window.app_id()).then(|| window.app_id()); - if handle_state.states.as_ref().is_none_or(|states| { + let new_states = if handle_state.states.as_ref().is_none_or(|states| { (states.contains(&States::Maximized) != window.is_maximized()) || (states.contains(&States::Fullscreen) != window.is_fullscreen()) || (states.contains(&States::Activated) != window.is_activated()) || (states.contains(&States::Minimized) != window.is_minimized()) + || (instance.version() >= zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE + && states.contains(&States::Sticky) != window.is_sticky()) }) { let mut states = Vec::new(); if window.is_maximized() { @@ -543,8 +526,64 @@ where { states.push(States::Sticky); } - handle_state.states = Some(states.clone()); + Some(states) + } else { + None + }; + let geometry_changed = if !window.is_resizing() { + let geometry = window.global_geometry(); + if handle_state.geometry != geometry { + handle_state.geometry = geometry; + true + } else { + false + } + } else { + false + }; + + let outputs_changed = state.outputs != handle_state.outputs + || handle_state.wl_outputs.iter().any(|o| !o.is_alive()); + + let workspaces_changed = state.workspaces != handle_state.workspaces; + + if new_title.is_none() + && new_app_id.is_none() + && new_states.is_none() + && !geometry_changed + && !outputs_changed + && !workspaces_changed + { + return false; + } + + let foreign_toplevel_handle = state.foreign_handle.as_ref(); + + if let Some(title) = new_title { + handle_state.title = title; + if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE { + instance.title(handle_state.title.clone()); + } + if let Some(handle) = foreign_toplevel_handle { + handle.send_title(&handle_state.title); + } + changed = true; + } + + if let Some(app_id) = new_app_id { + handle_state.app_id = app_id; + if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE { + instance.app_id(handle_state.app_id.clone()); + } + if let Some(handle) = foreign_toplevel_handle { + handle.send_app_id(&handle_state.app_id); + } + changed = true; + } + + if let Some(states) = new_states { + handle_state.states = Some(states.clone()); let states = states .iter() .flat_map(|state| (*state as u32).to_ne_bytes()) @@ -553,17 +592,9 @@ where changed = true; } - let mut geometry_changed = false; - if !window.is_resizing() { - let geometry = window.global_geometry(); - if handle_state.geometry != geometry { - handle_state.geometry = geometry; - changed = true; - geometry_changed = true; - } - } - - if let Ok(client) = dh.get_client(instance.id()) { + if (outputs_changed || geometry_changed) + && let Ok(client) = dh.get_client(instance.id()) + { handle_state.outputs = state.outputs.clone(); let handle_state = &mut *handle_state; @@ -599,27 +630,29 @@ where }); } - for new_workspace in state - .workspaces - .iter() - .filter(|w| !handle_state.workspaces.contains(w)) - { - for handle in workspace_state.raw_ext_workspace_handles(new_workspace, &instance.id()) { - instance.ext_workspace_enter(handle); - changed = true; + if workspaces_changed { + for new_workspace in state + .workspaces + .iter() + .filter(|w| !handle_state.workspaces.contains(w)) + { + for handle in workspace_state.raw_ext_workspace_handles(new_workspace, &instance.id()) { + instance.ext_workspace_enter(handle); + changed = true; + } } - } - for old_workspace in handle_state - .workspaces - .iter() - .filter(|w| !state.workspaces.contains(w)) - { - for handle in workspace_state.raw_ext_workspace_handles(old_workspace, &instance.id()) { - instance.ext_workspace_leave(handle); - changed = true; + for old_workspace in handle_state + .workspaces + .iter() + .filter(|w| !state.workspaces.contains(w)) + { + for handle in workspace_state.raw_ext_workspace_handles(old_workspace, &instance.id()) { + instance.ext_workspace_leave(handle); + changed = true; + } } + handle_state.workspaces = state.workspaces.clone(); } - handle_state.workspaces = state.workspaces.clone(); if changed { if instance.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE { diff --git a/src/wayland/protocols/toplevel_management.rs b/src/wayland/protocols/toplevel_management.rs index 9ec5132a..f252fde5 100644 --- a/src/wayland/protocols/toplevel_management.rs +++ b/src/wayland/protocols/toplevel_management.rs @@ -237,11 +237,10 @@ where window_from_handle::<::Window>(toplevel).unwrap(); if let Some(toplevel_state) = window.user_data().get::() { let mut toplevel_state = toplevel_state.lock().unwrap(); - if width == 0 && height == 0 { - toplevel_state - .rectangles - .retain(|(s, _)| s.id() != surface.id()); - } else { + toplevel_state + .rectangles + .retain(|(s, _)| s.id() != surface.id()); + if width != 0 || height != 0 { toplevel_state.rectangles.push(( surface.downgrade(), Rectangle::new((x, y).into(), (width, height).into()), diff --git a/src/xwayland.rs b/src/xwayland.rs index 4c2f3b55..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(), @@ -575,16 +576,15 @@ impl Common { .filter(|(i, _)| *i != set.active), ) .flat_map(|(_, workspace)| { + let focus_last = + workspace.focus_stack.get(seat).last().cloned(); workspace - .get_fullscreen() + .get_fullscreen_surfaces() .filter(|f| { - workspace - .focus_stack - .get(seat) - .last() - .is_some_and(|t| &t == f) + focus_last.as_ref().is_some_and(|t| t == &f.surface) }) - .cloned() + .map(|f| f.surface.clone()) + .collect::>() .into_iter() .chain(workspace.mapped().flat_map(|mapped| { let active = mapped.active_window(); @@ -603,15 +603,14 @@ impl Common { })) .chain( workspace - .get_fullscreen() + .get_fullscreen_surfaces() .filter(|f| { - workspace - .focus_stack - .get(seat) - .last() - .is_none_or(|t| &t != f) + focus_last + .as_ref() + .is_none_or(|t| t != &f.surface) }) - .cloned() + .map(|f| f.surface.clone()) + .collect::>() .into_iter(), ) .chain(