diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2463c111..5fe858c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,3 +25,4 @@ jobs: - run: cargo check --no-default-features - run: cargo check --features debug - run: cargo check --features profile-with-tracy + - run: cargo fmt --all -- --check diff --git a/Cargo.lock b/Cargo.lock index 9cd1c16c..7fcf9c27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,7 +89,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "thiserror", ] @@ -168,9 +168,9 @@ checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "as-raw-xcb-connection" @@ -178,22 +178,13 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" -[[package]] -name = "ash" -version = "0.37.3+1.3.251" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" -dependencies = [ - "libloading 0.7.4", -] - [[package]] name = "ash" version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.8.5", + "libloading", ] [[package]] @@ -302,7 +293,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -337,7 +328,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -391,18 +382,18 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bit-set" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" [[package]] name = "bit_field" @@ -491,7 +482,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -617,7 +608,7 @@ dependencies = [ [[package]] name = "clipboard_macos" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "objc", "objc-foundation", @@ -627,7 +618,7 @@ dependencies = [ [[package]] name = "clipboard_wayland" version = "0.2.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "dnd", "mime 0.1.0", @@ -637,7 +628,7 @@ dependencies = [ [[package]] name = "clipboard_x11" version = "0.4.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "thiserror", "x11rb", @@ -811,6 +802,7 @@ dependencies = [ "cosmic-config", "cosmic-protocols", "cosmic-settings-config", + "drm-ffi 0.8.0", "edid-rs", "egui", "egui_plot", @@ -832,6 +824,7 @@ dependencies = [ "profiling", "rand", "regex", + "reis", "ron", "rust-embed", "rustix", @@ -853,7 +846,7 @@ dependencies = [ "xcursor", "xdg", "xdg-user", - "xkbcommon 0.7.0", + "xkbcommon 0.8.0", "zbus", ] @@ -869,7 +862,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic/#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "atomicwrites", "calloop 0.14.1", @@ -888,7 +881,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic/#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "quote", "syn 1.0.109", @@ -897,7 +890,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#91aeb55052a8e6e15a7ddd53e039a9350f16fa69" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#d218c76b58c7a3b20dd5e7943f93fc306a1b81b8" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -910,7 +903,7 @@ dependencies = [ [[package]] name = "cosmic-settings-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-settings-daemon#1ed68808e85ce681da882446ec572d44c68a6866" +source = "git+https://github.com/pop-os/cosmic-settings-daemon#747e482ca197497ee3bc5f6e9dcd23c73e592e47" dependencies = [ "cosmic-config", "serde", @@ -923,15 +916,15 @@ dependencies = [ [[package]] name = "cosmic-text" version = "0.12.1" -source = "git+https://github.com/pop-os/cosmic-text.git#4fe90bb6126c22f589b46768d7754d65ae300c5e" +source = "git+https://github.com/pop-os/cosmic-text.git#1f4065c1c3399efad58841082212f7c039b58480" dependencies = [ "bitflags 2.6.0", - "fontdb", + "fontdb 0.16.2", "log", "rangemap", "rayon", - "rustc-hash", - "rustybuzz 0.14.1", + "rustc-hash 1.1.0", + "rustybuzz", "self_cell 1.0.4", "smol_str", "swash", @@ -946,7 +939,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "almost", "cosmic-config", @@ -1058,11 +1051,12 @@ checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] name = "d3d12" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" dependencies = [ "bitflags 2.6.0", - "libloading 0.8.5", + "libloading", "winapi", ] @@ -1087,7 +1081,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1098,7 +1092,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1139,7 +1133,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1188,7 +1182,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1197,7 +1191,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.5", + "libloading", ] [[package]] @@ -1212,7 +1206,7 @@ dependencies = [ [[package]] name = "dnd" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "bitflags 2.6.0", "mime 0.1.0", @@ -1221,6 +1215,15 @@ dependencies = [ "smithay-clipboard", ] +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -1269,6 +1272,16 @@ dependencies = [ "rustix", ] +[[package]] +name = "drm-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +dependencies = [ + "drm-sys 0.7.0", + "rustix", +] + [[package]] name = "drm-ffi" version = "0.9.0" @@ -1295,6 +1308,16 @@ dependencies = [ "linux-raw-sys 0.6.4", ] +[[package]] +name = "drm-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.4", +] + [[package]] name = "drm-sys" version = "0.8.0" @@ -1344,7 +1367,7 @@ dependencies = [ "enum-map", "log", "mime_guess2", - "resvg", + "resvg 0.37.0", ] [[package]] @@ -1422,7 +1445,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1443,7 +1466,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1630,7 +1653,7 @@ dependencies = [ "fluent-syntax", "intl-memoizer", "intl_pluralrules", - "rustc-hash", + "rustc-hash 1.1.0", "self_cell 0.10.3", "smallvec", "unic-langid", @@ -1701,6 +1724,20 @@ dependencies = [ "ttf-parser 0.20.0", ] +[[package]] +name = "fontdb" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.9.4", + "slotmap", + "tinyvec", + "ttf-parser 0.21.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1719,7 +1756,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1737,16 +1774,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fraction" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" -dependencies = [ - "lazy_static", - "num", -] - [[package]] name = "freedesktop-icons" version = "0.2.6" @@ -1839,7 +1866,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1938,16 +1965,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gif" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gif" version = "0.13.1" @@ -1977,9 +1994,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.24.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" [[package]] name = "glow" @@ -2019,24 +2036,13 @@ dependencies = [ [[package]] name = "glutin_wgl_sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +checksum = "0a4e1951bbd9434a81aa496fe59ccc2235af3820d27b85f9314e279609211e2c" dependencies = [ "gl_generator", ] -[[package]] -name = "glyphon" -version = "0.5.0" -source = "git+https://github.com/pop-os/glyphon.git?tag=v0.5.0#1b0646ff8f74da92d3be704dfc2257d7f4d7eed8" -dependencies = [ - "cosmic-text", - "etagere", - "lru", - "wgpu", -] - [[package]] name = "gpu-alloc" version = "0.6.0" @@ -2058,9 +2064,9 @@ dependencies = [ [[package]] name = "gpu-allocator" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" dependencies = [ "log", "presser", @@ -2071,9 +2077,9 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", @@ -2082,9 +2088,9 @@ dependencies = [ [[package]] name = "gpu-descriptor-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ "bitflags 2.6.0", ] @@ -2140,7 +2146,7 @@ dependencies = [ "bitflags 2.6.0", "com", "libc", - "libloading 0.8.5", + "libloading", "thiserror", "widestring", "winapi", @@ -2247,7 +2253,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.72", + "syn 2.0.90", "unic-langid", ] @@ -2261,7 +2267,7 @@ dependencies = [ "i18n-config", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2289,8 +2295,8 @@ dependencies = [ [[package]] name = "iced" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "dnd", "iced_core", @@ -2305,45 +2311,60 @@ dependencies = [ [[package]] name = "iced_core" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "bitflags 2.6.0", + "bytes", "dnd", + "glam", "log", "mime 0.1.0", "num-traits", + "once_cell", "palette", "raw-window-handle", + "rustc-hash 2.0.0", "serde", "smol_str", "thiserror", - "web-time 0.2.4", + "web-time", "window_clipboard", - "xxhash-rust", ] [[package]] name = "iced_futures" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "futures", "iced_core", "log", + "rustc-hash 2.0.0", "wasm-bindgen-futures", "wasm-timer", ] +[[package]] +name = "iced_glyphon" +version = "0.6.0" +source = "git+https://github.com/pop-os/glyphon.git?tag=iced-0.14-dev#6ef9d12a20cfd0f7bdf38136a26ded9f7459ec8b" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.0.0", + "wgpu", +] + [[package]] name = "iced_graphics" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "bitflags 2.6.0", "bytemuck", "cosmic-text", - "glam", "half", "iced_core", "iced_futures", @@ -2353,16 +2374,15 @@ dependencies = [ "lyon_path", "once_cell", "raw-window-handle", - "rustc-hash", + "rustc-hash 2.0.0", "thiserror", "unicode-segmentation", - "xxhash-rust", ] [[package]] name = "iced_renderer" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2373,63 +2393,56 @@ dependencies = [ [[package]] name = "iced_runtime" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ + "bytes", "dnd", "iced_core", "iced_futures", + "raw-window-handle", "thiserror", "window_clipboard", ] -[[package]] -name = "iced_style" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" -dependencies = [ - "iced_core", - "once_cell", - "palette", -] - [[package]] name = "iced_tiny_skia" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "bytemuck", "cosmic-text", "iced_graphics", - "kurbo", + "kurbo 0.10.4", "log", - "resvg", - "rustc-hash", + "resvg 0.42.0", + "rustc-hash 2.0.0", "softbuffer", "tiny-skia", - "xxhash-rust", ] [[package]] name = "iced_wgpu" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "as-raw-xcb-connection", "bitflags 2.6.0", "bytemuck", "futures", "glam", - "glyphon", "guillotiere", + "iced_glyphon", "iced_graphics", "log", "lyon", "once_cell", "raw-window-handle", - "resvg", + "resvg 0.42.0", + "rustc-hash 2.0.0", "rustix", "smithay-client-toolkit", + "thiserror", "tiny-xlib", "wayland-backend", "wayland-client", @@ -2441,15 +2454,16 @@ dependencies = [ [[package]] name = "iced_widget" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "dnd", "iced_renderer", "iced_runtime", - "iced_style", "num-traits", + "once_cell", "ouroboros", + "rustc-hash 2.0.0", "thiserror", "unicode-segmentation", "window_clipboard", @@ -2489,7 +2503,7 @@ dependencies = [ "byteorder", "color_quant", "exr", - "gif 0.13.1", + "gif", "jpeg-decoder", "num-traits", "png", @@ -2609,6 +2623,15 @@ version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2690,7 +2713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.5", + "libloading", "pkg-config", ] @@ -2738,6 +2761,26 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "kurbo" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "kurbo" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2759,7 +2802,7 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "apply", "chrono", @@ -2767,14 +2810,12 @@ dependencies = [ "cosmic-theme", "css-color", "derive_setters", - "fraction", "freedesktop-icons", "iced", "iced_core", "iced_futures", "iced_renderer", "iced_runtime", - "iced_style", "iced_tiny_skia", "iced_widget", "lazy_static", @@ -2789,16 +2830,6 @@ dependencies = [ "ustr", ] -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libloading" version = "0.8.5" @@ -2896,6 +2927,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0b5399f6804fbab912acbd8878ed3532d506b7c951b8f9f164ef90fef39e3f4" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "locale_config" version = "0.3.0" @@ -2953,9 +2990,6 @@ name = "lru" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" -dependencies = [ - "hashbrown 0.14.5", -] [[package]] name = "lyon" @@ -3062,9 +3096,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ "bitflags 2.6.0", "block", @@ -3078,7 +3112,7 @@ dependencies = [ [[package]] name = "mime" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "smithay-clipboard", ] @@ -3141,18 +3175,19 @@ checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" [[package]] name = "naga" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ "arrayvec", "bit-set", "bitflags 2.6.0", + "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", "indexmap 2.3.0", "log", - "num-traits", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror", @@ -3168,7 +3203,7 @@ dependencies = [ "bitflags 2.6.0", "jni-sys", "log", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", "thiserror", @@ -3180,6 +3215,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -3259,76 +3303,12 @@ dependencies = [ "winapi", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -3367,7 +3347,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -3386,7 +3366,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] @@ -3603,15 +3582,6 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - [[package]] name = "objc_id" version = "0.1.1" @@ -3682,9 +3652,9 @@ dependencies = [ [[package]] name = "ouroboros" -version = "0.17.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" dependencies = [ "aliasable", "ouroboros_macro", @@ -3693,15 +3663,16 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.17.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" dependencies = [ "heck", - "proc-macro-error", + "itertools", "proc-macro2", + "proc-macro2-diagnostics", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -3741,7 +3712,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -3840,7 +3811,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -3849,7 +3820,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] @@ -3875,7 +3846,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -4009,13 +3980,26 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "version_check", + "yansi", +] + [[package]] name = "profiling" version = "1.0.15" @@ -4033,7 +4017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -4229,6 +4213,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "reis" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "827073dbe443c57fd72ae05491c6b94213218627ac6ac169850673b0cb7034f1" +dependencies = [ + "calloop 0.14.1", + "rustix", +] + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -4241,15 +4235,28 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" dependencies = [ - "gif 0.12.0", + "log", + "pico-args", + "rgb", + "svgtypes 0.13.0", + "tiny-skia", + "usvg 0.37.0", +] + +[[package]] +name = "resvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051" +dependencies = [ + "gif", "jpeg-decoder", "log", "pico-args", - "png", "rgb", - "svgtypes", + "svgtypes 0.15.2", "tiny-skia", - "usvg", + "usvg 0.42.0", ] [[package]] @@ -4305,7 +4312,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.72", + "syn 2.0.90", "walkdir", ] @@ -4341,6 +4348,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustix" version = "0.38.34" @@ -4360,22 +4373,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" -[[package]] -name = "rustybuzz" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" -dependencies = [ - "bitflags 2.6.0", - "bytemuck", - "smallvec", - "ttf-parser 0.20.0", - "unicode-bidi-mirroring 0.1.0", - "unicode-ccc 0.1.2", - "unicode-properties", - "unicode-script", -] - [[package]] name = "rustybuzz" version = "0.14.1" @@ -4387,8 +4384,8 @@ dependencies = [ "libm", "smallvec", "ttf-parser 0.21.1", - "unicode-bidi-mirroring 0.2.0", - "unicode-ccc 0.2.0", + "unicode-bidi-mirroring", + "unicode-ccc", "unicode-properties", "unicode-script", ] @@ -4471,7 +4468,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -4495,7 +4492,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -4534,7 +4531,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -4598,6 +4595,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "skrifa" version = "0.20.0" @@ -4635,10 +4638,10 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/smithay//smithay?rev=df79eeb#df79eeba63a8e9c2d33b9be2418aee6a940135e7" +source = "git+https://github.com/smithay//smithay?rev=bc1d732#bc1d7320f95cdf17f9e7aa6867cccc5903548032" dependencies = [ "appendlist", - "ash 0.38.0+1.3.281", + "ash", "bitflags 2.6.0", "calloop 0.14.1", "cc", @@ -4656,7 +4659,7 @@ dependencies = [ "indexmap 2.3.0", "input", "libc", - "libloading 0.8.5", + "libloading", "libseat", "once_cell", "pixman", @@ -4845,8 +4848,18 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" dependencies = [ - "kurbo", - "siphasher", + "kurbo 0.9.5", + "siphasher 0.3.11", +] + +[[package]] +name = "svgtypes" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" +dependencies = [ + "kurbo 0.11.1", + "siphasher 1.0.1", ] [[package]] @@ -4873,9 +4886,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -4925,22 +4938,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -5040,7 +5053,7 @@ checksum = "1d52f22673960ad13af14ff4025997312def1223bfa7c8e4949d099e6b3d5d1c" dependencies = [ "as-raw-xcb-connection", "ctor-lite", - "libloading 0.8.5", + "libloading", "pkg-config", "tracing", ] @@ -5125,9 +5138,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -5137,20 +5150,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -5240,7 +5253,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" dependencies = [ - "rustc-hash", + "rustc-hash 1.1.0", ] [[package]] @@ -5306,24 +5319,12 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" -[[package]] -name = "unicode-bidi-mirroring" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" - [[package]] name = "unicode-bidi-mirroring" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" -[[package]] -name = "unicode-ccc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" - [[package]] name = "unicode-ccc" version = "0.2.0" @@ -5421,42 +5422,52 @@ dependencies = [ "log", "pico-args", "usvg-parser", - "usvg-text-layout", "usvg-tree", "xmlwriter", ] [[package]] -name = "usvg-parser" -version = "0.37.0" +name = "usvg" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" dependencies = [ + "base64 0.22.1", "data-url", "flate2", + "fontdb 0.18.0", "imagesize", - "kurbo", + "kurbo 0.11.1", "log", - "roxmltree 0.19.0", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", "simplecss", - "siphasher", - "svgtypes", - "usvg-tree", + "siphasher 1.0.1", + "strict-num", + "svgtypes 0.15.2", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", ] [[package]] -name = "usvg-text-layout" +name = "usvg-parser" version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d383a3965de199d7f96d4e11a44dd859f46e86de7f3dca9a39bf82605da0a37c" +checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" dependencies = [ - "fontdb", - "kurbo", + "data-url", + "flate2", + "imagesize", + "kurbo 0.9.5", "log", - "rustybuzz 0.12.1", - "unicode-bidi", - "unicode-script", - "unicode-vo", + "roxmltree 0.19.0", + "simplecss", + "siphasher 0.3.11", + "svgtypes 0.13.0", "usvg-tree", ] @@ -5468,7 +5479,7 @@ checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" dependencies = [ "rctree", "strict-num", - "svgtypes", + "svgtypes 0.13.0", "tiny-skia-path", ] @@ -5531,7 +5542,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -5565,7 +5576,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5607,9 +5618,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", "rustix", @@ -5651,9 +5662,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.4" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -5690,9 +5701,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" +checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -5715,9 +5726,9 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f18d47038c0b10479e695d99ed073e400ccd9bdbb60e6e503c96f62adcb12b6" +checksum = "c89532cc712a2adb119eb4d09694b402576052254d0bb284f82ac1c47fb786ad" dependencies = [ "bitflags 2.6.0", "downcast-rs", @@ -5749,16 +5760,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web-time" version = "1.1.0" @@ -5777,12 +5778,13 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" dependencies = [ "arrayvec", - "cfg-if", "cfg_aliases 0.1.1", + "document-features", "js-sys", "log", "naga", @@ -5801,14 +5803,15 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", "bit-vec", "bitflags 2.6.0", "cfg_aliases 0.1.1", - "codespan-reporting", + "document-features", "indexmap 2.3.0", "log", "naga", @@ -5816,22 +5819,22 @@ dependencies = [ "parking_lot 0.12.3", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", - "web-sys", "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-hal" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" dependencies = [ "android_system_properties", "arrayvec", - "ash 0.37.3+1.3.251", + "ash", "bit-set", "bitflags 2.6.0", "block", @@ -5847,10 +5850,11 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.5", + "libloading", "log", "metal", "naga", + "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", "parking_lot 0.12.3", @@ -5858,7 +5862,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "wasm-bindgen", @@ -5869,8 +5873,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" dependencies = [ "bitflags 2.6.0", "js-sys", @@ -5917,7 +5922,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window_clipboard" version = "0.4.1" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "clipboard-win", "clipboard_macos", @@ -5979,7 +5984,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -5990,7 +5995,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -6261,7 +6266,7 @@ dependencies = [ "wayland-protocols", "wayland-protocols-plasma", "web-sys", - "web-time 1.1.0", + "web-time", "windows-sys 0.52.0", "x11-dl", "x11rb", @@ -6306,7 +6311,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.5", + "libloading", "once_cell", "rustix", "x11rb-protocol", @@ -6407,10 +6412,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] -name = "xxhash-rust" -version = "0.8.12" +name = "yansi" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yazi" @@ -6465,7 +6470,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "zvariant_utils", ] @@ -6513,7 +6518,7 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -6524,7 +6529,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -6558,7 +6563,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "zvariant_utils", ] @@ -6570,5 +6575,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] diff --git a/Cargo.toml b/Cargo.toml index 57adee5b..6b8e669d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,12 +54,14 @@ wayland-scanner = "0.31.1" xcursor = "0.3.3" xdg = "^2.1" xdg-user = "0.2.1" -xkbcommon = "0.7" +xkbcommon = "0.8" zbus = "4.4.0" profiling = { version = "1.0" } rustix = { version = "0.38.32", features = ["process"] } smallvec = "1.13.2" rand = "0.8.5" +reis = { version = "0.4", features = ["calloop"] } +drm-ffi = "0.8.0" [dependencies.id_tree] branch = "feature/copy_clone" @@ -117,4 +119,4 @@ inherits = "release" lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/smithay//smithay", rev = "df79eeb" } +smithay = { git = "https://github.com/smithay//smithay", rev = "bc1d732" } diff --git a/data/keybindings.ron b/data/keybindings.ron index 986e827c..127f5887 100644 --- a/data/keybindings.ron +++ b/data/keybindings.ron @@ -78,7 +78,9 @@ (modifiers: [Super], key: "slash"): System(Launcher), (modifiers: [Super]): System(Launcher), (modifiers: [Alt], key: "Tab"): System(WindowSwitcher), + (modifiers: [Alt, Shift], key: "Tab"): System(WindowSwitcherPrevious), (modifiers: [Super], key: "Tab"): System(WindowSwitcher), + (modifiers: [Super, Shift], key: "Tab"): System(WindowSwitcherPrevious), (modifiers: [], key: "Print"): System(Screenshot), (modifiers: [], key: "XF86AudioRaiseVolume"): System(VolumeRaise), diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index 5fdd6446..be22b7f9 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - config::{OutputConfig, OutputState}, + config::{AdaptiveSync, OutputConfig, OutputState}, shell::Shell, utils::prelude::*, }; @@ -253,8 +253,6 @@ impl State { self.backend.kms().drm_devices.insert(drm_node, device); } - self.backend.kms().refresh_used_devices()?; - self.common .output_configuration_state .add_heads(wl_outputs.iter()); @@ -267,6 +265,8 @@ impl State { &self.common.xdg_activation_state, self.common.startup_done.clone(), ); + + self.backend.kms().refresh_used_devices()?; self.common.refresh(); Ok(()) @@ -305,12 +305,12 @@ impl State { .cloned() { let surface = device.surfaces.remove(&crtc).unwrap(); - // TODO: move up later outputs? - w -= surface - .output - .current_mode() - .map(|m| m.size.w as u32) - .unwrap_or(0); + if surface.output.mirroring().is_none() { + // TODO: move up later outputs? + w = w.saturating_sub( + surface.output.config().transformed_size().w as u32, + ); + } } if !changes.added.iter().any(|(c, _)| c == &conn) { @@ -349,8 +349,6 @@ impl State { } } - self.backend.kms().refresh_used_devices()?; - self.common .output_configuration_state .remove_heads(outputs_removed.iter()); @@ -376,6 +374,8 @@ impl State { self.common.refresh(); } + self.backend.kms().refresh_used_devices()?; + Ok(()) } @@ -664,6 +664,10 @@ fn populate_modes( max_bpc, scale, transform, + // Try opportunistic VRR by default, + // if not supported this will be turned off on `resume`, + // when we have the `Surface` to actually check for support. + vrr: AdaptiveSync::Enabled, ..std::mem::take(&mut *output_config) }; diff --git a/src/backend/kms/drm_helpers.rs b/src/backend/kms/drm_helpers.rs index 2724a8bd..0b7cdc3b 100644 --- a/src/backend/kms/drm_helpers.rs +++ b/src/backend/kms/drm_helpers.rs @@ -349,40 +349,6 @@ pub fn calculate_refresh_rate(mode: Mode) -> u32 { refresh as u32 } -pub fn supports_vrr(dev: &impl ControlDevice, conn: connector::Handle) -> Result { - get_property_val(dev, conn, "vrr_capable").map(|(val_type, val)| { - match val_type.convert_value(val) { - property::Value::UnsignedRange(res) => res == 1, - property::Value::Boolean(res) => res, - _ => false, - } - }) -} - -pub fn set_vrr( - dev: &impl ControlDevice, - crtc: crtc::Handle, - conn: connector::Handle, - vrr: bool, -) -> Result { - if supports_vrr(dev, conn)? { - dev.set_property( - conn, - get_prop(dev, crtc, "VRR_ENABLED")?, - property::Value::UnsignedRange(if vrr { 1 } else { 0 }).into(), - ) - .map_err(Into::::into) - .and_then(|_| get_property_val(dev, crtc, "VRR_ENABLED")) - .map(|(val_type, val)| match val_type.convert_value(val) { - property::Value::UnsignedRange(vrr) => vrr == 1, - property::Value::Boolean(vrr) => vrr, - _ => false, - }) - } else { - Ok(false) - } -} - pub fn get_max_bpc( dev: &impl ControlDevice, conn: connector::Handle, diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index f1e19c28..b1e54710 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,6 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{config::OutputState, shell::Shell, state::BackendData, utils::prelude::*}; +use crate::{ + config::{AdaptiveSync, OutputState}, + shell::Shell, + state::BackendData, + utils::prelude::*, +}; use anyhow::{Context, Result}; use calloop::LoopSignal; @@ -43,6 +48,7 @@ mod drm_helpers; pub mod render; mod socket; mod surface; +pub(crate) use surface::Surface; use device::*; pub use surface::Timings; @@ -527,7 +533,6 @@ impl KmsState { return Ok(Vec::new()); } - let mut all_outputs = Vec::new(); for device in self.drm_devices.values_mut() { // we only want outputs exposed to wayland - not leased ones // but that is also not all surface, because that doesn't contain all detected, but unmapped outputs @@ -586,7 +591,7 @@ impl KmsState { .flat_map(|encoder_handle| device.drm.get_encoder(*encoder_handle)) { for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { - if !free_crtcs.contains(&crtc) { + if free_crtcs.contains(&crtc) { new_pairings.insert(conn, crtc); break 'outer; } @@ -606,7 +611,34 @@ impl KmsState { } } - // reconfigure existing + // add new ones + let mut w = shell.read().unwrap().global_space().size.w as u32; + if !test_only { + for (conn, crtc) in new_pairings { + let (output, _) = device.connector_added( + self.primary_node.as_ref(), + conn, + Some(crtc), + (w, 0), + loop_handle, + shell.clone(), + startup_done.clone(), + )?; + if output.mirroring().is_none() { + w += output.config().transformed_size().w as u32; + } + } + } + } + + if !test_only { + self.refresh_used_devices() + .context("Failed to enable devices")?; + } + + let mut all_outputs = Vec::new(); + for device in self.drm_devices.values_mut() { + // reconfigure for (crtc, surface) in device.surfaces.iter_mut() { let output_config = surface.output.config(); @@ -636,10 +668,6 @@ impl KmsState { let gbm = device.gbm.clone(); let cursor_size = drm.cursor_size(); - let vrr = drm_helpers::set_vrr(drm, *crtc, conn, output_config.vrr) - .unwrap_or(false); - surface.output.set_adaptive_sync(vrr); - if let Some(bpc) = output_config.max_bpc { if let Err(err) = drm_helpers::set_max_bpc(drm, conn, bpc) { warn!( @@ -651,20 +679,38 @@ impl KmsState { } } + let vrr = output_config.vrr; std::mem::drop(output_config); - surface - .resume(drm_surface, gbm, cursor_size, vrr) - .context("Failed to create surface")?; - } else { - if output_config.vrr != surface.output.adaptive_sync() { - surface.output.set_adaptive_sync(drm_helpers::set_vrr( - drm, - surface.crtc, - surface.connector, - output_config.vrr, - )?); + + match surface.resume(drm_surface, gbm, cursor_size) { + Ok(_) => { + surface.output.set_adaptive_sync_support( + surface.adaptive_sync_support().ok(), + ); + if surface.use_adaptive_sync(vrr)? { + surface.output.set_adaptive_sync(vrr); + } else { + surface.output.config_mut().vrr = AdaptiveSync::Disabled; + surface.output.set_adaptive_sync(AdaptiveSync::Disabled); + } + } + Err(err) => { + surface.output.config_mut().enabled = OutputState::Disabled; + return Err(err).context("Failed to create surface"); + } } + } else { + let vrr = output_config.vrr; std::mem::drop(output_config); + if vrr != surface.output.adaptive_sync() { + if surface.use_adaptive_sync(vrr)? { + surface.output.set_adaptive_sync(vrr); + } else if vrr != AdaptiveSync::Disabled { + anyhow::bail!("Requested VRR mode unsupported"); + } else { + surface.output.set_adaptive_sync(AdaptiveSync::Disabled); + } + } surface .set_mode(*mode) .context("Failed to apply new mode")?; @@ -672,27 +718,19 @@ impl KmsState { } } - // add new ones - let mut w = shell.read().unwrap().global_space().size.w as u32; - if !test_only { - for (conn, crtc) in new_pairings { - let (output, _) = device.connector_added( - self.primary_node.as_ref(), - conn, - Some(crtc), - (0, w), - loop_handle, - shell.clone(), - startup_done.clone(), - )?; - if output.mirroring().is_none() { - w += output.config().transformed_size().w as u32; - } - all_outputs.push(output); - } - } - - all_outputs.extend(outputs); + all_outputs.extend( + device + .outputs + .iter() + .filter(|(conn, _)| { + !device + .leased_connectors + .iter() + .any(|(leased_conn, _)| *conn == leased_conn) + }) + .map(|(_, output)| output.clone()) + .collect::>(), + ); } // we need to handle mirroring, after all outputs have been enabled diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index f03b5c89..2fe8ef05 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -5,6 +5,7 @@ use crate::{ element::{CosmicElement, DamageElement}, init_shaders, workspace_elements, CursorMode, ElementFilter, GlMultiRenderer, CLEAR_COLOR, }, + config::AdaptiveSync, shell::Shell, state::SurfaceDmabufFeedback, utils::{prelude::*, quirks::workspace_overview_is_open}, @@ -27,7 +28,7 @@ use smithay::{ }, drm::{ compositor::{BlitFrameResultError, DrmCompositor, FrameError, PrimaryPlaneElement}, - DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, DrmSurface, + DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, DrmSurface, VrrSupport, }, egl::EGLContext, renderer::{ @@ -67,6 +68,7 @@ use smithay::{ utils::{Buffer as BufferCoords, Clock, Monotonic, Physical, Rectangle, Size, Transform}, wayland::{ dmabuf::{get_dmabuf, DmabufFeedbackBuilder}, + presentation::Refresh, seat::WaylandFocus, shm::{shm_format_to_fourcc, with_buffer_contents}, }, @@ -102,9 +104,9 @@ static NVIDIA_LOGO: &'static [u8] = include_bytes!("../../../../resources/icons/ #[derive(Debug)] pub struct Surface { - pub(super) connector: connector::Handle, - pub(super) crtc: crtc::Handle, - pub(super) output: Output, + pub(crate) connector: connector::Handle, + pub(super) _crtc: crtc::Handle, + pub(crate) output: Output, known_nodes: HashSet, active: Arc, @@ -114,6 +116,8 @@ pub struct Surface { loop_handle: LoopHandle<'static, State>, thread_command: Sender, thread_token: RegistrationToken, + + dpms: bool, } pub struct SurfaceThreadState { @@ -122,6 +126,7 @@ pub struct SurfaceThreadState { primary_node: DrmNode, target_node: DrmNode, active: Arc, + vrr_mode: AdaptiveSync, compositor: Option, state: QueueState, @@ -217,12 +222,11 @@ impl Default for QueueState { #[derive(Debug)] pub enum ThreadCommand { - Suspend, + Suspend(SyncSender<()>), Resume { surface: DrmSurface, gbm: GbmDevice, cursor_size: Size, - vrr: bool, result: SyncSender>, }, NodeAdded { @@ -237,7 +241,10 @@ pub enum ThreadCommand { VBlank(Option), ScheduleRender, SetMode(Mode, SyncSender>), + AdaptiveSyncAvailable(SyncSender>), + UseAdaptiveSync(AdaptiveSync), End, + DpmsOff, } #[derive(Debug)] @@ -341,7 +348,7 @@ impl Surface { Ok(Surface { connector, - crtc, + _crtc: crtc, output: output.clone(), known_nodes: HashSet::new(), active, @@ -350,6 +357,7 @@ impl Surface { loop_handle: evlh.clone(), thread_command: tx, thread_token, + dpms: true, }) } @@ -380,7 +388,9 @@ impl Surface { } pub fn schedule_render(&self) { - let _ = self.thread_command.send(ThreadCommand::ScheduleRender); + if self.dpms { + let _ = self.thread_command.send(ThreadCommand::ScheduleRender); + } } pub fn set_mirroring(&mut self, output: Option) { @@ -395,8 +405,37 @@ impl Surface { rx.recv().context("Surface thread died")? } + pub fn adaptive_sync_support(&self) -> Result { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + let _ = self + .thread_command + .send(ThreadCommand::AdaptiveSyncAvailable(tx)); + rx.recv().context("Surface thread died")? + } + + pub fn use_adaptive_sync(&mut self, vrr: AdaptiveSync) -> Result { + if vrr != AdaptiveSync::Disabled { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + let _ = self + .thread_command + .send(ThreadCommand::AdaptiveSyncAvailable(tx)); + match rx.recv().context("Surface thread died")?? { + VrrSupport::RequiresModeset if vrr == AdaptiveSync::Enabled => return Ok(false), + VrrSupport::NotSupported => return Ok(false), + _ => {} + }; + } + + let _ = self + .thread_command + .send(ThreadCommand::UseAdaptiveSync(vrr)); + Ok(true) + } + pub fn suspend(&mut self) { - let _ = self.thread_command.send(ThreadCommand::Suspend); + let (tx, rx) = std::sync::mpsc::sync_channel(1); + let _ = self.thread_command.send(ThreadCommand::Suspend(tx)); + let _ = rx.recv(); } pub fn resume( @@ -404,7 +443,6 @@ impl Surface { surface: DrmSurface, gbm: GbmDevice, cursor_size: Size, - vrr: bool, ) -> Result<()> { let (tx, rx) = std::sync::mpsc::sync_channel(1); self.plane_formats = surface @@ -425,12 +463,26 @@ impl Surface { surface, gbm, cursor_size, - vrr, result: tx, }); rx.recv().context("Surface thread died")? } + + pub fn get_dpms(&mut self) -> bool { + self.dpms + } + + pub fn set_dpms(&mut self, on: bool) { + if self.dpms != on { + self.dpms = on; + if on { + self.schedule_render(); + } else { + let _ = self.thread_command.send(ThreadCommand::DpmsOff); + } + } + } } impl Drop for Surface { @@ -481,6 +533,7 @@ fn surface_thread( target_node, active, compositor: None, + vrr_mode: AdaptiveSync::Disabled, state: QueueState::Idle, timings: Timings::new(None, false), @@ -502,15 +555,14 @@ fn surface_thread( event_loop .handle() .insert_source(thread_receiver, move |command, _, state| match command { - Event::Msg(ThreadCommand::Suspend) => state.suspend(), + Event::Msg(ThreadCommand::Suspend(tx)) => state.suspend(tx), Event::Msg(ThreadCommand::Resume { surface, gbm, cursor_size, - vrr, result, }) => { - let _ = result.send(state.resume(surface, gbm, cursor_size, vrr)); + let _ = result.send(state.resume(surface, gbm, cursor_size)); } Event::Msg(ThreadCommand::NodeAdded { node, gbm, egl }) => { if let Err(err) = state.node_added(node, gbm, egl) { @@ -540,6 +592,46 @@ fn surface_thread( let _ = result.send(Err(anyhow::anyhow!("Set mode with inactive surface"))); } } + Event::Msg(ThreadCommand::AdaptiveSyncAvailable(result)) => { + if let Some(compositor) = state.compositor.as_mut() { + let _ = result.send( + compositor + .vrr_supported( + compositor.pending_connectors().into_iter().next().unwrap(), + ) + .map_err(Into::into), + ); + } else { + let _ = result.send(Err(anyhow::anyhow!("Set vrr with inactive surface"))); + } + } + Event::Msg(ThreadCommand::UseAdaptiveSync(vrr)) => { + state.vrr_mode = vrr; + } + Event::Msg(ThreadCommand::DpmsOff) => { + if let Some(compositor) = state.compositor.as_mut() { + if let Err(err) = compositor.clear() { + error!("Failed to set DPMS off: {:?}", err); + } + match std::mem::replace(&mut state.state, QueueState::Idle) { + QueueState::Idle => {} + QueueState::Queued(token) + | QueueState::WaitingForEstimatedVBlank(token) => { + state.loop_handle.remove(token); + } + QueueState::WaitingForVBlank { .. } => { + state.timings.discard_current_frame() + } + QueueState::WaitingForEstimatedVBlankAndQueued { + estimated_vblank, + queued_render, + } => { + state.loop_handle.remove(estimated_vblank); + state.loop_handle.remove(queued_render); + } + }; + } + } Event::Closed | Event::Msg(ThreadCommand::End) => { signal.stop(); signal.wakeup(); @@ -552,7 +644,7 @@ fn surface_thread( } impl SurfaceThreadState { - fn suspend(&mut self) { + fn suspend(&mut self, tx: SyncSender<()>) { self.active.store(false, Ordering::SeqCst); let _ = self.compositor.take(); @@ -570,6 +662,8 @@ impl SurfaceThreadState { self.loop_handle.remove(queued_render); } }; + + let _ = tx.send(()); } fn resume( @@ -577,7 +671,6 @@ impl SurfaceThreadState { surface: DrmSurface, gbm: GbmDevice, cursor_size: Size, - vrr: bool, ) -> Result<()> { let driver = surface.get_driver().ok(); let mut planes = surface.planes().clone(); @@ -603,7 +696,6 @@ impl SurfaceThreadState { .set_refresh_interval(Some(Duration::from_secs_f64( 1_000.0 / drm_helpers::calculate_refresh_rate(surface.pending_mode()) as f64, ))); - self.timings.set_vrr(vrr); match DrmCompositor::new( &self.output, @@ -712,15 +804,24 @@ impl SurfaceThreadState { ) }; - feedback.presented( - clock, - self.output - .current_mode() - .map(|mode| Duration::from_secs_f64(1_000.0 / mode.refresh as f64)) - .unwrap_or_default(), - sequence as u64, - flags, - ); + let rate = self + .output + .current_mode() + .map(|mode| Duration::from_secs_f64(1_000.0 / mode.refresh as f64)); + let refresh = match rate { + Some(rate) + if self + .compositor + .as_ref() + .is_some_and(|comp| comp.vrr_enabled()) => + { + Refresh::Variable(rate) + } + Some(rate) => Refresh::Fixed(rate), + None => Refresh::Unknown, + }; + + feedback.presented(clock, refresh, sequence as u64, flags); self.timings.presented(clock); @@ -865,6 +966,11 @@ impl SurfaceThreadState { self.timings.start_render(&self.clock); + let mut vrr = match self.vrr_mode { + AdaptiveSync::Force => true, + _ => false, + }; + let mut elements = { let shell = self.shell.read().unwrap(); let output = self.mirroring.as_ref().unwrap_or(&self.output); @@ -874,6 +980,9 @@ impl SurfaceThreadState { let previous_workspace = previous_workspace .zip(previous_idx) .map(|((w, start), idx)| (w.handle, idx, start)); + if self.vrr_mode == AdaptiveSync::Enabled { + vrr = workspace.get_fullscreen().is_some(); + } let workspace = (workspace.handle, idx); std::mem::drop(shell); @@ -903,6 +1012,7 @@ impl SurfaceThreadState { anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) })? }; + self.timings.set_vrr(vrr); self.timings.elements_done(&self.clock); // we can't use the elements after `compositor.render_frame`, @@ -1058,8 +1168,14 @@ impl SurfaceThreadState { .collect::>(); renderer = self.api.single_renderer(&self.target_node).unwrap(); + if let Err(err) = compositor.use_vrr(false) { + warn!("Unable to set adaptive VRR state: {}", err); + } compositor.render_frame(&mut renderer, &elements, [0.0, 0.0, 0.0, 1.0]) } else { + if let Err(err) = compositor.use_vrr(vrr) { + warn!("Unable to set adaptive VRR state: {}", err); + } compositor.render_frame( &mut renderer, &elements, @@ -1160,13 +1276,23 @@ impl SurfaceThreadState { .into_iter() .flatten(); + // If the screen is rotated, we must convert damage to match output. + let adjusted = damage.iter().copied().map(|mut d| { + d.size = d + .size + .to_logical(1) + .to_buffer(1, output_transform) + .to_logical(1, Transform::Normal) + .to_physical(1); + d + }); match frame_result .blit_frame_result( output_size, output_transform, output_scale, &mut renderer, - damage.iter().copied(), + adjusted, filter, ) .map_err(|err| match err { diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index dfedb3b8..3f8af67e 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -24,7 +24,7 @@ where ::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - Workspace(RelocateRenderElement>), + Workspace(RelocateRenderElement>>), Cursor(RelocateRenderElement>), Dnd(WaylandSurfaceRenderElement), MoveGrab(CosmicMappedRenderElement), @@ -266,13 +266,13 @@ where } } -impl From> for CosmicElement +impl From>> for CosmicElement where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - fn from(elem: WorkspaceRenderElement) -> Self { + fn from(elem: CropRenderElement>) -> Self { Self::Workspace(RelocateRenderElement::from_element( elem, (0, 0), diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 80c78225..aa545068 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -4,6 +4,7 @@ use std::{ borrow::Borrow, cell::RefCell, collections::HashMap, + ops::ControlFlow, sync::{Arc, RwLock, Weak}, time::Instant, }; @@ -14,16 +15,13 @@ use crate::{ backend::{kms::render::gles::GbmGlowBackend, render::element::DamageElement}, shell::{ element::CosmicMappedKey, - focus::target::WindowGroup, + focus::{render_input_order, target::WindowGroup, Stage}, grabs::{SeatMenuGrabState, SeatMoveGrabState}, layout::tiling::ANIMATION_DURATION, - CosmicMappedRenderElement, OverviewMode, SeatExt, SessionLock, Trigger, WorkspaceDelta, + CosmicMappedRenderElement, OverviewMode, SeatExt, Trigger, WorkspaceDelta, WorkspaceRenderElement, }, - utils::{ - prelude::*, - quirks::{workspace_overview_is_open, WORKSPACE_OVERVIEW_NAMESPACE}, - }, + utils::{prelude::*, quirks::workspace_overview_is_open}, wayland::{ handlers::{ data_device::get_dnd_icon, @@ -34,9 +32,7 @@ use crate::{ }; use cosmic::Theme; -use cosmic_comp_config::workspace::WorkspaceLayout; use element::FromGlesError; -use keyframe::{ease, functions::EaseInOutCubic}; use smithay::{ backend::{ allocator::dmabuf::Dmabuf, @@ -46,7 +42,7 @@ use smithay::{ damage::{Error as RenderError, OutputDamageTracker, RenderOutputResult}, element::{ surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement}, - utils::{Relocate, RelocateRenderElement}, + utils::{CropRenderElement, Relocate, RelocateRenderElement}, AsRenderElements, Element, Id, Kind, RenderElement, }, gles::{ @@ -60,13 +56,12 @@ use smithay::{ TextureFilter, }, }, - desktop::{layer_map_for_output, PopupManager}, input::Seat, output::{Output, OutputNoMode}, - utils::{IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Time, Transform}, + utils::{IsAlive, Logical, Monotonic, Point, Rectangle, Scale, Time, Transform}, wayland::{ dmabuf::get_dmabuf, - shell::wlr_layer::Layer, + session_lock::LockSurface, shm::{shm_format_to_fourcc, with_buffer_contents}, }, }; @@ -504,60 +499,6 @@ pub enum ElementFilter { LayerShellOnly, } -#[derive(Clone, Debug)] -pub struct SplitRenderElements { - pub w_elements: Vec, - pub p_elements: Vec, -} - -impl Default for SplitRenderElements { - fn default() -> Self { - Self { - w_elements: Vec::new(), - p_elements: Vec::new(), - } - } -} - -impl SplitRenderElements { - pub fn extend(&mut self, other: Self) { - self.w_elements.extend(other.w_elements); - self.p_elements.extend(other.p_elements); - } - - pub fn extend_map E>(&mut self, other: SplitRenderElements, mut f: F) { - self.w_elements - .extend(other.w_elements.into_iter().map(&mut f)); - self.p_elements - .extend(other.p_elements.into_iter().map(&mut f)); - } - - pub fn join(mut self) -> Vec { - self.p_elements.extend(self.w_elements); - self.p_elements - } -} - -impl SplitRenderElements> -where - R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - CosmicMappedRenderElement: RenderElement, -{ - fn extend_from_workspace_elements>>( - &mut self, - other: SplitRenderElements, - offset: Point, - ) { - self.extend_map(other, |element| { - CosmicElement::Workspace(RelocateRenderElement::from_element( - element.into(), - offset, - Relocate::Relative, - )) - }) - } -} - #[profiling::function] pub fn workspace_elements( _gpu: Option<&DrmNode>, @@ -578,7 +519,7 @@ where CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::new(); let theme = shell.read().unwrap().theme().clone(); let seats = shell @@ -588,8 +529,9 @@ where .iter() .cloned() .collect::>(); + let scale = output.current_scale().fractional_scale(); - elements.p_elements.extend(cursor_elements( + elements.extend(cursor_elements( renderer, seats.iter(), &theme, @@ -602,7 +544,6 @@ where #[cfg(feature = "debug")] { let output_geo = output.geometry(); - let scale = output.current_scale().fractional_scale(); if let Some((state, timings)) = _fps { let debug_active = shell.read().unwrap().debug_active; @@ -621,23 +562,12 @@ where ) .map_err(FromGlesError::from_gles_error) .map_err(RenderError::Rendering)?; - elements.p_elements.push(fps_overlay.into()); + elements.push(fps_overlay.into()); } } let shell = shell.read().unwrap(); - // If session locked, only show session lock surfaces - if let Some(session_lock) = &shell.session_lock { - elements.p_elements.extend( - session_lock_elements(renderer, output, session_lock) - .into_iter() - .map(|x| WorkspaceRenderElement::from(x).into()), - ); - return Ok(elements.join()); - } - - let theme = theme.cosmic(); let overview = shell.overview_mode(); let (resize_mode, resize_indicator) = shell.resize_mode(); let resize_indicator = resize_indicator.map(|indicator| (resize_mode, indicator)); @@ -666,310 +596,252 @@ where .unwrap() .is_some(); let focused_output = last_active_seat.focused_or_active_output(); - let output_size = output.geometry().size; - let output_scale = output.current_scale().fractional_scale(); - let set = shell.workspaces.sets.get(output).ok_or(OutputNoMode)?; let workspace = set .workspaces .iter() .find(|w| w.handle == current.0) .ok_or(OutputNoMode)?; - let is_active_space = workspace.outputs().any(|o| o == &focused_output); - - let has_fullscreen = workspace - .fullscreen - .as_ref() - .filter(|f| !f.is_animating()) - .is_some(); - let overlay_elements = split_layer_elements(renderer, output, Layer::Overlay, element_filter); - - // overlay is above everything - elements - .p_elements - .extend(overlay_elements.p_elements.into_iter().map(Into::into)); - elements - .p_elements - .extend(overlay_elements.w_elements.into_iter().map(Into::into)); - - if !has_fullscreen { - elements.extend_from_workspace_elements( - split_layer_elements(renderer, output, Layer::Top, element_filter), - (0, 0).into(), - ); - }; - + let is_active_space = workspace.output == focused_output; let active_hint = if shell.active_hint { - theme.active_hint as u8 + theme.cosmic().active_hint as u8 } else { 0 }; - // overlay redirect windows - // they need to be over sticky windows, because they could be popups of sticky windows, - // and we can't differenciate that. - if element_filter != ElementFilter::LayerShellOnly { - elements.p_elements.extend( - shell - .override_redirect_windows - .iter() - .filter(|or| { - (*or) - .geometry() - .as_global() - .intersection(workspace.output.geometry()) - .is_some() - }) - .flat_map(|or| { - AsRenderElements::::render_elements::>( - or, - renderer, - (or.geometry().loc - workspace.output.geometry().loc.as_logical()) - .to_physical_precise_round(output_scale), - Scale::from(output_scale), - 1.0, - ) - }) - .map(|p_element| p_element.into()), - ); - } - - // sticky windows - if !has_fullscreen && element_filter != ElementFilter::LayerShellOnly { - let alpha = match &overview.0 { - OverviewMode::Started(_, started) => { - (1.0 - (Instant::now().duration_since(*started).as_millis() - / ANIMATION_DURATION.as_millis()) as f32) - .max(0.0) - * 0.4 - + 0.6 - } - OverviewMode::Ended(_, ended) => { - ((Instant::now().duration_since(*ended).as_millis() - / ANIMATION_DURATION.as_millis()) as f32) - * 0.4 - + 0.6 - } - OverviewMode::Active(_) => 0.6, - OverviewMode::None => 1.0, - }; - - let current_focus = (!move_active && is_active_space) - .then_some(last_active_seat) - .map(|seat| workspace.focus_stack.get(seat)); - - elements.extend_from_workspace_elements( - set.sticky_layer.render( - renderer, - current_focus.as_ref().and_then(|stack| stack.last()), - resize_indicator.clone(), - active_hint, - alpha, - theme, - ), - (0, 0).into(), - ); - } - - let offset = match previous.as_ref() { - Some((previous, previous_idx, start)) => { - let layout = shell.workspaces.layout; + let output_size = output + .geometry() + .size + .as_logical() + .to_physical_precise_round(scale); + let crop_to_output = |element: WorkspaceRenderElement| { + CropRenderElement::from_element( + element.into(), + scale, + Rectangle::from_loc_and_size((0, 0), output_size), + ) + }; - let workspace = shell - .workspaces - .space_for_handle(&previous) - .ok_or(OutputNoMode)?; - let has_fullscreen = workspace.fullscreen.is_some(); - let is_active_space = workspace.outputs().any(|o| o == &focused_output); - - let percentage = match start { - WorkspaceDelta::Shortcut(st) => ease( - EaseInOutCubic, - 0.0, - 1.0, - Instant::now().duration_since(*st).as_millis() as f32 - / ANIMATION_DURATION.as_millis() as f32, - ), - WorkspaceDelta::Gesture(prog) => *prog as f32, - WorkspaceDelta::GestureEnd(st, spring) => { - (spring.value_at(Instant::now().duration_since(*st)) as f32).clamp(0.0, 1.0) + render_input_order( + &*shell, + output, + previous, + current, + element_filter, + |stage| { + match stage { + Stage::SessionLock(lock_surface) => { + elements.extend( + session_lock_elements(renderer, output, lock_surface) + .into_iter() + .map(Into::into) + .flat_map(crop_to_output) + .map(Into::into), + ); } - }; - let offset = Point::::from(match (layout, *previous_idx < current.1) { - (WorkspaceLayout::Vertical, true) => { - (0, (-output_size.h as f32 * percentage).round() as i32) + Stage::LayerPopup { + popup, location, .. + } => { + elements.extend( + render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( + renderer, + popup.wl_surface(), + location + .to_local(output) + .as_logical() + .to_physical_precise_round(scale), + Scale::from(scale), + 1.0, + Kind::Unspecified, + ) + .into_iter() + .flat_map(crop_to_output) + .map(Into::into), + ); } - (WorkspaceLayout::Vertical, false) => { - (0, (output_size.h as f32 * percentage).round() as i32) + Stage::LayerSurface { layer, location } => { + elements.extend( + render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( + renderer, + &layer.wl_surface(), + location + .to_local(output) + .as_logical() + .to_physical_precise_round(scale), + Scale::from(scale), + 1.0, + Kind::Unspecified, + ) + .into_iter() + .flat_map(crop_to_output) + .map(Into::into), + ); } - (WorkspaceLayout::Horizontal, true) => { - ((-output_size.w as f32 * percentage).round() as i32, 0) + Stage::OverrideRedirect { surface, location } => { + elements.extend( + AsRenderElements::::render_elements::>( + surface, + renderer, + location + .to_local(output) + .as_logical() + .to_physical_precise_round(scale), + Scale::from(scale), + 1.0, + ) + .into_iter() + .flat_map(crop_to_output) + .map(Into::into), + ); } - (WorkspaceLayout::Horizontal, false) => { - ((output_size.w as f32 * percentage).round() as i32, 0) + Stage::StickyPopups(layout) => { + let alpha = match &overview.0 { + OverviewMode::Started(_, started) => { + (1.0 - (Instant::now().duration_since(*started).as_millis() + / ANIMATION_DURATION.as_millis()) + as f32) + .max(0.0) + * 0.4 + + 0.6 + } + OverviewMode::Ended(_, ended) => { + ((Instant::now().duration_since(*ended).as_millis() + / ANIMATION_DURATION.as_millis()) + as f32) + * 0.4 + + 0.6 + } + OverviewMode::Active(_) => 0.6, + OverviewMode::None => 1.0, + }; + + elements.extend( + layout + .render_popups(renderer, alpha) + .into_iter() + .map(Into::into) + .flat_map(crop_to_output) + .map(Into::into), + ); } - }); - - elements.extend_from_workspace_elements( - workspace - .render::( - renderer, - (!move_active && is_active_space).then_some(last_active_seat), - overview.clone(), - resize_indicator.clone(), - active_hint, - theme, + Stage::Sticky(layout) => { + let alpha = match &overview.0 { + OverviewMode::Started(_, started) => { + (1.0 - (Instant::now().duration_since(*started).as_millis() + / ANIMATION_DURATION.as_millis()) + as f32) + .max(0.0) + * 0.4 + + 0.6 + } + OverviewMode::Ended(_, ended) => { + ((Instant::now().duration_since(*ended).as_millis() + / ANIMATION_DURATION.as_millis()) + as f32) + * 0.4 + + 0.6 + } + OverviewMode::Active(_) => 0.6, + OverviewMode::None => 1.0, + }; + + let current_focus = (!move_active && is_active_space) + .then_some(last_active_seat) + .map(|seat| workspace.focus_stack.get(seat)); + + elements.extend( + layout + .render( + renderer, + current_focus.as_ref().and_then(|stack| stack.last()), + resize_indicator.clone(), + active_hint, + alpha, + &theme.cosmic(), + ) + .into_iter() + .map(Into::into) + .flat_map(crop_to_output) + .map(Into::into), ) - .map_err(|_| OutputNoMode)?, - offset.to_physical_precise_round(output_scale), - ); - - if !has_fullscreen { - elements.extend_from_workspace_elements( - background_layer_elements(renderer, output, element_filter), - offset.to_physical_precise_round(output_scale), - ); - } - - Point::::from(match (layout, *previous_idx < current.1) { - (WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y), - (WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)), - (WorkspaceLayout::Horizontal, true) => (output_size.w + offset.x, 0), - (WorkspaceLayout::Horizontal, false) => (-(output_size.w - offset.x), 0), - }) - } - None => (0, 0).into(), - }; - - if element_filter != ElementFilter::LayerShellOnly { - elements.extend_from_workspace_elements( - workspace - .render::( - renderer, - (!move_active && is_active_space).then_some(&last_active_seat), - overview, - resize_indicator, - active_hint, - theme, - ) - .map_err(|_| OutputNoMode)?, - offset.to_physical_precise_round(output_scale), - ); - } - - if !has_fullscreen { - elements.extend_from_workspace_elements( - background_layer_elements(renderer, output, element_filter), - offset.to_physical_precise_round(output_scale), - ); - } - - Ok(elements.join()) -} - -pub fn split_layer_elements( - renderer: &mut R, - output: &Output, - layer: Layer, - element_filter: ElementFilter, -) -> SplitRenderElements> -where - R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - ::TextureId: Clone + 'static, - ::Error: FromGlesError, - CosmicMappedRenderElement: RenderElement, - WorkspaceRenderElement: RenderElement, -{ - let layer_map = layer_map_for_output(output); - let output_scale = output.current_scale().fractional_scale(); - - let mut elements = SplitRenderElements::default(); - - layer_map - .layers_on(layer) - .rev() - .filter(|s| { - !(element_filter == ElementFilter::ExcludeWorkspaceOverview - && s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE) - }) - .filter_map(|surface| { - layer_map - .layer_geometry(surface) - .map(|geo| (geo.loc, surface)) - }) - .for_each(|(location, surface)| { - let location = location.to_physical_precise_round(output_scale); - let surface = surface.wl_surface(); - let scale = Scale::from(output_scale); - - elements - .p_elements - .extend(PopupManager::popups_for_surface(surface).flat_map( - |(popup, popup_offset)| { - let offset = (popup_offset - popup.geometry().loc) - .to_f64() - .to_physical(scale) - .to_i32_round(); - - render_elements_from_surface_tree( + } + Stage::WorkspacePopups { workspace, offset } => { + elements.extend( + match workspace.render_popups( renderer, - popup.wl_surface(), - location + offset, - scale, - 1.0, - Kind::Unspecified, - ) - }, - )); - - elements - .w_elements - .extend(render_elements_from_surface_tree( - renderer, - surface, - location, - scale, - 1.0, - Kind::Unspecified, - )); - }); + (!move_active && is_active_space).then_some(last_active_seat), + overview.clone(), + &theme.cosmic(), + ) { + Ok(elements) => { + elements + .into_iter() + .flat_map(crop_to_output) + .map(|element| { + CosmicElement::Workspace( + RelocateRenderElement::from_element( + element, + offset.to_physical_precise_round(scale), + Relocate::Relative, + ), + ) + }) + } + Err(_) => { + return ControlFlow::Break(Err(OutputNoMode)); + } + }, + ); + } + Stage::Workspace { workspace, offset } => { + elements.extend( + match workspace.render( + renderer, + (!move_active && is_active_space).then_some(last_active_seat), + overview.clone(), + resize_indicator.clone(), + active_hint, + &theme.cosmic(), + ) { + Ok(elements) => { + elements + .into_iter() + .flat_map(crop_to_output) + .map(|element| { + CosmicElement::Workspace( + RelocateRenderElement::from_element( + element, + offset.to_physical_precise_round(scale), + Relocate::Relative, + ), + ) + }) + } + Err(_) => { + return ControlFlow::Break(Err(OutputNoMode)); + } + }, + ); + } + }; - elements -} + ControlFlow::Continue(()) + }, + )?; -// bottom and background layer surfaces -pub fn background_layer_elements( - renderer: &mut R, - output: &Output, - element_filter: ElementFilter, -) -> SplitRenderElements> -where - R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - ::TextureId: Clone + 'static, - ::Error: FromGlesError, - CosmicMappedRenderElement: RenderElement, - WorkspaceRenderElement: RenderElement, -{ - let mut elements = split_layer_elements(renderer, output, Layer::Bottom, element_filter); - elements.extend(split_layer_elements( - renderer, - output, - Layer::Background, - element_filter, - )); - elements + Ok(elements) } fn session_lock_elements( renderer: &mut R, output: &Output, - session_lock: &SessionLock, + lock_surface: Option<&LockSurface>, ) -> Vec> where R: Renderer + ImportAll, ::TextureId: Clone + 'static, { - if let Some(surface) = session_lock.surfaces.get(output) { + if let Some(surface) = lock_surface { let scale = Scale::from(output.current_scale().fractional_scale()); render_elements_from_surface_tree( renderer, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 1bf3f527..a1a2bdce 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -29,7 +29,7 @@ use smithay::{ winit::platform::pump_events::PumpStatus, }, utils::Transform, - wayland::dmabuf::DmabufFeedbackBuilder, + wayland::{dmabuf::DmabufFeedbackBuilder, presentation::Refresh}, }; use std::{borrow::BorrowMut, cell::RefCell, time::Duration}; use tracing::{error, info, warn}; @@ -84,8 +84,12 @@ impl WinitState { state.clock.now(), self.output .current_mode() - .map(|mode| Duration::from_secs_f64(1_000.0 / mode.refresh as f64)) - .unwrap_or_default(), + .map(|mode| { + Refresh::Fixed(Duration::from_secs_f64( + 1_000.0 / mode.refresh as f64, + )) + }) + .unwrap_or(Refresh::Unknown), 0, wp_presentation_feedback::Kind::Vsync, ); diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 75096edd..ba33c14b 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -36,7 +36,7 @@ use smithay::{ wayland_server::DisplayHandle, }, utils::{DeviceFd, Transform}, - wayland::dmabuf::DmabufFeedbackBuilder, + wayland::{dmabuf::DmabufFeedbackBuilder, presentation::Refresh}, }; use std::{borrow::BorrowMut, cell::RefCell, os::unix::io::OwnedFd, time::Duration}; use tracing::{debug, error, info, warn}; @@ -234,8 +234,12 @@ impl Surface { state.clock.now(), self.output .current_mode() - .map(|mode| Duration::from_secs_f64(1_000.0 / mode.refresh as f64)) - .unwrap_or_default(), + .map(|mode| { + Refresh::Fixed(Duration::from_secs_f64( + 1_000.0 / mode.refresh as f64, + )) + }) + .unwrap_or(Refresh::Unknown), 0, wp_presentation_feedback::Kind::Vsync, ) diff --git a/src/config/mod.rs b/src/config/mod.rs index 77db2951..f8bd9a2f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -98,19 +98,34 @@ pub enum OutputState { Mirroring(String), } -fn default_enabled() -> OutputState { +fn default_state() -> OutputState { OutputState::Enabled } +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum AdaptiveSync { + #[serde(rename = "true")] + Enabled, + #[serde(rename = "false")] + Disabled, + Force, +} + +fn default_sync() -> AdaptiveSync { + AdaptiveSync::Enabled +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct OutputConfig { pub mode: ((i32, i32), Option), - pub vrr: bool, + #[serde(default = "default_sync")] + pub vrr: AdaptiveSync, pub scale: f64, #[serde(with = "TransformDef")] pub transform: Transform, pub position: (u32, u32), - #[serde(default = "default_enabled")] + #[serde(default = "default_state")] pub enabled: OutputState, #[serde(default, skip_serializing_if = "Option::is_none")] pub max_bpc: Option, @@ -120,7 +135,7 @@ impl Default for OutputConfig { fn default() -> OutputConfig { OutputConfig { mode: ((0, 0), None), - vrr: false, + vrr: AdaptiveSync::Enabled, scale: 1.0, transform: Transform::Normal, position: (0, 0), @@ -640,6 +655,7 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut } } } + state.common.atspi_ei.update_keymap(value.clone()); state.common.config.cosmic_conf.xkb_config = value; } "input_default" => { diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs index cea8c261..13b2bcc5 100644 --- a/src/dbus/mod.rs +++ b/src/dbus/mod.rs @@ -1,6 +1,8 @@ -use crate::state::{BackendData, State}; +use crate::state::{BackendData, Common, State}; use anyhow::{Context, Result}; use calloop::{InsertError, LoopHandle, RegistrationToken}; +use std::collections::HashMap; +use zbus::blocking::{fdo::DBusProxy, Connection}; mod power; @@ -64,3 +66,24 @@ pub fn init(evlh: &LoopHandle<'static, State>) -> Result> Ok(tokens) } + +/// 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(String::new()), + ), + ]))?; + + Ok(()) +} diff --git a/src/input/mod.rs b/src/input/mod.rs index 846fae87..8b40908e 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ + backend::render::ElementFilter, config::{ key_bindings::{ cosmic_keystate_from_smithay, cosmic_modifiers_eq_smithay, @@ -10,7 +11,11 @@ use crate::{ }, input::gestures::{GestureState, SwipeAction}, shell::{ - focus::target::{KeyboardFocusTarget, PointerFocusTarget}, + focus::{ + render_input_order, + target::{KeyboardFocusTarget, PointerFocusTarget}, + Stage, + }, grabs::{ReleaseMode, ResizeEdge}, layout::{ floating::ResizeGrabMarker, @@ -39,10 +44,7 @@ use smithay::{ TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent, }, - desktop::{ - layer_map_for_output, space::SpaceElement, utils::under_from_surface_tree, - WindowSurfaceType, - }, + desktop::{utils::under_from_surface_tree, WindowSurfaceType}, input::{ keyboard::{FilterResult, KeysymHandle, ModifiersState}, pointer::{ @@ -58,15 +60,13 @@ use smithay::{ reexports::{ input::Device as InputDevice, wayland_server::protocol::wl_shm::Format as ShmFormat, }, - utils::{Point, Serial, SERIAL_COUNTER}, + utils::{Point, Rectangle, Serial, SERIAL_COUNTER}, wayland::{ keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, pointer_constraints::{with_pointer_constraint, PointerConstraint}, seat::WaylandFocus, - shell::wlr_layer::Layer as WlrLayer, tablet_manager::{TabletDescriptor, TabletSeatTrait}, }, - xwayland::X11Surface, }; use tracing::{error, trace}; use xkbcommon::xkb::{Keycode, Keysym}; @@ -76,6 +76,7 @@ use std::{ borrow::Cow, cell::RefCell, collections::HashSet, + ops::ControlFlow, time::{Duration, Instant}, }; @@ -163,6 +164,8 @@ impl State { where ::Device: 'static, { + crate::wayland::handlers::output_power::set_all_surfaces_dpms_on(self); + use smithay::backend::input::Event; match event { InputEvent::DeviceAdded { device } => { @@ -329,9 +332,8 @@ impl State { } else if self.common.config.cosmic_conf.focus_follows_cursor { let shell = self.common.shell.read().unwrap(); let old_keyboard_target = - shell.keyboard_target_from_position(original_position, ¤t_output); - let new_keyboard_target = - shell.keyboard_target_from_position(position, &output); + State::element_under(original_position, ¤t_output, &*shell); + let new_keyboard_target = State::element_under(position, &output, &*shell); if old_keyboard_target != new_keyboard_target && new_keyboard_target.is_some() @@ -497,7 +499,8 @@ impl State { }); } - let shell = self.common.shell.read().unwrap(); + let mut shell = self.common.shell.write().unwrap(); + shell.update_pointer_position(position.to_local(&output), &output); if output != current_output { for session in cursor_sessions_for_output(&*shell, ¤t_output) { @@ -638,15 +641,15 @@ impl State { let global_position = seat.get_pointer().unwrap().current_location().as_global(); - let shell = self.common.shell.write().unwrap(); - let under = shell.keyboard_target_from_position(global_position, &output); - // Don't check override redirect windows, because we don't set keyboard focus to them explicitly. - // These cases are handled by the XwaylandKeyboardGrab. - if let Some(target) = shell.element_under(global_position, &output) { - if seat.get_keyboard().unwrap().modifier_state().logo - && !shortcuts_inhibited - { - if let Some(surface) = target.toplevel().map(Cow::into_owned) { + let under = { + let shell = self.common.shell.read().unwrap(); + State::element_under(global_position, &output, &shell) + }; + if let Some(target) = under { + if let Some(surface) = target.toplevel().map(Cow::into_owned) { + if seat.get_keyboard().unwrap().modifier_state().logo + && !shortcuts_inhibited + { let seat_clone = seat.clone(); let mouse_button = PointerButtonEvent::button(&event); @@ -754,10 +757,9 @@ impl State { } } } - } - std::mem::drop(shell); - Shell::set_focus(self, under.as_ref(), &seat, Some(serial), false); + Shell::set_focus(self, Some(&target), &seat, Some(serial), false); + } } } else { let mut shell = self.common.shell.write().unwrap(); @@ -1141,13 +1143,7 @@ impl State { return; }; - let geometry = output.geometry(); - - let position = geometry.loc.to_f64() - + event - .position_transformed(geometry.size.as_logical()) - .as_global(); - + let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1178,13 +1174,7 @@ impl State { return; }; - let geometry = output.geometry(); - - let position = geometry.loc.to_f64() - + event - .position_transformed(geometry.size.as_logical()) - .as_global(); - + let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1267,13 +1257,8 @@ impl State { else { return; }; - let geometry = output.geometry(); - - let position = event - .position_transformed(geometry.size.as_logical()) - .as_global() - + geometry.loc.to_f64(); + let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1336,13 +1321,8 @@ impl State { else { return; }; - let geometry = output.geometry(); - - let position = event - .position_transformed(geometry.size.as_logical()) - .as_global() - + geometry.loc.to_f64(); + let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1462,6 +1442,13 @@ impl State { .unwrap_or(false) }); + self.common.atspi_ei.input( + modifiers, + &handle, + event.state(), + event.time() as u64 * 1000, + ); + // Leave move overview mode, if any modifier was released if let Some(Trigger::KeyboardMove(action_modifiers)) = shell.overview_mode().0.active_trigger() @@ -1623,6 +1610,57 @@ impl State { ))); } + if event.state() == KeyState::Released { + let removed = self + .common + .atspi_ei + .active_virtual_mods + .remove(&event.key_code()); + // If `Caps_Lock` is a virtual modifier, and is in locked state, clear it + if removed && handle.modified_sym() == Keysym::Caps_Lock { + if (modifiers.serialized.locked & 2) != 0 { + let serial = SERIAL_COUNTER.next_serial(); + let time = self.common.clock.now().as_millis(); + keyboard.input( + self, + event.key_code(), + KeyState::Pressed, + serial, + time, + |_, _, _| FilterResult::<()>::Forward, + ); + let serial = SERIAL_COUNTER.next_serial(); + keyboard.input( + self, + event.key_code(), + KeyState::Released, + serial, + time, + |_, _, _| FilterResult::<()>::Forward, + ); + } + } + } else if event.state() == KeyState::Pressed + && self + .common + .atspi_ei + .virtual_mods + .contains(&event.key_code()) + { + self.common + .atspi_ei + .active_virtual_mods + .insert(event.key_code()); + + tracing::debug!( + "active virtual mods: {:?}", + self.common.atspi_ei.active_virtual_mods + ); + seat.supressed_keys().add(&handle, None); + + return FilterResult::Intercept(None); + } + // Skip released events for initially surpressed keys if event.state() == KeyState::Released { if let Some(tokens) = seat.supressed_keys().filter(&handle) { @@ -1647,6 +1685,15 @@ impl State { return FilterResult::Intercept(None); } + if self.common.atspi_ei.has_keyboard_grab() + || self + .common + .atspi_ei + .has_key_grab(modifiers.serialized.layout_effective, event.key_code()) + { + return FilterResult::Intercept(None); + } + // handle the rest of the global shortcuts let mut clear_queue = true; if !shortcuts_inhibited { @@ -1814,142 +1861,263 @@ impl State { } } - // TODO: Try to get rid of the *mutable* Shell references (needed for hovered_stack in floating_layout) + pub fn element_under( + global_pos: Point, + output: &Output, + shell: &Shell, + ) -> Option { + let (previous_workspace, workspace) = shell.workspaces.active(output); + let (previous_idx, idx) = shell.workspaces.active_num(output); + let previous_workspace = previous_workspace + .zip(previous_idx) + .map(|((w, start), idx)| (w.handle, idx, start)); + let workspace = (workspace.handle, idx); + let element_filter = if workspace_overview_is_open(output) { + ElementFilter::LayerShellOnly + } else { + ElementFilter::All + }; + + render_input_order( + shell, + output, + previous_workspace, + workspace, + element_filter, + |stage| { + match stage { + Stage::SessionLock(lock_surface) => { + return ControlFlow::Break(Ok(lock_surface + .cloned() + .map(KeyboardFocusTarget::LockSurface))) + } + Stage::LayerPopup { + layer, + popup, + location, + } => { + if layer.can_receive_keyboard_focus() { + let surface = popup.wl_surface(); + if under_from_surface_tree( + surface, + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { + return ControlFlow::Break(Ok(Some( + KeyboardFocusTarget::LayerSurface(layer), + ))); + } + } + } + Stage::LayerSurface { layer, location } => { + if layer.can_receive_keyboard_focus() { + if under_from_surface_tree( + layer.wl_surface(), + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { + return ControlFlow::Break(Ok(Some( + KeyboardFocusTarget::LayerSurface(layer), + ))); + } + } + } + Stage::OverrideRedirect { .. } => { + // Override redirect windows take a grab on their own via + // the Xwayland keyboard grab protocol. Don't focus them via click. + } + Stage::StickyPopups(layout) => { + if let Some(element) = + layout.popup_element_under(global_pos.to_local(output)) + { + return ControlFlow::Break(Ok(Some(element))); + } + } + Stage::Sticky(layout) => { + if let Some(element) = + layout.toplevel_element_under(global_pos.to_local(output)) + { + return ControlFlow::Break(Ok(Some(element))); + } + } + Stage::WorkspacePopups { workspace, offset } => { + let location = global_pos + offset.as_global().to_f64(); + let output = workspace.output(); + let output_geo = output.geometry().to_local(output); + if Rectangle::from_loc_and_size(offset.as_local(), output_geo.size) + .intersection(output_geo) + .is_some_and(|geometry| { + geometry.contains(global_pos.to_local(output).to_i32_round()) + }) + { + if let Some(element) = workspace.popup_element_under(location) { + return ControlFlow::Break(Ok(Some(element))); + } + } + } + Stage::Workspace { workspace, offset } => { + let location = global_pos + offset.as_global().to_f64(); + let output = workspace.output(); + let output_geo = output.geometry().to_local(output); + if Rectangle::from_loc_and_size(offset.as_local(), output_geo.size) + .intersection(output_geo) + .is_some_and(|geometry| { + geometry.contains(global_pos.to_local(output).to_i32_round()) + }) + { + if let Some(element) = workspace.toplevel_element_under(location) { + return ControlFlow::Break(Ok(Some(element))); + } + } + } + } + ControlFlow::Continue(()) + }, + ) + .ok() + .flatten() + } + pub fn surface_under( global_pos: Point, output: &Output, - shell: &mut Shell, + shell: &Shell, ) -> Option<(PointerFocusTarget, Point)> { - let session_lock = shell.session_lock.as_ref(); + let (previous_workspace, workspace) = shell.workspaces.active(output); + let (previous_idx, idx) = shell.workspaces.active_num(output); + let previous_workspace = previous_workspace + .zip(previous_idx) + .map(|((w, start), idx)| (w.handle, idx, start)); + let workspace = (workspace.handle, idx); + + let element_filter = if workspace_overview_is_open(output) { + ElementFilter::LayerShellOnly + } else { + ElementFilter::All + }; + let relative_pos = global_pos.to_local(output); let output_geo = output.geometry(); - - if let Some(session_lock) = session_lock { - return session_lock.surfaces.get(output).map(|surface| { - ( - PointerFocusTarget::WlSurface { - surface: surface.wl_surface().clone(), - toplevel: None, - }, - output_geo.loc.to_f64(), - ) - }); - } - - if let Some(window) = shell.workspaces.active(output).1.get_fullscreen() { - let layers = layer_map_for_output(output); - if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos.as_logical()) { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if let Some((wl_surface, surface_loc)) = layer.surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) { - return Some(( - PointerFocusTarget::WlSurface { - surface: wl_surface, - toplevel: None, - }, - (output_geo.loc + layer_loc.as_global() + surface_loc.as_global()).to_f64(), - )); - } - } - if let Some((surface, geo)) = shell - .override_redirect_windows - .iter() - .find(|or| { - or.is_in_input_region( - &(global_pos.as_logical() - X11Surface::geometry(*or).loc.to_f64()), - ) - }) - .and_then(|or| { - or.wl_surface() - .map(|surface| (surface, X11Surface::geometry(or).loc.as_global().to_f64())) - }) - { - return Some(( - PointerFocusTarget::WlSurface { - surface, - toplevel: None, - }, - geo, - )); - } - PointerFocusTarget::under_surface(window, relative_pos.as_logical()).map( - |(target, surface_loc)| { - (target, (output_geo.loc + surface_loc.as_global()).to_f64()) - }, - ) - } else { - { - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, relative_pos.as_logical()) - .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos.as_logical())) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if let Some((wl_surface, surface_loc)) = layer.surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) { - return Some(( - PointerFocusTarget::WlSurface { - surface: wl_surface, - toplevel: None, - }, - (output_geo.loc + layer_loc.as_global() + surface_loc.as_global()) - .to_f64(), - )); + let overview = shell.overview_mode().0; + + render_input_order( + shell, + output, + previous_workspace, + workspace, + element_filter, + |stage| { + match stage { + Stage::SessionLock(lock_surface) => { + return ControlFlow::Break(Ok(lock_surface.map(|surface| { + ( + PointerFocusTarget::WlSurface { + surface: surface.wl_surface().clone(), + toplevel: None, + }, + output_geo.loc.to_f64(), + ) + }))); } - } - } - if let Some((surface, geo)) = shell - .override_redirect_windows - .iter() - .find(|or| { - or.is_in_input_region( - &(global_pos.as_logical() - X11Surface::geometry(*or).loc.to_f64()), - ) - }) - .and_then(|or| { - or.wl_surface() - .map(|surface| (surface, X11Surface::geometry(or).loc.as_global().to_f64())) - }) - { - return Some(( - PointerFocusTarget::WlSurface { - surface, - toplevel: None, - }, - geo, - )); - } - if let Some((target, loc)) = shell.surface_under(global_pos, output) { - return Some((target, loc)); - } - { - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Bottom, relative_pos.as_logical()) - .or_else(|| layers.layer_under(WlrLayer::Background, relative_pos.as_logical())) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if let Some((wl_surface, surface_loc)) = layer.surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) { - return Some(( - PointerFocusTarget::WlSurface { - surface: wl_surface, - toplevel: None, - }, - (output_geo.loc + layer_loc.as_global() + surface_loc.as_global()) - .to_f64(), - )); + Stage::LayerPopup { + popup, location, .. + } => { + let surface = popup.wl_surface(); + if let Some((surface, surface_loc)) = under_from_surface_tree( + surface, + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::ALL, + ) { + return ControlFlow::Break(Ok(Some(( + PointerFocusTarget::WlSurface { + surface, + toplevel: None, + }, + surface_loc.as_global().to_f64(), + )))); + } + } + Stage::LayerSurface { layer, location } => { + let surface = layer.wl_surface(); + if let Some((surface, surface_loc)) = under_from_surface_tree( + surface, + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::ALL, + ) { + return ControlFlow::Break(Ok(Some(( + PointerFocusTarget::WlSurface { + surface, + toplevel: None, + }, + surface_loc.as_global().to_f64(), + )))); + } + } + Stage::OverrideRedirect { surface, location } => { + if let Some(surface) = surface.wl_surface() { + if let Some((surface, surface_loc)) = under_from_surface_tree( + &surface, + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::ALL, + ) { + return ControlFlow::Break(Ok(Some(( + PointerFocusTarget::WlSurface { + surface, + toplevel: None, + }, + surface_loc.as_global().to_f64(), + )))); + } + } + } + Stage::StickyPopups(floating_layer) => { + if let Some(under) = floating_layer + .popup_surface_under(relative_pos) + .map(|(target, point)| (target, point.to_global(output))) + { + return ControlFlow::Break(Ok(Some(under))); + } + } + Stage::Sticky(floating_layer) => { + if let Some(under) = floating_layer + .toplevel_surface_under(relative_pos) + .map(|(target, point)| (target, point.to_global(output))) + { + return ControlFlow::Break(Ok(Some(under))); + } + } + Stage::WorkspacePopups { workspace, offset } => { + let global_pos = global_pos + offset.to_f64().as_global(); + if let Some(under) = + workspace.popup_surface_under(global_pos, overview.clone()) + { + return ControlFlow::Break(Ok(Some(under))); + } + } + Stage::Workspace { workspace, offset } => { + let global_pos = global_pos + offset.to_f64().as_global(); + if let Some(under) = + workspace.toplevel_surface_under(global_pos, overview.clone()) + { + return ControlFlow::Break(Ok(Some(under))); + } } } - } - None - } + + ControlFlow::Continue(()) + }, + ) + .ok() + .flatten() } } @@ -1971,6 +2139,23 @@ fn cursor_sessions_for_output( .chain(output.cursor_sessions().into_iter()) } +fn transform_output_mapped_position<'a, B, E>(output: &Output, event: &E) -> Point +where + B: InputBackend, + E: AbsolutePositionEvent, + B::Device: 'static, +{ + let geometry = output.geometry(); + let transform = output.current_transform(); + let size = transform + .invert() + .transform_size(geometry.size.as_logical()); + geometry.loc.to_f64() + + transform + .transform_point_in(event.position_transformed(size), &size.to_f64()) + .as_global() +} + // TODO Is it possible to determine mapping for external touchscreen? // Support map_to_region like sway? fn mapped_output_for_device<'a, D: Device + 'static>( diff --git a/src/main.rs b/src/main.rs index 4c9c4860..43b8a698 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use anyhow::{Context, Result}; use state::State; use std::{env, ffi::OsString, os::unix::process::CommandExt, process, sync::Arc}; use tracing::{error, info, warn}; +use wayland::protocols::overlap_notify::OverlapNotifyState; use crate::wayland::handlers::compositor::client_compositor_state; @@ -44,9 +45,13 @@ impl State { // into systemd and the session? self.ready.call_once(|| { // potentially tell systemd we are setup now - #[cfg(feature = "systemd")] if let state::BackendData::Kms(_) = &self.backend { + #[cfg(feature = "systemd")] systemd::ready(&self.common); + #[cfg(not(feature = "systemd"))] + if let Err(err) = dbus::ready(&self.common) { + error!(?err, "Failed to update the D-Bus activation environment"); + } } // potentially tell the session we are setup now @@ -131,6 +136,7 @@ fn main() -> Result<()> { } state.common.refresh(); state::Common::refresh_focus(state); + OverlapNotifyState::refresh(state); state.common.update_x11_stacking_order(); { diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 4a2cfb67..2a8cebab 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -1,8 +1,5 @@ use crate::{ - backend::render::{ - element::{AsGlowRenderer, FromGlesError}, - SplitRenderElements, - }, + backend::render::element::{AsGlowRenderer, FromGlesError}, state::State, utils::{iced::IcedElementInternal, prelude::*}, }; @@ -305,10 +302,11 @@ impl CosmicMapped { pub fn focus_under( &self, relative_pos: Point, + surface_type: WindowSurfaceType, ) -> Option<(PointerFocusTarget, Point)> { match &self.element { - CosmicMappedInternal::Stack(stack) => stack.focus_under(relative_pos), - CosmicMappedInternal::Window(window) => window.focus_under(relative_pos), + CosmicMappedInternal::Stack(stack) => stack.focus_under(relative_pos, surface_type), + CosmicMappedInternal::Window(window) => window.focus_under(relative_pos, surface_type), _ => unreachable!(), } } @@ -657,13 +655,42 @@ impl CosmicMapped { } } - pub fn split_render_elements( + pub fn popup_render_elements( &self, renderer: &mut R, location: smithay::utils::Point, scale: smithay::utils::Scale, alpha: f32, - ) -> SplitRenderElements + ) -> Vec + where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + C: From>, + { + match &self.element { + CosmicMappedInternal::Stack(s) => s + .popup_render_elements::>( + renderer, location, scale, alpha, + ), + CosmicMappedInternal::Window(w) => w + .popup_render_elements::>( + renderer, location, scale, alpha, + ), + _ => unreachable!(), + } + .into_iter() + .map(C::from) + .collect() + } + + pub fn render_elements( + &self, + renderer: &mut R, + location: smithay::utils::Point, + scale: smithay::utils::Scale, + alpha: f32, + ) -> Vec where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -671,7 +698,7 @@ impl CosmicMapped { C: From>, { #[cfg(feature = "debug")] - let debug_elements = if let Some(debug) = self.debug.lock().unwrap().as_mut() { + let mut elements = if let Some(debug) = self.debug.lock().unwrap().as_mut() { let window = self.active_window(); let window_geo = window.geometry(); let (min_size, max_size, size) = @@ -840,30 +867,21 @@ impl CosmicMapped { Vec::new() }; #[cfg(not(feature = "debug"))] - let debug_elements = Vec::new(); - - let mut elements = SplitRenderElements { - w_elements: debug_elements, - p_elements: Vec::new(), - }; + let mut elements = Vec::new(); #[cfg_attr(not(feature = "debug"), allow(unused_mut))] - elements.extend_map( - match &self.element { - CosmicMappedInternal::Stack(s) => s - .split_render_elements::>( - renderer, location, scale, alpha, - ), - CosmicMappedInternal::Window(w) => w - .split_render_elements::>( - renderer, location, scale, alpha, - ), - _ => unreachable!(), - }, - C::from, - ); + elements.extend(match &self.element { + CosmicMappedInternal::Stack(s) => s.render_elements::>( + renderer, location, scale, alpha, + ), + CosmicMappedInternal::Window(w) => w + .render_elements::>( + renderer, location, scale, alpha, + ), + _ => unreachable!(), + }); - elements + elements.into_iter().map(C::from).collect() } pub(crate) fn update_theme(&self, theme: cosmic::Theme) { diff --git a/src/shell/element/resize_indicator.rs b/src/shell/element/resize_indicator.rs index 99c975ea..21f73428 100644 --- a/src/shell/element/resize_indicator.rs +++ b/src/shell/element/resize_indicator.rs @@ -9,7 +9,10 @@ use crate::{ use calloop::LoopHandle; use cosmic::{ - iced::widget::{column, container, horizontal_space, row, vertical_space}, + iced::{ + widget::{column, container, horizontal_space, row, vertical_space}, + Alignment, + }, iced_core::{Background, Border, Color, Length}, theme, widget::{icon::from_name, text}, @@ -66,7 +69,7 @@ impl Program for ResizeIndicatorInternal { fn view(&self) -> cosmic::Element<'_, Self::Message> { let edges = self.edges.lock().unwrap(); let icon_container_style = || { - theme::Container::custom(|theme| container::Appearance { + theme::Container::custom(|theme| container::Style { icon_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)), background: Some(Background::Color(theme.cosmic().accent_color().into())), @@ -90,14 +93,13 @@ impl Program for ResizeIndicatorInternal { .prefer_svg(true) .apply(container) .padding(8) - .style(icon_container_style()) + .class(icon_container_style()) .width(Length::Shrink) .apply(container) - .center_x() - .width(Length::Fill) + .center_x(Length::Fill) .into() } else { - vertical_space(36).into() + vertical_space().height(36).into() }, row(vec![ if edges.contains(ResizeEdge::LEFT) { @@ -110,35 +112,32 @@ impl Program for ResizeIndicatorInternal { .prefer_svg(true) .apply(container) .padding(8) - .style(icon_container_style()) + .class(icon_container_style()) .width(Length::Shrink) .apply(container) - .center_y() - .height(Length::Fill) + .center_y(Length::Fill) .into() } else { - horizontal_space(36).into() + horizontal_space().width(36).into() }, row(vec![ text::heading(&self.shortcut1).into(), text::body(fl!("grow-window")).into(), - horizontal_space(40).into(), + horizontal_space().width(40).into(), text::heading(&self.shortcut2).into(), text::body(fl!("shrink-window")).into(), ]) .apply(container) - .center_x() - .center_y() + .align_x(Alignment::Center) + .align_y(Alignment::Center) .padding(16) .apply(container) - .style(icon_container_style()) + .class(icon_container_style()) .width(Length::Shrink) .height(Length::Shrink) .apply(container) - .height(Length::Fill) - .width(Length::Fill) - .center_x() - .center_y() + .center_x(Length::Fill) + .center_y(Length::Fill) .into(), if edges.contains(ResizeEdge::RIGHT) { from_name(if self.direction == ResizeDirection::Outwards { @@ -150,14 +149,13 @@ impl Program for ResizeIndicatorInternal { .prefer_svg(true) .apply(container) .padding(8) - .style(icon_container_style()) + .class(icon_container_style()) .height(Length::Shrink) .apply(container) - .center_y() - .height(Length::Fill) + .center_y(Length::Fill) .into() } else { - horizontal_space(36).into() + horizontal_space().width(36).into() }, ]) .width(Length::Fill) @@ -173,14 +171,13 @@ impl Program for ResizeIndicatorInternal { .prefer_svg(true) .apply(container) .padding(8) - .style(icon_container_style()) + .class(icon_container_style()) .width(Length::Shrink) .apply(container) - .center_x() - .width(Length::Fill) + .center_x(Length::Fill) .into() } else { - vertical_space(36).into() + vertical_space().height(36).into() }, ]) .into() diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 2a9167a4..90ae4d22 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1,6 +1,6 @@ use super::{surface::RESIZE_BORDER, window::Focus, CosmicSurface}; use crate::{ - backend::render::{cursor::CursorState, SplitRenderElements}, + backend::render::cursor::CursorState, shell::{ focus::target::PointerFocusTarget, grabs::{ReleaseMode, ResizeEdge}, @@ -14,9 +14,9 @@ use crate::{ }; use calloop::LoopHandle; use cosmic::{ - iced::{id::Id, widget as iced_widget}, + iced::{id::Id, widget as iced_widget, Alignment}, iced_core::{border::Radius, Background, Border, Color, Length}, - iced_runtime::Command, + iced_runtime::Task, iced_widget::scrollable::AbsoluteOffset, theme, widget as cosmic_widget, Apply, Element as CosmicElement, Theme, }; @@ -170,7 +170,7 @@ impl CosmicStack { if let Some(mut geo) = p.geometry.lock().unwrap().clone() { geo.loc.y += TAB_HEIGHT; geo.size.h -= TAB_HEIGHT; - window.set_geometry(geo); + window.set_geometry(geo, TAB_HEIGHT as u32); } window.send_configure(); if let Some(idx) = idx { @@ -420,48 +420,52 @@ impl CosmicStack { pub fn focus_under( &self, mut relative_pos: Point, + surface_type: WindowSurfaceType, ) -> Option<(PointerFocusTarget, Point)> { self.0.with_program(|p| { let mut stack_ui = None; let geo = p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry(); - let point_i32 = relative_pos.to_i32_round::(); - if (point_i32.x - geo.loc.x >= -RESIZE_BORDER && point_i32.x - geo.loc.x < 0) - || (point_i32.y - geo.loc.y >= -RESIZE_BORDER && point_i32.y - geo.loc.y < 0) - || (point_i32.x - geo.loc.x >= geo.size.w - && point_i32.x - geo.loc.x < geo.size.w + RESIZE_BORDER) - || (point_i32.y - geo.loc.y >= geo.size.h - && point_i32.y - geo.loc.y < geo.size.h + TAB_HEIGHT + RESIZE_BORDER) - { - stack_ui = Some(( - PointerFocusTarget::StackUI(self.clone()), - Point::from((0., 0.)), - )); - } + if surface_type.contains(WindowSurfaceType::TOPLEVEL) { + let point_i32 = relative_pos.to_i32_round::(); + if (point_i32.x - geo.loc.x >= -RESIZE_BORDER && point_i32.x - geo.loc.x < 0) + || (point_i32.y - geo.loc.y >= -RESIZE_BORDER && point_i32.y - geo.loc.y < 0) + || (point_i32.x - geo.loc.x >= geo.size.w + && point_i32.x - geo.loc.x < geo.size.w + RESIZE_BORDER) + || (point_i32.y - geo.loc.y >= geo.size.h + && point_i32.y - geo.loc.y < geo.size.h + TAB_HEIGHT + RESIZE_BORDER) + { + stack_ui = Some(( + PointerFocusTarget::StackUI(self.clone()), + Point::from((0., 0.)), + )); + } - if point_i32.y - geo.loc.y < TAB_HEIGHT { - stack_ui = Some(( - PointerFocusTarget::StackUI(self.clone()), - Point::from((0., 0.)), - )); + if point_i32.y - geo.loc.y < TAB_HEIGHT { + stack_ui = Some(( + PointerFocusTarget::StackUI(self.clone()), + Point::from((0., 0.)), + )); + } } relative_pos.y -= TAB_HEIGHT as f64; let active_window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)]; - active_window - .0 - .surface_under(relative_pos, WindowSurfaceType::ALL) - .map(|(surface, surface_offset)| { - ( - PointerFocusTarget::WlSurface { - surface, - toplevel: Some(active_window.clone().into()), - }, - surface_offset.to_f64() + Point::from((0., TAB_HEIGHT as f64)), - ) - }) - .or(stack_ui) + stack_ui.or_else(|| { + active_window + .0 + .surface_under(relative_pos, surface_type) + .map(|(surface, surface_offset)| { + ( + PointerFocusTarget::WlSurface { + surface, + toplevel: Some(active_window.clone().into()), + }, + surface_offset.to_f64() + Point::from((0., TAB_HEIGHT as f64)), + ) + }) + }) }) } @@ -486,7 +490,7 @@ impl CosmicStack { let win_geo = Rectangle::from_loc_and_size(loc, size); for window in p.windows.lock().unwrap().iter() { - window.set_geometry(win_geo); + window.set_geometry(win_geo, TAB_HEIGHT as u32); } *p.geometry.lock().unwrap() = Some(geo); @@ -541,13 +545,40 @@ impl CosmicStack { self.0.loop_handle() } - pub fn split_render_elements( + pub fn popup_render_elements( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + alpha: f32, + ) -> Vec + where + R: Renderer + ImportAll + ImportMem, + ::TextureId: Send + Clone + 'static, + C: From>, + { + let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32)); + self.0.with_program(|p| { + let windows = p.windows.lock().unwrap(); + let active = p.active.load(Ordering::SeqCst); + + windows[active] + .popup_render_elements::>( + renderer, window_loc, scale, alpha, + ) + .into_iter() + .map(C::from) + .collect() + }) + } + + pub fn render_elements( &self, renderer: &mut R, location: Point, scale: Scale, alpha: f32, - ) -> SplitRenderElements + ) -> Vec where R: Renderer + ImportAll + ImportMem, ::TextureId: Send + Clone + 'static, @@ -564,28 +595,20 @@ impl CosmicStack { let stack_loc = location + offset; let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32)); - let w_elements = AsRenderElements::::render_elements::>( + let mut elements = AsRenderElements::::render_elements::>( &self.0, renderer, stack_loc, scale, alpha, ); - let mut elements = SplitRenderElements { - w_elements: w_elements.into_iter().map(C::from).collect(), - p_elements: Vec::new(), - }; - - elements.extend_map( - self.0.with_program(|p| { - let windows = p.windows.lock().unwrap(); - let active = p.active.load(Ordering::SeqCst); + elements.extend(self.0.with_program(|p| { + let windows = p.windows.lock().unwrap(); + let active = p.active.load(Ordering::SeqCst); - windows[active].split_render_elements::>( - renderer, window_loc, scale, alpha, - ) - }), - C::from, - ); + windows[active].render_elements::>( + renderer, window_loc, scale, alpha, + ) + })); - elements + elements.into_iter().map(C::from).collect() } pub(crate) fn set_theme(&self, theme: cosmic::Theme) { @@ -689,7 +712,7 @@ impl Program for CosmicStackInternal { &mut self, message: Self::Message, loop_handle: &LoopHandle<'static, crate::state::State>, - ) -> Command { + ) -> Task { match message { Message::DragStart => { if let Some((seat, serial)) = self.last_seat.lock().unwrap().clone() { @@ -851,7 +874,7 @@ impl Program for CosmicStackInternal { } _ => unreachable!(), } - Command::none() + Task::none() } fn view(&self) -> CosmicElement<'_, Self::Message> { @@ -867,8 +890,8 @@ impl Program for CosmicStackInternal { .size(16) .prefer_svg(true) .icon() - .style(if group_focused { - theme::Svg::custom(|theme| iced_widget::svg::Appearance { + .class(if group_focused { + theme::Svg::custom(|theme| iced_widget::svg::Style { color: Some(if theme.cosmic().is_dark { Color::BLACK } else { @@ -880,7 +903,7 @@ impl Program for CosmicStackInternal { }) .apply(iced_widget::container) .padding([4, 24]) - .center_y() + .align_y(Alignment::Center) .apply(iced_widget::mouse_area) .on_press(Message::DragStart) .on_right_press(Message::Menu) @@ -912,7 +935,8 @@ impl Program for CosmicStackInternal { .height(Length::Fill) .width(Length::Fill), ), - iced_widget::horizontal_space(0) + iced_widget::horizontal_space() + .width(Length::Fixed(0.0)) .apply(iced_widget::container) .padding([64, 24]) .apply(iced_widget::mouse_area) @@ -930,10 +954,10 @@ impl Program for CosmicStackInternal { iced_widget::row(elements) .height(TAB_HEIGHT as u16) - .width(Length::Fill) //width as u16) + .width(Length::Fill) .apply(iced_widget::container) - .center_y() - .style(theme::Container::custom(move |theme| { + .align_y(Alignment::Center) + .class(theme::Container::custom(move |theme| { let background = if group_focused { Some(Background::Color(theme.cosmic().accent_color().into())) } else { @@ -942,7 +966,7 @@ impl Program for CosmicStackInternal { ))) }; - iced_widget::container::Appearance { + iced_widget::container::Style { icon_color: Some(Color::from(theme.cosmic().background.on)), text_color: Some(Color::from(theme.cosmic().background.on)), background, @@ -1015,7 +1039,7 @@ impl SpaceElement for CosmicStack { }) } fn is_in_input_region(&self, point: &Point) -> bool { - self.focus_under(*point).is_some() + self.focus_under(*point, WindowSurfaceType::ALL).is_some() } fn set_activate(&self, activated: bool) { SpaceElement::set_activate(&self.0, activated); diff --git a/src/shell/element/stack/tab.rs b/src/shell/element/stack/tab.rs index 9d6dfd6c..f3532a72 100644 --- a/src/shell/element/stack/tab.rs +++ b/src/shell/element/stack/tab.rs @@ -5,11 +5,7 @@ use cosmic::{ alignment, event, layout::{Layout, Limits, Node}, mouse, overlay, renderer, - widget::{ - operation::{Operation, OperationOutputWrapper}, - tree::Tree, - Id, Widget, - }, + widget::{operation::Operation, tree::Tree, Id, Widget}, Border, Clipboard, Color, Length, Rectangle, Shell, Size, }, iced_widget::scrollable::AbsoluteOffset, @@ -54,19 +50,19 @@ pub(super) enum TabRuleTheme { impl From for theme::Rule { fn from(theme: TabRuleTheme) -> Self { match theme { - TabRuleTheme::ActiveActivated => Self::custom(|theme| widget::rule::Appearance { + TabRuleTheme::ActiveActivated => Self::custom(|theme| widget::rule::Style { color: theme.cosmic().accent_color().into(), width: 4, radius: 0.0.into(), fill_mode: FillMode::Full, }), - TabRuleTheme::ActiveDeactivated => Self::custom(|theme| widget::rule::Appearance { + TabRuleTheme::ActiveDeactivated => Self::custom(|theme| widget::rule::Style { color: theme.cosmic().palette.neutral_5.into(), width: 4, radius: 0.0.into(), fill_mode: FillMode::Full, }), - TabRuleTheme::Default => Self::custom(|theme| widget::rule::Appearance { + TabRuleTheme::Default => Self::custom(|theme| widget::rule::Style { color: theme.cosmic().palette.neutral_5.into(), width: 4, radius: 8.0.into(), @@ -96,11 +92,11 @@ impl TabBackgroundTheme { } } -impl From for theme::Container { +impl From for theme::Container<'_> { fn from(background_theme: TabBackgroundTheme) -> Self { match background_theme { TabBackgroundTheme::ActiveActivated => { - Self::custom(move |theme| widget::container::Appearance { + Self::custom(move |theme| widget::container::Style { icon_color: Some(Color::from(theme.cosmic().accent_text_color())), text_color: Some(Color::from(theme.cosmic().accent_text_color())), background: Some(background_theme.background_color(theme).into()), @@ -113,7 +109,7 @@ impl From for theme::Container { }) } TabBackgroundTheme::ActiveDeactivated => { - Self::custom(move |theme| widget::container::Appearance { + Self::custom(move |theme| widget::container::Style { icon_color: None, text_color: None, background: Some(background_theme.background_color(theme).into()), @@ -215,20 +211,19 @@ impl Tab { .icon() .apply(widget::button) .padding(0) - .style(theme::iced::Button::Text); + .class(theme::iced::Button::Text); if let Some(close_message) = self.close_message { close_button = close_button.on_press(close_message); } let items = vec![ - widget::vertical_rule(4).style(self.rule_theme).into(), + widget::vertical_rule(4).class(self.rule_theme).into(), self.app_icon .clone() .apply(widget::container) - .height(Length::Fill) .width(Length::Shrink) .padding([2, 4]) - .center_y() + .center_y(Length::Fill) .into(), tab_text(self.title, self.active) .font(self.font) @@ -238,10 +233,9 @@ impl Tab { .into(), close_button .apply(widget::container) - .height(Length::Fill) .width(Length::Shrink) .padding([2, 4]) - .center_y() + .center_y(Length::Fill) .align_x(alignment::Horizontal::Right) .into(), ]; @@ -269,7 +263,7 @@ pub(super) struct TabInternal<'a, Message: TabMessage> { id: Id, idx: usize, active: bool, - background: theme::Container, + background: theme::Container<'a>, elements: Vec>, press_message: Option, right_click_message: Option, @@ -347,7 +341,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.elements @@ -454,8 +448,8 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - use cosmic::widget::container::StyleSheet; - let style = theme.appearance(&self.background); + use cosmic::widget::container::Catalog; + let style = theme.style(&self.background); draw_background(renderer, &style, layout.bounds()); @@ -486,7 +480,8 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, + translation: cosmic::iced::Vector, ) -> Option> { - overlay::from_children(&mut self.elements, tree, layout, renderer) + overlay::from_children(&mut self.elements, tree, layout, renderer, translation) } } diff --git a/src/shell/element/stack/tab_text.rs b/src/shell/element/stack/tab_text.rs index 185edf59..d3a136a5 100644 --- a/src/shell/element/stack/tab_text.rs +++ b/src/shell/element/stack/tab_text.rs @@ -84,7 +84,7 @@ impl TabText { vertical_alignment: alignment::Vertical::Center, shaping: Shaping::Advanced, line_height: LineHeight::default(), - wrap: cosmic::iced::advanced::text::Wrap::None, + wrapping: cosmic::iced::advanced::text::Wrapping::None, }) } } diff --git a/src/shell/element/stack/tabs.rs b/src/shell/element/stack/tabs.rs index 08aa329b..bf4a096d 100644 --- a/src/shell/element/stack/tabs.rs +++ b/src/shell/element/stack/tabs.rs @@ -8,7 +8,7 @@ use cosmic::{ widget::{ operation::{ scrollable::{AbsoluteOffset, RelativeOffset}, - Operation, OperationOutputWrapper, Scrollable, + Operation, Scrollable, }, tree::{self, Tree}, Widget, @@ -16,10 +16,9 @@ use cosmic::{ Background, Border, Clipboard, Color, Length, Point, Rectangle, Renderer, Shell, Size, Vector, }, - iced_style::container::StyleSheet as ContainerStyleSheet, iced_widget::container::draw_background, theme, - widget::icon::from_name, + widget::{container::Catalog, icon::from_name}, Apply, }; use keyframe::{ @@ -45,6 +44,7 @@ struct ScrollAnimationState { start_time: Instant, start: Offset, end: Offset, + extra: Offset, } #[derive(Debug, Clone)] @@ -75,9 +75,24 @@ impl Scrollable for State { start_time: Instant::now(), start: self.offset_x, end: new_offset, + extra: Offset::Absolute(0.), }); self.offset_x = new_offset; } + + fn scroll_by( + &mut self, + offset: AbsoluteOffset, + _bounds: Rectangle, + _content_bounds: Rectangle, + ) { + self.scroll_animation = Some(ScrollAnimationState { + start_time: Instant::now(), + start: self.offset_x, + end: self.offset_x, + extra: Offset::Absolute(offset.x.max(0.0)), + }); + } } impl Default for State { @@ -145,7 +160,7 @@ where Element::new(tab.internal(i)) }); - let tabs_rule = widget::vertical_rule(4).style(if tabs.len() - 1 == active { + let tabs_rule = widget::vertical_rule(4).class(if tabs.len() - 1 == active { if activated { TabRuleTheme::ActiveActivated } else { @@ -166,7 +181,7 @@ where .prefer_svg(true) .icon() .apply(widget::button) - .style(theme::iced::Button::Text) + .class(theme::iced::Button::Text) .on_press(Message::scroll_back()); let next_button = from_name("go-next-symbolic") @@ -174,17 +189,17 @@ where .prefer_svg(true) .icon() .apply(widget::button) - .style(theme::iced::Button::Text) + .class(theme::iced::Button::Text) .on_press(Message::scroll_further()); let mut elements = Vec::with_capacity(tabs.len() + 5); - elements.push(widget::vertical_rule(4).style(rule_style).into()); + elements.push(widget::vertical_rule(4).class(rule_style).into()); elements.push(prev_button.into()); elements.extend(tabs); elements.push(tabs_rule.into()); elements.push(next_button.into()); - elements.push(widget::vertical_rule(4).style(rule_style).into()); + elements.push(widget::vertical_rule(4).class(rule_style).into()); Tabs { elements, @@ -239,6 +254,7 @@ impl State { Vector::new( animation.start.absolute(bounds.width, content_bounds.width) + + animation.extra.absolute(bounds.width, content_bounds.width) * percentage + (animation.end.absolute(bounds.width, content_bounds.width) - animation.start.absolute(bounds.width, content_bounds.width)) * percentage, @@ -490,9 +506,9 @@ where height: b.bounds().height, }); - let background_style = ContainerStyleSheet::appearance( + let background_style = Catalog::style( theme, - &theme::Container::custom(|theme| widget::container::Appearance { + &theme::Container::custom(|theme| widget::container::Style { icon_color: None, text_color: None, background: Some(Background::Color(super::tab::primary_container_color( @@ -666,10 +682,12 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { let state = tree.state.downcast_mut::(); let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); state.cleanup_old_animations(); @@ -677,6 +695,7 @@ where state, self.id.as_ref(), bounds, + content_bounds, Vector { x: 0.0, y: 0.0 }, /* seemingly unused */ ); @@ -819,6 +838,7 @@ where start_time: Instant::now(), start: Offset::Absolute(offset.x), end: Offset::Absolute(new_offset.x), + extra: Offset::Absolute(0.), }); state.offset_x = Offset::Absolute(new_offset.x); } @@ -993,7 +1013,8 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, + translation: cosmic::iced::Vector, ) -> Option> { - overlay::from_children(&mut self.elements, tree, layout, renderer) + overlay::from_children(&mut self.elements, tree, layout, renderer, translation) } } diff --git a/src/shell/element/stack_hover.rs b/src/shell/element/stack_hover.rs index 26177bfd..a260f9ea 100644 --- a/src/shell/element/stack_hover.rs +++ b/src/shell/element/stack_hover.rs @@ -38,16 +38,16 @@ impl Program for StackHoverInternal { .prefer_svg(true) .icon() .into(), - horizontal_space(16).into(), + horizontal_space().width(16).into(), text::title3(fl!("stack-windows")).into(), ]) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(container) - .center_x() - .center_y() + .align_x(Alignment::Center) + .align_y(Alignment::Center) .padding(16) .apply(container) - .style(theme::Container::custom(|theme| container::Appearance { + .class(theme::Container::custom(|theme| container::Style { icon_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)), background: Some(Background::Color(theme.cosmic().accent_color().into())), @@ -61,10 +61,8 @@ impl Program for StackHoverInternal { .width(Length::Shrink) .height(Length::Shrink) .apply(container) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() + .center_x(Length::Fill) + .center_y(Length::Fill) .into() } } diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index d13272e6..3c6ad3f2 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -35,7 +35,9 @@ use smithay::{ }, wayland_server::protocol::wl_surface::WlSurface, }, - utils::{user_data::UserDataMap, IsAlive, Logical, Rectangle, Serial, Size}, + utils::{ + user_data::UserDataMap, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, + }, wayland::{ compositor::{with_states, SurfaceData}, seat::WaylandFocus, @@ -45,7 +47,6 @@ use smithay::{ }; use crate::{ - backend::render::SplitRenderElements, state::{State, SurfaceDmabufFeedback}, utils::prelude::*, wayland::handlers::decoration::PreferredDecorationMode, @@ -91,7 +92,7 @@ struct Minimized(AtomicBool); struct Sticky(AtomicBool); #[derive(Default)] -pub struct GlobalGeometry(pub Mutex>>); +struct GlobalGeometry(Mutex>>); pub const SSD_HEIGHT: i32 = 36; pub const RESIZE_BORDER: i32 = 10; @@ -138,14 +139,30 @@ impl CosmicSurface { } } - pub fn set_geometry(&self, geo: Rectangle) { + pub fn global_geometry(&self) -> Option> { *self .0 .user_data() .get_or_insert_threadsafe(GlobalGeometry::default) .0 .lock() - .unwrap() = Some(geo); + .unwrap() + } + + pub fn set_geometry(&self, geo: Rectangle, ssd_height: u32) { + { + let mut geo = geo; + geo.size.h += ssd_height as i32; + geo.loc.y -= ssd_height as i32; + + *self + .0 + .user_data() + .get_or_insert_threadsafe(GlobalGeometry::default) + .0 + .lock() + .unwrap() = Some(geo); + } match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { toplevel.with_pending_state(|state| state.size = Some(geo.size.as_logical())) @@ -590,13 +607,13 @@ impl CosmicSurface { self.0.user_data() } - pub fn split_render_elements( + pub fn popup_render_elements( &self, renderer: &mut R, - location: smithay::utils::Point, - scale: smithay::utils::Scale, + location: Point, + scale: Scale, alpha: f32, - ) -> SplitRenderElements + ) -> Vec where R: Renderer + ImportAll, ::TextureId: Clone + 'static, @@ -605,9 +622,8 @@ impl CosmicSurface { match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { let surface = toplevel.wl_surface(); - - let p_elements = PopupManager::popups_for_surface(surface) - .flat_map(|(popup, popup_offset)| { + PopupManager::popups_for_surface(surface) + .flat_map(move |(popup, popup_offset)| { let offset = (self.0.geometry().loc + popup_offset - popup.geometry().loc) .to_physical_precise_round(scale); @@ -620,26 +636,40 @@ impl CosmicSurface { element::Kind::Unspecified, ) }) - .collect(); + .collect() + } + WindowSurface::X11(_) => Vec::new(), + } + } - let w_elements = render_elements_from_surface_tree( + pub fn render_elements( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + alpha: f32, + ) -> Vec + where + R: Renderer + ImportAll, + ::TextureId: Clone + 'static, + C: From>, + { + match self.0.underlying_surface() { + WindowSurface::Wayland(toplevel) => { + let surface = toplevel.wl_surface(); + + render_elements_from_surface_tree( renderer, surface, location, scale, alpha, element::Kind::Unspecified, - ); - - SplitRenderElements { - w_elements, - p_elements, - } + ) + } + WindowSurface::X11(surface) => { + surface.render_elements(renderer, location, scale, alpha) } - WindowSurface::X11(surface) => SplitRenderElements { - w_elements: surface.render_elements(renderer, location, scale, alpha), - p_elements: Vec::new(), - }, } } @@ -663,10 +693,7 @@ impl SpaceElement for CosmicSurface { SpaceElement::bbox(&self.0) } - fn is_in_input_region( - &self, - point: &smithay::utils::Point, - ) -> bool { + fn is_in_input_region(&self, point: &Point) -> bool { SpaceElement::is_in_input_region(&self.0, point) } @@ -784,8 +811,8 @@ where fn render_elements>( &self, renderer: &mut R, - location: smithay::utils::Point, - scale: smithay::utils::Scale, + location: Point, + scale: Scale, alpha: f32, ) -> Vec { self.0.render_elements(renderer, location, scale, alpha) diff --git a/src/shell/element/swap_indicator.rs b/src/shell/element/swap_indicator.rs index 713af6a2..13033b00 100644 --- a/src/shell/element/swap_indicator.rs +++ b/src/shell/element/swap_indicator.rs @@ -34,16 +34,16 @@ impl Program for SwapIndicatorInternal { .prefer_svg(true) .icon() .into(), - horizontal_space(16).into(), + horizontal_space().width(16).into(), text::title3(fl!("swap-windows")).into(), ]) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(container) - .center_x() - .center_y() + .align_x(Alignment::Center) + .align_y(Alignment::Center) .padding(16) .apply(container) - .style(theme::Container::custom(|theme| container::Appearance { + .class(theme::Container::custom(|theme| container::Style { icon_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)), background: Some(Background::Color(theme.cosmic().accent_color().into())), @@ -57,10 +57,8 @@ impl Program for SwapIndicatorInternal { .width(Length::Shrink) .height(Length::Shrink) .apply(container) - .height(Length::Fill) - .width(Length::Fill) - .center_x() - .center_y() + .center_x(Length::Fill) + .center_y(Length::Fill) .into() } } diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 887ce664..c88c6121 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,5 +1,5 @@ use crate::{ - backend::render::{cursor::CursorState, SplitRenderElements}, + backend::render::cursor::CursorState, shell::{ focus::target::PointerFocusTarget, grabs::{ReleaseMode, ResizeEdge}, @@ -11,7 +11,7 @@ use crate::{ }, }; use calloop::LoopHandle; -use cosmic::iced::{Color, Command}; +use cosmic::iced::{Color, Task}; use smithay::{ backend::{ input::KeyState, @@ -41,7 +41,7 @@ use smithay::{ output::Output, reexports::wayland_server::protocol::wl_surface::WlSurface, render_elements, - utils::{IsAlive, Logical, Point, Rectangle, Serial, Size}, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, wayland::seat::WaylandFocus, }; use std::{ @@ -210,16 +210,11 @@ impl CosmicWindow { pub fn set_geometry(&self, geo: Rectangle) { self.0.with_program(|p| { - let loc = ( - geo.loc.x, - geo.loc.y + if p.has_ssd(true) { SSD_HEIGHT } else { 0 }, - ); - let size = ( - geo.size.w, - std::cmp::max(geo.size.h - if p.has_ssd(true) { SSD_HEIGHT } else { 0 }, 0), - ); + let ssd_height = if p.has_ssd(true) { SSD_HEIGHT } else { 0 }; + let loc = (geo.loc.x, geo.loc.y + ssd_height); + let size = (geo.size.w, std::cmp::max(geo.size.h - ssd_height, 0)); p.window - .set_geometry(Rectangle::from_loc_and_size(loc, size)); + .set_geometry(Rectangle::from_loc_and_size(loc, size), ssd_height as u32); }); } @@ -243,11 +238,12 @@ impl CosmicWindow { pub fn focus_under( &self, mut relative_pos: Point, + surface_type: WindowSurfaceType, ) -> Option<(PointerFocusTarget, Point)> { self.0.with_program(|p| { let mut offset = Point::from((0., 0.)); let mut window_ui = None; - if p.has_ssd(false) { + if p.has_ssd(false) && surface_type.contains(WindowSurfaceType::TOPLEVEL) { let geo = p.window.geometry(); let point_i32 = relative_pos.to_i32_round::(); @@ -255,7 +251,7 @@ impl CosmicWindow { || (point_i32.y - geo.loc.y >= -RESIZE_BORDER && point_i32.y - geo.loc.y < 0) || (point_i32.x - geo.loc.x >= geo.size.w && point_i32.x - geo.loc.x < geo.size.w + RESIZE_BORDER) - || (point_i32.y - geo.loc.y >= geo.size.h + || (point_i32.y - geo.loc.y >= geo.size.h + SSD_HEIGHT && point_i32.y - geo.loc.y < geo.size.h + SSD_HEIGHT + RESIZE_BORDER) { window_ui = Some(( @@ -270,24 +266,26 @@ impl CosmicWindow { Point::from((0., 0.)), )); } + } + if p.has_ssd(false) { relative_pos.y -= SSD_HEIGHT as f64; offset.y += SSD_HEIGHT as f64; } - p.window - .0 - .surface_under(relative_pos, WindowSurfaceType::ALL) - .map(|(surface, surface_offset)| { - ( - PointerFocusTarget::WlSurface { - surface, - toplevel: Some(p.window.clone().into()), - }, - (offset + surface_offset.to_f64()), - ) - }) - .or(window_ui) + window_ui.or_else(|| { + p.window.0.surface_under(relative_pos, surface_type).map( + |(surface, surface_offset)| { + ( + PointerFocusTarget::WlSurface { + surface, + toplevel: Some(p.window.clone().into()), + }, + (offset + surface_offset.to_f64()), + ) + }, + ) + }) }) } @@ -308,13 +306,13 @@ impl CosmicWindow { self.0.loop_handle() } - pub fn split_render_elements( + pub fn popup_render_elements( &self, renderer: &mut R, - location: smithay::utils::Point, - scale: smithay::utils::Scale, + location: Point, + scale: Scale, alpha: f32, - ) -> SplitRenderElements + ) -> Vec where R: Renderer + ImportAll + ImportMem, ::TextureId: Send + Clone + 'static, @@ -328,17 +326,44 @@ impl CosmicWindow { location }; - let mut elements = SplitRenderElements::default(); + self.0.with_program(|p| { + p.window + .popup_render_elements::>( + renderer, window_loc, scale, alpha, + ) + .into_iter() + .map(C::from) + .collect() + }) + } - elements.extend_map( - self.0.with_program(|p| { - p.window - .split_render_elements::>( - renderer, window_loc, scale, alpha, - ) - }), - C::from, - ); + pub fn render_elements( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + alpha: f32, + ) -> Vec + where + R: Renderer + ImportAll + ImportMem, + ::TextureId: Send + Clone + 'static, + C: From>, + { + let has_ssd = self.0.with_program(|p| p.has_ssd(false)); + + let window_loc = if has_ssd { + location + Point::from((0, (SSD_HEIGHT as f64 * scale.y) as i32)) + } else { + location + }; + + let mut elements = Vec::new(); + + elements.extend(self.0.with_program(|p| { + p.window.render_elements::>( + renderer, window_loc, scale, alpha, + ) + })); if has_ssd { let ssd_loc = location @@ -346,16 +371,12 @@ impl CosmicWindow { .0 .with_program(|p| p.window.geometry().loc) .to_physical_precise_round(scale); - elements.w_elements.extend( - AsRenderElements::::render_elements::>( - &self.0, renderer, ssd_loc, scale, alpha, - ) - .into_iter() - .map(C::from), - ) + elements.extend(AsRenderElements::::render_elements::< + CosmicWindowRenderElement, + >(&self.0, renderer, ssd_loc, scale, alpha)) } - elements + elements.into_iter().map(C::from).collect() } pub(crate) fn set_theme(&self, theme: cosmic::Theme) { @@ -383,7 +404,7 @@ impl Program for CosmicWindowInternal { &mut self, message: Self::Message, loop_handle: &LoopHandle<'static, crate::state::State>, - ) -> Command { + ) -> Task { match message { Message::DragStart => { if let Some((seat, serial)) = self.last_seat.lock().unwrap().clone() { @@ -483,7 +504,7 @@ impl Program for CosmicWindowInternal { } } } - Command::none() + Task::none() } fn background_color(&self, theme: &cosmic::Theme) -> Color { @@ -496,7 +517,7 @@ impl Program for CosmicWindowInternal { fn view(&self) -> cosmic::Element<'_, Self::Message> { let mut header = cosmic::widget::header_bar() - .start(cosmic::widget::horizontal_space(32)) + .start(cosmic::widget::horizontal_space().width(32)) .title(self.last_title.lock().unwrap().clone()) .on_drag(Message::DragStart) .on_close(Message::Close) @@ -507,12 +528,12 @@ impl Program for CosmicWindowInternal { if cosmic::config::show_minimize() { header = header .on_minimize(Message::Minimize) - .start(cosmic::widget::horizontal_space(40)); // 32 + 8 spacing + .start(cosmic::widget::horizontal_space().width(40)); // 32 + 8 spacing } if cosmic::config::show_maximize() { header = header .on_maximize(Message::Maximize) - .start(cosmic::widget::horizontal_space(40)); // 32 + 8 spacing + .start(cosmic::widget::horizontal_space().width(40)); // 32 + 8 spacing } header.into() @@ -538,7 +559,7 @@ impl SpaceElement for CosmicWindow { }) } fn is_in_input_region(&self, point: &Point) -> bool { - self.focus_under(*point).is_some() + self.focus_under(*point, WindowSurfaceType::ALL).is_some() } fn set_activate(&self, activated: bool) { if self diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index e2038375..f4a6ca17 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -21,10 +21,12 @@ use std::{borrow::Cow, mem, sync::Mutex}; use tracing::{debug, trace}; +pub use self::order::{render_input_order, Stage}; use self::target::{KeyboardFocusTarget, WindowGroup}; use super::{grabs::SeatMoveGrabState, layout::floating::FloatingLayout, SeatExt}; +mod order; pub mod target; pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet>); @@ -170,7 +172,7 @@ impl Shell { let focused_windows = self .seats .iter() - .flat_map(|seat| { + .map(|seat| { if matches!( seat.get_keyboard().unwrap().current_focus(), Some(KeyboardFocusTarget::Group(_)) @@ -178,11 +180,10 @@ impl Shell { return None; } - Some(self.outputs().flat_map(|o| { - let space = self.active_space(o); - let stack = space.focus_stack.get(seat); - stack.last().cloned() - })) + let output = seat.focused_or_active_output(); + let space = self.active_space(&output); + let stack = space.focus_stack.get(seat); + stack.last().cloned() }) .flatten() .collect::>(); @@ -201,7 +202,7 @@ impl Shell { m.window.configure(); } - let workspace = self.workspaces.active_mut(&output); + let workspace = &mut set.workspaces[set.active]; for focused in focused_windows.iter() { raise_with_children(&mut workspace.floating_layer, focused); } @@ -213,6 +214,16 @@ impl Shell { m.window.set_activated(false); m.window.configure(); } + + for (i, workspace) in set.workspaces.iter().enumerate() { + if i == set.active { + continue; + } + for window in workspace.mapped() { + window.set_activated(false); + window.configure(); + } + } } } } @@ -230,8 +241,8 @@ fn update_focus_state( if should_update_cursor && state.common.config.cosmic_conf.cursor_follows_focus { if target.is_some() { //need to borrow mutably for surface under - let mut shell = state.common.shell.write().unwrap(); - // get geometry of the target element + let shell = state.common.shell.read().unwrap(); + // get the top left corner of the target element let geometry = shell.focused_geometry(target.unwrap()); if let Some(geometry) = geometry { // get the center of the target element @@ -245,10 +256,9 @@ fn update_focus_state( .cloned() .unwrap_or(seat.active_output()); - let focus = shell - .surface_under(new_pos, &output) + let focus = State::surface_under(new_pos, &output, &*shell) .map(|(focus, loc)| (focus, loc.as_logical())); - //drop here to avoid multiple mutable borrows + //drop here to avoid multiple borrows mem::drop(shell); seat.get_pointer().unwrap().motion( state, @@ -334,19 +344,27 @@ impl Common { .cloned() .collect::>(); for seat in &seats { - update_pointer_focus(state, &seat); - - let mut shell = state.common.shell.write().unwrap(); - let output = seat.focused_or_active_output(); + { + let shell = state.common.shell.read().unwrap(); + let focused_output = seat.focused_output(); + let active_output = seat.active_output(); - // If the focused or active output is not in the list of outputs, switch to the first output - if !shell.outputs().any(|o| o == &output) { - if let Some(other) = shell.outputs().next() { - seat.set_active_output(other); + // If the focused or active output is not in the list of outputs, switch to the first output + if focused_output.is_some_and(|f| !shell.outputs().any(|o| &f == o)) { + seat.set_focused_output(None); + } + if !shell.outputs().any(|o| o == &active_output) { + if let Some(other) = shell.outputs().next() { + seat.set_active_output(other); + } + continue; } - continue; } + update_pointer_focus(state, &seat); + + let output = seat.focused_or_active_output(); + let mut shell = state.common.shell.write().unwrap(); let last_known_focus = ActiveFocus::get(&seat); if let Some(target) = last_known_focus { diff --git a/src/shell/focus/order.rs b/src/shell/focus/order.rs new file mode 100644 index 00000000..e03eb607 --- /dev/null +++ b/src/shell/focus/order.rs @@ -0,0 +1,372 @@ +use std::{ops::ControlFlow, time::Instant}; + +use cosmic_comp_config::workspace::WorkspaceLayout; +use keyframe::{ease, functions::EaseInOutCubic}; +use smithay::{ + desktop::{layer_map_for_output, LayerSurface, PopupKind, PopupManager}, + output::{Output, OutputNoMode}, + utils::{Logical, Point}, + wayland::{session_lock::LockSurface, shell::wlr_layer::Layer}, + xwayland::X11Surface, +}; + +use crate::{ + backend::render::ElementFilter, + shell::{ + layout::{floating::FloatingLayout, tiling::ANIMATION_DURATION}, + Shell, Workspace, WorkspaceDelta, + }, + utils::{geometry::*, prelude::OutputExt, quirks::WORKSPACE_OVERVIEW_NAMESPACE}, + wayland::protocols::workspace::WorkspaceHandle, +}; + +pub enum Stage<'a> { + SessionLock(Option<&'a LockSurface>), + LayerPopup { + layer: LayerSurface, + popup: &'a PopupKind, + location: Point, + }, + LayerSurface { + layer: LayerSurface, + location: Point, + }, + OverrideRedirect { + surface: &'a X11Surface, + location: Point, + }, + StickyPopups(&'a FloatingLayout), + Sticky(&'a FloatingLayout), + WorkspacePopups { + workspace: &'a Workspace, + offset: Point, + }, + Workspace { + workspace: &'a Workspace, + offset: Point, + }, +} + +pub fn render_input_order( + shell: &Shell, + output: &Output, + previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>, + current: (WorkspaceHandle, usize), + element_filter: ElementFilter, + callback: impl FnMut(Stage) -> ControlFlow, ()>, +) -> Result { + match render_input_order_internal(shell, output, previous, current, element_filter, callback) { + ControlFlow::Break(result) => result, + ControlFlow::Continue(_) => Ok(R::default()), + } +} + +fn render_input_order_internal( + shell: &Shell, + output: &Output, + previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>, + current: (WorkspaceHandle, usize), + element_filter: ElementFilter, + mut callback: impl FnMut(Stage) -> ControlFlow, ()>, +) -> ControlFlow, ()> { + // Session Lock + if let Some(session_lock) = &shell.session_lock { + return callback(Stage::SessionLock(session_lock.surfaces.get(output))); + } + + // Overlay-level layer shell + // overlay is above everything + for (layer, popup, location) in layer_popups(output, Layer::Overlay, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location, + })?; + } + for (layer, location) in layer_surfaces(output, Layer::Overlay, element_filter) { + callback(Stage::LayerSurface { layer, location })?; + } + + // calculate a bunch of stuff for workspace transitions + + let Some(set) = shell.workspaces.sets.get(output) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + let Some(workspace) = set.workspaces.iter().find(|w| w.handle == current.0) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + let output_size = output.geometry().size; + let has_fullscreen = workspace + .fullscreen + .as_ref() + .filter(|f| !f.is_animating()) + .is_some(); + + let (previous, current_offset) = match previous.as_ref() { + Some((previous, previous_idx, start)) => { + let layout = shell.workspaces.layout; + + let Some(workspace) = shell.workspaces.space_for_handle(&previous) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + let has_fullscreen = workspace.fullscreen.is_some(); + + let percentage = match start { + WorkspaceDelta::Shortcut(st) => ease( + EaseInOutCubic, + 0.0, + 1.0, + Instant::now().duration_since(*st).as_millis() as f32 + / ANIMATION_DURATION.as_millis() as f32, + ), + WorkspaceDelta::Gesture(prog) => *prog as f32, + WorkspaceDelta::GestureEnd(st, spring) => { + (spring.value_at(Instant::now().duration_since(*st)) as f32).clamp(0.0, 1.0) + } + }; + + let offset = Point::::from(match (layout, *previous_idx < current.1) { + (WorkspaceLayout::Vertical, true) => { + (0, (-output_size.h as f32 * percentage).round() as i32) + } + (WorkspaceLayout::Vertical, false) => { + (0, (output_size.h as f32 * percentage).round() as i32) + } + (WorkspaceLayout::Horizontal, true) => { + ((-output_size.w as f32 * percentage).round() as i32, 0) + } + (WorkspaceLayout::Horizontal, false) => { + ((output_size.w as f32 * percentage).round() as i32, 0) + } + }); + + ( + Some((previous, has_fullscreen, offset)), + Point::::from(match (layout, *previous_idx < current.1) { + (WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y), + (WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)), + (WorkspaceLayout::Horizontal, true) => (output_size.w + offset.x, 0), + (WorkspaceLayout::Horizontal, false) => (-(output_size.w - offset.x), 0), + }), + ) + } + None => (None, Point::default()), + }; + + // Top-level layer shell popups + if !has_fullscreen { + for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location, + })?; + } + } + + if element_filter != ElementFilter::LayerShellOnly { + // overlay redirect windows + // they need to be over sticky windows, because they could be popups of sticky windows, + // and we can't differenciate that. + for (surface, location) in shell + .override_redirect_windows + .iter() + .filter(|or| { + (*or) + .geometry() + .as_global() + .intersection(output.geometry()) + .is_some() + }) + .map(|or| (or, or.geometry().loc.as_global())) + { + callback(Stage::OverrideRedirect { surface, location })?; + } + + // sticky window popups + if !has_fullscreen { + callback(Stage::StickyPopups(&set.sticky_layer))?; + } + } + + if element_filter != ElementFilter::LayerShellOnly { + // previous workspace popups + if let Some((previous_handle, _, offset)) = previous.as_ref() { + let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + + callback(Stage::WorkspacePopups { + workspace, + offset: *offset, + })?; + } + + // current workspace popups + let Some(workspace) = shell.workspaces.space_for_handle(¤t.0) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + + callback(Stage::WorkspacePopups { + workspace, + offset: current_offset, + })?; + } + + if !has_fullscreen { + // bottom layer popups + for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location, + })?; + } + } + + if let Some((_, has_fullscreen, offset)) = previous.as_ref() { + if !has_fullscreen { + // previous bottom layer popups + for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location: location + offset.as_global(), + })?; + } + } + } + + if !has_fullscreen { + // background layer popups + for (layer, popup, location) in layer_popups(output, Layer::Background, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location, + })?; + } + } + + if let Some((_, has_fullscreen, offset)) = previous.as_ref() { + if !has_fullscreen { + // previous background layer popups + for (layer, popup, location) in layer_popups(output, Layer::Background, element_filter) + { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location: location + offset.as_global(), + })?; + } + } + } + + if !has_fullscreen { + // top-layer shell + 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))?; + } + } + + if element_filter != ElementFilter::LayerShellOnly { + // workspace windows + callback(Stage::Workspace { + workspace, + offset: current_offset, + })?; + + // previous workspace windows + if let Some((previous_handle, _, offset)) = previous.as_ref() { + let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + callback(Stage::Workspace { + workspace, + offset: *offset, + })?; + } + } + + if !has_fullscreen { + // bottom layer + for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { + location += current_offset.as_global(); + callback(Stage::LayerSurface { layer, location })?; + } + } + + if let Some((_, has_fullscreen, offset)) = previous.as_ref() { + if !has_fullscreen { + // previous bottom layer + for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { + location += offset.as_global(); + callback(Stage::LayerSurface { layer, location })?; + } + } + } + + if !has_fullscreen { + // background layer + for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) { + location += current_offset.as_global(); + callback(Stage::LayerSurface { layer, location })?; + } + } + + if let Some((_, has_fullscreen, offset)) = previous.as_ref() { + if !has_fullscreen { + // previous background layer + for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) { + location += offset.as_global(); + callback(Stage::LayerSurface { layer, location })?; + } + } + } + + ControlFlow::Continue(()) +} + +fn layer_popups<'a>( + output: &'a Output, + layer: Layer, + element_filter: ElementFilter, +) -> impl Iterator)> + 'a { + layer_surfaces(output, layer, element_filter).flat_map(move |(surface, location)| { + let location_clone = location.clone(); + let surface_clone = surface.clone(); + PopupManager::popups_for_surface(surface.wl_surface()).map(move |(popup, popup_offset)| { + let offset = (popup_offset - popup.geometry().loc).as_global(); + (surface_clone.clone(), popup, (location_clone + offset)) + }) + }) +} + +fn layer_surfaces<'a>( + output: &'a Output, + layer: Layer, + element_filter: ElementFilter, +) -> impl Iterator)> + 'a { + // we want to avoid deadlocks on the layer-map in callbacks, so we need to clone the layer surfaces + let layers = { + let layer_map = layer_map_for_output(output); + layer_map + .layers_on(layer) + .rev() + .map(|s| (s.clone(), layer_map.layer_geometry(s).unwrap())) + .collect::>() + }; + + layers + .into_iter() + .filter(move |(s, _)| { + !(element_filter == ElementFilter::ExcludeWorkspaceOverview + && s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE) + }) + .map(|(surface, geometry)| (surface, geometry.loc.as_local().to_global(output))) +} diff --git a/src/shell/grabs/menu/item.rs b/src/shell/grabs/menu/item.rs index b6aa0227..bf69e3ab 100644 --- a/src/shell/grabs/menu/item.rs +++ b/src/shell/grabs/menu/item.rs @@ -3,17 +3,17 @@ use cosmic::{ iced_core::{ event, layout, mouse, overlay, renderer::{Quad, Style}, - widget::{tree, Id, OperationOutputWrapper, Tree, Widget}, + widget::{tree, Id, Tree, Widget}, Background, Border, Clipboard, Color, Event, Layout, Length, Rectangle, Renderer as IcedRenderer, Shell, Size, }, - widget::button::StyleSheet, + widget::button::Catalog, }; pub struct SubmenuItem<'a, Message> { elem: cosmic::Element<'a, Message>, idx: usize, - styling: ::Style, + styling: ::Class, } impl<'a, Message> SubmenuItem<'a, Message> { @@ -25,7 +25,7 @@ impl<'a, Message> SubmenuItem<'a, Message> { } } - pub fn style(mut self, style: ::Style) -> Self { + pub fn style(mut self, style: ::Class) -> Self { self.styling = style; self } @@ -133,7 +133,7 @@ where state: &mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, - operation: &mut dyn cosmic::widget::Operation>, + operation: &mut dyn cosmic::widget::Operation<()>, ) { let state = &mut state.children[0]; let layout = layout.children().next().unwrap(); @@ -206,10 +206,13 @@ where state: &'b mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, + translation: cosmic::iced::Vector, ) -> Option> { let state = &mut state.children[0]; let layout = layout.children().next().unwrap(); - self.elem.as_widget_mut().overlay(state, layout, renderer) + self.elem + .as_widget_mut() + .overlay(state, layout, renderer, translation) } } diff --git a/src/shell/grabs/menu/mod.rs b/src/shell/grabs/menu/mod.rs index c0a95019..62fcaa7e 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -7,10 +7,10 @@ use calloop::LoopHandle; use cosmic::{ iced::{Alignment, Background}, iced_core::{alignment::Horizontal, Border, Length, Rectangle as IcedRectangle}, - iced_widget::{self, text::Appearance as TextAppearance, Column, Row}, + iced_widget::{self, text::Style as TextStyle, Column, Row}, theme, widget::{button, divider, horizontal_space, icon::from_name, text}, - Apply as _, Command, + Apply as _, Task, }; use smithay::{ backend::{ @@ -202,7 +202,7 @@ impl Program for ContextMenu { &mut self, message: Self::Message, loop_handle: &LoopHandle<'static, crate::state::State>, - ) -> Command { + ) -> Task { match message { Message::ItemPressed(idx) => { if let Some(Item::Entry { on_press, .. }) = self.items.get_mut(idx) { @@ -331,7 +331,7 @@ impl Program for ContextMenu { } }; - Command::none() + Task::none() } fn view(&self) -> cosmic::Element<'_, Self::Message> { @@ -350,7 +350,7 @@ impl Program for ContextMenu { match item { Item::Separator => divider::horizontal::light().into(), Item::Submenu { title, .. } => Row::with_children(vec![ - horizontal_space(16).into(), + horizontal_space().width(16).into(), text::body(title).width(mode).into(), from_name("go-next-symbolic") .size(16) @@ -361,7 +361,7 @@ impl Program for ContextMenu { .spacing(8) .width(width) .padding([8, 16]) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(|row| item::SubmenuItem::new(row, idx)) .style(theme::Button::MenuItem) .into(), @@ -378,20 +378,20 @@ impl Program for ContextMenu { .size(16) .prefer_svg(true) .icon() - .style(theme::Svg::custom(|theme| iced_widget::svg::Appearance { + .class(theme::Svg::custom(|theme| iced_widget::svg::Style { color: Some(theme.cosmic().accent.base.into()), })) .into() } else { - horizontal_space(16).into() + horizontal_space().width(16).into() }, text::body(title) .width(mode) - .style(if *disabled { + .class(if *disabled { theme::Text::Custom(|theme| { let mut color = theme.cosmic().background.component.on; color.alpha *= 0.5; - TextAppearance { + TextStyle { color: Some(color.into()), } }) @@ -399,17 +399,17 @@ impl Program for ContextMenu { theme::Text::Default }) .into(), - horizontal_space(16).into(), + horizontal_space().width(16).into(), ]; if let Some(shortcut) = shortcut.as_ref() { components.push( text::body(shortcut) - .horizontal_alignment(Horizontal::Right) + .align_x(Horizontal::Right) .width(Length::Shrink) - .style(theme::Text::Custom(|theme| { + .class(theme::Text::Custom(|theme| { let mut color = theme.cosmic().background.component.on; color.alpha *= 0.75; - TextAppearance { + TextStyle { color: Some(color.into()), } })) @@ -420,12 +420,12 @@ impl Program for ContextMenu { Row::with_children(components) .spacing(8) .width(mode) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(button::custom) .width(width) .padding([8, 16]) .on_press_maybe((!disabled).then_some(Message::ItemPressed(idx))) - .style(theme::Button::MenuItem) + .class(theme::Button::MenuItem) .into() } } @@ -433,15 +433,15 @@ impl Program for ContextMenu { .width(Length::Shrink) .apply(iced_widget::container) .padding(1) - .style(theme::Container::custom(|theme| { + .class(theme::Container::custom(|theme| { let cosmic = theme.cosmic(); let component = &cosmic.background.component; - iced_widget::container::Appearance { + iced_widget::container::Style { icon_color: Some(cosmic.accent.base.into()), text_color: Some(component.on.into()), background: Some(Background::Color(component.base.into())), border: Border { - radius: 8.0.into(), + radius: cosmic.radius_s().into(), width: 1.0, color: component.divider.into(), }, diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 9a41702e..5081ead8 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -2,8 +2,7 @@ use crate::{ backend::render::{ - cursor::CursorState, element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, - SplitRenderElements, Usage, + cursor::CursorState, element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, Usage, }, shell::{ element::{ @@ -28,7 +27,7 @@ use smithay::{ ImportAll, ImportMem, Renderer, }, }, - desktop::{layer_map_for_output, space::SpaceElement}, + desktop::{layer_map_for_output, space::SpaceElement, WindowSurfaceType}, input::{ pointer::{ AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent, @@ -146,6 +145,7 @@ impl MoveGrabState { }; let gaps = (theme.gaps.0 as i32, theme.gaps.1 as i32); + let thickness = self.indicator_thickness.max(1); let snapping_indicator = match &self.snapping_zone { Some(t) if &self.cursor_output == output => { @@ -156,7 +156,7 @@ impl MoveGrabState { renderer, Key::Window(Usage::SnappingIndicator, self.window.key()), overlay_geometry, - 3, + thickness, theme.radius_s()[0] as u8, // TODO: Fix once shaders support 4 corner radii customization 1.0, output_scale.x, @@ -181,12 +181,18 @@ impl MoveGrabState { _ => vec![], }; - let SplitRenderElements { - w_elements, - p_elements, - } = self + let w_elements = self .window - .split_render_elements::>( + .render_elements::>( + renderer, + (render_location - self.window.geometry().loc) + .to_physical_precise_round(output_scale), + output_scale, + alpha, + ); + let p_elements = self + .window + .popup_render_elements::>( renderer, (render_location - self.window.geometry().loc) .to_physical_precise_round(output_scale), @@ -858,9 +864,10 @@ impl Drop for MoveGrab { let pointer = seat.get_pointer().unwrap(); let current_location = pointer.current_location(); - if let Some((target, offset)) = - mapped.focus_under(current_location - position.as_logical().to_f64()) - { + if let Some((target, offset)) = mapped.focus_under( + current_location - position.as_logical().to_f64(), + WindowSurfaceType::ALL, + ) { pointer.motion( state, Some(( diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs index e0ee2911..5cbd26b4 100644 --- a/src/shell/layout/floating/grabs/resize.rs +++ b/src/shell/layout/floating/grabs/resize.rs @@ -3,6 +3,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use crate::{ + backend::render::cursor::CursorState, shell::{ element::CosmicMapped, focus::target::PointerFocusTarget, @@ -15,7 +16,7 @@ use smithay::{ desktop::{space::SpaceElement, WindowSurface}, input::{ pointer::{ - AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, + AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, @@ -390,6 +391,22 @@ impl ResizeSurfaceGrab { .0 .store(true, Ordering::SeqCst); + let shape = match edges { + ResizeEdge::TOP_LEFT => Some(CursorIcon::NwResize), + ResizeEdge::TOP_RIGHT => Some(CursorIcon::NeResize), + ResizeEdge::BOTTOM_LEFT => Some(CursorIcon::SwResize), + ResizeEdge::BOTTOM_RIGHT => Some(CursorIcon::SeResize), + ResizeEdge::TOP => Some(CursorIcon::NResize), + ResizeEdge::RIGHT => Some(CursorIcon::EResize), + ResizeEdge::BOTTOM => Some(CursorIcon::SResize), + ResizeEdge::LEFT => Some(CursorIcon::WResize), + _ => None, + }; + if let Some(shape) = shape { + let cursor_state = seat.user_data().get::().unwrap(); + cursor_state.lock().unwrap().set_shape(shape); + } + ResizeSurfaceGrab { start_data, seat: seat.clone(), @@ -524,3 +541,10 @@ impl ResizeSurfaceGrab { } } } + +impl Drop for ResizeSurfaceGrab { + fn drop(&mut self) { + let cursor_state = self.seat.user_data().get::().unwrap(); + cursor_state.lock().unwrap().unset_shape(); + } +} diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 140fb5d5..4507d7ea 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -24,7 +24,7 @@ use smithay::{ }; use crate::{ - backend::render::{element::AsGlowRenderer, IndicatorShader, Key, SplitRenderElements, Usage}, + backend::render::{element::AsGlowRenderer, IndicatorShader, Key, Usage}, shell::{ element::{ resize_indicator::ResizeIndicator, @@ -728,16 +728,139 @@ impl FloatingLayout { self.space.element_geometry(elem).map(RectExt::as_local) } - pub fn element_under(&self, location: Point) -> Option { + pub fn popup_element_under(&self, location: Point) -> Option { self.space - .element_under(location.as_logical()) - .map(|(mapped, _)| mapped.clone().into()) + .elements() + .rev() + .map(|e| { + ( + e, + self.space.element_location(e).unwrap() - e.geometry().loc, + ) + }) + .filter(|(e, render_location)| { + let mut bbox = e.bbox(); + bbox.loc += *render_location; + bbox.to_f64().contains(location.as_logical()) + }) + .find_map(|(e, render_location)| { + let render_location = render_location.as_local().to_f64(); + let point = location - render_location; + if e.focus_under( + point.as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { + Some(e.clone().into()) + } else { + None + } + }) } - pub fn surface_under( - &mut self, + pub fn toplevel_element_under( + &self, + location: Point, + ) -> Option { + self.space + .elements() + .rev() + .map(|e| { + ( + e, + self.space.element_location(e).unwrap() - e.geometry().loc, + ) + }) + .filter(|(e, render_location)| { + let mut bbox = e.bbox(); + bbox.loc += *render_location; + bbox.to_f64().contains(location.as_logical()) + }) + .find_map(|(e, render_location)| { + let render_location = render_location.as_local().to_f64(); + let point = location - render_location; + if e.focus_under( + point.as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { + Some(e.clone().into()) + } else { + None + } + }) + } + + pub fn popup_surface_under( + &self, location: Point, ) -> Option<(PointerFocusTarget, Point)> { + self.space + .elements() + .rev() + .map(|e| { + ( + e, + self.space.element_location(e).unwrap() - e.geometry().loc, + ) + }) + .filter(|(e, render_location)| { + let mut bbox = e.bbox(); + bbox.loc += *render_location; + bbox.to_f64().contains(location.as_logical()) + }) + .find_map(|(e, render_location)| { + let render_location = render_location.as_local().to_f64(); + let point = location - render_location; + e.focus_under( + point.as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .map(|(surface, surface_offset)| { + (surface, render_location + surface_offset.as_local()) + }) + }) + } + + pub fn toplevel_surface_under( + &self, + location: Point, + ) -> Option<(PointerFocusTarget, Point)> { + self.space + .elements() + .rev() + .map(|e| { + ( + e, + self.space.element_location(e).unwrap() - e.geometry().loc, + ) + }) + .filter(|(e, render_location)| { + let mut bbox = e.bbox(); + bbox.loc += *render_location; + bbox.to_f64().contains(location.as_logical()) + }) + .find_map(|(e, render_location)| { + let render_location = render_location.as_local().to_f64(); + let point = location - render_location; + e.focus_under( + point.as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .map(|(surface, surface_offset)| { + (surface, render_location + surface_offset.as_local()) + }) + }) + } + + pub fn update_pointer_position(&mut self, location: Option>) { + let Some(location) = location else { + self.hovered_stack.take(); + return; + }; + let res = self .space .element_under(location.as_logical()) @@ -754,15 +877,6 @@ impl FloatingLayout { } else { self.hovered_stack.take(); } - - res.and_then(|(element, space_offset)| { - let point = location - space_offset.to_f64(); - element - .focus_under(point.as_logical()) - .map(|(surface, surface_offset)| { - (surface, space_offset.to_f64() + surface_offset.as_local()) - }) - }) } pub fn stacking_indicator(&self) -> Option> { @@ -1260,6 +1374,52 @@ impl FloatingLayout { } self.refresh(); //fixup any out of bounds elements } + #[profiling::function] + pub fn render_popups( + &self, + renderer: &mut R, + alpha: f32, + ) -> Vec> + where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, + { + let output = self.space.outputs().next().unwrap(); + let output_scale = output.current_scale().fractional_scale(); + + let mut elements = Vec::default(); + + for elem in self + .animations + .iter() + .filter(|(_, anim)| matches!(anim, Animation::Minimize { .. })) + .map(|(elem, _)| elem) + .chain(self.space.elements().rev()) + { + let (geometry, alpha) = self + .animations + .get(elem) + .map(|anim| (*anim.previous_geometry(), alpha * anim.alpha())) + .unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha)); + + let render_location = geometry.loc - elem.geometry().loc.as_local(); + elements.extend( + elem.popup_render_elements( + renderer, + render_location + .as_logical() + .to_physical_precise_round(output_scale), + output_scale.into(), + alpha, + ), + ); + } + + elements + } #[profiling::function] pub fn render( @@ -1270,7 +1430,7 @@ impl FloatingLayout { indicator_thickness: u8, alpha: f32, theme: &cosmic::theme::CosmicTheme, - ) -> SplitRenderElements> + ) -> Vec> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -1285,7 +1445,7 @@ impl FloatingLayout { }; let output_scale = output.current_scale().fractional_scale(); - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::default(); for elem in self .animations @@ -1301,10 +1461,7 @@ impl FloatingLayout { .unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha)); let render_location = geometry.loc - elem.geometry().loc.as_local(); - let SplitRenderElements { - mut w_elements, - p_elements, - } = elem.split_render_elements( + let mut window_elements = elem.render_elements( renderer, render_location .as_logical() @@ -1331,7 +1488,7 @@ impl FloatingLayout { y: geometry.size.h as f64 / buffer_size.h as f64, }; - w_elements = w_elements + window_elements = window_elements .into_iter() .map(|element| match element { CosmicMappedRenderElement::Stack(elem) => { @@ -1387,7 +1544,7 @@ impl FloatingLayout { resize.resize(resize_geometry.size.as_logical()); resize.output_enter(output, Rectangle::default() /* unused */); - elements.w_elements.extend( + window_elements.extend( resize .render_elements::>( renderer, @@ -1419,12 +1576,11 @@ impl FloatingLayout { active_window_hint.blue, ], ); - elements.w_elements.push(element.into()); + window_elements.push(element.into()); } } - elements.w_elements.extend(w_elements); - elements.p_elements.extend(p_elements); + elements.extend(window_elements); } elements diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index feeebcd8..395a3466 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -2,8 +2,8 @@ use crate::{ backend::render::{ - element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, SplitRenderElements, Usage, - ACTIVE_GROUP_COLOR, GROUP_COLOR, + element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, Usage, ACTIVE_GROUP_COLOR, + GROUP_COLOR, }, shell::{ element::{ @@ -56,11 +56,11 @@ use smithay::{ glow::GlowRenderer, ImportAll, ImportMem, Renderer, }, - desktop::{layer_map_for_output, space::SpaceElement, PopupKind}, + desktop::{layer_map_for_output, space::SpaceElement, PopupKind, WindowSurfaceType}, input::Seat, output::Output, reexports::wayland_server::Client, - utils::{IsAlive, Logical, Point, Rectangle, Scale, Size}, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Size}, wayland::{compositor::add_blocker, seat::WaylandFocus}, }; use std::{ @@ -3105,16 +3105,24 @@ impl TilingLayout { None } - pub fn element_under(&self, location_f64: Point) -> Option { + pub fn popup_element_under( + &self, + location_f64: Point, + ) -> Option { let location = location_f64.to_i32_round(); for (mapped, geo) in self.mapped() { if !mapped.bbox().contains((location - geo.loc).as_logical()) { continue; } - if mapped.is_in_input_region( - &((location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64()), - ) { + + if mapped + .focus_under( + (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { return Some(mapped.clone().into()); } } @@ -3122,33 +3130,67 @@ impl TilingLayout { None } - pub fn surface_under( - &mut self, + pub fn toplevel_element_under( + &self, + location_f64: Point, + ) -> Option { + let location = location_f64.to_i32_round(); + + for (mapped, geo) in self.mapped() { + if !mapped.bbox().contains((location - geo.loc).as_logical()) { + continue; + } + + if mapped + .focus_under( + (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { + return Some(mapped.clone().into()); + } + } + + None + } + + pub fn popup_surface_under( + &self, location_f64: Point, overview: OverviewMode, ) -> Option<(PointerFocusTarget, Point)> { - let gaps = self.gaps(); - let last_overview_hover = &mut self.last_overview_hover; - let placeholder_id = &self.placeholder_id; - let tree = &self.queue.trees.back().unwrap().0; - let root = tree.root_node_id()?; let location = location_f64.to_i32_round(); - { - let output_geo = - Rectangle::from_loc_and_size((0, 0), self.output.geometry().size.as_logical()) - .as_local(); - if !output_geo.contains(location) { - return None; + if matches!(overview, OverviewMode::None) { + for (mapped, geo) in self.mapped() { + if !mapped.bbox().contains((location - geo.loc).as_logical()) { + continue; + } + 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, + ) { + return Some(( + target, + geo.loc.to_f64() - mapped.geometry().loc.as_local().to_f64() + + surface_offset.as_local(), + )); + } } } - if !matches!( - overview, - OverviewMode::Started(_, _) | OverviewMode::Active(_) - ) { - last_overview_hover.take(); - } + None + } + + pub fn toplevel_surface_under( + &self, + location_f64: Point, + overview: OverviewMode, + ) -> Option<(PointerFocusTarget, Point)> { + let tree = &self.queue.trees.back().unwrap().0; + let root = tree.root_node_id()?; + let location = location_f64.to_i32_round(); if matches!(overview, OverviewMode::None) { for (mapped, geo) in self.mapped() { @@ -3157,6 +3199,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, ) { return Some(( target, @@ -3204,7 +3247,10 @@ impl TilingLayout { + mapped.geometry().loc.to_f64().as_local()) .as_logical(); mapped - .focus_under(test_point) + .focus_under( + test_point, + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) .map(|(surface, surface_offset)| { ( surface, @@ -3257,7 +3303,37 @@ impl TilingLayout { } _ => None, } - } else if matches!( + } else { + None + } + } + + pub fn update_pointer_position( + &mut self, + location_f64: Option>, + overview: OverviewMode, + ) { + let gaps = self.gaps(); + let last_overview_hover = &mut self.last_overview_hover; + let placeholder_id = &self.placeholder_id; + let tree = &self.queue.trees.back().unwrap().0; + let Some(root) = tree.root_node_id() else { + return; + }; + + if !matches!( + overview, + OverviewMode::Started(_, _) | OverviewMode::Active(_) + ) || location_f64.is_none() + { + last_overview_hover.take(); + return; + } + + let location_f64 = location_f64.unwrap(); + let location = location_f64.to_i32_round(); + + if matches!( overview.active_trigger(), Some(Trigger::Pointer(_) | Trigger::Touch(_)) ) { @@ -3319,7 +3395,9 @@ impl TilingLayout { } if let Some(res_id) = result { - let mut last_geometry = *geometries.get(&res_id)?; + let Some(mut last_geometry) = geometries.get(&res_id).copied() else { + return; + }; let node = tree.get(&res_id).unwrap(); let data = node.data().clone(); @@ -3716,10 +3794,6 @@ impl TilingLayout { } } } - - None - } else { - None } } @@ -3863,7 +3937,7 @@ impl TilingLayout { resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, theme: &cosmic::theme::CosmicTheme, - ) -> Result>, OutputNotMapped> + ) -> Result>, OutputNotMapped> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -3896,7 +3970,7 @@ impl TilingLayout { }; let draw_groups = overview.0.alpha(); - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::default(); let is_overview = !matches!(overview.0, OverviewMode::None); let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_)))) @@ -3931,7 +4005,7 @@ impl TilingLayout { .unzip(); // all old windows we want to fade out - elements.extend(render_old_tree( + elements.extend(render_old_tree_windows( reference_tree, target_tree, renderer, @@ -3969,7 +4043,7 @@ impl TilingLayout { .unzip(); // all alive windows - elements.extend(render_new_tree( + elements.extend(render_new_tree_windows( target_tree, reference_tree, renderer, @@ -4001,8 +4075,135 @@ impl TilingLayout { // tiling hints if let Some(group_elements) = group_elements { - elements.w_elements.extend(group_elements); + elements.extend(group_elements); + } + + Ok(elements) + } + + #[profiling::function] + pub fn render_popups( + &self, + renderer: &mut R, + seat: Option<&Seat>, + non_exclusive_zone: Rectangle, + overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), + theme: &cosmic::theme::CosmicTheme, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, + { + let output_scale = self.output.current_scale().fractional_scale(); + + let (target_tree, duration, _) = if self.queue.animation_start.is_some() { + self.queue + .trees + .get(1) + .expect("Animation ongoing, should have two trees") + } else { + self.queue.trees.front().unwrap() + }; + let reference_tree = self + .queue + .animation_start + .is_some() + .then(|| &self.queue.trees.front().unwrap().0); + + let percentage = if let Some(animation_start) = self.queue.animation_start { + let percentage = Instant::now().duration_since(animation_start).as_millis() as f32 + / duration.as_millis() as f32; + ease(EaseInOutCubic, 0.0, 1.0, percentage) + } else { + 1.0 + }; + let draw_groups = overview.0.alpha(); + + let mut elements = Vec::default(); + + let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_)))) + .then(|| self.last_overview_hover.as_ref().map(|x| &x.1)); + let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() { + Some(desc.clone()) + } else { + None + }; + + // all gone windows and fade them out + let old_geometries = if let Some(reference_tree) = reference_tree.as_ref() { + let (geometries, _) = if let Some(transition) = draw_groups { + Some(geometries_for_groupview( + reference_tree, + &mut *renderer, + non_exclusive_zone, + seat, // TODO: Would be better to be an old focus, + // but for that we have to associate focus with a tree (and animate focus changes properly) + 1.0 - transition, + transition, + output_scale, + &self.placeholder_id, + is_mouse_tiling, + swap_desc.clone(), + overview.1.as_ref().and_then(|(_, tree)| tree.clone()), + theme, + )) + } else { + None + } + .unzip(); + + // all old windows we want to fade out + elements.extend(render_old_tree_popups( + reference_tree, + target_tree, + renderer, + geometries.clone(), + output_scale, + percentage, + swap_desc.is_some(), + )); + + geometries + } else { + None + }; + + let (geometries, _) = if let Some(transition) = draw_groups { + Some(geometries_for_groupview( + target_tree, + &mut *renderer, + non_exclusive_zone, + seat, + transition, + transition, + output_scale, + &self.placeholder_id, + is_mouse_tiling, + swap_desc.clone(), + overview.1.as_ref().and_then(|(_, tree)| tree.clone()), + theme, + )) + } else { + None } + .unzip(); + + // all alive windows + elements.extend(render_new_tree_popups( + target_tree, + reference_tree, + renderer, + geometries, + old_geometries, + seat, + &self.output, + percentage, + overview, + swap_desc.clone(), + )); Ok(elements) } @@ -4690,7 +4891,48 @@ where (geometries, elements) } -fn render_old_tree( +fn render_old_tree_popups( + reference_tree: &Tree, + target_tree: &Tree, + renderer: &mut R, + geometries: Option>>, + output_scale: f64, + percentage: f32, + is_swap_mode: bool, +) -> Vec> +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, +{ + let mut elements = Vec::default(); + + render_old_tree( + reference_tree, + target_tree, + geometries, + output_scale, + percentage, + is_swap_mode, + |mapped, elem_geometry, geo, alpha, _| { + elements.extend( + mapped.popup_render_elements::>( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + Scale::from(output_scale), + alpha, + ), + ); + }, + ); + + elements +} + +fn render_old_tree_windows( reference_tree: &Tree, target_tree: &Tree, renderer: &mut R, @@ -4700,7 +4942,7 @@ fn render_old_tree( indicator_thickness: u8, is_swap_mode: bool, theme: &cosmic::theme::CosmicTheme, -) -> SplitRenderElements> +) -> Vec> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -4709,19 +4951,91 @@ where CosmicStackRenderElement: RenderElement, { let window_hint = crate::theme::active_window_hint(theme); - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::default(); + + render_old_tree( + reference_tree, + target_tree, + geometries, + output_scale, + percentage, + is_swap_mode, + |mapped, elem_geometry, geo, alpha, is_minimizing| { + let window_elements = mapped.render_elements::>( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, + Scale::from(output_scale), + alpha, + ); - if let Some(root) = reference_tree.root_node_id() { - let geometries = geometries.unwrap_or_default(); - reference_tree - .traverse_pre_order_ids(root) - .unwrap() - .filter(|node_id| reference_tree.get(node_id).unwrap().data().is_mapped(None)) - .map( - |node_id| match reference_tree.get(&node_id).unwrap().data() { - Data::Mapped { - mapped, - last_geometry, + elements.extend(window_elements.into_iter().flat_map(|element| { + match element { + CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( + std::iter::once(elem), + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + geo.as_logical().to_physical_precise_round(output_scale), + elem_geometry, + ConstrainScaleBehavior::Stretch, + ConstrainAlign::CENTER, + output_scale, + ) + .next() + .map(CosmicMappedRenderElement::TiledStack), + CosmicMappedRenderElement::Window(elem) => constrain_render_elements( + std::iter::once(elem), + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + geo.as_logical().to_physical_precise_round(output_scale), + elem_geometry, + ConstrainScaleBehavior::Stretch, + ConstrainAlign::CENTER, + output_scale, + ) + .next() + .map(CosmicMappedRenderElement::TiledWindow), + x => Some(x), + } + })); + if is_minimizing && indicator_thickness > 0 { + elements.push(CosmicMappedRenderElement::FocusIndicator( + IndicatorShader::focus_element( + renderer, + Key::Window(Usage::FocusIndicator, mapped.clone().key()), + geo, + indicator_thickness, + output_scale, + alpha, + [window_hint.red, window_hint.green, window_hint.blue], + ), + )); + } + }, + ); + + elements +} + +fn render_old_tree( + reference_tree: &Tree, + target_tree: &Tree, + geometries: Option>>, + output_scale: f64, + percentage: f32, + is_swap_mode: bool, + mut processor: impl FnMut(&CosmicMapped, Rectangle, Rectangle, f32, bool), +) { + if let Some(root) = reference_tree.root_node_id() { + let geometries = geometries.unwrap_or_default(); + reference_tree + .traverse_pre_order_ids(root) + .unwrap() + .filter(|node_id| reference_tree.get(node_id).unwrap().data().is_mapped(None)) + .map( + |node_id| match reference_tree.get(&node_id).unwrap().data() { + Data::Mapped { + mapped, + last_geometry, minimize_rect, .. } => ( @@ -4779,71 +5093,71 @@ where }; let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); - let SplitRenderElements { - w_elements, - p_elements, - } = mapped.split_render_elements::>( - renderer, - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - Scale::from(output_scale), - alpha, - ); - elements - .w_elements - .extend(w_elements.into_iter().flat_map(|element| { - match element { - CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( - std::iter::once(elem), - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - geo.as_logical().to_physical_precise_round(output_scale), - elem_geometry, - ConstrainScaleBehavior::Stretch, - ConstrainAlign::CENTER, - output_scale, - ) - .next() - .map(CosmicMappedRenderElement::TiledStack), - CosmicMappedRenderElement::Window(elem) => constrain_render_elements( - std::iter::once(elem), - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - geo.as_logical().to_physical_precise_round(output_scale), - elem_geometry, - ConstrainScaleBehavior::Stretch, - ConstrainAlign::CENTER, - output_scale, - ) - .next() - .map(CosmicMappedRenderElement::TiledWindow), - x => Some(x), - } - })); - if minimize_geo.is_some() && indicator_thickness > 0 { - elements - .w_elements - .push(CosmicMappedRenderElement::FocusIndicator( - IndicatorShader::focus_element( - renderer, - Key::Window(Usage::FocusIndicator, mapped.clone().key()), - geo, - indicator_thickness, - output_scale, - alpha, - [window_hint.red, window_hint.green, window_hint.blue], - ), - )); - } - elements.p_elements.extend(p_elements); + processor(mapped, elem_geometry, geo, alpha, minimize_geo.is_some()) }); } +} - elements +fn render_new_tree_popups( + target_tree: &Tree, + reference_tree: Option<&Tree>, + renderer: &mut R, + geometries: Option>>, + old_geometries: Option>>, + seat: Option<&Seat>, + output: &Output, + percentage: f32, + overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), + swap_desc: Option, +) -> Vec> +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, +{ + let mut popup_elements = Vec::new(); + let output_scale = output.current_scale().fractional_scale(); + + let is_active_output = seat + .map(|seat| &seat.active_output() == output) + .unwrap_or(false); + + let (_, swap_tree) = overview.1.unzip(); + let swap_desc = swap_desc.filter(|_| is_active_output); + let swap_tree = swap_tree.flatten().filter(|_| is_active_output); + + render_new_tree( + target_tree, + reference_tree, + geometries, + old_geometries, + percentage, + swap_tree, + swap_desc.as_ref(), + |_node_id, data, geo, _original_geo, alpha, _| { + if let Data::Mapped { mapped, .. } = data { + let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); + + popup_elements.extend( + mapped.popup_render_elements::>( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + Scale::from(output_scale), + alpha, + ), + ); + } + }, + ); + + popup_elements } -fn render_new_tree( +fn render_new_tree_windows( target_tree: &Tree, reference_tree: Option<&Tree>, renderer: &mut R, @@ -4862,7 +5176,7 @@ fn render_new_tree( swapping_stack_surface_id: &Id, placeholder_id: &Id, theme: &cosmic::theme::CosmicTheme, -) -> SplitRenderElements> +) -> Vec> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -4905,7 +5219,6 @@ where let mut animating_window_elements = Vec::new(); let mut window_elements = Vec::new(); - let mut popup_elements = Vec::new(); let mut group_backdrop = None; let mut indicators = Vec::new(); @@ -4916,10 +5229,11 @@ where let output_scale = output.current_scale().fractional_scale(); let (swap_indicator, swap_tree) = overview.1.unzip(); - let swap_tree = swap_tree.flatten().filter(|_| is_active_output); let swap_desc = swap_desc.filter(|_| is_active_output); + let swap_tree = swap_tree.flatten().filter(|_| is_active_output); let window_hint = crate::theme::active_window_hint(theme); let group_color = GROUP_COLOR; + // render placeholder, if we are swapping to an empty workspace if target_tree.root_node_id().is_none() && swap_desc.is_some() { window_elements.push( @@ -4968,165 +5282,48 @@ where (swap_geo.loc.as_logical() - window_geo.loc).to_physical_precise_round(output_scale); swap_elements.extend( - window - .render_elements::>( - renderer, - render_loc, - output_scale.into(), - 1.0, - ) - .into_iter() - .map(|window| { - CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element( - window, - swap_geo - .loc - .as_logical() - .to_physical_precise_round(output_scale), - ease( - Linear, - 1.0, - swap_factor(window_geo.size), - transition.unwrap_or(1.0), - ), - )) - }), + AsRenderElements::render_elements::>( + &window, + renderer, + render_loc, + output_scale.into(), + 1.0, + ) + .into_iter() + .map(|window| { + CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element( + window, + swap_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale), + ease( + Linear, + 1.0, + swap_factor(window_geo.size), + transition.unwrap_or(1.0), + ), + )) + }), ) } // render actual tree nodes - let old_geometries = old_geometries.unwrap_or_default(); - let geometries = geometries.unwrap_or_default(); - target_tree - .root_node_id() - .into_iter() - .flat_map(|root| target_tree.traverse_pre_order_ids(root).unwrap()) - .map(|id| (target_tree, id)) - .chain( - swap_tree - .into_iter() - .flat_map(|tree| { - let sub_root = &swap_desc.as_ref().unwrap().node; - if swap_desc.as_ref().unwrap().stack_window.is_none() { - Some( - tree.traverse_pre_order_ids(sub_root) - .unwrap() - .map(move |id| (tree, id)), - ) - } else { - None - } - }) - .flatten(), - ) - .for_each(|(target_tree, node_id)| { - let data = target_tree.get(&node_id).unwrap().data(); - let (original_geo, scaled_geo) = (data.geometry(), geometries.get(&node_id)); - - let (old_original_geo, old_scaled_geo) = - if let Some(reference_tree) = reference_tree.as_ref() { - if let Some(root) = reference_tree.root_node_id() { - reference_tree - .traverse_pre_order_ids(root) - .unwrap() - .find(|id| &node_id == id) - .map(|node_id| { - ( - reference_tree.get(&node_id).unwrap().data().geometry(), - old_geometries.get(&node_id), - ) - }) - } else { - None - } - } else { - None - } - .unzip(); - let mut old_geo = old_original_geo.map(|original_geo| { - let (scale, offset) = old_scaled_geo - .unwrap() - .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) - .unwrap_or_else(|| (1.0.into(), (0, 0).into())); - ( - old_scaled_geo - .unwrap() - .map(|adapted_geo| { - Rectangle::from_loc_and_size( - adapted_geo.loc + offset, - ( - (original_geo.size.w as f64 * scale).round() as i32, - (original_geo.size.h as f64 * scale).round() as i32, - ), - ) - }) - .unwrap_or(*original_geo), - 1.0, - ) - }); - - let was_minimized = if let Data::Mapped { - minimize_rect: Some(minimize_rect), - .. - } = &data - { - old_geo = Some((*minimize_rect, (percentage * 2.0).min(1.0))); - true - } else { - false - }; - - let (scale, offset) = scaled_geo - .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) - .unwrap_or_else(|| (1.0.into(), (0, 0).into())); - let new_geo = scaled_geo - .map(|adapted_geo| { - Rectangle::from_loc_and_size( - adapted_geo.loc + offset, - ( - (original_geo.size.w as f64 * scale).round() as i32, - (original_geo.size.h as f64 * scale).round() as i32, - ), - ) - }) - .unwrap_or(*original_geo); - - let (geo, alpha, animating) = if let Some((old_geo, alpha)) = old_geo.filter(|_| { - swap_desc - .as_ref() - .map(|desc| desc.node != node_id && desc.stack_window.is_none()) - .unwrap_or(true) - }) { - ( - if was_minimized { - ease( - EaseInOutCubic, - EaseRectangle(old_geo), - EaseRectangle(new_geo), - percentage, - ) - .unwrap() - } else { - ease( - Linear, - EaseRectangle(old_geo), - EaseRectangle(new_geo), - percentage, - ) - .unwrap() - }, - alpha, - old_geo != new_geo, - ) - } else { - (new_geo, percentage, false) - }; - + render_new_tree( + target_tree, + reference_tree, + geometries, + old_geometries, + percentage, + swap_tree, + swap_desc.as_ref(), + |node_id, data, geo, original_geo, alpha, animating| { if swap_desc.as_ref().map(|desc| &desc.node) == Some(&node_id) || focused.as_ref() == Some(&node_id) { if indicator_thickness > 0 || data.is_group() { let mut geo = geo.clone(); + if data.is_group() { let outer_gap: i32 = (if is_overview { GAP_KEYBOARD } else { 4 } as f32 * percentage) @@ -5251,10 +5448,8 @@ where if let Data::Mapped { mapped, .. } = data { let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); - let SplitRenderElements { - mut w_elements, - p_elements, - } = mapped.split_render_elements::>( + + let mut elements = mapped.render_elements::>( renderer, //original_location, geo.loc.as_logical().to_physical_precise_round(output_scale) @@ -5262,6 +5457,7 @@ where Scale::from(output_scale), alpha, ); + if swap_desc .as_ref() .filter(|swap_desc| swap_desc.node == node_id) @@ -5284,7 +5480,7 @@ where { let mut geo = mapped.active_window_geometry().as_local(); geo.loc += original_geo.loc; - w_elements.insert( + elements.insert( 0, CosmicMappedRenderElement::Overlay(BackdropShader::element( renderer, @@ -5305,7 +5501,7 @@ where (ConstrainScaleBehavior::CutOff, ConstrainAlign::TOP_LEFT) }; - let w_elements = w_elements.into_iter().flat_map(|element| match element { + let elements = elements.into_iter().flat_map(|element| match element { CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( std::iter::once(elem), geo.loc.as_logical().to_physical_precise_round(output_scale) @@ -5344,6 +5540,7 @@ where .map(CosmicMappedRenderElement::TiledOverlay), x => Some(x), }); + if swap_desc .as_ref() .map(|swap_desc| { @@ -5356,21 +5553,19 @@ where }) .unwrap_or(false) { - swap_elements.extend(w_elements); + swap_elements.extend(elements); } else { if animating { - animating_window_elements.extend(w_elements); + animating_window_elements.extend(elements); } else { - window_elements.extend(w_elements); - } - if !mapped.is_maximized(false) { - popup_elements.extend(p_elements); + window_elements.extend(elements); } } } - }); + }, + ); - window_elements = resize_elements + resize_elements .into_iter() .flatten() .chain(swap_elements) @@ -5378,12 +5573,147 @@ where .chain(window_elements) .chain(animating_window_elements) .chain(group_backdrop.into_iter().map(Into::into)) - .collect(); + .collect() +} - SplitRenderElements { - w_elements: window_elements, - p_elements: popup_elements, - } +fn render_new_tree( + target_tree: &Tree, + reference_tree: Option<&Tree>, + geometries: Option>>, + old_geometries: Option>>, + percentage: f32, + swap_tree: Option<&Tree>, + swap_desc: Option<&NodeDesc>, + mut processor: impl FnMut(NodeId, &Data, Rectangle, &Rectangle, f32, bool), +) { + let old_geometries = old_geometries.unwrap_or_default(); + let geometries = geometries.unwrap_or_default(); + target_tree + .root_node_id() + .into_iter() + .flat_map(|root| target_tree.traverse_pre_order_ids(root).unwrap()) + .map(|id| (target_tree, id)) + .chain( + swap_tree + .into_iter() + .flat_map(|tree| { + let sub_root = &swap_desc.unwrap().node; + if swap_desc.unwrap().stack_window.is_none() { + Some( + tree.traverse_pre_order_ids(sub_root) + .unwrap() + .map(move |id| (tree, id)), + ) + } else { + None + } + }) + .flatten(), + ) + .for_each(|(target_tree, node_id)| { + let data = target_tree.get(&node_id).unwrap().data(); + let (original_geo, scaled_geo) = (data.geometry(), geometries.get(&node_id)); + + let (old_original_geo, old_scaled_geo) = + if let Some(reference_tree) = reference_tree.as_ref() { + if let Some(root) = reference_tree.root_node_id() { + reference_tree + .traverse_pre_order_ids(root) + .unwrap() + .find(|id| &node_id == id) + .map(|node_id| { + ( + reference_tree.get(&node_id).unwrap().data().geometry(), + old_geometries.get(&node_id), + ) + }) + } else { + None + } + } else { + None + } + .unzip(); + let mut old_geo = old_original_geo.map(|original_geo| { + let (scale, offset) = old_scaled_geo + .unwrap() + .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) + .unwrap_or_else(|| (1.0.into(), (0, 0).into())); + ( + old_scaled_geo + .unwrap() + .map(|adapted_geo| { + Rectangle::from_loc_and_size( + adapted_geo.loc + offset, + ( + (original_geo.size.w as f64 * scale).round() as i32, + (original_geo.size.h as f64 * scale).round() as i32, + ), + ) + }) + .unwrap_or(*original_geo), + 1.0, + ) + }); + + let was_minimized = if let Data::Mapped { + minimize_rect: Some(minimize_rect), + .. + } = &data + { + old_geo = Some((*minimize_rect, (percentage * 2.0).min(1.0))); + true + } else { + false + }; + + let (scale, offset) = scaled_geo + .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) + .unwrap_or_else(|| (1.0.into(), (0, 0).into())); + let new_geo = scaled_geo + .map(|adapted_geo| { + Rectangle::from_loc_and_size( + adapted_geo.loc + offset, + ( + (original_geo.size.w as f64 * scale).round() as i32, + (original_geo.size.h as f64 * scale).round() as i32, + ), + ) + }) + .unwrap_or(*original_geo); + + let (geo, alpha, animating) = if let Some((old_geo, alpha)) = old_geo.filter(|_| { + swap_desc + .map(|desc| desc.node != node_id && desc.stack_window.is_none()) + .unwrap_or(true) + }) { + ( + if was_minimized { + ease( + EaseInOutCubic, + EaseRectangle(old_geo), + EaseRectangle(new_geo), + percentage, + ) + .unwrap() + } else { + ease( + Linear, + EaseRectangle(old_geo), + EaseRectangle(new_geo), + percentage, + ) + .unwrap() + }, + alpha, + old_geo != new_geo, + ) + } else { + (new_geo, percentage, false) + }; + + processor(node_id, data, geo, original_geo, alpha, animating) + }); } fn scale_to_center( diff --git a/src/shell/mod.rs b/src/shell/mod.rs index aea58b9c..aec25ea2 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -54,8 +54,6 @@ use smithay::{ xwayland::X11Surface, }; -use smithay::wayland::shell::wlr_layer::Layer as WlrLayer; - use crate::{ backend::render::animations::spring::{Spring, SpringParams}, config::Config, @@ -419,22 +417,6 @@ impl WorkspaceSet { theme: cosmic::Theme, ) -> WorkspaceSet { let group_handle = state.create_workspace_group(); - let workspaces = { - let workspace = create_workspace( - state, - output, - &group_handle, - true, - tiling_enabled, - theme.clone(), - ); - workspace_set_idx(state, 1, idx, &workspace.handle); - state.set_workspace_capabilities( - &workspace.handle, - [WorkspaceCapabilities::Activate].into_iter(), - ); - vec![workspace] - }; let sticky_layer = FloatingLayout::new(theme.clone(), output); WorkspaceSet { @@ -446,7 +428,7 @@ impl WorkspaceSet { theme, sticky_layer, minimized_windows: Vec::new(), - workspaces, + workspaces: Vec::new(), output: output.clone(), } } @@ -660,7 +642,7 @@ impl Workspaces { return; } - let set = self + let mut set = self .backup_set .take() .map(|mut set| { @@ -678,35 +660,51 @@ impl Workspaces { }); workspace_state.add_group_output(&set.group, &output); - self.sets.insert(output.clone(), set); + // Remove workspaces that prefer this output from other sets let mut moved_workspaces = Vec::new(); - for set in self.sets.values_mut() { - let (preferrs, doesnt) = set + for other_set in self.sets.values_mut() { + let active_handle = other_set.workspaces[set.active].handle; + let (prefers, doesnt) = other_set .workspaces .drain(..) - .partition(|w| w.preferrs_output(output)); - moved_workspaces.extend(preferrs); - set.workspaces = doesnt; - if set.workspaces.is_empty() { - set.add_empty_workspace(workspace_state); + .partition(|w| w.prefers_output(output)); + moved_workspaces.extend(prefers); + other_set.workspaces = doesnt; + if other_set.workspaces.is_empty() { + other_set.add_empty_workspace(workspace_state); + } + for (i, workspace) in other_set.workspaces.iter_mut().enumerate() { + workspace_set_idx( + workspace_state, + i as u8 + 1, + other_set.idx, + &workspace.handle, + ); } - set.active = set.active.min(set.workspaces.len() - 1); + other_set.active = other_set + .workspaces + .iter() + .position(|w| w.handle == active_handle) + .unwrap_or(other_set.workspaces.len() - 1); } - { - let set = self.sets.get_mut(output).unwrap(); - for workspace in &mut moved_workspaces { - move_workspace_to_group(workspace, &set.group, workspace_state); - } - set.workspaces.extend(moved_workspaces); - for (i, workspace) in set.workspaces.iter_mut().enumerate() { - workspace.set_output(output); - workspace.refresh(xdg_activation_state); - workspace_set_idx(workspace_state, i as u8 + 1, set.idx, &workspace.handle); - if i == set.active { - workspace_state.add_workspace_state(&workspace.handle, WState::Active); - } + + // Add `moved_workspaces` to set, and update output and index of workspaces + for workspace in &mut moved_workspaces { + move_workspace_to_group(workspace, &set.group, workspace_state); + } + set.workspaces.extend(moved_workspaces); + if set.workspaces.is_empty() { + set.add_empty_workspace(workspace_state); + } + for (i, workspace) in set.workspaces.iter_mut().enumerate() { + workspace.set_output(output); + workspace.refresh(xdg_activation_state); + workspace_set_idx(workspace_state, i as u8 + 1, set.idx, &workspace.handle); + if i == set.active { + workspace_state.add_workspace_state(&workspace.handle, WState::Active); } } + self.sets.insert(output.clone(), set); } pub fn remove_output<'a>( @@ -737,18 +735,25 @@ impl Workspaces { if &seat.active_output() == output { seat.set_active_output(&new_output); } + if seat.focused_output().as_ref() == Some(output) { + seat.set_focused_output(None); + } } let new_set = self.sets.get_mut(&new_output).unwrap(); let workspace_group = new_set.group; for mut workspace in set.workspaces.into_iter() { - // update workspace protocol state - move_workspace_to_group(&mut workspace, &workspace_group, workspace_state); + if workspace.is_empty() { + workspace_state.remove_workspace(workspace.handle); + } else { + // update workspace protocol state + move_workspace_to_group(&mut workspace, &workspace_group, workspace_state); - // update mapping - workspace.set_output(&new_output); - workspace.refresh(xdg_activation_state); - new_set.workspaces.push(workspace); + // update mapping + workspace.set_output(&new_output); + workspace.refresh(xdg_activation_state); + new_set.workspaces.push(workspace); + } } for window in set.sticky_layer.mapped() { @@ -1569,97 +1574,6 @@ impl Shell { } } - /// Derives a keyboard focus target from a global position, and indicates whether the - /// the shell should start a move request event. Used during cursor related focus checks - pub fn keyboard_target_from_position( - &self, - global_position: Point, - output: &Output, - ) -> Option { - let relative_pos = global_position.to_local(output); - - let mut under: Option = None; - // if the lockscreen is active - if let Some(session_lock) = self.session_lock.as_ref() { - under = session_lock - .surfaces - .get(output) - .map(|lock| lock.clone().into()); - // if the output can receive keyboard focus - } else if let Some(window) = self.active_space(output).get_fullscreen() { - let layers = layer_map_for_output(output); - if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos.as_logical()) { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if layer.can_receive_keyboard_focus() - && layer - .surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .is_some() - { - under = Some(layer.clone().into()); - } - } else { - under = Some(window.clone().into()); - } - } else { - let done = { - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, relative_pos.as_logical()) - .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos.as_logical())) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if layer.can_receive_keyboard_focus() - && layer - .surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .is_some() - { - under = Some(layer.clone().into()); - true - } else { - false - } - } else { - false - } - }; - if !done { - // Don't check override redirect windows, because we don't set keyboard focus to them explicitly. - // These cases are handled by the XwaylandKeyboardGrab. - if let Some(target) = self.element_under(global_position, output) { - under = Some(target); - } else { - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Bottom, relative_pos.as_logical()) - .or_else(|| { - layers.layer_under(WlrLayer::Background, relative_pos.as_logical()) - }) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if layer.can_receive_keyboard_focus() - && layer - .surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .is_some() - { - under = Some(layer.clone().into()); - } - }; - } - } - } - - under - } - /// Coerce a keyboard focus target into a CosmicMapped element. This is useful when performing window specific /// actions, such as closing a window pub fn focused_element(&self, focus_target: &KeyboardFocusTarget) -> Option { @@ -2090,6 +2004,27 @@ impl Shell { self.pending_windows.retain(|(s, _, _)| s.alive()); } + pub fn update_pointer_position(&mut self, location: Point, output: &Output) { + for (o, set) in self.workspaces.sets.iter_mut() { + if o == output { + set.sticky_layer.update_pointer_position(Some(location)); + for (i, workspace) in set.workspaces.iter_mut().enumerate() { + if i == set.active { + workspace + .update_pointer_position(Some(location), self.overview_mode.clone()); + } else { + workspace.update_pointer_position(None, self.overview_mode.clone()); + } + } + } else { + set.sticky_layer.update_pointer_position(None); + for workspace in &mut set.workspaces { + workspace.update_pointer_position(None, self.overview_mode.clone()); + } + } + } + } + pub fn remap_unfullscreened_window( &mut self, mapped: CosmicMapped, @@ -2419,33 +2354,6 @@ impl Shell { } } - pub fn element_under( - &self, - location: Point, - output: &Output, - ) -> Option { - self.workspaces.sets.get(output).and_then(|set| { - set.sticky_layer - .space - .element_under(location.to_local(output).as_logical()) - .map(|(mapped, _)| mapped.clone().into()) - .or_else(|| set.workspaces[set.active].element_under(location)) - }) - } - pub fn surface_under( - &mut self, - location: Point, - output: &Output, - ) -> Option<(PointerFocusTarget, Point)> { - let overview = self.overview_mode.clone(); - self.workspaces.sets.get_mut(output).and_then(|set| { - set.sticky_layer - .surface_under(location.to_local(output)) - .map(|(target, offset)| (target, offset.to_global(output))) - .or_else(|| set.workspaces[set.active].surface_under(location, overview)) - }) - } - #[must_use] pub fn move_window( &mut self, @@ -2819,7 +2727,7 @@ impl Shell { let mapped = if move_out_of_stack { let new_mapped: CosmicMapped = CosmicWindow::new(window.clone(), evlh.clone(), self.theme.clone()).into(); - start_data.set_focus(new_mapped.focus_under((0., 0.).into())); + start_data.set_focus(new_mapped.focus_under((0., 0.).into(), WindowSurfaceType::ALL)); new_mapped } else { old_mapped.clone() @@ -3287,7 +3195,7 @@ impl Shell { let element_offset = (new_loc - geometry.loc).as_logical(); let focus = mapped - .focus_under(element_offset.to_f64()) + .focus_under(element_offset.to_f64(), WindowSurfaceType::ALL) .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()); @@ -3634,7 +3542,12 @@ impl Shell { let workspace = &mut set.workspaces[set.active]; let maybe_window = workspace.focus_stack.get(seat).iter().next().cloned(); if let Some(window) = maybe_window { - if set.sticky_layer.mapped().any(|m| m == &window) { + let was_maximized = window.is_maximized(false); + if was_maximized { + workspace.unmaximize_request(&window); + } + + let res = if set.sticky_layer.mapped().any(|m| m == &window) { set.sticky_layer .toggle_stacking_focused(seat, workspace.focus_stack.get_mut(seat)) } else if workspace.tiling_layer.mapped().any(|(m, _)| m == &window) { @@ -3647,7 +3560,15 @@ impl Shell { .toggle_stacking_focused(seat, workspace.focus_stack.get_mut(seat)) } else { None + }; + + if was_maximized { + if let Some(KeyboardFocusTarget::Element(mapped)) = res.as_ref() { + self.maximize_request(mapped, seat); + } } + + res } else { None } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 44af522e..05e71538 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,7 +1,7 @@ use crate::{ backend::render::{ element::{AsGlowRenderer, FromGlesError}, - BackdropShader, SplitRenderElements, + BackdropShader, }, shell::{ layout::{floating::FloatingLayout, tiling::TilingLayout}, @@ -34,7 +34,7 @@ use smithay::{ utils::{DamageSet, OpaqueRegions}, ImportAll, ImportMem, Renderer, }, - desktop::{layer_map_for_output, space::SpaceElement}, + desktop::{layer_map_for_output, space::SpaceElement, WindowSurfaceType}, input::Seat, output::Output, reexports::wayland_server::{Client, Resource}, @@ -374,7 +374,7 @@ impl Workspace { self.output = output.clone(); } - pub fn preferrs_output(&self, output: &Output) -> bool { + pub fn prefers_output(&self, output: &Output) -> bool { self.output_stack.contains(&output.name()) } @@ -434,6 +434,27 @@ impl Workspace { } } + fn fullscreen_geometry(&self) -> Option> { + self.fullscreen.as_ref().map(|fullscreen| { + let bbox = fullscreen.surface.bbox().as_local(); + + let mut full_geo = + Rectangle::from_loc_and_size((0, 0), 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 + }) + } + pub fn element_for_surface(&self, surface: &S) -> Option<&CosmicMapped> where CosmicSurface: PartialEq, @@ -445,25 +466,151 @@ impl Workspace { .find(|e| e.windows().any(|(w, _)| &w == surface)) } - pub fn element_under(&self, location: Point) -> Option { + pub fn popup_element_under(&self, location: Point) -> Option { + if !self.output.geometry().contains(location.to_i32_round()) { + return None; + } let location = location.to_local(&self.output); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if !fullscreen.is_animating() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen + .surface + .0 + .surface_under( + (location + geometry.loc.to_f64()).as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + .then(|| KeyboardFocusTarget::Fullscreen(fullscreen.surface.clone())); + } + } + self.floating_layer - .element_under(location) - .or_else(|| self.tiling_layer.element_under(location)) + .popup_element_under(location) + .or_else(|| self.tiling_layer.popup_element_under(location)) } - pub fn surface_under( - &mut self, + pub fn toplevel_element_under( + &self, + location: Point, + ) -> Option { + if !self.output.geometry().contains(location.to_i32_round()) { + return None; + } + let location = location.to_local(&self.output); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if !fullscreen.is_animating() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen + .surface + .0 + .surface_under( + (location + geometry.loc.to_f64()).as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + .then(|| KeyboardFocusTarget::Fullscreen(fullscreen.surface.clone())); + } + } + + self.floating_layer + .toplevel_element_under(location) + .or_else(|| self.tiling_layer.toplevel_element_under(location)) + } + + pub fn popup_surface_under( + &self, + location: Point, + overview: OverviewMode, + ) -> Option<(PointerFocusTarget, Point)> { + if !self.output.geometry().contains(location.to_i32_round()) { + return None; + } + let location = location.to_local(&self.output); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if !fullscreen.is_animating() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen + .surface + .0 + .surface_under( + (location + geometry.loc.to_f64()).as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .map(|(surface, surface_offset)| { + ( + PointerFocusTarget::WlSurface { + surface, + toplevel: Some(fullscreen.surface.clone().into()), + }, + (geometry.loc + surface_offset.as_local()) + .to_global(&self.output) + .to_f64(), + ) + }); + } + } + + self.floating_layer + .popup_surface_under(location) + .or_else(|| self.tiling_layer.popup_surface_under(location, overview)) + .map(|(m, p)| (m, p.to_global(&self.output))) + } + + pub fn toplevel_surface_under( + &self, location: Point, overview: OverviewMode, ) -> Option<(PointerFocusTarget, Point)> { + if !self.output.geometry().contains(location.to_i32_round()) { + return None; + } let location = location.to_local(&self.output); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if !fullscreen.is_animating() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen + .surface + .0 + .surface_under( + (location + geometry.loc.to_f64()).as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .map(|(surface, surface_offset)| { + ( + PointerFocusTarget::WlSurface { + surface, + toplevel: Some(fullscreen.surface.clone().into()), + }, + (geometry.loc + surface_offset.as_local()) + .to_global(&self.output) + .to_f64(), + ) + }); + } + } + self.floating_layer - .surface_under(location) - .or_else(|| self.tiling_layer.surface_under(location, overview)) + .toplevel_surface_under(location) + .or_else(|| self.tiling_layer.toplevel_surface_under(location, overview)) .map(|(m, p)| (m, p.to_global(&self.output))) } + pub fn update_pointer_position( + &mut self, + location: Option>, + overview: OverviewMode, + ) { + self.floating_layer.update_pointer_position(location); + self.tiling_layer + .update_pointer_position(location, overview); + } + pub fn element_geometry(&self, elem: &CosmicMapped) -> Option> { self.floating_layer .element_geometry(elem) @@ -650,7 +797,7 @@ impl Workspace { } else { None }; - fullscreen.surface.set_geometry(geo); + fullscreen.surface.set_geometry(geo, 0); fullscreen.surface.send_configure(); } @@ -688,7 +835,7 @@ impl Workspace { window.set_fullscreen(true); let geo = self.output.geometry(); - let original_geometry = window.geometry().as_global(); + let original_geometry = window.global_geometry().unwrap_or_default(); let signal = if let Some(surface) = window.wl_surface() { let signal = Arc::new(AtomicBool::new(false)); add_blocker( @@ -701,7 +848,7 @@ impl Workspace { } else { None }; - window.set_geometry(geo); + window.set_geometry(geo, 0); window.send_configure(); self.fullscreen = Some(FullscreenSurface { @@ -731,7 +878,7 @@ impl Workspace { .filter(|f| &f.surface == window && f.ended_at.is_none()) { window.set_fullscreen(false); - window.set_geometry(f.original_geometry); + window.set_geometry(f.original_geometry, 0); self.floating_layer.refresh(); self.tiling_layer.recalculate(); @@ -921,10 +1068,6 @@ impl Workspace { .chain(self.tiling_layer.mapped().map(|(w, _)| w)) } - pub fn outputs(&self) -> impl Iterator { - self.floating_layer.space.outputs() - } - pub fn is_empty(&self) -> bool { self.floating_layer.mapped().next().is_none() && self.tiling_layer.mapped().next().is_none() @@ -1012,7 +1155,7 @@ impl Workspace { resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, theme: &CosmicTheme, - ) -> Result>, OutputNotMapped> + ) -> Result>, OutputNotMapped> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -1021,7 +1164,7 @@ impl Workspace { CosmicStackRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::default(); let output_scale = self.output.current_scale().fractional_scale(); let zone = { @@ -1104,26 +1247,19 @@ impl Workspace { y: target_geo.size.h as f64 / bbox.size.h as f64, }; - let SplitRenderElements { - w_elements, - p_elements, - } = fullscreen - .surface - .split_render_elements::>( - renderer, - render_loc, - output_scale.into(), - alpha, - ); - elements.w_elements.extend( - w_elements + elements.extend( + fullscreen + .surface + .render_elements::>( + renderer, + render_loc, + output_scale.into(), + alpha, + ) .into_iter() .map(|elem| RescaleRenderElement::from_element(elem, render_loc, scale)) .map(Into::into), ); - elements - .p_elements - .extend(p_elements.into_iter().map(Into::into)) } if self @@ -1155,16 +1291,18 @@ impl Workspace { OverviewMode::None => 1.0, }; - elements.extend_map( - self.floating_layer.render::( - renderer, - focused.as_ref(), - resize_indicator.clone(), - indicator_thickness, - alpha, - theme, - ), - WorkspaceRenderElement::from, + elements.extend( + self.floating_layer + .render::( + renderer, + focused.as_ref(), + resize_indicator.clone(), + indicator_thickness, + alpha, + theme, + ) + .into_iter() + .map(WorkspaceRenderElement::from), ); let alpha = match &overview.0 { @@ -1181,21 +1319,23 @@ impl Workspace { }; //tiling surfaces - elements.extend_map( - self.tiling_layer.render::( - renderer, - draw_focus_indicator, - zone, - overview, - resize_indicator, - indicator_thickness, - theme, - )?, - WorkspaceRenderElement::from, + elements.extend( + self.tiling_layer + .render::( + renderer, + draw_focus_indicator, + zone, + overview, + resize_indicator, + indicator_thickness, + theme, + )? + .into_iter() + .map(WorkspaceRenderElement::from), ); if let Some(alpha) = alpha { - elements.w_elements.push( + elements.push( Into::>::into(BackdropShader::element( renderer, self.backdrop_id.clone(), @@ -1214,6 +1354,159 @@ impl Workspace { Ok(elements) } + + #[profiling::function] + pub fn render_popups<'a, R>( + &self, + renderer: &mut R, + draw_focus_indicator: Option<&Seat>, + overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), + theme: &CosmicTheme, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, + WorkspaceRenderElement: RenderElement, + { + let mut elements = Vec::default(); + + let output_scale = self.output.current_scale().fractional_scale(); + let zone = { + let layer_map = layer_map_for_output(&self.output); + layer_map.non_exclusive_zone().as_local() + }; + + if let Some(fullscreen) = self.fullscreen.as_ref() { + // fullscreen window + let bbox = fullscreen.surface.bbox().as_local(); + let element_geo = Rectangle::from_loc_and_size( + self.element_for_surface(&fullscreen.surface) + .and_then(|elem| { + self.floating_layer + .element_geometry(elem) + .or_else(|| self.tiling_layer.element_geometry(elem)) + .map(|mut geo| { + geo.loc -= elem.geometry().loc.as_local(); + geo + }) + }) + .unwrap_or(bbox) + .loc, + fullscreen.original_geometry.size.as_local(), + ); + + let mut full_geo = + Rectangle::from_loc_and_size((0, 0), self.output.geometry().size.as_local()); + if fullscreen.start_at.is_none() { + 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 (target_geo, alpha) = match (fullscreen.start_at, fullscreen.ended_at) { + (Some(started), _) => { + let duration = Instant::now().duration_since(started).as_secs_f64() + / FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); + ( + ease( + EaseInOutCubic, + EaseRectangle(element_geo), + EaseRectangle(full_geo), + duration, + ) + .0, + ease(EaseInOutCubic, 0.0, 1.0, duration), + ) + } + (_, Some(ended)) => { + let duration = Instant::now().duration_since(ended).as_secs_f64() + / FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); + ( + ease( + EaseInOutCubic, + EaseRectangle(full_geo), + EaseRectangle(element_geo), + duration, + ) + .0, + ease(EaseInOutCubic, 1.0, 0.0, duration), + ) + } + (None, None) => (full_geo, 1.0), + }; + + let render_loc = target_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale); + + elements.extend( + fullscreen + .surface + .popup_render_elements::>( + renderer, + render_loc, + output_scale.into(), + alpha, + ) + .into_iter() + .map(Into::into), + ); + } + + if self + .fullscreen + .as_ref() + .map(|f| f.start_at.is_some() || f.ended_at.is_some()) + .unwrap_or(true) + { + // floating surfaces + let alpha = match &overview.0 { + OverviewMode::Started(_, started) => { + (1.0 - (Instant::now().duration_since(*started).as_millis() + / ANIMATION_DURATION.as_millis()) as f32) + .max(0.0) + * 0.4 + + 0.6 + } + OverviewMode::Ended(_, ended) => { + ((Instant::now().duration_since(*ended).as_millis() + / ANIMATION_DURATION.as_millis()) as f32) + * 0.4 + + 0.6 + } + OverviewMode::Active(_) => 0.6, + OverviewMode::None => 1.0, + }; + + elements.extend( + self.floating_layer + .render_popups::(renderer, alpha) + .into_iter() + .map(WorkspaceRenderElement::from), + ); + + //tiling surfaces + elements.extend( + self.tiling_layer + .render_popups::(renderer, draw_focus_indicator, zone, overview, theme)? + .into_iter() + .map(WorkspaceRenderElement::from), + ); + } + + Ok(elements) + } } impl FocusStacks { diff --git a/src/state.rs b/src/state.rs index 682b0bfd..ce3b456f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,10 +11,14 @@ use crate::{ input::{gestures::GestureState, PointerFocusState}, shell::{grabs::SeatMoveGrabState, CosmicSurface, SeatExt, Shell}, utils::prelude::OutputExt, + wayland::handlers::screencopy::SessionHolder, wayland::protocols::{ + atspi::AtspiState, drm::WlDrmState, image_source::ImageSourceState, output_configuration::OutputConfigurationState, + output_power::OutputPowerState, + overlap_notify::OverlapNotifyState, screencopy::ScreencopyState, toplevel_info::ToplevelInfoState, toplevel_management::{ManagementCapabilities, ToplevelManagementState}, @@ -36,7 +40,7 @@ use smithay::{ renderer::{ element::{ default_primary_scanout_output_compare, utils::select_dmabuf_feedback, - RenderElementStates, + RenderElementState, RenderElementStates, }, ImportDma, }, @@ -106,6 +110,7 @@ use time::UtcOffset; use std::{ cell::RefCell, + cmp::min, collections::HashSet, ffi::OsString, process::Child, @@ -198,6 +203,7 @@ pub struct Common { pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub output_state: OutputManagerState, pub output_configuration_state: OutputConfigurationState, + pub output_power_state: OutputPowerState, pub presentation_state: PresentationState, pub primary_selection_state: PrimarySelectionState, pub data_control_state: Option, @@ -214,6 +220,7 @@ pub struct Common { pub viewporter_state: ViewporterState, pub kde_decoration_state: KdeDecorationState, pub xdg_decoration_state: XdgDecorationState, + pub overlap_notify_state: OverlapNotifyState, // shell-related wayland state pub xdg_shell_state: XdgShellState, @@ -227,6 +234,9 @@ pub struct Common { pub xwayland_state: Option, pub xwayland_shell_state: XWaylandShellState, pub pointer_focus_state: Option, + + pub atspi_state: AtspiState, + pub atspi_ei: crate::wayland::handlers::atspi::AtspiEiState, } #[derive(Debug)] @@ -352,6 +362,9 @@ impl BackendData { self.schedule_render(&output); } + // Update layout for changes in resolution, scale, orientation + shell.workspaces.recalculate(); + loop_handle.insert_idle(|state| state.common.update_xwayland_scale()); Ok(()) @@ -486,7 +499,11 @@ impl State { let fractional_scale_state = FractionalScaleManagerState::new::(dh); let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(dh); let output_state = OutputManagerState::new_with_xdg_output::(dh); - let output_configuration_state = OutputConfigurationState::new(dh, client_is_privileged); + let output_configuration_state = + OutputConfigurationState::new(dh, handle.clone(), client_is_privileged); + let output_power_state = OutputPowerState::new::(dh, client_is_privileged); + let overlap_notify_state = + OverlapNotifyState::new::(dh, client_has_no_security_context); let presentation_state = PresentationState::new::(dh, clock.id() as u32); let primary_selection_state = PrimarySelectionState::new::(dh); let image_source_state = ImageSourceState::new::(dh, client_is_privileged); @@ -556,6 +573,9 @@ impl State { tracing::warn!(?err, "Failed to initialize dbus handlers"); } + // TODO: Restrict to only specific client? + let atspi_state = AtspiState::new::(dh, client_is_privileged); + State { common: Common { config, @@ -593,6 +613,8 @@ impl State { keyboard_shortcuts_inhibit_state, output_state, output_configuration_state, + output_power_state, + overlap_notify_state, presentation_state, primary_selection_state, data_control_state, @@ -611,6 +633,9 @@ impl State { xwayland_state: None, xwayland_shell_state, pointer_focus_state: None, + + atspi_state, + atspi_ei: Default::default(), }, backend: BackendData::Unset, ready: Once::new(), @@ -632,6 +657,19 @@ impl State { } } +fn primary_scanout_output_compare<'a>( + current_output: &'a Output, + current_state: &RenderElementState, + next_output: &'a Output, + next_state: &RenderElementState, +) -> &'a Output { + if !crate::wayland::protocols::output_configuration::head_is_enabled(current_output) { + return next_output; + } + + default_primary_scanout_output_compare(current_output, current_state, next_output, next_state) +} + impl Common { pub fn update_primary_output( &self, @@ -645,7 +683,7 @@ impl Common { output, states, render_element_states, - default_primary_scanout_output_compare, + primary_scanout_output_compare, ); if let Some(output) = primary_scanout_output { with_fractional_scale(states, |fraction_scale| { @@ -921,7 +959,17 @@ impl Common { None } }; - let throttle = Some(Duration::from_millis(995)); + const THROTTLE: Option = Some(Duration::from_millis(995)); + const SCREENCOPY_THROTTLE: Option = Some(Duration::from_nanos(16_666_666)); + + fn throttle(session_holder: &impl SessionHolder) -> Option { + if session_holder.sessions().is_empty() && session_holder.cursor_sessions().is_empty() { + THROTTLE + } else { + SCREENCOPY_THROTTLE + } + } + let shell = self.shell.read().unwrap(); if let Some(session_lock) = shell.session_lock.as_ref() { @@ -968,7 +1016,7 @@ impl Common { if let Some(move_grab) = seat.user_data().get::() { if let Some(grab_state) = move_grab.lock().unwrap().as_ref() { for (window, _) in grab_state.element().windows() { - window.send_frame(output, time, throttle, should_send); + window.send_frame(output, time, throttle(&window), should_send); } } } @@ -983,21 +1031,21 @@ impl Common { .mapped() .for_each(|mapped| { for (window, _) in mapped.windows() { - window.send_frame(output, time, throttle, should_send); + window.send_frame(output, time, throttle(&window), should_send); } }); let active = shell.active_space(output); active.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { - window.send_frame(output, time, throttle, should_send); + window.send_frame(output, time, throttle(&window), should_send); } }); // other (throttled) windows active.minimized_windows.iter().for_each(|m| { for (window, _) in m.window.windows() { - window.send_frame(output, time, throttle, |_, _| None); + window.send_frame(output, time, throttle(&window), |_, _| None); } }); for space in shell @@ -1007,25 +1055,26 @@ impl Common { { space.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { + let throttle = min(throttle(space), throttle(&window)); window.send_frame(output, time, throttle, |_, _| None); } }); space.minimized_windows.iter().for_each(|m| { for (window, _) in m.window.windows() { - window.send_frame(output, time, throttle, |_, _| None); + window.send_frame(output, time, throttle(&window), |_, _| None); } }) } shell.override_redirect_windows.iter().for_each(|or| { if let Some(wl_surface) = or.wl_surface() { - send_frames_surface_tree(&wl_surface, output, time, throttle, should_send); + send_frames_surface_tree(&wl_surface, output, time, THROTTLE, should_send); } }); let map = smithay::desktop::layer_map_for_output(output); for layer_surface in map.layers() { - layer_surface.send_frame(output, time, throttle, should_send); + layer_surface.send_frame(output, time, THROTTLE, should_send); } } } diff --git a/src/utils/iced.rs b/src/utils/iced.rs index 7ab7a14f..90f5bab9 100644 --- a/src/utils/iced.rs +++ b/src/utils/iced.rs @@ -10,26 +10,27 @@ use cosmic::{ iced::{ advanced::widget::Tree, event::Event, + futures::{FutureExt, StreamExt}, keyboard::{Event as KeyboardEvent, Modifiers as IcedModifiers}, mouse::{Button as MouseButton, Cursor, Event as MouseEvent, ScrollDelta}, touch::{Event as TouchEvent, Finger}, - window::{Event as WindowEvent, Id}, - Command, Limits, Point as IcedPoint, Rectangle as IcedRectangle, Size as IcedSize, + window::Event as WindowEvent, + Limits, Point as IcedPoint, Size as IcedSize, Task, }, - iced_core::{clipboard::Null as NullClipboard, renderer::Style, Color, Length, Pixels}, - iced_renderer::graphics::Renderer as IcedGraphicsRenderer, + iced_core::{clipboard::Null as NullClipboard, id::Id, renderer::Style, Color, Length, Pixels}, iced_runtime::{ - command::Action, program::{Program as IcedProgram, State}, - Debug, + task::into_stream, + Action, Debug, }, Theme, }; use iced_tiny_skia::{ graphics::{damage, Viewport}, - Backend, Primitive, + Layer, }; +use once_cell::sync::Lazy; use ordered_float::OrderedFloat; use smithay::{ backend::{ @@ -67,6 +68,8 @@ use smithay::{ }, }; +static ID: Lazy = Lazy::new(|| Id::new("Program")); + pub struct IcedElement(pub(crate) Arc>>); impl fmt::Debug for IcedElement

{ @@ -104,9 +107,9 @@ pub trait Program { &mut self, message: Self::Message, loop_handle: &LoopHandle<'static, crate::state::State>, - ) -> Command { + ) -> Task { let _ = (message, loop_handle); - Command::none() + Task::none() } fn view(&self) -> cosmic::Element<'_, Self::Message>; @@ -131,7 +134,7 @@ impl IcedProgram for ProgramWrapper

{ type Renderer = cosmic::Renderer; type Theme = cosmic::Theme; - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { self.0.update(message, &self.1) } @@ -143,7 +146,7 @@ impl IcedProgram for ProgramWrapper

{ pub(crate) struct IcedElementInternal { // draw buffer outputs: HashSet, - buffers: HashMap, (MemoryRenderBuffer, Option<(Vec, Color)>)>, + buffers: HashMap, (MemoryRenderBuffer, Option<(Vec, Color)>)>, pending_update: Option, // state @@ -159,9 +162,9 @@ pub(crate) struct IcedElementInternal { // futures handle: LoopHandle<'static, crate::state::State>, - scheduler: Scheduler<

::Message>, + scheduler: Scheduler::Message>>, executor_token: Option, - rx: Receiver<

::Message>, + rx: Receiver::Message>>, } impl Clone for IcedElementInternal

{ @@ -178,14 +181,10 @@ impl Clone for IcedElementInternal

{ if !self.state.is_queue_empty() { tracing::warn!("Missing force_update call"); } - let mut renderer = cosmic::Renderer::TinySkia(IcedGraphicsRenderer::new( - Backend::new(), - cosmic::font::default(), - Pixels(16.0), - )); + let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0)); let mut debug = Debug::new(); let state = State::new( - Id::MAIN, + ID.clone(), ProgramWrapper(self.state.program().0.clone(), handle.clone()), IcedSize::new(self.size.w as f32, self.size.h as f32), &mut renderer, @@ -244,15 +243,11 @@ impl IcedElement

{ theme: cosmic::Theme, ) -> IcedElement

{ let size = size.into(); - let mut renderer = cosmic::Renderer::TinySkia(IcedGraphicsRenderer::new( - Backend::new(), - cosmic::font::default(), - Pixels(16.0), - )); + let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0)); let mut debug = Debug::new(); let state = State::new( - Id::MAIN, + ID.clone(), ProgramWrapper(program, handle.clone()), IcedSize::new(size.w as f32, size.h as f32), &mut renderer, @@ -368,8 +363,8 @@ impl IcedElement

{ impl IcedElementInternal

{ #[profiling::function] - fn update(&mut self, mut force: bool) -> Vec::Message>> { - while let Ok(message) = self.rx.try_recv() { + fn update(&mut self, mut force: bool) -> Vec::Message>> { + while let Ok(Some(message)) = self.rx.try_recv() { self.state.queue_message(message); force = true; } @@ -383,17 +378,16 @@ impl IcedElementInternal

{ .map(|p| IcedPoint::new(p.x as f32, p.y as f32)) .map(Cursor::Available) .unwrap_or(Cursor::Unavailable); - let actions = self .state .update( - Id::MAIN, + ID.clone(), IcedSize::new(self.size.w as f32, self.size.h as f32), cursor, &mut self.renderer, &self.theme, &Style { - scale_factor: 1.0, //TODO: why is this + scale_factor: 1.0, // TODO: why is this icon_color: self.theme.cosmic().on_bg_color().into(), text_color: self.theme.cosmic().on_bg_color().into(), }, @@ -402,17 +396,15 @@ impl IcedElementInternal

{ ) .1; - actions - .into_iter() - .filter_map(|action| { - if let Action::Future(future) = action { - let _ = self.scheduler.schedule(future); - None - } else { - Some(action) - } - }) - .collect::>() + if let Some(action) = actions { + if let Some(t) = into_stream(action) { + let _ = self.scheduler.schedule(t.into_future().map(|f| match f.0 { + Some(Action::Output(msg)) => Some(msg), + _ => None, + })); + } + } + Vec::new() } } @@ -751,14 +743,11 @@ impl SpaceElement for IcedElement

{ fn set_activate(&self, activated: bool) { let mut internal = self.0.lock().unwrap(); - internal.state.queue_event(Event::Window( - Id::MAIN, - if activated { - WindowEvent::Focused - } else { - WindowEvent::Unfocused - }, - )); + internal.state.queue_event(Event::Window(if activated { + WindowEvent::Focused + } else { + WindowEvent::Unfocused + })); let _ = internal.update(true); // TODO } @@ -860,9 +849,11 @@ where } else { false }; + if force { + internal_ref.pending_update = None; + } let _ = internal_ref.update(force); - - if let Some((buffer, ref mut old_primitives)) = + if let Some((buffer, ref mut old_layers)) = internal_ref.buffers.get_mut(&OrderedFloat(scale.x)) { let size: Size = internal_ref @@ -870,73 +861,86 @@ where .to_f64() .to_buffer(scale.x, Transform::Normal) .to_i32_round(); - if size.w > 0 && size.h > 0 { - let cosmic::Renderer::TinySkia(renderer) = &mut internal_ref.renderer; let state_ref = &internal_ref.state; let mut clip_mask = tiny_skia::Mask::new(size.w as u32, size.h as u32).unwrap(); let overlay = internal_ref.debug.overlay(); let theme = &internal_ref.theme; - buffer - .render() - .draw(move |buf| { - let mut pixels = - tiny_skia::PixmapMut::from_bytes(buf, size.w as u32, size.h as u32) - .expect("Failed to create pixel map"); - - renderer.with_primitives(|backend, primitives| { - let background_color = state_ref.program().0.background_color(theme); - let bounds = IcedSize::new(size.w as u32, size.h as u32); - let viewport = Viewport::with_physical_size(bounds, scale.x); - - let mut damage = old_primitives - .as_ref() - .and_then(|(last_primitives, last_color)| { - (last_color == &background_color) - .then(|| damage::list(last_primitives, primitives)) - }) - .unwrap_or_else(|| { - vec![IcedRectangle::with_size(viewport.logical_size())] - }); - damage = damage::group(damage, scale.x as f32, bounds); - - if !damage.is_empty() { - backend.draw( - &mut pixels, - &mut clip_mask, - primitives, - &viewport, - &damage, - background_color, - &overlay, - ); - - *old_primitives = Some((primitives.to_vec(), background_color)); - } - - let damage = damage + _ = buffer.render().draw(|buf| { + let mut pixels = + tiny_skia::PixmapMut::from_bytes(buf, size.w as u32, size.h as u32) + .expect("Failed to create pixel map"); + + let background_color = state_ref.program().0.background_color(theme); + let bounds = IcedSize::new(size.w as u32, size.h as u32); + let viewport = Viewport::with_physical_size(bounds, scale.x); + let scale_x = scale.x as f32; + let current_layers = internal_ref.renderer.layers(); + let mut damage: Vec<_> = old_layers + .as_ref() + .and_then(|(last_primitives, last_color)| { + (last_color == &background_color).then(|| { + damage::diff( + &last_primitives, + current_layers, + |_| { + vec![cosmic::iced::Rectangle::new( + cosmic::iced::Point::default(), + viewport.logical_size(), + )] + }, + Layer::damage, + ) .into_iter() - .map(|x| x.snap()) - .map(|damage_rect| { - Rectangle::from_loc_and_size( - (damage_rect.x as i32, damage_rect.y as i32), - (damage_rect.width as i32, damage_rect.height as i32), - ) - }) - .collect::>(); + .filter(|d| { + let width = d.width as u32; + let height = d.height as u32; - state_ref.program().0.foreground( - &mut pixels, - &damage, - scale.x as f32, - theme, - ); + width > 1 && height > 1 + }) + .collect() + }) + }) + .unwrap_or_else(|| { + vec![cosmic::iced::Rectangle::with_size(viewport.logical_size())] + }); + damage = damage::group( + damage, + cosmic::iced::Rectangle::with_size(viewport.logical_size()), + ); + + if !damage.is_empty() { + *old_layers = Some((current_layers.to_vec(), background_color)); + + internal_ref.renderer.draw( + &mut pixels, + &mut clip_mask, + &viewport, + &damage, + background_color, + &overlay, + ); + } - Result::<_, ()>::Ok(damage) + let damage = damage + .into_iter() + .map(|d| d * scale_x) + .filter_map(|x| x.snap()) + .map(|damage_rect| { + Rectangle::from_loc_and_size( + (damage_rect.x as i32, damage_rect.y as i32), + (damage_rect.width as i32, damage_rect.height as i32), + ) }) - }) - .unwrap(); + .collect::>(); + state_ref + .program() + .0 + .foreground(&mut pixels, &damage, scale.x as f32, theme); + + Result::<_, ()>::Ok(damage) + }); } if let Ok(buffer) = MemoryRenderBufferRenderElement::from_buffer( @@ -946,7 +950,9 @@ where Some(alpha), Some(Rectangle::from_loc_and_size( (0., 0.), - size.to_f64().to_logical(1.0, Transform::Normal), + size.to_f64() + .to_logical(1., Transform::Normal) + .to_i32_round(), )), Some(internal_ref.size), Kind::Unspecified, diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 39d71a7e..6fc68ed5 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -1,10 +1,11 @@ use smithay::{ + backend::drm::VrrSupport as Support, output::{Output, WeakOutput}, utils::{Rectangle, Transform}, }; pub use super::geometry::*; -use crate::config::{OutputConfig, OutputState}; +use crate::config::{AdaptiveSync, OutputConfig, OutputState}; pub use crate::shell::{SeatExt, Shell, Workspace}; pub use crate::state::{Common, State}; pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups; @@ -12,15 +13,17 @@ pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups; use std::{ cell::{Ref, RefCell, RefMut}, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicU8, Ordering}, Mutex, }, }; pub trait OutputExt { fn geometry(&self) -> Rectangle; - fn adaptive_sync(&self) -> bool; - fn set_adaptive_sync(&self, vrr: bool); + fn adaptive_sync(&self) -> AdaptiveSync; + fn set_adaptive_sync(&self, vrr: AdaptiveSync); + fn adaptive_sync_support(&self) -> Option; + fn set_adaptive_sync_support(&self, vrr: Option); fn mirroring(&self) -> Option; fn set_mirroring(&self, output: Option); @@ -29,8 +32,8 @@ pub trait OutputExt { fn config_mut(&self) -> RefMut<'_, OutputConfig>; } -struct Vrr(AtomicBool); - +struct Vrr(AtomicU8); +struct VrrSupport(AtomicU8); struct Mirroring(Mutex>); impl OutputExt for Output { @@ -49,20 +52,53 @@ impl OutputExt for Output { .as_global() } - fn adaptive_sync(&self) -> bool { + fn adaptive_sync(&self) -> AdaptiveSync { self.user_data() .get::() - .map(|vrr| vrr.0.load(Ordering::SeqCst)) - .unwrap_or(false) + .map(|vrr| match vrr.0.load(Ordering::SeqCst) { + 2 => AdaptiveSync::Force, + 1 => AdaptiveSync::Enabled, + _ => AdaptiveSync::Disabled, + }) + .unwrap_or(AdaptiveSync::Disabled) } - fn set_adaptive_sync(&self, vrr: bool) { + fn set_adaptive_sync(&self, vrr: AdaptiveSync) { let user_data = self.user_data(); - user_data.insert_if_missing_threadsafe(|| Vrr(AtomicBool::new(false))); - user_data - .get::() - .unwrap() - .0 - .store(vrr, Ordering::SeqCst); + user_data.insert_if_missing_threadsafe(|| Vrr(AtomicU8::new(0))); + user_data.get::().unwrap().0.store( + match vrr { + AdaptiveSync::Disabled => 0, + AdaptiveSync::Enabled => 1, + AdaptiveSync::Force => 2, + }, + Ordering::SeqCst, + ); + } + + fn adaptive_sync_support(&self) -> Option { + self.user_data() + .get::() + .map(|vrr| match vrr.0.load(Ordering::SeqCst) { + 0 => None, + 2 => Some(Support::RequiresModeset), + 3 => Some(Support::Supported), + _ => Some(Support::NotSupported), + }) + .flatten() + } + + fn set_adaptive_sync_support(&self, vrr: Option) { + let user_data = self.user_data(); + user_data.insert_if_missing_threadsafe(|| VrrSupport(AtomicU8::new(0))); + user_data.get::().unwrap().0.store( + match vrr { + None => 0, + Some(Support::NotSupported) => 1, + Some(Support::RequiresModeset) => 2, + Some(Support::Supported) => 3, + }, + Ordering::SeqCst, + ); } fn mirroring(&self) -> Option { diff --git a/src/wayland/handlers/atspi.rs b/src/wayland/handlers/atspi.rs new file mode 100644 index 00000000..277e0ef4 --- /dev/null +++ b/src/wayland/handlers/atspi.rs @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic_comp_config::XkbConfig; +use cosmic_protocols::atspi::v1::server::cosmic_atspi_manager_v1::CosmicAtspiManagerV1; +use reis::{ + calloop::{EisRequestSource, EisRequestSourceEvent}, + eis::{self, device::DeviceType}, + request::{Connection, Device, DeviceCapability, EisRequest, Seat}, +}; +use smithay::{ + backend::input::{KeyState, Keycode}, + input::keyboard::ModifiersState, + utils::SealedFile, +}; +use std::{ + collections::{HashMap, HashSet}, + ffi::{CStr, CString}, + mem, + os::unix::{io::AsFd, net::UnixStream}, +}; +use xkbcommon::xkb; + +use crate::{ + state::State, + wayland::protocols::atspi::{delegate_atspi, AtspiHandler}, +}; + +#[derive(PartialEq, Debug)] +pub struct AtspiKeyGrab { + pub mods: u32, + pub virtual_mods: HashSet, + pub key: Keycode, +} + +#[derive(Debug, Default)] +struct AtspiClient { + key_grabs: Vec, + has_keyboard_grab: bool, + // TODO: purge old instances + keyboards: Vec<(Connection, Device, eis::Keyboard)>, +} + +impl AtspiClient { + fn add_keyboard( + &mut self, + connection: &Connection, + seat: &Seat, + keymap: &xkb::Keymap, + modifiers: &ModifiersState, + ) { + let keymap_text = keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1); + let name = CStr::from_bytes_with_nul(b"eis-keymap\0").unwrap(); + let file = SealedFile::with_content(name, &CString::new(keymap_text).unwrap()).unwrap(); + + let device = seat.add_device( + Some("keyboard"), + DeviceType::Virtual, + &[DeviceCapability::Keyboard], + |device| { + let keyboard = device.interface::().unwrap(); + keyboard.keymap( + eis::keyboard::KeymapType::Xkb, + file.size() as u32 - 1, + file.as_fd(), + ); + }, + ); + device.resumed(); + + let keyboard = device.interface::().unwrap(); + + connection.with_next_serial(|serial| { + keyboard.modifiers( + serial, + modifiers.serialized.depressed, + modifiers.serialized.locked, + modifiers.serialized.latched, + modifiers.serialized.layout_effective, + ) + }); + + device.start_emulating(0); + + self.keyboards.push((connection.clone(), device, keyboard)); + } +} + +#[derive(Debug, Default)] +pub struct AtspiEiState { + modifiers: ModifiersState, + clients: HashMap, + pub virtual_mods: HashSet, + pub active_virtual_mods: HashSet, +} + +impl AtspiEiState { + pub fn input( + &mut self, + modifiers: &smithay::input::keyboard::ModifiersState, + keysym: &smithay::input::keyboard::KeysymHandle, + state: KeyState, + time: u64, + ) { + let state = match state { + KeyState::Pressed => eis::keyboard::KeyState::Press, + KeyState::Released => eis::keyboard::KeyState::Released, + }; + if &self.modifiers != modifiers { + self.modifiers = *modifiers; + for client in self.clients.values() { + for (connection, _, keyboard) in &client.keyboards { + connection.with_next_serial(|serial| { + keyboard.modifiers( + serial, + modifiers.serialized.depressed, + modifiers.serialized.locked, + modifiers.serialized.latched, + modifiers.serialized.layout_effective, + ) + }); + } + } + } + for client in self.clients.values() { + for (connection, device, keyboard) in &client.keyboards { + keyboard.key(keysym.raw_code().raw() - 8, state); + device.frame(time); + let _ = connection.flush(); + } + } + } + + pub fn has_keyboard_grab(&self) -> bool { + self.clients.values().any(|client| client.has_keyboard_grab) + } + + /// Key grab exists for mods, key, with active virtual mods + pub fn has_key_grab(&self, mods: u32, key: Keycode) -> bool { + self.clients + .values() + .flat_map(|client| &client.key_grabs) + .any(|grab| { + grab.mods == mods + && grab.virtual_mods == self.active_virtual_mods + && grab.key == key + }) + } + + fn update_virtual_mods(&mut self) { + self.virtual_mods.clear(); + self.virtual_mods.extend( + self.clients + .values() + .flat_map(|client| &client.key_grabs) + .flat_map(|grab| &grab.virtual_mods), + ); + } + + pub fn update_keymap(&mut self, xkb_config: XkbConfig) { + let keymap = keymap_or_default(xkb_config); + for client in self.clients.values_mut() { + let old_keyboards = mem::take(&mut client.keyboards); + for (connection, device, _keyboard) in old_keyboards { + device.remove(); + client.add_keyboard(&connection, device.seat(), &keymap, &self.modifiers); + let _ = connection.flush(); + } + } + } +} + +impl AtspiHandler for State { + fn client_connected(&mut self, manager: &CosmicAtspiManagerV1, socket: UnixStream) { + self.common + .atspi_ei + .clients + .insert(manager.clone(), AtspiClient::default()); + + let context = eis::Context::new(socket).unwrap(); + let source = EisRequestSource::new(context, 0); + let manager = manager.clone(); + self.common + .event_loop_handle + .insert_source(source, move |event, connected_state, state| { + Ok(handle_event(&manager, event, connected_state, state)) + }) + .unwrap(); + } + + fn client_disconnected(&mut self, manager: &CosmicAtspiManagerV1) { + self.common.atspi_ei.clients.remove(manager); + self.common.atspi_ei.update_virtual_mods(); + } + + fn add_key_grab( + &mut self, + manager: &CosmicAtspiManagerV1, + mods: u32, + virtual_mods: Vec, + key: Keycode, + ) { + let grab = AtspiKeyGrab { + mods, + virtual_mods: virtual_mods.into_iter().collect(), + key, + }; + let client = self.common.atspi_ei.clients.get_mut(manager).unwrap(); + client.key_grabs.push(grab); + self.common.atspi_ei.update_virtual_mods(); + } + + fn remove_key_grab( + &mut self, + manager: &CosmicAtspiManagerV1, + mods: u32, + virtual_mods: Vec, + key: Keycode, + ) { + let grab = AtspiKeyGrab { + mods, + virtual_mods: virtual_mods.into_iter().collect(), + key, + }; + let client = self.common.atspi_ei.clients.get_mut(manager).unwrap(); + if let Some(idx) = client.key_grabs.iter().position(|x| *x == grab) { + client.key_grabs.remove(idx); + } + self.common.atspi_ei.update_virtual_mods(); + } + + fn grab_keyboard(&mut self, manager: &CosmicAtspiManagerV1) { + let client = self.common.atspi_ei.clients.get_mut(manager).unwrap(); + client.has_keyboard_grab = true; + } + + fn ungrab_keyboard(&mut self, manager: &CosmicAtspiManagerV1) { + let client = self.common.atspi_ei.clients.get_mut(manager).unwrap(); + client.has_keyboard_grab = false; + } +} + +fn handle_event( + manager: &CosmicAtspiManagerV1, + event: Result, + connection: &Connection, + state: &mut State, +) -> calloop::PostAction { + let Some(client) = state.common.atspi_ei.clients.get_mut(manager) else { + return calloop::PostAction::Remove; + }; + match event { + Ok(EisRequestSourceEvent::Connected) => { + if connection.context_type() != reis::ei::handshake::ContextType::Receiver { + return calloop::PostAction::Remove; + } + let _seat = connection.add_seat(Some("default"), &[DeviceCapability::Keyboard]); + } + Ok(EisRequestSourceEvent::Request(EisRequest::Disconnect)) => { + return calloop::PostAction::Remove; + } + Ok(EisRequestSourceEvent::Request(EisRequest::Bind(request))) => { + if connection.has_interface("ei_keyboard") + && request.capabilities & 2 << DeviceCapability::Keyboard as u64 != 0 + { + let keymap = keymap_or_default(state.common.config.xkb_config()); + client.add_keyboard( + connection, + &request.seat, + &keymap, + &state.common.atspi_ei.modifiers, + ); + } + } + Ok(EisRequestSourceEvent::Request(_request)) => { + // seat / keyboard / device release? + } + Ok(EisRequestSourceEvent::InvalidObject(_)) => {} + Err(_) => { + // TODO + } + } + let _ = connection.flush(); + calloop::PostAction::Continue +} + +// TODO: use keymap of seat? +fn keymap_or_default(xkb_config: XkbConfig) -> xkb::Keymap { + keymap(xkb_config).unwrap_or_else(|| keymap(XkbConfig::default()).unwrap()) +} + +fn keymap(xkb_config: XkbConfig) -> Option { + let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + xkb::Keymap::new_from_names( + &context, + &xkb_config.rules, + &xkb_config.model, + &xkb_config.layout, + &xkb_config.variant, + xkb_config.options.clone(), + xkb::KEYMAP_COMPILE_NO_FLAGS, + ) +} + +delegate_atspi!(State); diff --git a/src/wayland/handlers/data_device.rs b/src/wayland/handlers/data_device.rs index 913aef92..672a7cb7 100644 --- a/src/wayland/handlers/data_device.rs +++ b/src/wayland/handlers/data_device.rs @@ -97,7 +97,8 @@ impl ClientDndGrabHandler for State { .lock() .unwrap() = icon.map(|surface| DnDIcon { surface, offset }) } - fn dropped(&mut self, seat: Seat) { + + fn dropped(&mut self, _target: Option, _validated: bool, seat: Seat) { seat.user_data() .get::>>() .unwrap() diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 82415aa4..870d74e2 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod alpha_modifier; +pub mod atspi; pub mod buffer; pub mod compositor; pub mod data_control; @@ -19,6 +20,8 @@ pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; pub mod output_configuration; +pub mod output_power; +pub mod overlap_notify; pub mod pointer_constraints; pub mod pointer_gestures; pub mod presentation; diff --git a/src/wayland/handlers/output_power.rs b/src/wayland/handlers/output_power.rs new file mode 100644 index 00000000..515c604e --- /dev/null +++ b/src/wayland/handlers/output_power.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::output::Output; + +use crate::{ + backend::kms::Surface, + state::{BackendData, State}, + utils::prelude::OutputExt, + wayland::protocols::output_power::{ + delegate_output_power, OutputPowerHandler, OutputPowerState, + }, +}; + +pub fn set_all_surfaces_dpms_on(state: &mut State) { + let mut changed = false; + for surface in kms_surfaces(state) { + if !surface.get_dpms() { + surface.set_dpms(true); + changed = true; + } + } + + if changed { + OutputPowerState::refresh(state); + } +} + +fn kms_surfaces(state: &mut State) -> impl Iterator { + if let BackendData::Kms(ref mut kms_state) = &mut state.backend { + Some( + kms_state + .drm_devices + .values_mut() + .flat_map(|device| device.surfaces.values_mut()), + ) + } else { + None + } + .into_iter() + .flatten() +} + +// Get KMS `Surface` for output, and for all outputs mirroring it +fn kms_surfaces_for_output<'a>( + state: &'a mut State, + output: &'a Output, +) -> impl Iterator + 'a { + kms_surfaces(state).filter(move |surface| { + surface.output == *output || surface.output.mirroring().as_ref() == Some(&output) + }) +} + +// Get KMS `Surface` for output +fn primary_kms_surface_for_output<'a>( + state: &'a mut State, + output: &Output, +) -> Option<&'a mut Surface> { + kms_surfaces(state).find(|surface| surface.output == *output) +} + +impl OutputPowerHandler for State { + fn output_power_state(&mut self) -> &mut OutputPowerState { + &mut self.common.output_power_state + } + + fn get_dpms(&mut self, output: &Output) -> Option { + let surface = primary_kms_surface_for_output(self, output)?; + Some(surface.get_dpms()) + } + + fn set_dpms(&mut self, output: &Output, on: bool) { + for surface in kms_surfaces_for_output(self, output) { + surface.set_dpms(on); + } + } +} + +delegate_output_power!(State); diff --git a/src/wayland/handlers/overlap_notify.rs b/src/wayland/handlers/overlap_notify.rs new file mode 100644 index 00000000..5afd464f --- /dev/null +++ b/src/wayland/handlers/overlap_notify.rs @@ -0,0 +1,55 @@ +use smithay::{ + desktop::{layer_map_for_output, LayerSurface, WindowSurfaceType}, + output::Output, + reexports::wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, +}; + +use crate::{ + state::State, + wayland::protocols::overlap_notify::{ + delegate_overlap_notify, OverlapNotifyHandler, OverlapNotifyState, + }, +}; + +impl OverlapNotifyHandler for State { + fn overlap_notify_state(&mut self) -> &mut OverlapNotifyState { + &mut self.common.overlap_notify_state + } + + fn layer_surface_from_resource(&self, resource: ZwlrLayerSurfaceV1) -> Option { + self.common + .layer_shell_state + .layer_surfaces() + .find(|l| l.shell_surface() == &resource) + .and_then(|l| { + let shell = self.common.shell.read().unwrap(); + let outputs = shell.outputs(); + let ret = outputs.map(|o| layer_map_for_output(o)).find_map(|s| { + s.layer_for_surface(l.wl_surface(), WindowSurfaceType::ALL) + .cloned() + }); + drop(shell); + ret + }) + } + + fn outputs(&self) -> impl Iterator { + let shell = self.common.shell.read().unwrap(); + shell.outputs().cloned().collect::>().into_iter() + } + + fn active_workspaces( + &self, + ) -> impl Iterator { + let shell = self.common.shell.read().unwrap(); + shell + .workspaces + .sets + .iter() + .map(|(_, set)| set.workspaces[set.active].handle) + .collect::>() + .into_iter() + } +} + +delegate_overlap_notify!(State); diff --git a/src/wayland/handlers/screencopy/render.rs b/src/wayland/handlers/screencopy/render.rs index 54f3070e..eda0951b 100644 --- a/src/wayland/handlers/screencopy/render.rs +++ b/src/wayland/handlers/screencopy/render.rs @@ -12,7 +12,7 @@ use smithay::{ gles::{GlesError, GlesRenderbuffer}, sync::SyncPoint, utils::with_renderer_surface_state, - Bind, Blit, BufferType, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, + Bind, Blit, BufferType, Color32F, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, }, }, desktop::space::SpaceElement, @@ -36,7 +36,7 @@ use crate::{ backend::render::{ cursor, element::{AsGlowRenderer, CosmicElement, DamageElement, FromGlesError}, - render_workspace, CursorMode, ElementFilter, RendererRef, CLEAR_COLOR, + render_workspace, CursorMode, ElementFilter, RendererRef, }, shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement}, state::{Common, KmsNodes, State}, @@ -573,12 +573,7 @@ pub fn render_window_to_buffer( renderer.bind(render_buffer).map_err(DTError::Rendering)?; } - dt.render_output( - renderer, - age, - &elements, - CLEAR_COLOR, // TODO use a theme neutral color - ) + dt.render_output(renderer, age, &elements, Color32F::TRANSPARENT) } let common = &mut state.common; diff --git a/src/wayland/handlers/toplevel_info.rs b/src/wayland/handlers/toplevel_info.rs index 2b196c39..18b16a07 100644 --- a/src/wayland/handlers/toplevel_info.rs +++ b/src/wayland/handlers/toplevel_info.rs @@ -3,7 +3,7 @@ use smithay::utils::{user_data::UserDataMap, Rectangle}; use crate::{ - shell::{element::surface::GlobalGeometry, CosmicSurface}, + shell::CosmicSurface, state::State, utils::prelude::Global, wayland::protocols::toplevel_info::{ @@ -52,13 +52,7 @@ impl Window for CosmicSurface { } fn global_geometry(&self) -> Option> { - self.user_data() - .get_or_insert(GlobalGeometry::default) - .0 - .lock() - .unwrap() - .clone() - .filter(|_| !self.is_minimized()) + CosmicSurface::global_geometry(self) } fn user_data(&self) -> &UserDataMap { diff --git a/src/wayland/handlers/xdg_activation.rs b/src/wayland/handlers/xdg_activation.rs index 78a488e6..7decb4bf 100644 --- a/src/wayland/handlers/xdg_activation.rs +++ b/src/wayland/handlers/xdg_activation.rs @@ -152,7 +152,7 @@ impl XdgActivationHandler for State { } } - if workspace == ¤t_workspace.handle || in_current_workspace { + if in_current_workspace { let target = element.into(); std::mem::drop(shell); diff --git a/src/wayland/protocols/atspi.rs b/src/wayland/protocols/atspi.rs new file mode 100644 index 00000000..c4f73ee7 --- /dev/null +++ b/src/wayland/protocols/atspi.rs @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic_protocols::atspi::v1::server::cosmic_atspi_manager_v1; + +use smithay::{ + backend::input::Keycode, + reexports::wayland_server::{ + backend::GlobalId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, + }, +}; +use std::os::unix::{io::AsFd, net::UnixStream}; +use wayland_backend::server::ClientId; + +pub trait AtspiHandler { + fn client_connected( + &mut self, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + key_event_socket: UnixStream, + ); + fn client_disconnected(&mut self, manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1); + fn add_key_grab( + &mut self, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + mods: u32, + virtual_mods: Vec, + key: Keycode, + ); + fn remove_key_grab( + &mut self, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + mods: u32, + virtual_mods: Vec, + key: Keycode, + ); + fn grab_keyboard(&mut self, manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1); + fn ungrab_keyboard(&mut self, manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1); +} + +#[derive(Debug)] +pub struct AtspiState { + global: GlobalId, +} + +impl AtspiState { + pub fn new(dh: &DisplayHandle, client_filter: F) -> AtspiState + where + D: GlobalDispatch + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + AtspiGlobalData { + filter: Box::new(client_filter), + }, + ); + AtspiState { global } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } +} + +pub struct AtspiGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +impl GlobalDispatch + for AtspiState +where + D: GlobalDispatch + + Dispatch + + AtspiHandler + + 'static, +{ + fn bind( + state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &AtspiGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let instance = data_init.init(resource, ()); + let (client_socket, server_socket) = UnixStream::pair().unwrap(); + state.client_connected(&instance, server_socket); + instance.key_events_eis(client_socket.as_fd()); + } + + fn can_view(client: Client, global_data: &AtspiGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for AtspiState +where + D: Dispatch + AtspiHandler + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + request: cosmic_atspi_manager_v1::Request, + _data: &(), + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + cosmic_atspi_manager_v1::Request::AddKeyGrab { + mods, + virtual_mods, + key, + } => { + let virtual_mods = virtual_mods + .chunks_exact(4) + .map(|x| (u32::from_ne_bytes(<[u8; 4]>::try_from(x).unwrap()) + 8).into()) + .collect(); + state.add_key_grab(manager, mods, virtual_mods, (key + 8).into()); + } + cosmic_atspi_manager_v1::Request::RemoveKeyGrab { + mods, + virtual_mods, + key, + } => { + let virtual_mods = virtual_mods + .chunks_exact(4) + .map(|x| (u32::from_ne_bytes(<[u8; 4]>::try_from(x).unwrap()) + 8).into()) + .collect(); + state.remove_key_grab(manager, mods, virtual_mods, (key + 8).into()); + } + cosmic_atspi_manager_v1::Request::GrabKeyboard => { + state.grab_keyboard(manager); + } + cosmic_atspi_manager_v1::Request::UngrabKeyboard => { + state.ungrab_keyboard(manager); + } + cosmic_atspi_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + _data: &(), + ) { + state.client_disconnected(manager); + } +} + +macro_rules! delegate_atspi { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::atspi::v1::server::cosmic_atspi_manager_v1::CosmicAtspiManagerV1: $crate::wayland::protocols::atspi::AtspiGlobalData + ] => $crate::wayland::protocols::atspi::AtspiState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::atspi::v1::server::cosmic_atspi_manager_v1::CosmicAtspiManagerV1: () + ] => $crate::wayland::protocols::atspi::AtspiState); + }; +} +pub(crate) use delegate_atspi; diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 6010d69e..3fe5285d 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -1,8 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only +pub mod atspi; pub mod drm; pub mod image_source; pub mod output_configuration; +pub mod output_power; pub mod overlap_notify; pub mod screencopy; pub mod toplevel_info; diff --git a/src/wayland/protocols/output_configuration/handlers/cosmic.rs b/src/wayland/protocols/output_configuration/handlers/cosmic.rs index bbd98de9..b0b768df 100644 --- a/src/wayland/protocols/output_configuration/handlers/cosmic.rs +++ b/src/wayland/protocols/output_configuration/handlers/cosmic.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use smithay::{ - output::{Mode, Output}, + output::Mode, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, @@ -54,7 +54,7 @@ impl Dispatch for OutputConfigurationState where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -99,10 +99,9 @@ where } } zcosmic_output_manager_v1::Request::GetConfiguration { extended, config } => { - if let Some(pending) = config.data::() { - let obj = data_init.init(extended, config.downgrade()); - pending.lock().unwrap().extension_obj = Some(obj); - } + let pending = config.data::().unwrap(); + let obj = data_init.init(extended, config.downgrade()); + pending.lock().unwrap().extension_obj = Some(obj); } zcosmic_output_manager_v1::Request::GetConfigurationHead { extended, @@ -150,7 +149,7 @@ impl Dispatch, where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -178,54 +177,55 @@ where mirroring, } => { if let Ok(obj) = obj.upgrade() { - if let Some(data) = obj.data::() { - if let Some(output) = mirroring.data::() { - let mut pending = data.lock().unwrap(); - if pending.heads.iter().any(|(h, _)| *h == head) { - obj.post_error( - zwlr_output_configuration_v1::Error::AlreadyConfiguredHead, - format!("{:?} was already configured", head), - ); - return; - } + let data = obj.data::().unwrap(); + if let Some(output) = mirroring.data::().unwrap().upgrade() { + let mut pending = data.lock().unwrap(); + if pending.heads.iter().any(|(h, _)| *h == head) { + obj.post_error( + zwlr_output_configuration_v1::Error::AlreadyConfiguredHead, + format!("{:?} was already configured", head), + ); + return; + } - if pending.heads.iter().any(|(h, c)| { - match c.as_ref() { - Some(c) => { - if let Some(conf) = c.data::() { - match conf.lock().unwrap().mirroring.as_ref() { - Some(mirrored) => { - head.data::().is_some_and(|o| o == mirrored) // we are already a mirror target -> invalid - || *h == mirroring // our target already mirrors -> invalid - } - None => false, - } - } else { - *h == mirroring // unknown state for our mirror target -> invalid + if pending.heads.iter().any(|(h, c)| { + match c.as_ref() { + Some(c) => { + let conf = c.data::().unwrap(); + match conf.lock().unwrap().mirroring.as_ref() { + Some(mirrored) => { + head.data::().unwrap() == mirrored // we are already a mirror target -> invalid + || *h == mirroring // our target already mirrors -> invalid } + None => false, } - None => *h == mirroring, // disabled state for our mirror target -> invalid } - }) { - extension_obj.post_error( - zcosmic_output_configuration_v1::Error::MirroredHeadBusy, - format!("{:?} can't mirror, it is either a mirror target itself or {:?} is not enabled/already mirroring", head, mirroring), - ); + None => *h == mirroring, // disabled state for our mirror target -> invalid } - - let output_conf = PendingOutputConfiguration::default(); - output_conf.lock().unwrap().mirroring = Some(output.clone()); - let conf_head = data_init.init(id, output_conf); - pending.heads.push((head, Some(conf_head))); + }) { + extension_obj.post_error( + zcosmic_output_configuration_v1::Error::MirroredHeadBusy, + format!("{:?} can't mirror, it is either a mirror target itself or {:?} is not enabled/already mirroring", head, mirroring), + ); } + + let output_conf = PendingOutputConfiguration::default(); + output_conf.lock().unwrap().mirroring = Some(output.clone()); + let conf_head = data_init.init(id, output_conf); + pending.heads.push((head, Some(conf_head))); + } else { + let output_conf = PendingOutputConfiguration::default(); + data_init.init(id, output_conf); } + } else { + let output_conf = PendingOutputConfiguration::default(); + data_init.init(id, output_conf); } } zcosmic_output_configuration_v1::Request::Release => { if let Ok(obj) = obj.upgrade() { - if let Some(data) = obj.data::() { - data.lock().unwrap().extension_obj.take(); - } + let data = obj.data::().unwrap(); + data.lock().unwrap().extension_obj.take(); } } _ => {} @@ -238,7 +238,7 @@ impl Dispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -257,17 +257,38 @@ where match request { zcosmic_output_configuration_head_v1::Request::SetScale1000 { scale_1000 } => { if let Ok(obj) = obj.upgrade() { - if let Some(data) = obj.data::() { - let mut pending = data.lock().unwrap(); - if pending.scale.is_some() { - obj.post_error( - zwlr_output_configuration_head_v1::Error::AlreadySet, - format!("{:?} already had a scale configured", obj), - ); - return; - } - pending.scale = Some((scale_1000 as f64) / 1000.0); + let data = obj.data::().unwrap(); + let mut pending = data.lock().unwrap(); + if pending.scale.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had a scale configured", obj), + ); + return; } + pending.scale = Some((scale_1000 as f64) / 1000.0); + } + } + zcosmic_output_configuration_head_v1::Request::SetAdaptiveSyncExt { state } => { + if let Ok(obj) = obj.upgrade() { + let data = obj.data::().unwrap(); + let mut pending = data.lock().unwrap(); + if pending.adaptive_sync.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had an adaptive_sync state configured", obj), + ); + return; + } + pending.adaptive_sync = match state.into_result() { + Ok(zcosmic_output_head_v1::AdaptiveSyncStateExt::Always) => { + Some(AdaptiveSync::Force) + } + Ok(zcosmic_output_head_v1::AdaptiveSyncStateExt::Automatic) => { + Some(AdaptiveSync::Enabled) + } + _ => Some(AdaptiveSync::Disabled), + }; } } _ => {} diff --git a/src/wayland/protocols/output_configuration/handlers/wlr.rs b/src/wayland/protocols/output_configuration/handlers/wlr.rs index 84800f6e..4666b114 100644 --- a/src/wayland/protocols/output_configuration/handlers/wlr.rs +++ b/src/wayland/protocols/output_configuration/handlers/wlr.rs @@ -2,7 +2,7 @@ use cosmic_protocols::output_management::v1::server::zcosmic_output_configuration_v1; use smithay::{ - output::{Mode, Output}, + output::{Mode, Output, WeakOutput}, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, @@ -26,7 +26,7 @@ impl GlobalDispatch for OutputC where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -63,7 +63,7 @@ impl Dispatch for OutputConfigurationState where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -116,11 +116,11 @@ where } } -impl Dispatch for OutputConfigurationState +impl Dispatch for OutputConfigurationState where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -132,7 +132,7 @@ where _client: &Client, obj: &ZwlrOutputHeadV1, request: zwlr_output_head_v1::Request, - _data: &Output, + _data: &WeakOutput, _dh: &DisplayHandle, _data_init: &mut DataInit<'_, D>, ) { @@ -146,7 +146,7 @@ where } } - fn destroyed(state: &mut D, _client: ClientId, obj: &ZwlrOutputHeadV1, _data: &Output) { + fn destroyed(state: &mut D, _client: ClientId, obj: &ZwlrOutputHeadV1, _data: &WeakOutput) { for instance in &mut state.output_configuration_state().instances { instance.heads.retain(|h| &h.obj != obj); } @@ -157,7 +157,7 @@ impl Dispatch for OutputConfigurationState where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -191,7 +191,7 @@ impl Dispatch for OutputC where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -233,16 +233,10 @@ where if pending.heads.iter().any(|(_, c)| match c { Some(conf) => { - if let Some(output_conf) = conf.data::() { - if let Some(output) = head.data::() { - let pending_conf = output_conf.lock().unwrap(); - pending_conf.mirroring.as_ref().is_some_and(|o| o == output) - } else { - false - } - } else { - false - } + let output_conf = conf.data::().unwrap(); + let output = head.data::().unwrap(); + let pending_conf = output_conf.lock().unwrap(); + pending_conf.mirroring.as_ref().is_some_and(|o| o == output) } None => false, }) { @@ -382,7 +376,7 @@ impl Dispatch where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -481,8 +475,8 @@ where } pending.adaptive_sync = Some(match state.into_result() { Ok(state) => match state { - zwlr_output_head_v1::AdaptiveSyncState::Enabled => true, - _ => false, + zwlr_output_head_v1::AdaptiveSyncState::Enabled => AdaptiveSync::Force, + _ => AdaptiveSync::Disabled, }, Err(err) => { obj.post_error( diff --git a/src/wayland/protocols/output_configuration/mod.rs b/src/wayland/protocols/output_configuration/mod.rs index e575a126..9cffb102 100644 --- a/src/wayland/protocols/output_configuration/mod.rs +++ b/src/wayland/protocols/output_configuration/mod.rs @@ -1,12 +1,18 @@ // SPDX-License-Identifier: GPL-3.0-only +use calloop::{ + timer::{TimeoutAction, Timer}, + LoopHandle, +}; use cosmic_protocols::output_management::v1::server::{ zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1, zcosmic_output_configuration_v1::ZcosmicOutputConfigurationV1, - zcosmic_output_head_v1::ZcosmicOutputHeadV1, zcosmic_output_manager_v1::ZcosmicOutputManagerV1, + zcosmic_output_head_v1::{self, ZcosmicOutputHeadV1}, + zcosmic_output_manager_v1::ZcosmicOutputManagerV1, }; use smithay::{ - output::{Mode, Output}, + backend::drm::VrrSupport, + output::{Mode, Output, WeakOutput}, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, @@ -23,10 +29,17 @@ use smithay::{ utils::{Logical, Physical, Point, Size, Transform}, wayland::output::WlOutputData, }; -use std::{convert::TryFrom, sync::Mutex}; +use std::{convert::TryFrom, sync::Mutex, time::Duration}; mod handlers; +pub fn head_is_enabled(output: &Output) -> bool { + output + .user_data() + .get::() + .map_or(false, |inner| inner.lock().unwrap().enabled) +} + #[derive(Debug)] pub struct OutputConfigurationState { outputs: Vec, @@ -36,6 +49,7 @@ pub struct OutputConfigurationState { global: GlobalId, extension_global: GlobalId, dh: DisplayHandle, + event_loop_handle: LoopHandle<'static, D>, _dispatch: std::marker::PhantomData, } @@ -91,7 +105,7 @@ pub struct PendingOutputConfigurationInner { position: Option>, transform: Option, scale: Option, - adaptive_sync: Option, + adaptive_sync: Option, } pub type PendingOutputConfiguration = Mutex; @@ -103,7 +117,7 @@ pub enum OutputConfiguration { position: Option>, transform: Option, scale: Option, - adaptive_sync: Option, + adaptive_sync: Option, }, Disabled, } @@ -147,7 +161,7 @@ where D: GlobalDispatch + GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -159,7 +173,11 @@ where + OutputConfigurationHandler + 'static, { - pub fn new(dh: &DisplayHandle, client_filter: F) -> OutputConfigurationState + pub fn new( + dh: &DisplayHandle, + event_loop_handle: LoopHandle<'static, D>, + client_filter: F, + ) -> OutputConfigurationState where F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static, { @@ -171,7 +189,7 @@ where ); let extension_global = dh.create_global::( - 1, + 2, OutputMngrGlobalData { filter: Box::new(client_filter), }, @@ -185,6 +203,7 @@ where global, extension_global, dh: dh.clone(), + event_loop_handle: event_loop_handle.clone(), _dispatch: std::marker::PhantomData, } } @@ -203,12 +222,22 @@ where .collect::>(); for output in new_outputs { - output.user_data().insert_if_missing(|| { + let added = output.user_data().insert_if_missing(|| { OutputState::new(OutputStateInner { enabled: true, global: None, }) }); + if !added { + // If head was previous added, enable it again + let mut inner = output + .user_data() + .get::() + .unwrap() + .lock() + .unwrap(); + inner.enabled = true; + } self.outputs.push(output.clone()); } } @@ -219,10 +248,9 @@ where self.removed_outputs.push(output.clone()); if let Some(inner) = output.user_data().get::() { let mut inner = inner.lock().unwrap(); - // if it gets re-added it should start with being enabled and no global - inner.enabled = true; + inner.enabled = false; if let Some(global) = inner.global.take() { - self.dh.remove_global::(global); + remove_global_with_timer(&self.dh, &self.event_loop_handle, global); } } } @@ -274,7 +302,11 @@ where inner.global = Some(output.create_global::(&self.dh)); } if !inner.enabled && inner.global.is_some() { - self.dh.remove_global::(inner.global.take().unwrap()); + remove_global_with_timer( + &self.dh, + &self.event_loop_handle, + inner.global.take().unwrap(), + ); } } for manager in self.instances.iter_mut() { @@ -296,7 +328,7 @@ fn send_head_to_client(dh: &DisplayHandle, mngr: &mut OutputMngrInstance, out where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + OutputConfigurationHandler @@ -314,7 +346,7 @@ where if let Ok(head) = client.create_resource::( dh, mngr.obj.version(), - output.clone(), + output.downgrade(), ) { mngr.obj.head(&head); let data = OutputHeadInstance { @@ -418,18 +450,50 @@ where let scale = output.current_scale().fractional_scale(); instance.obj.scale(scale); + + if instance.obj.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE { + instance + .obj + .adaptive_sync(if output.adaptive_sync() == AdaptiveSync::Disabled { + zwlr_output_head_v1::AdaptiveSyncState::Disabled + } else { + zwlr_output_head_v1::AdaptiveSyncState::Enabled + }); + } + if let Some(extension_obj) = instance.extension_obj.as_ref() { extension_obj.scale_1000((scale * 1000.0).round() as i32); extension_obj.mirroring(output.mirroring().map(|o| o.name())); - } - if instance.obj.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE { - instance.obj.adaptive_sync(if output.adaptive_sync() { - zwlr_output_head_v1::AdaptiveSyncState::Enabled - } else { - zwlr_output_head_v1::AdaptiveSyncState::Disabled - }); + if extension_obj.version() >= zcosmic_output_head_v1::EVT_ADAPTIVE_SYNC_EXT_SINCE { + extension_obj.adaptive_sync_ext(match output.adaptive_sync() { + AdaptiveSync::Disabled => { + zcosmic_output_head_v1::AdaptiveSyncStateExt::Disabled + } + AdaptiveSync::Enabled => { + zcosmic_output_head_v1::AdaptiveSyncStateExt::Automatic + } + AdaptiveSync::Force => zcosmic_output_head_v1::AdaptiveSyncStateExt::Always, + }); + + extension_obj.adaptive_sync_available( + match output + .adaptive_sync_support() + .unwrap_or(VrrSupport::NotSupported) + { + VrrSupport::NotSupported => { + zcosmic_output_head_v1::AdaptiveSyncAvailability::Unsupported + } + VrrSupport::RequiresModeset => { + zcosmic_output_head_v1::AdaptiveSyncAvailability::RequiresModeset + } + VrrSupport::Supported => { + zcosmic_output_head_v1::AdaptiveSyncAvailability::Supported + } + }, + ); + } } } @@ -443,6 +507,26 @@ where } } +fn remove_global_with_timer( + dh: &DisplayHandle, + event_loop_handle: &LoopHandle, + id: GlobalId, +) { + dh.disable_global::(id.clone()); + let source = Timer::from_duration(Duration::from_secs(5)); + let dh = dh.clone(); + let res = event_loop_handle.insert_source(source, move |_, _, _state| { + dh.remove_global::(id.clone()); + TimeoutAction::Drop + }); + if let Err(err) = res { + tracing::error!( + "failed to insert timer source to destroy output global: {}", + err + ); + } +} + macro_rules! delegate_output_configuration { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ @@ -452,7 +536,7 @@ macro_rules! delegate_output_configuration { smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: () ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::ZwlrOutputHeadV1: smithay::output::Output + smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::ZwlrOutputHeadV1: smithay::output::WeakOutput ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_mode_v1::ZwlrOutputModeV1: smithay::output::Mode @@ -482,4 +566,4 @@ macro_rules! delegate_output_configuration { } pub(crate) use delegate_output_configuration; -use crate::utils::prelude::OutputExt; +use crate::{config::AdaptiveSync, utils::prelude::OutputExt}; diff --git a/src/wayland/protocols/output_power.rs b/src/wayland/protocols/output_power.rs new file mode 100644 index 00000000..d977275a --- /dev/null +++ b/src/wayland/protocols/output_power.rs @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + output::{Output, WeakOutput}, + reexports::{ + wayland_protocols_wlr::output_power_management::v1::server::{ + zwlr_output_power_manager_v1::{self, ZwlrOutputPowerManagerV1}, + zwlr_output_power_v1::{self, ZwlrOutputPowerV1}, + }, + wayland_server::{ + backend::GlobalId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, + Resource, + }, + }, +}; +use std::{collections::HashMap, mem}; +use wayland_backend::{protocol::WEnum, server::ClientId}; + +pub trait OutputPowerHandler { + fn output_power_state(&mut self) -> &mut OutputPowerState; + fn get_dpms(&mut self, output: &Output) -> Option; + fn set_dpms(&mut self, output: &Output, on: bool); +} + +#[derive(Debug)] +pub struct OutputPowerState { + global: GlobalId, + output_powers: HashMap, +} + +impl OutputPowerState { + pub fn new(dh: &DisplayHandle, client_filter: F) -> OutputPowerState + where + D: GlobalDispatch + 'static, + F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + OutputPowerManagerGlobalData { + filter: Box::new(client_filter.clone()), + }, + ); + + OutputPowerState { + global, + output_powers: HashMap::new(), + } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } + + /// Send `mode` events for any output powers where dpms state has changed. + /// + /// This is handled automatically for changes made through the protocol. + pub fn refresh(state: &mut D) { + let mut output_powers = mem::take(&mut state.output_power_state().output_powers); + for (output_power, old_mode) in output_powers.iter_mut() { + let data = output_power.data::().unwrap(); + if let Some(output) = data.output.upgrade() { + if let Some(on) = state.get_dpms(&output) { + let mode = output_power_mode(on); + if mode != *old_mode { + output_power.mode(mode); + *old_mode = mode; + } + } + } + } + state.output_power_state().output_powers = output_powers; + } +} + +pub struct OutputPowerManagerGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +pub struct OutputPowerData { + output: WeakOutput, +} + +impl GlobalDispatch + for OutputPowerState +where + D: GlobalDispatch + + Dispatch + + 'static, +{ + fn bind( + _state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &OutputPowerManagerGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } + + fn can_view(client: Client, global_data: &OutputPowerManagerGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for OutputPowerState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + OutputPowerHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _obj: &ZwlrOutputPowerManagerV1, + request: zwlr_output_power_manager_v1::Request, + _data: &(), + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_power_manager_v1::Request::GetOutputPower { id, output } => { + let output = Output::from_resource(&output); + let output_power = data_init.init( + id, + OutputPowerData { + output: output.as_ref().map(|o| o.downgrade()).unwrap_or_default(), + }, + ); + if let Some(on) = output.as_ref().and_then(|o| state.get_dpms(o)) { + let mode = output_power_mode(on); + output_power.mode(mode); + state + .output_power_state() + .output_powers + .insert(output_power, mode); + } else { + output_power.failed(); + } + } + zwlr_output_power_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +impl Dispatch for OutputPowerState +where + D: Dispatch + OutputPowerHandler + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + obj: &ZwlrOutputPowerV1, + request: zwlr_output_power_v1::Request, + data: &OutputPowerData, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_power_v1::Request::SetMode { mode } => { + if let Some(output) = data.output.upgrade() { + let on = match mode { + WEnum::Value(zwlr_output_power_v1::Mode::On) => true, + WEnum::Value(zwlr_output_power_v1::Mode::Off) => false, + _ => { + return; + } + }; + state.set_dpms(&output, on); + if let Some(on) = state.get_dpms(&output) { + let mode = output_power_mode(on); + for (output_power, old_mode) in + state.output_power_state().output_powers.iter_mut() + { + let data = output_power.data::().unwrap(); + if data.output == output && mode != *old_mode { + output_power.mode(mode); + *old_mode = mode; + } + } + } else { + obj.failed(); + state.output_power_state().output_powers.remove(obj); + } + } else { + obj.failed(); + state.output_power_state().output_powers.remove(obj); + } + } + zwlr_output_power_v1::Request::Destroy => {} + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + obj: &ZwlrOutputPowerV1, + _data: &OutputPowerData, + ) { + state.output_power_state().output_powers.remove(obj); + } +} + +fn output_power_mode(on: bool) -> zwlr_output_power_v1::Mode { + if on { + zwlr_output_power_v1::Mode::On + } else { + zwlr_output_power_v1::Mode::Off + } +} + +macro_rules! delegate_output_power { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_manager_v1::ZwlrOutputPowerManagerV1: $crate::wayland::protocols::output_power::OutputPowerManagerGlobalData + ] => $crate::wayland::protocols::output_power::OutputPowerState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_manager_v1::ZwlrOutputPowerManagerV1: () + ] => $crate::wayland::protocols::output_power::OutputPowerState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_v1::ZwlrOutputPowerV1: $crate::wayland::protocols::output_power::OutputPowerData + ] => $crate::wayland::protocols::output_power::OutputPowerState); + }; +} +pub(crate) use delegate_output_power; diff --git a/src/wayland/protocols/overlap_notify.rs b/src/wayland/protocols/overlap_notify.rs index 2a79c268..1c7d2864 100644 --- a/src/wayland/protocols/overlap_notify.rs +++ b/src/wayland/protocols/overlap_notify.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{collections::HashMap, sync::Mutex}; +use std::{ + collections::{HashMap, HashSet}, + sync::Mutex, +}; use cosmic_protocols::{ overlap_notify::v1::server::{ @@ -12,7 +15,6 @@ use cosmic_protocols::{ zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, }, }; -use rand::distributions::{Alphanumeric, DistString}; use smithay::{ desktop::{layer_map_for_output, LayerSurface}, output::Output, @@ -29,14 +31,18 @@ use smithay::{ shell::wlr_layer::{ExclusiveZone, Layer}, }, }; -use wayland_backend::server::GlobalId; +use wayland_backend::server::{GlobalId, ObjectId}; use crate::utils::prelude::{RectExt, RectGlobalExt, RectLocalExt}; -use super::toplevel_info::{ - ToplevelHandleState, ToplevelInfoGlobalData, ToplevelInfoHandler, ToplevelState, Window, +use super::{ + toplevel_info::{ + ToplevelHandleState, ToplevelInfoGlobalData, ToplevelInfoHandler, ToplevelState, Window, + }, + workspace::WorkspaceHandle, }; +#[derive(Debug)] pub struct OverlapNotifyState { instances: Vec, global: GlobalId, @@ -82,8 +88,10 @@ impl OverlapNotifyState { + 'static, W: Window + 'static, { + let active_workspaces: Vec<_> = state.active_workspaces().collect(); for output in state.outputs() { - let map = layer_map_for_output(output); + let map = layer_map_for_output(&output); + for layer_surface in map.layers() { if let Some(data) = layer_surface .user_data() @@ -94,14 +102,33 @@ impl OverlapNotifyState { if inner.has_active_notifications() { let mut new_snapshot = OverlapSnapshot::default(); - let layer_geo = layer_surface.bbox().as_local().to_global(output); - - for window in state.toplevel_info_state().registered_toplevels() { + let layer_geo = map + .layer_geometry(layer_surface) + .unwrap_or_default() + .as_local() + .to_global(&output); + + for window in + state + .toplevel_info_state() + .registered_toplevels() + .filter(|w| { + let state = w + .user_data() + .get::() + .unwrap() + .lock() + .unwrap(); + active_workspaces.iter().any(|active_workspace| { + state.in_workspace(&active_workspace) + }) + }) + { if let Some(window_geo) = window.global_geometry() { if let Some(intersection) = layer_geo.intersection(window_geo) { - // relative to window location + // relative to layer location let region = Rectangle::from_loc_and_size( - intersection.loc - window_geo.loc, + intersection.loc - layer_geo.loc, intersection.size, ) .as_logical(); @@ -111,11 +138,18 @@ impl OverlapNotifyState { } for other_surface in map.layers().filter(|s| *s != layer_surface) { - let other_geo = other_surface.bbox().as_local().to_global(output); + if other_surface.wl_surface().id() == layer_surface.wl_surface().id() { + continue; + } + let other_geo = map + .layer_geometry(other_surface) + .unwrap_or_default() + .as_local() + .to_global(&output); if let Some(intersection) = layer_geo.intersection(other_geo) { - // relative to window location + // relative to layer location let region = Rectangle::from_loc_and_size( - intersection.loc - other_geo.loc, + intersection.loc - layer_geo.loc, intersection.size, ) .as_logical(); @@ -134,7 +168,8 @@ impl OverlapNotifyState { pub trait OverlapNotifyHandler: ToplevelInfoHandler { fn overlap_notify_state(&mut self) -> &mut OverlapNotifyState; fn layer_surface_from_resource(&self, resource: ZwlrLayerSurfaceV1) -> Option; - fn outputs(&self) -> impl Iterator; + fn outputs(&self) -> impl Iterator; + fn active_workspaces(&self) -> impl Iterator; } pub struct OverlapNotifyGlobalData { @@ -156,20 +191,29 @@ impl LayerOverlapNotificationDataInternal { } pub fn add_notification(&mut self, new_notification: ZcosmicOverlapNotificationV1) { - for (toplevel, overlap) in &self.last_snapshot.toplevel_overlaps { - if let Ok(toplevel) = toplevel.upgrade() { - new_notification.toplevel_enter( - &toplevel, - overlap.loc.x, - overlap.loc.y, - overlap.size.w, - overlap.size.h, - ); + if let Some(client) = new_notification.client() { + for (toplevel, overlap) in &self.last_snapshot.toplevel_overlaps { + if let Some(toplevel) = toplevel + .upgrade() + .ok() + .filter(|handle| handle.client().is_some_and(|c| c == client)) + { + new_notification.toplevel_enter( + &toplevel, + overlap.loc.x, + overlap.loc.y, + overlap.size.w, + overlap.size.h, + ); + } } } - for (layer_surface, (exclusive, layer, overlap)) in &self.last_snapshot.layer_overlaps { + for (_, (identifier, namespace, exclusive, layer, overlap)) in + &self.last_snapshot.layer_overlaps + { new_notification.layer_enter( - layer_surface.clone(), + identifier.clone(), + namespace.clone(), if *exclusive { 1 } else { 0 }, match layer { Layer::Background => WlrLayer::Background, @@ -196,44 +240,63 @@ impl LayerOverlapNotificationDataInternal { for toplevel in self.last_snapshot.toplevel_overlaps.keys() { if !new_snapshot.toplevel_overlaps.contains_key(toplevel) { if let Ok(toplevel) = toplevel.upgrade() { - for notification in ¬ifications { - notification.toplevel_leave(&toplevel); + if let Some(client) = toplevel.client() { + for notification in notifications + .iter() + .filter(|n| n.client().is_some_and(|c| c == client)) + { + notification.toplevel_leave(&toplevel); + } } } } } for (toplevel, overlap) in &new_snapshot.toplevel_overlaps { - if !self.last_snapshot.toplevel_overlaps.contains_key(toplevel) { + if !self + .last_snapshot + .toplevel_overlaps + .get(toplevel) + .is_some_and(|old_overlap| old_overlap == overlap) + { if let Ok(toplevel) = toplevel.upgrade() { - for notification in ¬ifications { - notification.toplevel_enter( - &toplevel, - overlap.loc.x, - overlap.loc.y, - overlap.size.w, - overlap.size.h, - ); + if let Some(client) = toplevel.client() { + for notification in notifications + .iter() + .filter(|n| n.client().is_some_and(|c| c == client)) + { + notification.toplevel_enter( + &toplevel, + overlap.loc.x, + overlap.loc.y, + overlap.size.w, + overlap.size.h, + ); + } } } } } - for layer_surface in self.last_snapshot.layer_overlaps.keys() { - if new_snapshot.layer_overlaps.contains_key(layer_surface) { + for (layer_surface, (identifier, ..)) in &self.last_snapshot.layer_overlaps { + if !new_snapshot.layer_overlaps.contains_key(layer_surface) { for notification in ¬ifications { - notification.layer_leave(layer_surface.clone()); + notification.layer_leave(identifier.clone()); } } } - for (layer_surface, (exclusive, layer, overlap)) in &new_snapshot.layer_overlaps { + for (layer_surface, (identifier, namespace, exclusive, layer, overlap)) in + &new_snapshot.layer_overlaps + { if !self .last_snapshot .layer_overlaps - .contains_key(layer_surface) + .get(layer_surface) + .is_some_and(|(_, _, _, _, old_overlap)| old_overlap == overlap) { for notification in ¬ifications { notification.layer_enter( - layer_surface.clone(), + identifier.clone(), + namespace.clone(), if *exclusive { 1 } else { 0 }, match layer { Layer::Background => WlrLayer::Background, @@ -257,7 +320,7 @@ impl LayerOverlapNotificationDataInternal { #[derive(Debug, Default, Clone)] struct OverlapSnapshot { toplevel_overlaps: HashMap, Rectangle>, - layer_overlaps: HashMap)>, + layer_overlaps: HashMap)>, } impl OverlapSnapshot { @@ -283,18 +346,29 @@ impl OverlapSnapshot { ExclusiveZone::Exclusive(_) ); let layer = layer_surface.layer(); - let identifier = layer_surface.user_data().get_or_insert(Identifier::default); - - self.layer_overlaps - .insert(identifier.0.clone(), (exclusive, layer, overlap)); + let id = layer_surface.wl_surface().id(); + let identifier = layer_surface + .user_data() + .get_or_insert(|| Identifier::from(id.clone())); + + self.layer_overlaps.insert( + id, + ( + identifier.0.clone(), + layer_surface.namespace().to_string(), + exclusive, + layer, + overlap, + ), + ); } } struct Identifier(String); -impl Default for Identifier { - fn default() -> Self { - Identifier(Alphanumeric.sample_string(&mut rand::thread_rng(), 32)) +impl From for Identifier { + fn from(value: ObjectId) -> Self { + Identifier(value.to_string()) } } @@ -392,3 +466,18 @@ where } } } + +macro_rules! delegate_overlap_notify { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::overlap_notify::v1::server::zcosmic_overlap_notify_v1::ZcosmicOverlapNotifyV1: $crate::wayland::protocols::overlap_notify::OverlapNotifyGlobalData + ] => $crate::wayland::protocols::overlap_notify::OverlapNotifyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::overlap_notify::v1::server::zcosmic_overlap_notify_v1::ZcosmicOverlapNotifyV1: () + ] => $crate::wayland::protocols::overlap_notify::OverlapNotifyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::overlap_notify::v1::server::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1: () + ] => $crate::wayland::protocols::overlap_notify::OverlapNotifyState); + }; +} +pub(crate) use delegate_overlap_notify; diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs index 5345c8e7..02fdc213 100644 --- a/src/wayland/protocols/screencopy.rs +++ b/src/wayland/protocols/screencopy.rs @@ -139,14 +139,10 @@ impl Session { let node = Vec::from(dma.node.dev_id().to_ne_bytes()); self.obj.dmabuf_device(node); for (fmt, modifiers) in &dma.formats { - let mut modifiers = modifiers.clone(); - let modifiers: Vec = { - let ptr = modifiers.as_mut_ptr() as *mut u8; - let len = modifiers.len() * 4; - let cap = modifiers.capacity() * 4; - std::mem::forget(modifiers); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let modifiers = modifiers + .iter() + .flat_map(|modifier| u64::from(*modifier).to_ne_bytes()) + .collect::>(); self.obj.dmabuf_format(*fmt as u32, modifiers); } } @@ -249,14 +245,10 @@ impl CursorSession { let node = Vec::from(dma.node.dev_id().to_ne_bytes()); session_obj.dmabuf_device(node); for (fmt, modifiers) in &dma.formats { - let mut modifiers = modifiers.clone(); - let modifiers: Vec = { - let ptr = modifiers.as_mut_ptr() as *mut u8; - let len = modifiers.len() * 4; - let cap = modifiers.capacity() * 4; - std::mem::forget(modifiers); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let modifiers = modifiers + .iter() + .flat_map(|modifier| u64::from(*modifier).to_ne_bytes()) + .collect::>(); session_obj.dmabuf_format(*fmt as u32, modifiers); } } @@ -747,14 +739,10 @@ where let node = Vec::from(dma.node.dev_id().to_ne_bytes()); session.dmabuf_device(node); for (fmt, modifiers) in &dma.formats { - let mut modifiers = modifiers.clone(); - let modifiers: Vec = { - let ptr = modifiers.as_mut_ptr() as *mut u8; - let len = modifiers.len() * 4; - let cap = modifiers.capacity() * 4; - std::mem::forget(modifiers); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let modifiers = modifiers + .iter() + .flat_map(|modifier| u64::from(*modifier).to_ne_bytes()) + .collect::>(); session.dmabuf_format(*fmt as u32, modifiers); } } diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 9eb680b1..44db2031 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -62,7 +62,7 @@ pub struct ToplevelInfoGlobalData { #[derive(Default)] pub(super) struct ToplevelStateInner { foreign_handle: Option, - instances: Vec, + instances: Vec<(Weak, ZcosmicToplevelHandleV1)>, outputs: Vec, workspaces: Vec, pub(super) rectangles: Vec<(Weak, Rectangle)>, @@ -80,6 +80,10 @@ impl ToplevelStateInner { pub fn foreign_handle(&self) -> Option<&ForeignToplevelHandle> { self.foreign_handle.as_ref() } + + pub fn in_workspace(&self, handle: &WorkspaceHandle) -> bool { + self.workspaces.contains(handle) + } } pub struct ToplevelHandleStateInner { @@ -90,7 +94,7 @@ pub struct ToplevelHandleStateInner { title: String, app_id: String, states: Vec, - pub(super) window: W, + pub(super) window: Option, } pub type ToplevelHandleState = Mutex>; @@ -104,7 +108,20 @@ impl ToplevelHandleStateInner { title: String::new(), app_id: String::new(), states: Vec::new(), - window: window.clone(), + window: Some(window.clone()), + }) + } + + fn empty() -> ToplevelHandleState { + ToplevelHandleState::new(ToplevelHandleStateInner { + outputs: Vec::new(), + geometry: None, + wl_outputs: HashSet::new(), + workspaces: Vec::new(), + title: String::new(), + app_id: String::new(), + states: Vec::new(), + window: None, }) } } @@ -185,8 +202,9 @@ where .lock() .unwrap() .instances - .push(instance); + .push((obj.downgrade(), instance)); } else { + let _ = data_init.init(cosmic_toplevel, ToplevelHandleStateInner::empty()); error!(?foreign_toplevel, "Toplevel for foreign-toplevel-list not registered for cosmic-toplevel-info."); } } @@ -241,7 +259,11 @@ where ) { for toplevel in &state.toplevel_info_state_mut().toplevels { if let Some(state) = toplevel.user_data().get::() { - state.lock().unwrap().instances.retain(|i| i != resource); + state + .lock() + .unwrap() + .instances + .retain(|(_, i)| i != resource); } } } @@ -322,7 +344,7 @@ where pub fn remove_toplevel(&mut self, toplevel: &W) { if let Some(state) = toplevel.user_data().get::() { let mut state_inner = state.lock().unwrap(); - for handle in &state_inner.instances { + for (_info, handle) in &state_inner.instances { // don't send events to stopped instances if handle.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE && self @@ -368,7 +390,7 @@ where } true } else { - for handle in &state.instances { + for (_info, handle) in &state.instances { // don't send events to stopped instances if handle.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE && self @@ -424,11 +446,7 @@ where .unwrap() .lock() .unwrap(); - let instance = match state - .instances - .iter() - .find(|i| i.id().same_client_as(&info.id())) - { + let (_info, instance) = match state.instances.iter().find(|(i, _)| i == info) { Some(i) => i, None => { if info.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE { @@ -441,7 +459,7 @@ where ) { info.toplevel(&toplevel_handle); - state.instances.push(toplevel_handle); + state.instances.push((info.downgrade(), toplevel_handle)); state.instances.last().unwrap() } else { return false; @@ -509,14 +527,10 @@ where } handle_state.states = states.clone(); - let states: Vec = { - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = states.as_mut_ptr() as *mut u8; - let len = states.len() * ratio; - let cap = states.capacity() * ratio; - std::mem::forget(states); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let states = states + .iter() + .flat_map(|state| (*state as u32).to_ne_bytes()) + .collect::>(); instance.state(states); changed = true; } @@ -604,7 +618,7 @@ where pub fn window_from_handle(handle: ZcosmicToplevelHandleV1) -> Option { handle .data::>() - .map(|state| state.lock().unwrap().window.clone()) + .and_then(|state| state.lock().unwrap().window.clone()) } macro_rules! delegate_toplevel_info { diff --git a/src/wayland/protocols/toplevel_management.rs b/src/wayland/protocols/toplevel_management.rs index efd8d325..6755334c 100644 --- a/src/wayland/protocols/toplevel_management.rs +++ b/src/wayland/protocols/toplevel_management.rs @@ -151,15 +151,13 @@ where data_init: &mut DataInit<'_, D>, ) { let instance = data_init.init(resource, ()); - let capabilities = { - let mut caps = state.toplevel_management_state().capabilities.clone(); - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = caps.as_mut_ptr() as *mut u8; - let len = caps.len() * ratio; - let cap = caps.capacity() * ratio; - std::mem::forget(caps); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let capabilities = state + .toplevel_management_state() + .capabilities + .iter() + .flat_map(|cap| (*cap as u32).to_ne_bytes()) + .collect::>(); + instance.capabilities(capabilities); state.toplevel_management_state().instances.push(instance); } diff --git a/src/wayland/protocols/workspace.rs b/src/wayland/protocols/workspace.rs index 60b1380c..6d661ad7 100644 --- a/src/wayland/protocols/workspace.rs +++ b/src/wayland/protocols/workspace.rs @@ -929,15 +929,11 @@ where } if handle_state.capabilities != group.capabilities { - let caps: Vec = { - let mut caps = group.capabilities.clone(); - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = caps.as_mut_ptr() as *mut u8; - let len = caps.len() * ratio; - let cap = caps.capacity() * ratio; - std::mem::forget(caps); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let caps = group + .capabilities + .iter() + .flat_map(|cap| (*cap as u32).to_ne_bytes()) + .collect::>(); instance.capabilities(caps); handle_state.capabilities = group.capabilities.clone(); changed = true; @@ -1005,44 +1001,31 @@ where changed = true; } if handle_state.coordinates != workspace.coordinates { - let coords: Vec = { - let mut coords = workspace.coordinates.clone(); - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = coords.as_mut_ptr() as *mut u8; - let len = coords.len() * ratio; - let cap = coords.capacity() * ratio; - std::mem::forget(coords); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let coords = workspace + .coordinates + .iter() + .flat_map(|coord| coord.to_ne_bytes()) + .collect::>(); instance.coordinates(coords); handle_state.coordinates = workspace.coordinates.clone(); changed = true; } if handle_state.capabilities != workspace.capabilities { - let caps: Vec = { - let mut caps = workspace.capabilities.clone(); - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = caps.as_mut_ptr() as *mut u8; - let len = caps.len() * ratio; - let cap = caps.capacity() * ratio; - std::mem::forget(caps); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let caps = workspace + .capabilities + .iter() + .flat_map(|cap| (*cap as u32).to_ne_bytes()) + .collect::>(); instance.capabilities(caps); handle_state.capabilities = workspace.capabilities.clone(); changed = true; } if handle_state.states != workspace.states { - let states: Vec = { - let mut states = workspace.states.iter().cloned().collect::>(); - let ratio = std::mem::size_of::() - / std::mem::size_of::(); - let ptr = states.as_mut_ptr() as *mut u8; - let len = states.len() * ratio; - let cap = states.capacity() * ratio; - std::mem::forget(states); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let states = workspace + .states + .iter() + .flat_map(|state| (*state as u32).to_ne_bytes()) + .collect::>(); instance.state(states); handle_state.states = workspace.states.clone(); changed = true;