diff --git a/Cargo.lock b/Cargo.lock index 3e15fee6..80dce136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04a9283dace1c41c265496614998d5b9c4a97b3eb770e804f007c5144bf03f2b" +checksum = "4dcdbc68024b653943864d436fe8a24b028095bc1cf91a8926f8241e4aaffe59" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -35,10 +35,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107" dependencies = [ + "cfg-if", "getrandom", "once_cell", "version_check", @@ -55,9 +56,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" dependencies = [ "backtrace", ] @@ -84,10 +85,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164" [[package]] -name = "atomic_float" -version = "0.1.0" +name = "arrayref" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "atomic_refcell" @@ -122,16 +135,16 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.5.4", "object", "rustc-demangle", ] [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" @@ -156,24 +169,24 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytemuck" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9" +checksum = "5fe233b960f12f8007e3db2d136e3cb1c291bfd7396e384ee76025fc1a3932b4" dependencies = [ "proc-macro2", "quote", @@ -181,13 +194,19 @@ dependencies = [ ] [[package]] -name = "calloop" -version = "0.10.1" +name = "byteorder" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "calloop" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcf530afb40e45e14440701e5e996d7fd139e84a912a4d83a8d6a0fb3e58663" dependencies = [ "log", - "nix 0.24.2", + "nix 0.25.0", "slotmap", "thiserror", "vec_map", @@ -195,9 +214,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" [[package]] name = "cfg-if" @@ -217,9 +236,9 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ "bitflags", "block", @@ -246,6 +265,12 @@ dependencies = [ "objc", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "core-foundation" version = "0.9.3" @@ -292,7 +317,6 @@ name = "cosmic-comp" version = "0.1.0" dependencies = [ "anyhow", - "atomic_float", "bitflags", "cosmic-protocols", "edid-rs", @@ -316,7 +340,7 @@ dependencies = [ "smithay-egui", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.10", + "wayland-scanner 0.30.0-beta.13", "xcursor", "xdg", "xkbcommon 0.4.1", @@ -325,12 +349,12 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#3ff11df30ef551e1ccbdcb091930fe0d72266195" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#a3e0aa740a3e0f8f7b486fef0d62fa09a1dfa328" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.10", - "wayland-scanner 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.13", + "wayland-scanner 0.30.0-beta.13", "wayland-server", ] @@ -424,10 +448,19 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.10.5" +name = "data-url" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +dependencies = [ + "matches", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -542,9 +575,9 @@ checksum = "2ab5fa33485cd85ac354df485819a63360fefa312fe04cffe65e6f175be1522c" [[package]] name = "egui" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb095a8b9feb9b7ff8f00b6776dffcef059538a3f4a91238e03c900e9c9ad9a2" +checksum = "fc9fcd393c3daaaf5909008a1d948319d538b79c51871e4df0993260260a94e4" dependencies = [ "ahash", "epaint", @@ -552,20 +585,51 @@ dependencies = [ ] [[package]] -name = "emath" -version = "0.18.0" +name = "egui_extras" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c223f58c7e38abe1770f367b969f1b3fbd4704b67666bcb65dbb1adb0980ba72" +checksum = "f698f685bb0ad39e87109e2f695ded0bccde77d5d40bbf7590cb5561c1e3039d" +dependencies = [ + "egui", + "resvg", + "tiny-skia", + "usvg", +] + +[[package]] +name = "egui_glow" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad77d4a00402bae9658ee64be148f4b2a0b38e4fc7874970575ca01ed1c5b75d" +dependencies = [ + "bytemuck", + "egui", + "glow", + "memoffset", + "tracing", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "emath" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9542a40106fdba943a055f418d1746a050e1a903a049b030c2b097d4686a33cf" +dependencies = [ + "bytemuck", +] [[package]] name = "epaint" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c29567088888e8ac3e8f61bbb2ddc820207ebb8d69eefde5bcefa06d65e4e89" +checksum = "5ba04741be7f6602b1a1b28f1082cce45948a7032961c52814f8946b28493300" dependencies = [ "ab_glyph", "ahash", "atomic_refcell", + "bytemuck", "emath", "nohash-hasher", "parking_lot", @@ -608,15 +672,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.5.4", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontconfig-parser" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be17a530a842f8a7a60f4397a08e8f08872849a5e31b20c7bd7301dac483296" +dependencies = [ + "roxmltree 0.15.1", +] + +[[package]] +name = "fontdb" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52186a39c335aa6f79fc0bf1c3cf854870b6ad4e50a7bb8a59b4ba1331f478a" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "ttf-parser 0.17.1", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -676,15 +767,25 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.26.2" @@ -702,6 +803,18 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glow" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -742,10 +855,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "indexmap" -version = "1.9.1" +name = "image" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -791,7 +917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f65dae1d3aa98e6877917ab4e6fdbfdfb00e95885ea7c4f4f29e3a5dfc08fdf" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -806,6 +932,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jpeg-decoder" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" + [[package]] name = "js-sys" version = "0.3.60" @@ -821,6 +953,15 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kurbo" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" +dependencies = [ + "arrayvec 0.7.2", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -829,15 +970,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.133" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -921,6 +1062,12 @@ dependencies = [ "libc", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.5.0" @@ -929,9 +1076,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" dependencies = [ "libc", ] @@ -961,15 +1108,24 @@ dependencies = [ ] [[package]] -name = "mio" -version = "0.8.4" +name = "miniz_oxide" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1055,6 +1211,19 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -1071,6 +1240,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -1130,17 +1320,17 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "owned_ttf_parser" -version = "0.15.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" +checksum = "18904d3c65493a9f0d7542293d1a7f69bfdc309a6b9ef4f46dc3e58b0577edc5" dependencies = [ - "ttf-parser", + "ttf-parser 0.17.1", ] [[package]] @@ -1155,15 +1345,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1173,28 +1363,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] -name = "pkg-config" -version = "0.3.25" +name = "pico-args" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "png" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" dependencies = [ "bitflags", "crc32fast", "flate2", - "miniz_oxide", + "miniz_oxide 0.6.2", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" @@ -1282,6 +1484,12 @@ dependencies = [ "cty", ] +[[package]] +name = "rctree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae028b272a6e99d9f8260ceefa3caa09300a8d6c8d2b2001316474bc52122e9" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1304,9 +1512,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -1315,9 +1523,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -1328,6 +1536,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "resvg" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34489194784b86c03c3d688258e2ba73f3c82700ba4673ee2ecad5ae540b9438" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "png", + "rgb", + "svgfilters", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rgb" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3" +dependencies = [ + "bytemuck", +] + [[package]] name = "ron" version = "0.7.1" @@ -1339,6 +1574,24 @@ dependencies = [ "serde", ] +[[package]] +name = "roxmltree" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "roxmltree" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9de9831a129b122e7e61f242db509fa9d0838008bf0b29bb0624669edfe48a" +dependencies = [ + "xmlparser", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1351,12 +1604,37 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +[[package]] +name = "rustybuzz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a617c811f5c9a7060fe511d35d13bf5b9f0463ce36d63ce666d05779df2b4eba" +dependencies = [ + "bitflags", + "bytemuck", + "smallvec", + "ttf-parser 0.15.2", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "safe_arch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] + [[package]] name = "scan_fmt" version = "0.2.6" @@ -1365,9 +1643,9 @@ checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -1386,18 +1664,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.145" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.145" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -1406,9 +1684,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" dependencies = [ "itoa", "ryu", @@ -1426,6 +1704,21 @@ dependencies = [ "digest", ] +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slog" version = "2.7.0" @@ -1497,11 +1790,12 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/Smithay//smithay?rev=625cbca5#625cbca54981021000d5a33c1bdc711056ff6000" +source = "git+https://github.com/pop-os/smithay?rev=c8aaa059e8#c8aaa059e841886ed9e689e0cc0d0cb821b97b71" dependencies = [ "appendlist", "bitflags", "calloop", + "cc", "cgmath", "downcast-rs", "drm", @@ -1509,6 +1803,7 @@ dependencies = [ "drm-fourcc", "gbm", "gl_generator", + "glow", "indexmap", "input", "io-lifetimes", @@ -1518,6 +1813,7 @@ dependencies = [ "libseat", "nix 0.24.2", "once_cell", + "pkg-config", "rand", "scan_fmt", "slog", @@ -1527,11 +1823,11 @@ dependencies = [ "udev", "wayland-backend", "wayland-egl", - "wayland-protocols 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.13", "wayland-protocols-misc", "wayland-protocols-wlr", "wayland-server", - "wayland-sys 0.30.0-beta.10", + "wayland-sys 0.30.0-beta.13", "winit", "x11rb", "xkbcommon 0.5.0", @@ -1559,13 +1855,14 @@ dependencies = [ [[package]] name = "smithay-egui" version = "0.1.0" -source = "git+https://github.com/Smithay/smithay-egui.git?rev=939febaf#939febafd4df990b412213abff992ab4a4b9bd44" +source = "git+https://github.com/Smithay/smithay-egui.git?rev=9fe1fa5e01#9fe1fa5e01dc39c145f036564e971a50f84864ff" dependencies = [ "cgmath", "egui", - "lazy_static", + "egui_extras", + "egui_glow", + "image", "memoffset", - "slog", "smithay", "xkbcommon 0.4.1", ] @@ -1589,10 +1886,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] -name = "syn" -version = "1.0.101" +name = "svgfilters" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" +checksum = "639abcebc15fdc2df179f37d6f5463d660c1c79cd552c12343a4600827a04bce" +dependencies = [ + "float-cmp", + "rgb", +] + +[[package]] +name = "svgtypes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22975e8a2bac6a76bb54f898a6b18764633b00e780330f0b689f65afb3975564" +dependencies = [ + "siphasher", +] + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -1661,21 +1977,46 @@ dependencies = [ [[package]] name = "time" -version = "0.3.14" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", "libc", "num_threads", + "serde", + "time-core", "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.4" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + +[[package]] +name = "tiny-skia" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d049bfef0eaa2521e75d9ffb5ce86ad54480932ae19b85f78bec6f52c4d30d78" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "bytemuck", + "cfg-if", + "png", + "safe_arch", +] [[package]] name = "toml" @@ -1686,12 +2027,38 @@ dependencies = [ "serde", ] +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + [[package]] name = "ttf-parser" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" +[[package]] +name = "ttf-parser" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" + [[package]] name = "typenum" version = "1.15.0" @@ -1709,12 +2076,75 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-general-category" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07547e3ee45e28326cc23faac56d44f58f16ab23e413db526debce3b0bfd2742" + [[package]] name = "unicode-ident" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +[[package]] +name = "unicode-script" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "usvg" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a82565b5c96dcbb58c9bdbb6aa3642abd395a6a6b480658532c6f74c3c4b7a" +dependencies = [ + "base64", + "data-url", + "flate2", + "float-cmp", + "fontdb", + "kurbo", + "log", + "pico-args", + "rctree", + "roxmltree 0.14.1", + "rustybuzz", + "simplecss", + "siphasher", + "svgtypes", + "ttf-parser 0.15.2", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "uuid" version = "0.8.2" @@ -1798,17 +2228,17 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wayland-backend" -version = "0.1.0-beta.10" +version = "0.1.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c089c872513a8dcc2b5cc26209d83f5ffab1841d8c7973edf2583620ebc018" +checksum = "f3f96c52ca34b33e6bb55497327428be54dad648973aa5d3d1e02982d2fcc298" dependencies = [ "cc", "downcast-rs", "io-lifetimes", - "nix 0.24.2", + "nix 0.25.0", "scoped-tls", "smallvec", - "wayland-sys 0.30.0-beta.10", + "wayland-sys 0.30.0-beta.13", ] [[package]] @@ -1852,13 +2282,13 @@ dependencies = [ [[package]] name = "wayland-egl" -version = "0.30.0-beta.10" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cb5591d75142e1b180691df4d27f164a09abbd6efc7cba9b119ca40646809c" +checksum = "da29b4a38d5cd89b90786305b62c21889d7d97bb1e2ccf46a94906832898e20e" dependencies = [ "thiserror", "wayland-backend", - "wayland-sys 0.30.0-beta.10", + "wayland-sys 0.30.0-beta.13", ] [[package]] @@ -1875,39 +2305,39 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.30.0-beta.10" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60406a2fb43ff670ece4976cdd0537bbcd1e583b54c3cd9a1b61065e966df0a7" +checksum = "e32104ba51988754f4429dbb59c7b7fd2ccf1feaa0009bb59e49a5e2f91e985d" dependencies = [ "bitflags", "wayland-backend", - "wayland-scanner 0.30.0-beta.10", + "wayland-scanner 0.30.0-beta.13", "wayland-server", ] [[package]] name = "wayland-protocols-misc" -version = "0.1.0-beta.10" +version = "0.1.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653c3e200343bcfae719b668959efb9475bf9fcf43591cccfead308d32d67049" +checksum = "d6d0be545a3b35869060efa6e19622f6b071a9ee8d09da5ffa48f072524fddaa" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.10", - "wayland-scanner 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.13", + "wayland-scanner 0.30.0-beta.13", "wayland-server", ] [[package]] name = "wayland-protocols-wlr" -version = "0.1.0-beta.10" +version = "0.1.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1cfe42e0cfcce47a421844ee82e49316cfab3ec6bcbb3a1076a0a2dd6c8d61e" +checksum = "130613a48629204e4ab50ea4b5de2efa2e17c58f71a5af15b39535f99421f8c3" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.10", - "wayland-scanner 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.13", + "wayland-scanner 0.30.0-beta.13", "wayland-server", ] @@ -1924,9 +2354,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.30.0-beta.10" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1398566f240c3ef845bdfd310c8ee6ba800d4db695d007f23f2190eeedfff93" +checksum = "1fb55ca28710dcc155078db4e2bd29aacf24bd311261c48750f38c6c5430bf1f" dependencies = [ "proc-macro2", "quick-xml", @@ -1936,16 +2366,16 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.30.0-beta.10" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030557c71dfa3d7e0d0b5975bbdfee2eee319fde3e4c420cde63474c61331347" +checksum = "ba5ed6de3d02e0bcfee03cf23b8f8e923fb3707d826bcb1ef10ef34647778e20" dependencies = [ "bitflags", "downcast-rs", - "nix 0.24.2", + "nix 0.25.0", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.10", + "wayland-scanner 0.30.0-beta.13", ] [[package]] @@ -1961,9 +2391,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.30.0-beta.10" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bacc6b678c3350910e5ad6c057b7503666080952c4ece0a7e8958fd33937b1f5" +checksum = "882bda56e8397384ad66ec0ebe763e0b3b53153a6327344f7d74b85981a271e1" dependencies = [ "dlib", "libc", @@ -1982,6 +2412,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "winapi" version = "0.3.9" @@ -2019,37 +2455,88 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -2057,10 +2544,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] -name = "winit" -version = "0.27.3" +name = "windows_x86_64_msvc" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22e94ba35ca3ff11820044bfa0dc48b95a3a15569c0068555566a12ef41c9e5" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winit" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb796d6fbd86b2fd896c9471e6f04d39d750076ebe5680a3958f00f5ab97657c" dependencies = [ "bitflags", "cocoa", @@ -2084,7 +2577,7 @@ dependencies = [ "wayland-client", "wayland-protocols 0.29.5", "web-sys", - "windows-sys", + "windows-sys 0.36.1", "x11-dl", ] @@ -2163,3 +2656,15 @@ name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "xmlparser" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" diff --git a/Cargo.toml b/Cargo.toml index d351a463..f4f6b11e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ slog-stdlog = "4.1" serde = { version = "1", features = ["derive"] } serde_json = "1" sendfd = "0.4.1" -egui = { version = "0.18.1", optional = true } +egui = { version = "0.19.0", optional = true } edid-rs = { version = "0.1" } png = "0.17.5" lazy_static = "1.4.0" @@ -28,28 +28,27 @@ xkbcommon = "0.4" indexmap = "1.8.0" xdg = "^2.1" ron = "0.7" -atomic_float = "0.1" libsystemd = "0.5" -wayland-backend = "=0.1.0-beta.10" -wayland-scanner = "=0.30.0-beta.10" +wayland-backend = "=0.1.0-beta.13" +wayland-scanner = "=0.30.0-beta.13" cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] } [dependencies.smithay] version = "0.3" git = "https://github.com/Smithay/smithay.git" -rev = "606d2d5c" +rev = "b297c93edc" default-features = false -features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_gl", "renderer_multi", "wayland_frontend", "slog-stdlog"] +features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_glow", "renderer_multi", "wayland_frontend", "slog-stdlog"] [dependencies.smithay-egui] git = "https://github.com/Smithay/smithay-egui.git" -rev = "939febaf" +rev = "9fe1fa5e01" +features = ["svg"] optional = true [features] default = [] debug = ["egui", "smithay-egui"] -experimental = [] [profile.dev] lto = "thin" @@ -62,4 +61,4 @@ debug = true lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/Smithay//smithay", rev = "625cbca5" } +smithay = { git = "https://github.com/pop-os/smithay", rev = "c8aaa059e8" } diff --git a/config.ron b/config.ron index 5a28839b..c1795c9c 100644 --- a/config.ron +++ b/config.ron @@ -1,51 +1,89 @@ ( key_bindings: { - (modifiers: [Logo, Shift], key: "Escape"): Terminate, - (modifiers: [Logo], key: "Escape"): Debug, - (modifiers: [Logo], key: "q"): Close, - (modifiers: [Logo], key: "1"): Workspace(1), - (modifiers: [Logo], key: "2"): Workspace(2), - (modifiers: [Logo], key: "3"): Workspace(3), - (modifiers: [Logo], key: "4"): Workspace(4), - (modifiers: [Logo], key: "5"): Workspace(5), - (modifiers: [Logo], key: "6"): Workspace(6), - (modifiers: [Logo], key: "7"): Workspace(7), - (modifiers: [Logo], key: "8"): Workspace(8), - (modifiers: [Logo], key: "9"): Workspace(9), - (modifiers: [Logo], key: "0"): Workspace(0), - (modifiers: [Logo, Shift], key: "1"): MoveToWorkspace(1), - (modifiers: [Logo, Shift], key: "2"): MoveToWorkspace(2), - (modifiers: [Logo, Shift], key: "3"): MoveToWorkspace(3), - (modifiers: [Logo, Shift], key: "4"): MoveToWorkspace(4), - (modifiers: [Logo, Shift], key: "5"): MoveToWorkspace(5), - (modifiers: [Logo, Shift], key: "6"): MoveToWorkspace(6), - (modifiers: [Logo, Shift], key: "7"): MoveToWorkspace(7), - (modifiers: [Logo, Shift], key: "8"): MoveToWorkspace(8), - (modifiers: [Logo, Shift], key: "9"): MoveToWorkspace(9), - (modifiers: [Logo, Shift], key: "0"): MoveToWorkspace(0), - (modifiers: [Logo], key: "Left"): Focus(Left), - (modifiers: [Logo], key: "Right"): Focus(Right), - (modifiers: [Logo], key: "Up"): Focus(Up), - (modifiers: [Logo], key: "Down"): Focus(Down), - (modifiers: [Logo], key: "h"): Focus(Left), - (modifiers: [Logo], key: "j"): Focus(Down), - (modifiers: [Logo], key: "k"): Focus(Up), - (modifiers: [Logo], key: "l"): Focus(Right), -//TODO: automatic orientation with Logo+o toggling - (modifiers: [Logo], key: "v"): Orientation(Vertical), - (modifiers: [Logo], key: "o"): Orientation(Horizontal), - (modifiers: [Logo], key: "y"): ToggleTiling, - (modifiers: [Logo], key: "g"): ToggleWindowFloating, - (modifiers: [Logo, Shift], key: "f"): Fullscreen, - (modifiers: [Logo, Shift], key: "s"): Screenshot, + (modifiers: [Super, Shift], key: "Escape"): Terminate, + (modifiers: [Super], key: "Escape"): Debug, + (modifiers: [Super], key: "q"): Close, + + (modifiers: [Super], key: "1"): Workspace(1), + (modifiers: [Super], key: "2"): Workspace(2), + (modifiers: [Super], key: "3"): Workspace(3), + (modifiers: [Super], key: "4"): Workspace(4), + (modifiers: [Super], key: "5"): Workspace(5), + (modifiers: [Super], key: "6"): Workspace(6), + (modifiers: [Super], key: "7"): Workspace(7), + (modifiers: [Super], key: "8"): Workspace(8), + (modifiers: [Super], key: "9"): Workspace(9), + (modifiers: [Super], key: "0"): LastWorkspace, + (modifiers: [Super, Shift], key: "1"): MoveToWorkspace(1), + (modifiers: [Super, Shift], key: "2"): MoveToWorkspace(2), + (modifiers: [Super, Shift], key: "3"): MoveToWorkspace(3), + (modifiers: [Super, Shift], key: "4"): MoveToWorkspace(4), + (modifiers: [Super, Shift], key: "5"): MoveToWorkspace(5), + (modifiers: [Super, Shift], key: "6"): MoveToWorkspace(6), + (modifiers: [Super, Shift], key: "7"): MoveToWorkspace(7), + (modifiers: [Super, Shift], key: "8"): MoveToWorkspace(8), + (modifiers: [Super, Shift], key: "9"): MoveToWorkspace(9), + (modifiers: [Super, Shift], key: "0"): MoveToLastWorkspace, + + // TODO: Depends on workspace orientation + (modifiers: [Super, Ctrl], key: "Right"): NextWorkspace, + (modifiers: [Super, Ctrl], key: "Left"): PreviousWorkspace, + (modifiers: [Super, Ctrl, Shift], key: "Right"): MoveToNextWorkspace, + (modifiers: [Super, Ctrl, Shift], key: "Left"): MoveToPreviousWorkspace, + (modifiers: [Super, Ctrl], key: "l"): NextWorkspace, + (modifiers: [Super, Ctrl], key: "h"): PreviousWorkspace, + (modifiers: [Super, Ctrl, Shift], key: "l"): MoveToNextWorkspace, + (modifiers: [Super, Ctrl, Shift], key: "h"): MoveToPreviousWorkspace, + + (modifiers: [Super, Ctrl], key: "Down"): NextOutput, + (modifiers: [Super, Ctrl], key: "Up"): PreviousOutput, + (modifiers: [Super, Ctrl, Alt], key: "Down"): NextOutput, + (modifiers: [Super, Ctrl, Alt], key: "Up"): PreviousOutput, + (modifiers: [Super, Ctrl], key: "j"): NextOutput, + (modifiers: [Super, Ctrl], key: "k"): PreviousOutput, + (modifiers: [Super, Ctrl, Alt], key: "j"): NextOutput, + (modifiers: [Super, Ctrl, Alt], key: "k"): PreviousOutput, + + (modifiers: [Super], key: "Period"): NextOutput, + (modifiers: [Super], key: "Comma"): PreviousOutput, + (modifiers: [Super, Shift], key: "Period"): MoveToNextOutput, + (modifiers: [Super, Shift], key: "Comma"): MoveToPreviousOutput, + + (modifiers: [Super], key: "Left"): Focus(Left), + (modifiers: [Super], key: "Right"): Focus(Right), + (modifiers: [Super], key: "Up"): Focus(Up), + (modifiers: [Super], key: "Down"): Focus(Down), + (modifiers: [Super], key: "h"): Focus(Left), + (modifiers: [Super], key: "j"): Focus(Down), + (modifiers: [Super], key: "k"): Focus(Up), + (modifiers: [Super], key: "l"): Focus(Right), + + (modifiers: [Super, Shift], key: "Left"): Move(Left), + (modifiers: [Super, Shift], key: "Right"): Move(Right), + (modifiers: [Super, Shift], key: "Up"): Move(Up), + (modifiers: [Super, Shift], key: "Down"): Move(Down), + (modifiers: [Super, Shift], key: "h"): Move(Left), + (modifiers: [Super, Shift], key: "j"): Move(Down), + (modifiers: [Super, Shift], key: "k"): Move(Up), + (modifiers: [Super, Shift], key: "l"): Move(Right), + + (modifiers: [Super], key: "o"): ToggleOrientation, + + (modifiers: [Super], key: "y"): ToggleTiling, + (modifiers: [Super], key: "g"): ToggleWindowFloating, + + (modifiers: [Super], key: "m"): Maximize, + //TODO: ability to select default web browser - (modifiers: [Logo], key: "b"): Spawn("firefox"), + (modifiers: [Super], key: "b"): Spawn("firefox"), //TODO: ability to select default file browser - (modifiers: [Logo], key: "f"): Spawn("nautilus"), + (modifiers: [Super], key: "f"): Spawn("nautilus"), //TODO: ability to select default terminal - (modifiers: [Logo], key: "t"): Spawn("gnome-terminal"), - (modifiers: [Logo], key: "a"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicAppLibrary'"), - (modifiers: [Logo], key: "slash"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicLauncher'"), + (modifiers: [Super], key: "t"): Spawn("gnome-terminal"), + + (modifiers: [Super], key: "a"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicAppLibrary'"), + (modifiers: [Super], key: "slash"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicLauncher'"), + (modifiers: [], key: "XF86AudioRaiseVolume"): Spawn("amixer sset Master 5%+"), (modifiers: [], key: "XF86AudioLowerVolume"): Spawn("amixer sset Master 5%-"), (modifiers: [], key: "XF86AudioMute"): Spawn("amixer sset Master toggle"), @@ -53,5 +91,6 @@ (modifiers: [], key: "XF86MonBrightnessDown"): Spawn("busctl --user call com.system76.CosmicSettingsDaemon /com/system76/CosmicSettingsDaemon com.system76.CosmicSettingsDaemon DecreaseDisplayBrightness"), }, workspace_mode: OutputBound, + workspace_amount: Dynamic, floating_default: false, ) diff --git a/resources/icons/amd.svg b/resources/icons/amd.svg new file mode 100644 index 00000000..59275eda --- /dev/null +++ b/resources/icons/amd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/intel.svg b/resources/icons/intel.svg new file mode 100644 index 00000000..b7253316 --- /dev/null +++ b/resources/icons/intel.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/resources/icons/nvidia.svg b/resources/icons/nvidia.svg new file mode 100644 index 00000000..2b25de27 --- /dev/null +++ b/resources/icons/nvidia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e403b651..70772a38 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.63" \ No newline at end of file +channel = "1.65" \ No newline at end of file diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index fd860432..6652b632 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,14 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-only -#[cfg(feature = "debug")] -use crate::state::Fps; - use crate::{ backend::render, config::OutputConfig, shell::Shell, - state::{BackendData, ClientState, Common, Data}, + state::{BackendData, ClientState, Common, Data, Fps}, utils::prelude::*, + wayland::{ + handlers::screencopy::{PendingScreencopyBuffers, UserdataExt}, + protocols::screencopy::{BufferParams, Session as ScreencopySession}, + }, }; use anyhow::{Context, Result}; @@ -20,13 +21,16 @@ use smithay::{ input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ + damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, + glow::GlowRenderer, multigpu::{egl::EglGlesBackend, GpuManager}, - Bind, }, session::{auto::AutoSession, Session, Signal}, udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent}, }, + desktop::utils::OutputPresentationFeedback, + input::Seat, output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel}, reexports::{ calloop::{ @@ -36,6 +40,7 @@ use smithay::{ drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags}, input::Libinput, nix::{fcntl::OFlag, sys::stat::dev_t}, + wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource}, }, utils::{ @@ -51,7 +56,7 @@ use std::{ os::unix::io::{FromRawFd, OwnedFd}, path::PathBuf, rc::Rc, - time::{Duration, Instant}, + time::Duration, }; mod drm_helpers; @@ -60,9 +65,13 @@ mod socket; use session_fd::*; use socket::*; +use super::render::{CursorMode, GlMultiRenderer}; +// for now we assume we need at least 3ms +const MIN_RENDER_TIME: Duration = Duration::from_millis(3); + pub struct KmsState { devices: HashMap, - pub api: GpuManager, + pub api: GpuManager>, pub primary: DrmNode, session: AutoSession, signaler: Signaler, @@ -82,16 +91,21 @@ pub struct Device { } pub struct Surface { - surface: Option>>, SessionFd>>, + surface: Option< + GbmBufferedSurface< + Rc>>, + SessionFd, + Option, + >, + >, + damage_tracker: DamageTrackedRenderer, connector: connector::Handle, output: Output, - last_render: Option<(Dmabuf, Instant)>, - last_submit: Option, refresh_rate: u32, vrr: bool, pending: bool, + dirty: bool, render_timer_token: Option, - #[cfg(feature = "debug")] fps: Fps, } @@ -120,12 +134,12 @@ pub fn init_backend( } data.state.process_input_event(event); for output in data.state.common.shell.outputs() { - if let Err(err) = data - .state - .backend - .kms() - .schedule_render(&data.state.common.event_loop_handle, output) - { + if let Err(err) = data.state.backend.kms().schedule_render( + &data.state.common.event_loop_handle, + output, + None, + None, + ) { slog_scope::crit!( "Error scheduling event loop for output {}: {:?}", output.name(), @@ -142,7 +156,8 @@ pub fn init_backend( .map_err(|err| err.error) .context("Failed to initialize session event source")?; - let api = GpuManager::new(EglGlesBackend, None).context("Failed to initialize renderers")?; + let api = GpuManager::new(EglGlesBackend::::default(), None) + .context("Failed to initialize renderers")?; // TODO get this info from system76-power, if available and setup a watcher let primary = if let Some(path) = std::env::var("COSMIC_RENDER_DEVICE") @@ -238,20 +253,15 @@ pub fn init_backend( } } } - data.state.common.output_configuration_state.update(); + let seats = data.state.common.seats().cloned().collect::>(); data.state.common.config.read_outputs( - data.state.common.output_configuration_state.outputs(), + &mut data.state.common.output_configuration_state, &mut data.state.backend, &mut data.state.common.shell, + seats.into_iter(), &data.state.common.event_loop_handle, ); - data.state.common.shell.refresh_outputs(); - data.state - .common - .config - .write_outputs(data.state.common.output_configuration_state.outputs()); - for surface in data .state .backend @@ -263,12 +273,17 @@ pub fn init_backend( surface.pending = false; } for output in data.state.common.shell.outputs() { - if let Err(err) = data - .state - .backend - .kms() - .schedule_render(&data.state.common.event_loop_handle, output) - { + let sessions = output.pending_buffers().collect::>(); + if let Err(err) = data.state.backend.kms().schedule_render( + &data.state.common.event_loop_handle, + output, + None, + if !sessions.is_empty() { + Some(sessions) + } else { + None + }, + ) { slog_scope::crit!( "Error scheduling event loop for output {}: {:?}", output.name(), @@ -364,27 +379,87 @@ impl State { let dispatcher = Dispatcher::new(drm, move |event, metadata, data: &mut Data| match event { DrmEvent::VBlank(crtc) => { - if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) { - if let Some(surface) = device.surfaces.get_mut(&crtc) { - match surface.surface.as_mut().map(|x| x.frame_submitted()) { - Some(Ok(_)) => { - surface.last_submit = metadata.take().map(|data| data.time); - surface.pending = false; - data.state - .common - .shell - .active_space_mut(&surface.output) - .space - .send_frames( - data.state.common.start_time.elapsed().as_millis() - as u32, - ); + let rescheduled = + if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) { + if let Some(surface) = device.surfaces.get_mut(&crtc) { + #[cfg(feature = "debug")] + surface.fps.displayed(); + + match surface.surface.as_mut().map(|x| x.frame_submitted()) { + Some(Ok(feedback)) => { + if let Some(mut feedback) = feedback.flatten() { + let submit_time = + match metadata.take().map(|data| data.time) { + Some(DrmEventTime::Monotonic(tp)) => Some(tp), + _ => None, + }; + let seq = metadata + .as_ref() + .map(|metadata| metadata.sequence) + .unwrap_or(0); + + let (clock, flags) = if let Some(tp) = submit_time { + ( + tp.into(), + wp_presentation_feedback::Kind::Vsync + | wp_presentation_feedback::Kind::HwClock + | wp_presentation_feedback::Kind::HwCompletion, + ) + } else { + ( + data.state.common.clock.now(), + wp_presentation_feedback::Kind::Vsync, + ) + }; + + feedback.presented( + clock, + surface + .output + .current_mode() + .map(|mode| mode.refresh as u32) + .unwrap_or_default(), + seq as u64, + flags, + ); + } + + surface.pending = false; + surface.dirty.then(|| { + (surface.output.clone(), surface.fps.avg_rendertime(5)) + }) + } + Some(Err(err)) => { + slog_scope::warn!("Failed to submit frame: {}", err); + None + } + _ => None, // got disabled } - Some(Err(err)) => { - slog_scope::warn!("Failed to submit frame: {}", err) - } - None => {} // got disabled - }; + } else { + None + } + } else { + None + }; + + if let Some((output, avg_rendertime)) = rescheduled { + let mut scheduled_sessions = + data.state.workspace_session_for_output(&output); + if let Some(sessions) = output.user_data().get::() + { + scheduled_sessions + .get_or_insert_with(Vec::new) + .extend(sessions.borrow_mut().drain(..)); + } + + let repaint_delay = std::cmp::max(avg_rendertime, MIN_RENDER_TIME); + if let Err(err) = data.state.backend.kms().schedule_render( + &data.state.common.event_loop_handle, + &output, + Some(repaint_delay), + scheduled_sessions, + ) { + slog_scope::warn!("Failed to schedule render: {}", err); } } } @@ -424,47 +499,44 @@ impl State { let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices let mut wl_outputs = Vec::new(); let mut w = self.common.shell.global_space().size.w; - for (crtc, conn) in outputs { - match device.setup_surface(crtc, conn, (w, 0)) { - Ok(output) => { - w += output - .user_data() - .get::>() - .unwrap() - .borrow() - .mode_size() - .w; - wl_outputs.push(output); - } - Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), - }; + { + let backend = self.backend.kms(); + for (crtc, conn) in outputs { + let mut renderer = match backend.api.renderer(&render_node, &render_node) { + Ok(renderer) => renderer, + Err(err) => { + slog_scope::warn!("Failed to initialize output: {}", err); + continue; + } + }; + match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { + Ok(output) => { + w += output + .user_data() + .get::>() + .unwrap() + .borrow() + .mode_size() + .w; + wl_outputs.push(output); + } + Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), + }; + } + backend.devices.insert(drm_node, device); } - self.backend.kms().devices.insert(drm_node, device); self.common .output_configuration_state .add_heads(wl_outputs.iter()); - self.common.output_configuration_state.update(); - for output in wl_outputs { - if let Err(err) = self.backend.kms().apply_config_for_output( - &output, - &mut self.common.shell, - false, - &self.common.event_loop_handle, - ) { - slog_scope::warn!("Failed to initialize output: {}", err); - } - } + let seats = self.common.seats().cloned().collect::>(); self.common.config.read_outputs( - self.common.output_configuration_state.outputs(), + &mut self.common.output_configuration_state, &mut self.backend, &mut self.common.shell, + seats.into_iter(), &self.common.event_loop_handle, ); - self.common.shell.refresh_outputs(); - self.common - .config - .write_outputs(self.common.output_configuration_state.outputs()); Ok(()) } @@ -477,32 +549,45 @@ impl State { let drm_node = DrmNode::from_dev_id(dev)?; let mut outputs_removed = Vec::new(); let mut outputs_added = Vec::new(); - if let Some(device) = self.backend.kms().devices.get_mut(&drm_node) { - let changes = device.enumerate_surfaces()?; - let mut w = self.common.shell.global_space().size.w; - for crtc in changes.removed { - if let Some(surface) = device.surfaces.remove(&crtc) { - if let Some(token) = surface.render_timer_token { - self.common.event_loop_handle.remove(token); + { + let backend = self.backend.kms(); + if let Some(device) = backend.devices.get_mut(&drm_node) { + let changes = device.enumerate_surfaces()?; + let mut w = self.common.shell.global_space().size.w; + for crtc in changes.removed { + if let Some(surface) = device.surfaces.remove(&crtc) { + if let Some(token) = surface.render_timer_token { + self.common.event_loop_handle.remove(token); + } + w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0); + outputs_removed.push(surface.output.clone()); } - w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0); - outputs_removed.push(surface.output.clone()); } - } - for (crtc, conn) in changes.added { - match device.setup_surface(crtc, conn, (w, 0)) { - Ok(output) => { - w += output - .user_data() - .get::>() - .unwrap() - .borrow() - .mode_size() - .w; - outputs_added.push(output); - } - Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), - }; + for (crtc, conn) in changes.added { + let mut renderer = match backend + .api + .renderer(&device.render_node, &device.render_node) + { + Ok(renderer) => renderer, + Err(err) => { + slog_scope::warn!("Failed to initialize output: {}", err); + continue; + } + }; + match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { + Ok(output) => { + w += output + .user_data() + .get::>() + .unwrap() + .borrow() + .mode_size() + .w; + outputs_added.push(output); + } + Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), + }; + } } } @@ -512,30 +597,19 @@ impl State { self.common .output_configuration_state .add_heads(outputs_added.iter()); - for output in outputs_added { - if let Err(err) = self.backend.kms().apply_config_for_output( - &output, - &mut self.common.shell, - false, - &self.common.event_loop_handle, - ) { - slog_scope::warn!("Failed to initialize output: {}", err); - } - } + let seats = self.common.seats().cloned().collect::>(); for output in outputs_removed { - self.common.shell.remove_output(&output); + self.common + .shell + .remove_output(&output, seats.iter().cloned()); } - self.common.output_configuration_state.update(); self.common.config.read_outputs( - self.common.output_configuration_state.outputs(), + &mut self.common.output_configuration_state, &mut self.backend, &mut self.common.shell, + seats.into_iter(), &self.common.event_loop_handle, ); - self.common.shell.refresh_outputs(); - self.common - .config - .write_outputs(self.common.output_configuration_state.outputs()); Ok(()) } @@ -564,22 +638,23 @@ impl State { self.common .output_configuration_state .remove_heads(outputs_removed.iter()); - self.common.output_configuration_state.update(); + let seats = self.common.seats().cloned().collect::>(); if self.backend.kms().session.is_active() { for output in outputs_removed { - self.common.shell.remove_output(&output); + self.common + .shell + .remove_output(&output, seats.iter().cloned()); } self.common.config.read_outputs( - self.common.output_configuration_state.outputs(), + &mut self.common.output_configuration_state, &mut self.backend, &mut self.common.shell, + seats.into_iter(), &self.common.event_loop_handle, ); - self.common.shell.refresh_outputs(); - self.common - .config - .write_outputs(self.common.output_configuration_state.outputs()); + } else { + self.common.output_configuration_state.update(); } Ok(()) @@ -624,6 +699,7 @@ impl Device { crtc: crtc::Handle, conn: connector::Handle, position: (i32, i32), + renderer: &mut GlMultiRenderer<'_>, ) -> Result { let drm = &mut *self.drm.as_source_mut(); let crtc_info = drm.get_crtc(crtc)?; @@ -689,16 +765,15 @@ impl Device { let data = Surface { output: output.clone(), + damage_tracker: DamageTrackedRenderer::from_output(&output), surface: None, connector: conn, vrr, refresh_rate, - last_submit: None, - last_render: None, pending: false, + dirty: false, render_timer_token: None, - #[cfg(feature = "debug")] - fps: Fps::default(), + fps: Fps::new(renderer.as_mut()), }; self.surfaces.insert(crtc, data); @@ -717,8 +792,8 @@ fn render_node_for_output( let workspace = shell.active_space(output); let nodes = workspace .get_fullscreen(output) - .map(|w| vec![w]) - .unwrap_or_else(|| workspace.space.windows().collect::>()) + .map(|w| vec![w.clone()]) + .unwrap_or_else(|| workspace.windows().collect::>()) .into_iter() .flat_map(|w| { dh.get_client(w.toplevel().wl_surface().id()) @@ -750,44 +825,44 @@ impl Surface { pub fn render_output( &mut self, dh: &DisplayHandle, - api: &mut GpuManager, + api: &mut GpuManager>, target_node: &DrmNode, state: &mut Common, + screencopy: Option<&[(ScreencopySession, BufferParams)]>, ) -> Result<()> { if self.surface.is_none() { return Ok(()); } - if render::needs_buffer_reset(&self.output, state) { - self.surface.as_mut().unwrap().reset_buffers(); - } - let render_node = render_node_for_output(dh, &self.output, *target_node, &state.shell); - let mut renderer = api.renderer(&render_node, &target_node).unwrap(); + let mut renderer: GlMultiRenderer = api.renderer(&render_node, &target_node).unwrap(); let surface = self.surface.as_mut().unwrap(); let (buffer, age) = surface .next_buffer() .with_context(|| "Failed to allocate buffer")?; - renderer - .bind(buffer.clone()) - .with_context(|| "Failed to bind buffer")?; - - match render::render_output( + match render::render_output::( Some(&render_node), &mut renderer, - age, + buffer.clone(), + &mut self.damage_tracker, + age as usize, state, &self.output, - false, - #[cfg(feature = "debug")] + CursorMode::All, + screencopy.map(|sessions| (buffer, sessions)), Some(&mut self.fps), ) { - Ok(_) => { - self.last_render = Some((buffer, Instant::now())); + Ok((damage, states)) => { + let feedback = if damage.is_some() { + Some(state.take_presentation_feedback(&self.output, &states)) + } else { + None + }; + state.send_frames(&self.output, &states); surface - .queue_buffer() + .queue_buffer(feedback) .with_context(|| "Failed to submit buffer for display")?; } Err(err) => { @@ -795,6 +870,7 @@ impl Surface { anyhow::bail!("Rendering failed: {}", err); } }; + Ok(()) } } @@ -807,6 +883,7 @@ impl KmsState { pub fn apply_config_for_output( &mut self, output: &Output, + seats: impl Iterator>, shell: &mut Shell, test_only: bool, loop_handle: &LoopHandle<'_, Data>, @@ -829,10 +906,11 @@ impl KmsState { if !output_config.enabled { if !test_only { + shell.remove_output(output, seats); if surface.surface.take().is_some() { // just drop it - shell.remove_output(output); surface.pending = false; + surface.dirty = false; } } false @@ -856,7 +934,7 @@ impl KmsState { .ok_or(anyhow::anyhow!("Unknown mode"))?; if !test_only { - if let Some(gbm_surface) = surface.surface.as_mut() { + let res = if let Some(gbm_surface) = surface.surface.as_mut() { if output_config.vrr != surface.vrr { surface.vrr = drm_helpers::set_vrr( drm, @@ -888,9 +966,10 @@ impl KmsState { ) })?; surface.surface = Some(target); - shell.add_output(output); true - } + }; + shell.add_output(output); + res } else { false } @@ -901,7 +980,17 @@ impl KmsState { shell.refresh_outputs(); if recreated { - if let Err(err) = self.schedule_render(loop_handle, output) { + let sessions = output.pending_buffers().collect::>(); + if let Err(err) = self.schedule_render( + loop_handle, + output, + None, + if !sessions.is_empty() { + Some(sessions) + } else { + None + }, + ) { slog_scope::crit!( "Error scheduling event loop for output {}: {:?}", output.name(), @@ -964,6 +1053,8 @@ impl KmsState { &mut self, loop_handle: &LoopHandle<'_, Data>, output: &Output, + delay: Option, + mut screencopy_sessions: Option>, ) -> Result<(), InsertError> { if let Some((device, crtc, surface)) = self .devices @@ -975,20 +1066,8 @@ impl KmsState { return Ok(()); } if !surface.pending { + surface.dirty = false; surface.pending = true; - /* - let instant = surface - .last_submit - .as_ref() - .and_then(|x| match x { - DrmEventTime::Monotonic(instant) => Some(instant), - DrmEventTime::Realtime(_) => None, - }) - .map(|i| { - *i + Duration::from_secs_f64(1.0 / surface.refresh_rate as f64) - - Duration::from_millis(20) // render budget - }); - */ let device = *device; let crtc = *crtc; @@ -996,10 +1075,11 @@ impl KmsState { loop_handle.remove(token); } surface.render_timer_token = Some(loop_handle.insert_source( - //if surface.vrr || instant.is_none() { - Timer::immediate(), /*} else { - Timer::from_deadline(instant.unwrap()) - }*/ + if surface.vrr || delay.is_none() { + Timer::immediate() + } else { + Timer::from_duration(delay.unwrap()) + }, move |_time, _, data| { let backend = data.state.backend.kms(); if let Some(device) = backend.devices.get_mut(&device) { @@ -1009,11 +1089,17 @@ impl KmsState { &mut backend.api, &device.render_node, &mut data.state.common, + screencopy_sessions.as_deref(), ) { + if let Some(sessions) = screencopy_sessions.as_mut() { + for (session, params) in sessions.drain(..) { + data.state.common.still_pending(session, params); + } + } if backend.session.is_active() { slog_scope::error!("Error rendering: {}", err); return TimeoutAction::ToDuration(Duration::from_secs_f64( - 1.0 / surface.refresh_rate as f64, + (1000.0 / surface.refresh_rate as f64) - 0.003, )); } } @@ -1022,21 +1108,10 @@ impl KmsState { TimeoutAction::Drop }, )?); + } else { + surface.dirty = true; } } Ok(()) } - - pub fn capture_output(&self, output: &Output) -> Option<(DrmNode, Dmabuf, Instant)> { - self.devices.values().find_map(|dev| { - dev.surfaces - .values() - .find(|s| &s.output == output) - .and_then(|s| { - s.last_render - .clone() - .map(|(buf, time)| (dev.render_node.clone(), buf, time)) - }) - }) - } } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index a3b198eb..b90ff5c5 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -39,18 +39,22 @@ pub fn init_backend_auto( } } }; + if res.is_ok() { - for seat in &state.common.seats { - let output = state - .common - .shell - .outputs() - .next() - .with_context(|| "Backend initialized without output")? - .clone(); - seat.user_data() - .insert_if_missing(|| crate::input::ActiveOutput(std::cell::RefCell::new(output))); - } + let output = state + .common + .shell + .outputs() + .next() + .with_context(|| "Backend initialized without output")?; + let initial_seat = crate::input::add_seat( + dh, + &mut state.common.seat_state, + output, + &state.common.config, + "seat-0".into(), + ); + state.common.add_seat(initial_seat); } res } diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 7b5e8715..9c68a62d 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -2,14 +2,20 @@ use crate::utils::prelude::*; use smithay::{ - backend::renderer::{Frame, ImportAll, ImportMem, Renderer, Texture}, - desktop::space::{RenderElement, SpaceOutputTuple, SurfaceTree}, + backend::renderer::{ + element::{ + surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement}, + texture::{TextureBuffer, TextureRenderElement}, + }, + ImportAll, ImportMem, Renderer, + }, input::{ pointer::{CursorImageAttributes, CursorImageStatus}, Seat, }, reexports::wayland_server::protocol::wl_surface, - utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Transform}, + render_elements, + utils::{IsAlive, Logical, Monotonic, Point, Scale, Time, Transform}, wayland::compositor::{get_role, with_states}, }; use std::{ @@ -18,6 +24,7 @@ use std::{ collections::HashMap, io::Read, sync::Mutex, + time::Duration, }; use xcursor::{ parser::{parse_xcursor, Image}, @@ -119,13 +126,21 @@ fn load_icon(theme: &CursorTheme) -> Result, Error> { parse_xcursor(&cursor_data).ok_or(Error::Parse) } -pub fn draw_surface_cursor( - surface: wl_surface::WlSurface, +render_elements! { + pub CursorRenderElement where R: ImportAll; + Static=TextureRenderElement<::TextureId>, + Surface=WaylandSurfaceRenderElement, +} + +pub fn draw_surface_cursor( + surface: &wl_surface::WlSurface, location: impl Into>, -) -> SurfaceTree + scale: impl Into>, +) -> Vec> where { let mut position = location.into(); + let scale = scale.into(); let h = with_states(&surface, |states| { states .data_map @@ -136,129 +151,30 @@ where .hotspot }); position -= h; - SurfaceTree { - surface, - position, - z_index: 100, - } + + render_elements_from_surface_tree(surface, position.to_physical_precise_round(scale), scale) } -pub fn draw_dnd_icon( - surface: wl_surface::WlSurface, +pub fn draw_dnd_icon( + surface: &wl_surface::WlSurface, location: impl Into>, -) -> SurfaceTree { + scale: impl Into>, +) -> Vec> { if get_role(&surface) != Some("dnd_icon") { slog_scope::warn!( "Trying to display as a dnd icon a surface that does not have the DndIcon role." ); } - SurfaceTree { + let scale = scale.into(); + render_elements_from_surface_tree( surface, - position: location.into(), - z_index: 100, - } + location.into().to_physical_precise_round(scale), + scale, + ) } -pub struct PointerElement { - seat_id: usize, - texture: T, - position: Point, - size: Size, - new_frame: bool, -} - -impl PointerElement { - pub fn new( - seat: &Seat, - texture: T, - relative_pointer_pos: Point, - new_frame: bool, - ) -> PointerElement { - let size = texture.size().to_logical(1, Transform::Normal); - PointerElement { - seat_id: seat.id(), - texture, - position: relative_pointer_pos, - size, - new_frame, - } - } -} - -impl RenderElement for PointerElement<::TextureId> -where - R: Renderer + ImportAll, - ::TextureId: 'static, -{ - fn id(&self) -> usize { - self.seat_id - } - - fn location(&self, scale: impl Into>) -> Point { - self.position.to_physical(scale) - } - - fn geometry(&self, scale: impl Into>) -> Rectangle { - Rectangle::from_loc_and_size(self.position, self.size.to_f64()) - .to_physical(scale) - .to_i32_round() - } - - fn accumulated_damage( - &self, - scale: impl Into>, - _: Option>, - ) -> Vec> { - if self.new_frame { - let scale = scale.into(); - vec![Rectangle::from_loc_and_size( - self.position.to_physical(scale).to_i32_round(), - self.size.to_physical_precise_round(scale), - )] - } else { - vec![] - } - } - - fn opaque_regions( - &self, - _scale: impl Into>, - ) -> Option>> { - None - } - - fn draw( - &self, - _renderer: &mut R, - frame: &mut ::Frame, - scale: impl Into>, - position: Point, - damage: &[Rectangle], - _log: &slog::Logger, - ) -> Result<(), ::Error> { - let scale = scale.into(); - frame.render_texture_at( - &self.texture, - position.to_i32_round(), - 1, - scale, - Transform::Normal, - &damage - .iter() - .copied() - .map(|mut rect| { - rect.loc -= self.position.to_physical(scale).to_i32_round(); - rect - }) - .collect::>(), - 1.0, - )?; - Ok(()) - } -} - -struct CursorState { - cursor: Cursor, +pub struct CursorState { + pub cursor: Cursor, current_image: RefCell>, image_cache: RefCell)>>>, } @@ -273,79 +189,90 @@ impl Default for CursorState { } } -pub fn draw_cursor( +pub fn draw_cursor( renderer: &mut R, seat: &Seat, location: Point, - start_time: &std::time::Instant, + scale: Scale, + time: Time, draw_default: bool, -) -> Option +) -> Vec> where - I: From + From::TextureId>>, - R: Renderer + ImportAll + ImportMem, + R: Renderer + ImportMem + ImportAll, ::TextureId: Clone + 'static, { // draw the cursor as relevant - { - // reset the cursor if the surface is no longer alive - let cursor_status = seat - .user_data() - .get::>() - .map(|cell| { - let mut cursor_status = cell.borrow_mut(); - if let CursorImageStatus::Surface(ref surface) = *cursor_status { - if !surface.alive() { - *cursor_status = CursorImageStatus::Default; - } + // reset the cursor if the surface is no longer alive + let cursor_status = seat + .user_data() + .get::>() + .map(|cell| { + let mut cursor_status = cell.borrow_mut(); + if let CursorImageStatus::Surface(ref surface) = *cursor_status { + if !surface.alive() { + *cursor_status = CursorImageStatus::Default; } - cursor_status.clone() - }) - .unwrap_or(CursorImageStatus::Default); + } + cursor_status.clone() + }) + .unwrap_or(CursorImageStatus::Default); - if let CursorImageStatus::Surface(wl_surface) = cursor_status { - Some(draw_surface_cursor(wl_surface.clone(), location.to_i32_round()).into()) - } else if draw_default { - let seat_userdata = seat.user_data(); - seat_userdata.insert_if_missing(CursorState::default); - let state = seat_userdata.get::().unwrap(); - let frame = state - .cursor - .get_image(1, start_time.elapsed().as_millis() as u32); - let new_frame = state.current_image.borrow().as_ref() != Some(&frame); + if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { + return draw_surface_cursor(wl_surface, location.to_i32_round(), scale); + } else if draw_default && CursorImageStatus::Default == cursor_status { + let integer_scale = scale.x.max(scale.y).ceil() as u32; - let mut cache = state.image_cache.borrow_mut(); - let pointer_images = cache - .entry((TypeId::of::<::TextureId>(), renderer.id())) - .or_default(); - let pointer_image = pointer_images - .iter() - .find_map(|(image, texture)| if image == &frame { Some(texture) } else { None }) - .and_then(|texture| { - texture - .downcast_ref::<::TextureId>() - .cloned() - }) - .unwrap_or_else(|| { - let texture = renderer - .import_memory( - &frame.pixels_rgba, - (frame.width as i32, frame.height as i32).into(), - false, - ) - .expect("Failed to import cursor bitmap"); - pointer_images.push((frame.clone(), Box::new(texture.clone()))); - texture - }); - let hotspot = - Point::::from((frame.xhot as i32, frame.yhot as i32)).to_f64(); - *state.current_image.borrow_mut() = Some(frame); + let seat_userdata = seat.user_data(); + seat_userdata.insert_if_missing(CursorState::default); + let state = seat_userdata.get::().unwrap(); + let frame = state.cursor.get_image( + integer_scale, + Into::::into(time).as_millis() as u32, + ); - Some( - PointerElement::new(seat, pointer_image.clone(), location - hotspot, new_frame) - .into(), - ) - } else { - None - } + let mut cache = state.image_cache.borrow_mut(); + let pointer_images = cache + .entry((TypeId::of::>(), renderer.id())) + .or_default(); + + let maybe_image = pointer_images + .iter() + .find_map(|(image, texture)| if image == &frame { Some(texture) } else { None }) + .and_then(|texture| texture.downcast_ref::>()); + let pointer_image = match maybe_image { + Some(image) => image, + None => { + let texture = TextureBuffer::from_memory( + renderer, + &frame.pixels_rgba, + (frame.width as i32, frame.height as i32), + false, + integer_scale as i32, + Transform::Normal, + None, + ) + .expect("Failed to import cursor bitmap"); + pointer_images.push((frame.clone(), Box::new(texture.clone()))); + pointer_images + .last() + .and_then(|(_, i)| i.downcast_ref::>()) + .unwrap() + } + }; + + let hotspot = Point::::from((frame.xhot as i32, frame.yhot as i32)).to_f64(); + *state.current_image.borrow_mut() = Some(frame); + + return vec![CursorRenderElement::Static( + TextureRenderElement::from_texture_buffer( + (location - hotspot).to_physical(scale), + pointer_image, + None, + None, + None, + ), + )]; + } else { + Vec::new() } } diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs new file mode 100644 index 00000000..aeff0fc0 --- /dev/null +++ b/src/backend/render/element.rs @@ -0,0 +1,309 @@ +use crate::shell::{CosmicMappedRenderElement, WorkspaceRenderElement}; + +use smithay::{ + backend::renderer::{ + element::{texture::TextureRenderElement, Element, RenderElement, UnderlyingStorage}, + gles2::{Gles2Frame, Gles2Texture}, + glow::GlowRenderer, + multigpu::Error as MultiError, + Frame, ImportAll, Renderer, + }, + utils::{Physical, Point, Rectangle, Scale}, +}; + +use super::{cursor::CursorRenderElement, GlMultiFrame, GlMultiRenderer}; + +pub enum CosmicElement +where + R: AsGlowRenderer + Renderer + ImportAll, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + Workspace(WorkspaceRenderElement), + Cursor(CursorRenderElement), + MoveGrab(CosmicMappedRenderElement), + #[cfg(feature = "debug")] + Egui(TextureRenderElement), +} + +impl Element for CosmicElement +where + R: AsGlowRenderer + Renderer + ImportAll, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn id(&self) -> &smithay::backend::renderer::element::Id { + match self { + CosmicElement::Workspace(elem) => elem.id(), + CosmicElement::Cursor(elem) => elem.id(), + CosmicElement::MoveGrab(elem) => elem.id(), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.id(), + } + } + + fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter { + match self { + CosmicElement::Workspace(elem) => elem.current_commit(), + CosmicElement::Cursor(elem) => elem.current_commit(), + CosmicElement::MoveGrab(elem) => elem.current_commit(), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.current_commit(), + } + } + + fn src(&self) -> Rectangle { + match self { + CosmicElement::Workspace(elem) => elem.src(), + CosmicElement::Cursor(elem) => elem.src(), + CosmicElement::MoveGrab(elem) => elem.src(), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.src(), + } + } + + fn geometry(&self, scale: Scale) -> Rectangle { + match self { + CosmicElement::Workspace(elem) => elem.geometry(scale), + CosmicElement::Cursor(elem) => elem.geometry(scale), + CosmicElement::MoveGrab(elem) => elem.geometry(scale), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.geometry(scale), + } + } + + fn location(&self, scale: Scale) -> Point { + match self { + CosmicElement::Workspace(elem) => elem.location(scale), + CosmicElement::Cursor(elem) => elem.location(scale), + CosmicElement::MoveGrab(elem) => elem.location(scale), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.location(scale), + } + } + + fn transform(&self) -> smithay::utils::Transform { + match self { + CosmicElement::Workspace(elem) => elem.transform(), + CosmicElement::Cursor(elem) => elem.transform(), + CosmicElement::MoveGrab(elem) => elem.transform(), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.transform(), + } + } + + fn damage_since( + &self, + scale: Scale, + commit: Option, + ) -> Vec> { + match self { + CosmicElement::Workspace(elem) => elem.damage_since(scale, commit), + CosmicElement::Cursor(elem) => elem.damage_since(scale, commit), + CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.damage_since(scale, commit), + } + } + + fn opaque_regions(&self, scale: Scale) -> Vec> { + match self { + CosmicElement::Workspace(elem) => elem.opaque_regions(scale), + CosmicElement::Cursor(elem) => elem.opaque_regions(scale), + CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.opaque_regions(scale), + } + } +} + +impl RenderElement for CosmicElement { + fn draw( + &self, + renderer: &mut GlowRenderer, + frame: &mut ::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), ::Error> { + match self { + CosmicElement::Workspace(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + CosmicElement::Cursor(elem) => elem.draw(renderer, frame, location, scale, damage, log), + CosmicElement::MoveGrab(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.draw(renderer, frame, location, scale, damage, log), + } + } + + fn underlying_storage( + &self, + renderer: &GlowRenderer, + ) -> Option> { + match self { + CosmicElement::Workspace(elem) => elem.underlying_storage(renderer), + CosmicElement::Cursor(elem) => elem.underlying_storage(renderer), + CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.underlying_storage(renderer), + } + } +} + +impl<'a> RenderElement> for CosmicElement> { + fn draw( + &self, + renderer: &mut GlMultiRenderer<'a>, + frame: &mut as Renderer>::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), as Renderer>::Error> { + match self { + CosmicElement::Workspace(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + CosmicElement::Cursor(elem) => elem.draw(renderer, frame, location, scale, damage, log), + CosmicElement::MoveGrab(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => { + let glow_renderer = renderer.glow_renderer_mut(); + let gles2_frame = frame.gles2_frame_mut(); + elem.draw(glow_renderer, gles2_frame, location, scale, damage, log) + .map_err(|err| MultiError::Render(err)) + } + } + } + + fn underlying_storage( + &self, + renderer: &GlMultiRenderer<'a>, + ) -> Option>> { + match self { + CosmicElement::Workspace(elem) => elem.underlying_storage(renderer), + CosmicElement::Cursor(elem) => elem.underlying_storage(renderer), + CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => { + let glow_renderer = renderer.glow_renderer(); + match elem.underlying_storage(glow_renderer) { + Some(UnderlyingStorage::Wayland(buffer)) => { + Some(UnderlyingStorage::Wayland(buffer)) + } + _ => None, + } + } + } + } +} + +impl From> for CosmicElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: WorkspaceRenderElement) -> Self { + Self::Workspace(elem) + } +} + +impl From> for CosmicElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: CursorRenderElement) -> Self { + Self::Cursor(elem) + } +} + +impl From> for CosmicElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: CosmicMappedRenderElement) -> Self { + Self::MoveGrab(elem) + } +} + +#[cfg(feature = "debug")] +impl From> for CosmicElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: TextureRenderElement) -> Self { + Self::Egui(elem) + } +} + +pub trait AsGlowRenderer +where + Self: Renderer, + ::Frame: AsGles2Frame, +{ + fn glow_renderer(&self) -> &GlowRenderer; + fn glow_renderer_mut(&mut self) -> &mut GlowRenderer; +} + +impl AsGlowRenderer for GlowRenderer { + fn glow_renderer(&self) -> &GlowRenderer { + self + } + fn glow_renderer_mut(&mut self) -> &mut GlowRenderer { + self + } +} + +impl<'a> AsGlowRenderer for GlMultiRenderer<'a> { + fn glow_renderer(&self) -> &GlowRenderer { + self.as_ref() + } + fn glow_renderer_mut(&mut self) -> &mut GlowRenderer { + self.as_mut() + } +} + +pub trait AsGles2Frame +where + Self: Frame, +{ + fn gles2_frame(&self) -> &Gles2Frame; + fn gles2_frame_mut(&mut self) -> &mut Gles2Frame; +} + +impl AsGles2Frame for Gles2Frame { + fn gles2_frame(&self) -> &Gles2Frame { + self + } + fn gles2_frame_mut(&mut self) -> &mut Gles2Frame { + self + } +} + +impl AsGles2Frame for GlMultiFrame { + fn gles2_frame(&self) -> &Gles2Frame { + self.as_ref() + } + fn gles2_frame_mut(&mut self) -> &mut Gles2Frame { + self.as_mut() + } +} diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 5692974e..8ea52693 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -1,477 +1,314 @@ // SPDX-License-Identifier: GPL-3.0-only #[cfg(feature = "debug")] +use crate::{debug::fps_ui, utils::prelude::*}; use crate::{ - debug::{debug_ui, fps_ui, log_ui, EguiFrame}, - state::Fps, - utils::prelude::*, -}; -use crate::{ - shell::grabs::{MoveGrabRenderElement, SeatMoveGrabState}, - state::Common, - wayland::handlers::data_device::get_dnd_icon, -}; - -use slog::Logger; -use smithay::{ - backend::{ - drm::DrmNode, - renderer::{ - gles2::{Gles2Renderbuffer, Gles2Renderer, Gles2Texture}, - multigpu::{egl::EglGlesBackend, Error as MultiError, MultiFrame, MultiRenderer}, - Frame, ImportAll, Renderer, + shell::{layout::floating::SeatMoveGrabState, CosmicMappedRenderElement}, + state::{Common, Fps}, + wayland::{ + handlers::{data_device::get_dnd_icon, screencopy::render_session}, + protocols::{ + screencopy::{ + BufferParams, CursorMode as ScreencopyCursorMode, Session as ScreencopySession, + }, + workspace::WorkspaceHandle, }, }, - desktop::{ - draw_layer_popups, draw_layer_surface, draw_window, draw_window_popups, - layer_map_for_output, - space::{RenderElement, RenderError, SpaceOutputTuple, SurfaceTree}, - utils::damage_from_surface_tree, - Window, +}; + +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::FailureReason; +use smithay::{ + backend::{ + allocator::dmabuf::Dmabuf, + drm::DrmNode, + renderer::{ + buffer_dimensions, + damage::{ + DamageTrackedRenderer, DamageTrackedRendererError as RenderError, OutputNoMode, + }, + element::{RenderElement, RenderElementStates}, + gles2::{Gles2Error, Gles2Renderbuffer}, + glow::GlowRenderer, + multigpu::{egl::EglGlesBackend, MultiFrame, MultiRenderer}, + Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, TextureFilter, + }, }, output::Output, - utils::{Physical, Point, Rectangle, Scale, Transform}, - wayland::shell::wlr_layer::Layer as WlrLayer, + utils::{Physical, Rectangle}, + wayland::dmabuf::get_dmabuf, }; pub mod cursor; -use self::cursor::PointerElement; +use self::cursor::CursorRenderElement; +pub mod element; +use self::element::{AsGles2Frame, AsGlowRenderer, CosmicElement}; -pub type GlMultiRenderer<'a> = - MultiRenderer<'a, 'a, EglGlesBackend, EglGlesBackend, Gles2Renderbuffer>; -pub type GlMultiFrame = MultiFrame; +pub type GlMultiRenderer<'a> = MultiRenderer< + 'a, + 'a, + EglGlesBackend, + EglGlesBackend, + Gles2Renderbuffer, +>; +pub type GlMultiFrame = MultiFrame, EglGlesBackend>; -static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; +pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; -smithay::custom_elements! { - pub CustomElem<=Gles2Renderer>; - SurfaceTree=SurfaceTree, - PointerElement=PointerElement::, - MoveGrabRenderElement=MoveGrabRenderElement, - #[cfg(feature = "debug")] - EguiFrame=EguiFrame, +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CursorMode { + None, + NotDefault, + All, } -// TODO: due to the lifetime of MultiRenderer, we cannot be generic over CustomElem's renderer -// util after GATs land. So we generate with the macro for Gles2 and then -// do a manual impl for MultiRenderer. -impl RenderElement> for CustomElem { - fn id(&self) -> usize { - RenderElement::::id(self) - } - - fn location(&self, scale: impl Into>) -> Point { - RenderElement::::location(self, scale) - } - - fn geometry(&self, scale: impl Into>) -> Rectangle { - RenderElement::::geometry(self, scale) - } - - fn accumulated_damage( - &self, - scale: impl Into>, - for_values: Option>, - ) -> Vec> { - RenderElement::::accumulated_damage(self, scale, for_values) - } - - fn opaque_regions( - &self, - scale: impl Into>, - ) -> Option>> { - RenderElement::::opaque_regions(self, scale) - } - - fn draw( - &self, - renderer: &mut GlMultiRenderer<'_>, - frame: &mut GlMultiFrame, - scale: impl Into>, - location: Point, - damage: &[Rectangle], - log: &Logger, - ) -> Result<(), MultiError> { - RenderElement::::draw( - self, - renderer.as_mut(), - frame.as_mut(), - scale, - location, - damage, - log, - ) - .map_err(MultiError::Render) - } - - fn z_index(&self) -> u8 { - RenderElement::::z_index(self) - } -} - -pub trait AsGles2Renderer { - fn as_gles2(&mut self) -> &mut Gles2Renderer; -} -impl AsGles2Renderer for Gles2Renderer { - fn as_gles2(&mut self) -> &mut Gles2Renderer { - self - } -} -impl AsGles2Renderer for GlMultiRenderer<'_> { - fn as_gles2(&mut self) -> &mut Gles2Renderer { - self.as_mut() - } -} - -pub fn needs_buffer_reset(output: &Output, state: &Common) -> bool { - use std::sync::atomic::{AtomicBool, Ordering}; - struct DidCustomRendering(AtomicBool); - - let will_render_custom = { - let workspace = state.shell.active_space(output); - workspace.get_fullscreen(output).is_some() - }; - - let userdata = output.user_data(); - userdata.insert_if_missing(|| DidCustomRendering(AtomicBool::new(false))); - userdata - .get::() - .unwrap() - .0 - .swap(will_render_custom, Ordering::AcqRel) - != will_render_custom -} - -pub fn cursor_custom_elements( +pub fn cursor_elements( renderer: &mut R, state: &Common, output: &Output, - hardware_cursor: bool, -) -> Vec + mode: CursorMode, +) -> Vec where - R: AsGles2Renderer, + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::Frame: AsGles2Frame, + ::TextureId: Clone + 'static, + CosmicMappedRenderElement: RenderElement, + E: From> + From>, { - let mut custom_elements = Vec::new(); + let scale = output.current_scale().fractional_scale(); + let mut elements = Vec::new(); - for seat in &state.seats { + for seat in state.seats() { let pointer = match seat.get_pointer() { Some(ptr) => ptr, None => continue, }; - let location = state - .shell - .space_relative_output_geometry(pointer.current_location().to_i32_round(), output); + let location = pointer.current_location() - output.current_location().to_f64(); - if let Some(grab) = seat + if mode != CursorMode::None { + elements.extend( + cursor::draw_cursor( + renderer, + seat, + location, + scale.into(), + state.clock.now(), + mode != CursorMode::NotDefault, + ) + .into_iter() + .map(E::from), + ); + } + + if let Some(wl_surface) = get_dnd_icon(seat) { + elements.extend( + cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), scale) + .into_iter() + .map(E::from), + ); + } + + if let Some(grab_elements) = seat .user_data() .get::() .unwrap() .borrow() .as_ref() - .and_then(|state| state.render(seat, output)) + .map(|state| state.render::(seat, output)) { - custom_elements.push(grab); - } - - if let Some(wl_surface) = get_dnd_icon(seat) { - custom_elements.push(cursor::draw_dnd_icon(wl_surface, location.to_i32_round()).into()); - } - - if let Some(cursor) = cursor::draw_cursor( - renderer.as_gles2(), - seat, - location, - &state.start_time, - !hardware_cursor, - ) { - custom_elements.push(cursor) + elements.extend(grab_elements); } } - custom_elements + elements } -pub fn render_output( +pub fn render_output( gpu: Option<&DrmNode>, renderer: &mut R, - age: u8, + target: Target, + damage_tracker: &mut DamageTrackedRenderer, + age: usize, state: &mut Common, output: &Output, - hardware_cursor: bool, - #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, -) -> Result>>, RenderError> + cursor_mode: CursorMode, + screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, + fps: Option<&mut Fps>, +) -> Result<(Option>>, RenderElementStates), RenderError> where - R: Renderer + ImportAll + AsGles2Renderer, + R: Renderer + + ImportAll + + ImportMem + + ExportMem + + Bind + + Bind + + Offscreen + + Blit + + AsGlowRenderer, + ::Frame: AsGles2Frame, ::TextureId: Clone + 'static, - CustomElem: RenderElement, + ::Error: From, + CosmicElement: RenderElement, + CosmicMappedRenderElement: RenderElement, + Source: Clone, { - let workspace = state.shell.active_space(output).idx; + let handle = state.shell.workspaces.active(output).handle; render_workspace( gpu, renderer, + target, + damage_tracker, age, state, - workspace, output, - hardware_cursor, + &handle, + cursor_mode, + screencopy, + fps, ) } -pub fn render_workspace( +pub fn render_workspace( gpu: Option<&DrmNode>, renderer: &mut R, - age: u8, + target: Target, + damage_tracker: &mut DamageTrackedRenderer, + age: usize, state: &mut Common, - space_idx: u8, output: &Output, - hardware_cursor: bool, - #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, -) -> Result>>, RenderError> + handle: &WorkspaceHandle, + mut cursor_mode: CursorMode, + screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, + mut fps: Option<&mut Fps>, +) -> Result<(Option>>, RenderElementStates), RenderError> where - R: Renderer + ImportAll + AsGles2Renderer, + R: Renderer + + ImportAll + + ImportMem + + ExportMem + + Bind + + Bind + + Offscreen + + Blit + + AsGlowRenderer, + ::Frame: AsGles2Frame, ::TextureId: Clone + 'static, - CustomElem: RenderElement, + ::Error: From, + CosmicElement: RenderElement, + CosmicMappedRenderElement: RenderElement, + Source: Clone, { - #[cfg(feature = "debug")] if let Some(ref mut fps) = fps { fps.start(); } - let space_idx = space_idx as usize; - let workspace = &mut state.shell.spaces[space_idx]; - let maybe_fullscreen_window = workspace.get_fullscreen(output).cloned(); + let workspace = state.shell.space_for_handle(&handle).ok_or(OutputNoMode)?; - let res = if let Some(window) = maybe_fullscreen_window { - #[cfg(not(feature = "debug"))] - { - render_fullscreen(gpu, renderer, window, state, output, hardware_cursor) - } - #[cfg(feature = "debug")] - { - render_fullscreen( - gpu, - renderer, - window, - state, - output, - hardware_cursor, - fps.as_deref_mut(), - ) - } - } else { - #[cfg(not(feature = "debug"))] - { - render_desktop( - gpu, - renderer, - age, - state, - space_idx, - output, - hardware_cursor, - ) - } - #[cfg(feature = "debug")] - { - render_desktop( - gpu, - renderer, - age, - state, - space_idx, - output, - hardware_cursor, - fps.as_deref_mut(), - ) - } + let screencopy_contains_embedded = screencopy.as_ref().map_or(false, |(_, sessions)| { + sessions + .iter() + .any(|(s, _)| s.cursor_mode() == ScreencopyCursorMode::Embedded) + }); + // cursor handling without a cursor_plane in this case is horrible. + // because what if some session disagree and/or the backend wants to render with a different mode? + // It seems we would need to render to an offscreen buffer in those cases (and do multiple renders, which messes with damage tracking). + // So for now, we just pick the worst mode (embedded), if any requires it. + // + // Once we move to a cursor_plane, the default framebuffer will never contain a cursor and we can just composite the cursor for each session separately on top (or not). + if screencopy_contains_embedded { + cursor_mode = CursorMode::All; }; + let mut elements: Vec> = cursor_elements(renderer, state, output, cursor_mode); + #[cfg(feature = "debug")] - if let Some(ref mut fps) = fps { - fps.end(); + { + let output_geo = output.geometry(); + let scale = output.current_scale().fractional_scale(); + + if let Some(fps) = fps.as_mut() { + let fps_overlay = fps_ui( + gpu, + state, + renderer.glow_renderer_mut(), + fps, + Rectangle::from_loc_and_size( + (0, 0), + (output_geo.size.w.min(400), output_geo.size.h.min(800)), + ), + scale, + ) + .map_err(::Error::from) + .map_err(RenderError::Rendering)?; + elements.push(fps_overlay.into()); + } + } + + elements.extend( + workspace + .render_output::(output) + .map_err(|_| OutputNoMode)? + .into_iter() + .map(Into::into), + ); + + if let Some(fps) = fps.as_mut() { + fps.elements(); + } + + renderer.bind(target).map_err(RenderError::Rendering)?; + let res = damage_tracker.render_output(renderer, age, &elements, CLEAR_COLOR, None); + + if let Some(fps) = fps.as_mut() { + fps.render(); + } + + if let Some((source, buffers)) = screencopy { + if res.is_ok() { + for (session, params) in buffers { + match render_session( + gpu.cloned(), + renderer, + &session, + params, + output.current_transform(), + |_node, buffer, renderer, dtr, age| { + let res = dtr.damage_output(age, &elements, slog_scope::logger())?; + + if let (Some(ref damage), _) = &res { + if let Ok(dmabuf) = get_dmabuf(buffer) { + renderer.bind(dmabuf).map_err(RenderError::Rendering)?; + } else { + let size = buffer_dimensions(buffer).unwrap(); + let render_buffer = renderer + .create_buffer(size) + .map_err(RenderError::Rendering)?; + renderer + .bind(render_buffer) + .map_err(RenderError::Rendering)?; + } + for rect in damage { + renderer + .blit_from(source.clone(), *rect, *rect, TextureFilter::Nearest) + .map_err(RenderError::Rendering)?; + } + } + + Ok(res) + }, + ) { + Ok(true) => {} // success + Ok(false) => state.still_pending(session.clone(), params.clone()), + Err(err) => { + slog_scope::warn!("Error rendering to screencopy session: {}", err); + session.failed(FailureReason::Unspec); + } + } + } + } + if let Some(fps) = fps.as_mut() { + fps.screencopy(); + } } res } - -fn render_desktop( - _gpu: Option<&DrmNode>, - renderer: &mut R, - age: u8, - state: &mut Common, - space_idx: usize, - output: &Output, - hardware_cursor: bool, - #[cfg(feature = "debug")] fps: Option<&mut Fps>, -) -> Result>>, RenderError> -where - R: Renderer + ImportAll + AsGles2Renderer, - ::TextureId: Clone + 'static, - CustomElem: RenderElement, -{ - let mut custom_elements = Vec::::new(); - - #[cfg(feature = "debug")] - { - let workspace = &state.shell.spaces[space_idx]; - let output_geo = workspace - .space - .output_geometry(output) - .unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0))); - let scale = output.current_scale().fractional_scale(); - - if let Some(fps) = fps { - let fps_overlay = fps_ui( - _gpu, - state, - fps, - output_geo.to_f64().to_physical(scale), - scale, - ); - custom_elements.push(fps_overlay.into()); - } - - let area = Rectangle::::from_loc_and_size( - state - .shell - .space_relative_output_geometry((0.0f64, 0.0f64), output), - state.shell.global_space().to_f64().size, - ) - .to_physical(scale); - if let Some(log_ui) = log_ui(state, area, scale, output_geo.size.w as f32 * 0.6) { - custom_elements.push(log_ui.into()); - } - if let Some(debug_overlay) = debug_ui(state, area, scale) { - custom_elements.push(debug_overlay.into()); - } - } - - custom_elements.extend(cursor_custom_elements( - renderer, - state, - output, - hardware_cursor, - )); - - state.shell.spaces[space_idx].space.render_output( - renderer, - &output, - age as usize, - CLEAR_COLOR, - &*custom_elements, - ) -} - -fn render_fullscreen( - _gpu: Option<&DrmNode>, - renderer: &mut R, - window: Window, - state: &mut Common, - output: &Output, - hardware_cursor: bool, - #[cfg(feature = "debug")] fps: Option<&mut Fps>, -) -> Result>>, RenderError> -where - R: Renderer + ImportAll + AsGles2Renderer, - ::TextureId: Clone + 'static, - CustomElem: RenderElement, -{ - let transform = Transform::from(output.current_transform()); - let mode = output.current_mode().unwrap(); - let scale = output.current_scale().fractional_scale(); - - let mut custom_elements = Vec::::new(); - - #[cfg(feature = "debug")] - if let Some(fps) = fps { - let output_geo = output.geometry(); - let fps_overlay = fps_ui( - _gpu, - state, - fps, - Rectangle::from_loc_and_size((0, 0), output_geo.size) - .to_f64() - .to_physical(scale), - scale, - ); - custom_elements.push(fps_overlay.into()); - } - - custom_elements.extend(cursor_custom_elements( - renderer, - state, - output, - hardware_cursor, - )); - - renderer - .render(mode.size, transform, |renderer, frame| { - let mut damage = window.accumulated_damage((0.0, 0.0), scale, None); - frame.clear( - CLEAR_COLOR, - &[Rectangle::from_loc_and_size((0, 0), mode.size)], - )?; - draw_window( - renderer, - frame, - &window, - scale, - (0.0, 0.0), - &[Rectangle::from_loc_and_size((0, 0), mode.size)], - &slog_scope::logger(), - )?; - draw_window_popups( - renderer, - frame, - &window, - scale, - (0.0, 0.0), - &[Rectangle::from_loc_and_size((0, 0), mode.size)], - &slog_scope::logger(), - )?; - let layer_map = layer_map_for_output(output); - for layer_surface in layer_map.layers_on(WlrLayer::Overlay) { - let geo = layer_map.layer_geometry(&layer_surface).unwrap(); - draw_layer_surface( - renderer, - frame, - layer_surface, - scale, - geo.loc.to_f64().to_physical(scale), - &[Rectangle::from_loc_and_size( - (0, 0), - geo.size.to_physical_precise_round(scale), - )], - &slog_scope::logger(), - )?; - draw_layer_popups( - renderer, - frame, - layer_surface, - scale, - geo.loc.to_f64().to_physical(scale), - &[Rectangle::from_loc_and_size( - (0, 0), - geo.size.to_physical_precise_round(scale), - )], - &slog_scope::logger(), - )?; - damage.extend(damage_from_surface_tree( - layer_surface.wl_surface(), - geo.loc.to_f64().to_physical(scale), - scale, - None, - )); - } - for elem in custom_elements { - let loc = elem.location(scale); - let geo = elem.geometry(scale); - let elem_damage = elem.accumulated_damage(scale, None); - elem.draw(renderer, frame, scale, loc, &[geo], &slog_scope::logger())?; - damage.extend(elem_damage) - } - Ok(Some(damage)) - }) - .and_then(std::convert::identity) - .map_err(RenderError::::Rendering) -} diff --git a/src/backend/winit.rs b/src/backend/winit.rs index b6a67ed1..0e4a259b 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -6,17 +6,22 @@ use crate::{ input::Devices, state::{BackendData, Common, Data}, utils::prelude::*, + wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession}, }; use anyhow::{anyhow, Context, Result}; use smithay::{ backend::{ - renderer::{ImportDma, ImportEgl}, + renderer::{ + damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, ImportDma, + ImportEgl, + }, winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice}, }, desktop::layer_map_for_output, output::{Mode, Output, PhysicalProperties, Scale, Subpixel}, reexports::{ calloop::{ping, EventLoop}, + wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::DisplayHandle, }, utils::Transform, @@ -26,52 +31,71 @@ use std::cell::RefCell; #[cfg(feature = "debug")] use crate::state::Fps; +use super::render::CursorMode; + pub struct WinitState { // The winit backend currently has no notion of multiple windows - pub backend: WinitGraphicsBackend, + pub backend: WinitGraphicsBackend, output: Output, - age_reset: u8, + damage_tracker: DamageTrackedRenderer, + screencopy: Vec<(ScreencopySession, BufferParams)>, #[cfg(feature = "debug")] fps: Fps, } impl WinitState { pub fn render_output(&mut self, state: &mut Common) -> Result<()> { - if render::needs_buffer_reset(&self.output, state) { - self.reset_buffers(); - } - self.backend .bind() .with_context(|| "Failed to bind buffer")?; - let age = if self.age_reset > 0 { - self.age_reset -= 1; - 0 - } else { - self.backend.buffer_age().unwrap_or(0) - }; + let age = self.backend.buffer_age().unwrap_or(0); - match render::render_output( + let surface = self.backend.egl_surface(); + match render::render_output::<_, _, Gles2Renderbuffer, _>( None, self.backend.renderer(), - age as u8, + surface.clone(), + &mut self.damage_tracker, + age, state, &self.output, - true, + CursorMode::NotDefault, + if !self.screencopy.is_empty() { + Some((surface, &self.screencopy)) + } else { + None + }, + #[cfg(not(feature = "debug"))] + None, #[cfg(feature = "debug")] Some(&mut self.fps), ) { - Ok(damage) => { - state - .shell - .active_space_mut(&self.output) - .space - .send_frames(state.start_time.elapsed().as_millis() as u32); + Ok((damage, states)) => { + self.screencopy.clear(); self.backend - .submit(damage.as_ref().map(|x| &**x)) + .submit(damage.as_deref()) .with_context(|| "Failed to submit buffer for display")?; + #[cfg(feature = "debug")] + self.fps.displayed(); + state.send_frames(&self.output, &states); + if damage.is_some() { + let mut output_presentation_feedback = + state.take_presentation_feedback(&self.output, &states); + output_presentation_feedback.presented( + state.clock.now(), + self.output + .current_mode() + .map(|mode| mode.refresh as u32) + .unwrap_or_default(), + 0, + wp_presentation_feedback::Kind::Vsync, + ) + } } Err(err) => { + for (session, params) in self.screencopy.drain(..) { + state.still_pending(session, params) + } anyhow::bail!("Rendering failed: {}", err); } }; @@ -105,8 +129,10 @@ impl WinitState { } } - pub fn reset_buffers(&mut self) { - self.age_reset = 3; + pub fn pending_screencopy(&mut self, new: Option>) { + if let Some(sessions) = new { + self.screencopy.extend(sessions); + } } } @@ -133,7 +159,6 @@ pub fn init_backend( refresh: 60_000, }; let output = Output::new(name, props, None); - let _global = output.create_global::(dh); output.add_mode(mode); output.set_preferred(mode); output.change_current_state( @@ -180,8 +205,7 @@ pub fn init_backend( .handle() .insert_source(event_source, move |_, _, data| { match input.dispatch_new_events(|event| { - data.state - .process_winit_event(&data.display.handle(), event, &render_ping_handle) + data.state.process_winit_event(event, &render_ping_handle) }) { Ok(_) => { event_ping_handle.ping(); @@ -189,7 +213,11 @@ pub fn init_backend( } Err(winit::WinitError::WindowClosed) => { let output = data.state.backend.winit().output.clone(); - data.state.common.shell.remove_output(&output); + let seats = data.state.common.seats().cloned().collect::>(); + data.state + .common + .shell + .remove_output(&output, seats.into_iter()); if let Some(token) = token.take() { event_loop_handle.remove(token); } @@ -199,27 +227,30 @@ pub fn init_backend( .map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?; event_ping.ping(); + #[cfg(feature = "debug")] + let fps = Fps::new(backend.renderer()); + state.backend = BackendData::Winit(WinitState { backend, output: output.clone(), + damage_tracker: DamageTrackedRenderer::from_output(&output), + screencopy: Vec::new(), #[cfg(feature = "debug")] - fps: Fps::default(), - age_reset: 0, + fps, }); state .common .output_configuration_state .add_heads(std::iter::once(&output)); - state.common.output_configuration_state.update(); state.common.shell.add_output(&output); + let seats = state.common.seats().cloned().collect::>(); state.common.config.read_outputs( - std::iter::once(&output), + &mut state.common.output_configuration_state, &mut state.backend, &mut state.common.shell, + seats.iter().cloned(), &state.common.event_loop_handle, ); - state.common.shell.refresh_outputs(); - state.common.config.write_outputs(std::iter::once(&output)); Ok(()) } @@ -227,7 +258,7 @@ pub fn init_backend( fn init_egl_client_side( dh: &DisplayHandle, state: &mut State, - renderer: &mut WinitGraphicsBackend, + renderer: &mut WinitGraphicsBackend, ) -> Result<()> { let bind_result = renderer.renderer().bind_wl_display(dh); match bind_result { @@ -250,19 +281,14 @@ fn init_egl_client_side( } impl State { - pub fn process_winit_event( - &mut self, - dh: &DisplayHandle, - event: WinitEvent, - render_ping: &ping::Ping, - ) { + pub fn process_winit_event(&mut self, event: WinitEvent, render_ping: &ping::Ping) { // here we can handle special cases for winit inputs match event { WinitEvent::Focus(true) => { - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let devices = seat.user_data().get::().unwrap(); if devices.has_device(&WinitVirtualDevice) { - set_active_output(seat, &self.backend.winit().output); + seat.set_active_output(&self.backend.winit().output); break; } } @@ -286,7 +312,7 @@ impl State { output.delete_mode(output.current_mode().unwrap()); output.set_preferred(mode); output.change_current_state(Some(mode), None, None, None); - layer_map_for_output(output).arrange(dh); + layer_map_for_output(output).arrange(); self.common.output_configuration_state.update(); self.common.shell.refresh_outputs(); render_ping.ping(); diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 53c4c1ca..6d54691e 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -6,6 +6,7 @@ use crate::{ input::Devices, state::{BackendData, Common, Data}, utils::prelude::*, + wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession}, }; use anyhow::{Context, Result}; use smithay::{ @@ -13,7 +14,10 @@ use smithay::{ allocator::dmabuf::Dmabuf, egl::{EGLContext, EGLDisplay}, input::{Event, InputEvent}, - renderer::{gles2::Gles2Renderer, Bind, ImportDma, ImportEgl}, + renderer::{ + damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, Bind, + ImportDma, ImportEgl, + }, x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Input, X11Surface}, }, desktop::layer_map_for_output, @@ -21,6 +25,7 @@ use smithay::{ reexports::{ calloop::{ping, EventLoop, LoopHandle}, gbm::{Device as GbmDevice, FdWrapper}, + wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::DisplayHandle, }, utils::Transform, @@ -36,7 +41,7 @@ use crate::state::Fps; pub struct X11State { allocator: Arc>>, _egl: EGLDisplay, - pub renderer: Gles2Renderer, + pub renderer: GlowRenderer, surfaces: Vec, handle: X11Handle, } @@ -114,12 +119,14 @@ impl X11State { self.surfaces.push(Surface { window, surface, + damage_tracker: DamageTrackedRenderer::from_output(&output), output: output.clone(), render: ping.clone(), dirty: false, pending: true, + screencopy: Vec::new(), #[cfg(feature = "debug")] - fps: Fps::default(), + fps: Fps::new(&mut self.renderer), }); // schedule first render @@ -127,9 +134,16 @@ impl X11State { Ok(output) } - pub fn schedule_render(&mut self, output: &Output) { + pub fn schedule_render( + &mut self, + output: &Output, + screencopy: Option>, + ) { if let Some(surface) = self.surfaces.iter_mut().find(|s| s.output == *output) { surface.dirty = true; + if let Some(sessions) = screencopy { + surface.screencopy.extend(sessions); + } if !surface.pending { surface.render.ping(); } @@ -168,6 +182,8 @@ impl X11State { pub struct Surface { window: Window, + damage_tracker: DamageTrackedRenderer, + screencopy: Vec<(ScreencopySession, BufferParams)>, surface: X11Surface, output: Output, render: ping::Ping, @@ -178,44 +194,56 @@ pub struct Surface { } impl Surface { - pub fn render_output( - &mut self, - renderer: &mut Gles2Renderer, - state: &mut Common, - ) -> Result<()> { - if render::needs_buffer_reset(&self.output, state) { - self.surface.reset_buffers(); - } - + pub fn render_output(&mut self, renderer: &mut GlowRenderer, state: &mut Common) -> Result<()> { let (buffer, age) = self .surface .buffer() .with_context(|| "Failed to allocate buffer")?; - renderer - .bind(buffer) - .with_context(|| "Failed to bind buffer")?; - - match render::render_output( + match render::render_output::<_, _, Gles2Renderbuffer, _>( None, renderer, - age as u8, + buffer.clone(), + &mut self.damage_tracker, + age as usize, state, &self.output, - true, + render::CursorMode::NotDefault, + if !self.screencopy.is_empty() { + Some((buffer, &self.screencopy)) + } else { + None + }, + #[cfg(not(feature = "debug"))] + None, #[cfg(feature = "debug")] Some(&mut self.fps), ) { - Ok(_) => { - state - .shell - .active_space_mut(&self.output) - .space - .send_frames(state.start_time.elapsed().as_millis() as u32); + Ok((damage, states)) => { + self.screencopy.clear(); self.surface .submit() .with_context(|| "Failed to submit buffer for display")?; + #[cfg(feature = "debug")] + self.fps.displayed(); + state.send_frames(&self.output, &states); + if damage.is_some() { + let mut output_presentation_feedback = + state.take_presentation_feedback(&self.output, &states); + output_presentation_feedback.presented( + state.clock.now(), + self.output + .current_mode() + .map(|mode| mode.refresh as u32) + .unwrap_or_default(), + 0, + wp_presentation_feedback::Kind::Vsync, + ) + } } Err(err) => { + for (session, params) in self.screencopy.drain(..) { + state.still_pending(session, params) + } self.surface.reset_buffers(); anyhow::bail!("Rendering failed: {}", err); } @@ -247,7 +275,7 @@ pub fn init_backend( // Create the OpenGL context let context = EGLContext::new(&egl, None).with_context(|| "Failed to create EGL context")?; // Create a renderer - let mut renderer = unsafe { Gles2Renderer::new(context, None) } + let mut renderer = unsafe { GlowRenderer::new(context, None) } .with_context(|| "Failed to initialize renderer")?; init_egl_client_side(dh, state, &mut renderer)?; @@ -269,20 +297,19 @@ pub fn init_backend( .common .output_configuration_state .add_heads(std::iter::once(&output)); - state.common.output_configuration_state.update(); state.common.shell.add_output(&output); + let seats = state.common.seats().cloned().collect::>(); state.common.config.read_outputs( - std::iter::once(&output), + &mut state.common.output_configuration_state, &mut state.backend, &mut state.common.shell, + seats.iter().cloned(), &state.common.event_loop_handle, ); - state.common.shell.refresh_outputs(); - state.common.config.write_outputs(std::iter::once(&output)); event_loop .handle() - .insert_source(backend, |event, _, data| match event { + .insert_source(backend, move |event, _, data| match event { X11Event::CloseRequested { window_id } => { // TODO: drain_filter let mut outputs_removed = Vec::new(); @@ -303,7 +330,10 @@ pub fn init_backend( .surfaces .retain(|s| s.window.id() != window_id); for output in outputs_removed.into_iter() { - data.state.common.shell.remove_output(&output); + data.state + .common + .shell + .remove_output(&output, seats.iter().cloned()); } } X11Event::Resized { @@ -336,7 +366,7 @@ pub fn init_backend( output.delete_mode(output.current_mode().unwrap()); output.change_current_state(Some(mode), None, None, None); output.set_preferred(mode); - layer_map_for_output(output).arrange(&data.display.handle()); + layer_map_for_output(output).arrange(); data.state.common.output_configuration_state.update(); data.state.common.shell.refresh_outputs(); surface.dirty = true; @@ -368,11 +398,10 @@ pub fn init_backend( Ok(()) } -fn init_egl_client_side( - dh: &DisplayHandle, - state: &mut State, - renderer: &mut Gles2Renderer, -) -> Result<()> { +fn init_egl_client_side(dh: &DisplayHandle, state: &mut State, renderer: &mut R) -> Result<()> +where + R: ImportEgl + ImportDma, +{ let bind_result = renderer.bind_wl_display(dh); match bind_result { Ok(_) => { @@ -405,10 +434,10 @@ impl State { .unwrap(); let device = event.device(); - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let devices = seat.user_data().get::().unwrap(); if devices.has_device(&device) { - set_active_output(seat, &output); + seat.set_active_output(&output); break; } } @@ -420,8 +449,7 @@ impl State { self.process_input_event(event); // TODO actually figure out the output for output in self.common.shell.outputs() { - self.backend - .schedule_render(&self.common.event_loop_handle, output); + self.backend.x11().schedule_render(output, None); } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 3a225ea1..c78eecdb 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,10 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - shell::{focus::FocusDirection, Shell}, - state::{BackendData, Data}, + shell::{focus::FocusDirection, layout::tiling::Direction, Shell, WorkspaceAmount}, + state::{BackendData, Data, State}, + wayland::protocols::output_configuration::OutputConfigurationState, }; use serde::{Deserialize, Serialize}; +use smithay::input::Seat; pub use smithay::{ backend::input::KeyState, input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState}, @@ -32,6 +34,7 @@ pub struct Config { pub struct StaticConfig { pub key_bindings: HashMap, pub workspace_mode: WorkspaceMode, + pub workspace_amount: WorkspaceAmount, pub floating_default: bool, } @@ -215,6 +218,7 @@ impl Config { StaticConfig { key_bindings: HashMap::new(), workspace_mode: WorkspaceMode::Global, + workspace_amount: WorkspaceAmount::Dynamic, floating_default: false, } } @@ -276,12 +280,14 @@ impl Config { pub fn read_outputs( &mut self, - outputs: impl Iterator>, + output_state: &mut OutputConfigurationState, backend: &mut BackendData, shell: &mut Shell, + seats: impl Iterator>, loop_handle: &LoopHandle<'_, Data>, ) { - let outputs = outputs.map(|x| x.borrow().clone()).collect::>(); + let seats = seats.collect::>(); + let outputs = output_state.outputs().collect::>(); let mut infos = outputs .iter() .cloned() @@ -305,14 +311,19 @@ impl Config { for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter()) { let output = outputs.iter().find(|o| &o.name() == name).unwrap().clone(); + let enabled = output_config.enabled; *output .user_data() .get::>() .unwrap() .borrow_mut() = output_config; - if let Err(err) = - backend.apply_config_for_output(&output, false, shell, loop_handle) - { + if let Err(err) = backend.apply_config_for_output( + &output, + false, + shell, + seats.iter().cloned(), + loop_handle, + ) { slog_scope::warn!( "Failed to set new config for output {}: {}", output.name(), @@ -320,6 +331,12 @@ impl Config { ); reset = true; break; + } else { + if enabled { + output_state.enable_head(&output); + } else { + output_state.disable_head(&output); + } } } @@ -329,22 +346,36 @@ impl Config { .into_iter() .zip(known_good_configs.into_iter()) { + let enabled = output_config.enabled; *output .user_data() .get::>() .unwrap() .borrow_mut() = output_config; - if let Err(err) = - backend.apply_config_for_output(&output, false, shell, loop_handle) - { + if let Err(err) = backend.apply_config_for_output( + &output, + false, + shell, + seats.iter().cloned(), + loop_handle, + ) { slog_scope::error!( "Failed to reset config for output {}: {}", output.name(), err ); + } else { + if enabled { + output_state.enable_head(&output); + } else { + output_state.disable_head(&output); + } } } } + + output_state.update(); + self.write_outputs(output_state.outputs()); } } @@ -689,7 +720,7 @@ pub enum KeyModifier { Ctrl, Alt, Shift, - Logo, + Super, CapsLock, NumLock, } @@ -721,7 +752,7 @@ impl std::ops::AddAssign for KeyModifiers { KeyModifier::Ctrl => self.ctrl = true, KeyModifier::Alt => self.alt = true, KeyModifier::Shift => self.shift = true, - KeyModifier::Logo => self.logo = true, + KeyModifier::Super => self.logo = true, KeyModifier::CapsLock => self.caps_lock = true, KeyModifier::NumLock => self.num_lock = true, }; @@ -780,13 +811,30 @@ pub enum Action { Terminate, Debug, Close, + Workspace(u8), + NextWorkspace, + PreviousWorkspace, + LastWorkspace, MoveToWorkspace(u8), + MoveToNextWorkspace, + MoveToPreviousWorkspace, + MoveToLastWorkspace, + + NextOutput, + PreviousOutput, + MoveToNextOutput, + MoveToPreviousOutput, + Focus(FocusDirection), + Move(Direction), + + ToggleOrientation, Orientation(crate::shell::layout::Orientation), + ToggleTiling, ToggleWindowFloating, - Fullscreen, - Screenshot, + + Maximize, Spawn(String), } diff --git a/src/debug.rs b/src/debug.rs index f6d03048..6d90d669 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,22 +1,35 @@ // SPDX-License-Identifier: GPL-3.0-only +use std::collections::HashMap; + use crate::state::{Common, Fps}; +use egui::{Color32, Vec2}; use smithay::{ - backend::drm::DrmNode, - desktop::layer_map_for_output, - reexports::wayland_server::Resource, - utils::{IsAlive, Physical, Rectangle}, + backend::{ + drm::DrmNode, + renderer::{ + element::texture::TextureRenderElement, + gles2::{Gles2Error, Gles2Texture}, + glow::GlowRenderer, + }, + }, + utils::{Logical, Rectangle}, }; -pub use smithay_egui::EguiFrame; + +pub const ELEMENTS_COLOR: Color32 = Color32::from_rgb(70, 198, 115); +pub const RENDER_COLOR: Color32 = Color32::from_rgb(29, 114, 58); +pub const SCREENCOPY_COLOR: Color32 = Color32::from_rgb(253, 178, 39); +pub const DISPLAY_COLOR: Color32 = Color32::from_rgb(41, 184, 209); pub fn fps_ui( gpu: Option<&DrmNode>, state: &Common, + renderer: &mut GlowRenderer, fps: &mut Fps, - area: Rectangle, + area: Rectangle, scale: f64, -) -> EguiFrame { - use egui::widgets::plot::{Bar, BarChart, HLine, Legend, Plot}; +) -> Result, Gles2Error> { + use egui::widgets::plot::{Bar, BarChart, Legend, Plot}; let (max, min, avg, avg_fps) = ( fps.max_frametime().as_secs_f64(), @@ -24,404 +37,154 @@ pub fn fps_ui( fps.avg_frametime().as_secs_f64(), fps.avg_fps(), ); - let bars = fps + let (max_disp, min_disp) = ( + fps.max_time_to_display().as_secs_f64(), + fps.min_time_to_display().as_secs_f64(), + ); + + let amount = avg_fps.round() as usize * 2; + let ((bars_elements, bars_render), (bars_screencopy, bars_displayed)): ( + (Vec, Vec), + (Vec, Vec), + ) = fps .frames .iter() .rev() - .take(30) + .take(amount) .rev() .enumerate() - .map(|(i, (_, d))| { - let value = d.as_secs_f64(); - let transformed = ((value - min) / (max - min) * 255.0).round() as u8; - Bar::new(i as f64, transformed as f64).fill(egui::Color32::from_rgb( - transformed, - 255 - transformed, - 0, - )) - }) - .collect(); + .map(|(i, frame)| { + let elements_val = frame.duration_elements.as_secs_f64(); + let render_val = frame.duration_render.as_secs_f64(); + let screencopy_val = frame + .duration_screencopy + .as_ref() + .map(|val| val.as_secs_f64()) + .unwrap_or(0.0); + let displayed_val = frame.duration_displayed.as_secs_f64(); - fps.state.run( + let transformed_elements = + ((elements_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + let transformed_render = + ((render_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + let transformed_screencopy = + ((screencopy_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + let transformed_displayed = + ((displayed_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + ( + ( + Bar::new(i as f64, transformed_elements as f64).fill(ELEMENTS_COLOR), + Bar::new(i as f64, transformed_render as f64).fill(RENDER_COLOR), + ), + ( + Bar::new(i as f64, transformed_screencopy as f64).fill(SCREENCOPY_COLOR), + Bar::new(i as f64, transformed_displayed as f64).fill(DISPLAY_COLOR), + ), + ) + }) + .unzip(); + + let vendors = HashMap::from([ + ( + "0x10de", + fps.state + .with_image(renderer, "nvidia", |image, ctx| { + (image.texture_id(ctx), image.size_vec2()) + }) + .expect("Logo images not loaded?"), + ), + ( + "0x1002", + fps.state + .with_image(renderer, "amd", |image, ctx| { + (image.texture_id(ctx), image.size_vec2()) + }) + .expect("Logo images not loaded?"), + ), + ( + "0x8086", + fps.state + .with_image(renderer, "intel", |image, ctx| { + (image.texture_id(ctx), image.size_vec2()) + }) + .expect("Logo images not loaded?"), + ), + ]); + + fps.state.render( |ctx| { egui::Area::new("main") .anchor(egui::Align2::LEFT_TOP, (10.0, 10.0)) .show(ctx, |ui| { - let label_res = ui.label(format!( + ui.label(format!( "cosmic-comp version {}", std::env!("CARGO_PKG_VERSION") )); - if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..8)) { - ui.label(hash); + if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..10)) { + ui.label(format!(": {hash}")); } if !state.egui.active { - ui.label("Press Mod+Escape for debug menu"); + ui.label("Press Super+Escape for debug overlay"); } else { - ui.set_max_width(label_res.rect.min.x + label_res.rect.width()); + ui.set_max_width(300.0); ui.separator(); if let Some(gpu) = gpu { - ui.label(egui::RichText::new(format!("renderD{}", gpu.minor())).code()); + ui.horizontal(|ui| { + let resp = ui.label( + egui::RichText::new(format!("renderD{}", gpu.minor())).code(), + ); + if let Ok(vendor) = std::fs::read_to_string(format!( + "/sys/class/drm/renderD{}/device/vendor", + gpu.minor() + )) { + if let Some((texture_id, mut size)) = vendors.get(vendor.trim()) + { + let factor = resp.rect.height() / size.y; + size = Vec2::from([size.x * factor, resp.rect.height()]); + ui.image(*texture_id, size); + } + } + }); } ui.label(egui::RichText::new(format!("FPS: {:>7.3}", avg_fps)).heading()); ui.label("Frame Times:"); ui.label(egui::RichText::new(format!("avg: {:>7.6}", avg)).code()); ui.label(egui::RichText::new(format!("min: {:>7.6}", min)).code()); ui.label(egui::RichText::new(format!("max: {:>7.6}", max)).code()); - let fps_chart = BarChart::new(bars).vertical(); + let elements_chart = BarChart::new(bars_elements).vertical(); + let render_chart = BarChart::new(bars_render) + .stack_on(&[&elements_chart]) + .vertical(); + let screencopy_chart = BarChart::new(bars_screencopy) + .stack_on(&[&elements_chart, &render_chart]) + .vertical(); + let display_chart = BarChart::new(bars_displayed) + .stack_on(&[&elements_chart, &render_chart, &screencopy_chart]) + .vertical(); Plot::new("FPS") .legend(Legend::default()) - .view_aspect(33.0) + .view_aspect(50.0) .include_x(0.0) - .include_x(30.0) + .include_x(amount as f64) .include_y(0.0) - .include_y(300.0) + .include_y(300) .show_x(false) .show(ui, |plot_ui| { - plot_ui.bar_chart(fps_chart); - plot_ui.hline( - HLine::new(avg) - .highlight(true) - .color(egui::Color32::LIGHT_BLUE), - ); + plot_ui.bar_chart(elements_chart); + plot_ui.bar_chart(render_chart); + plot_ui.bar_chart(screencopy_chart); + plot_ui.bar_chart(display_chart); }); } }); }, + renderer, area, scale, - 1.0, - &state.start_time, - fps.modifiers.clone(), + 0.8, + state.clock.now().into(), ) } - -pub fn debug_ui( - state: &mut Common, - area: Rectangle, - scale: f64, -) -> Option { - if !state.egui.active { - return None; - } - - Some(state.egui.debug_state.run( - |ctx| { - use crate::utils::prelude::*; - - egui::Window::new("Workspaces") - .default_pos([0.0, 300.0]) - .vscroll(true) - .collapsible(true) - .show(ctx, |ui| { - use crate::{ - config::WorkspaceMode as ConfigMode, - shell::{OutputBoundState, WorkspaceMode, MAX_WORKSPACES}, - }; - - ui.set_min_width(250.0); - - // Mode - - ui.label(egui::RichText::new("Mode").heading()); - let mut mode = match &state.shell.workspace_mode { - WorkspaceMode::Global { .. } => ConfigMode::Global, - WorkspaceMode::OutputBound => ConfigMode::OutputBound, - }; - ui.radio_value(&mut mode, ConfigMode::OutputBound, "Output bound"); - ui.radio_value(&mut mode, ConfigMode::Global, "Global"); - state.shell.set_mode(mode); - - let mode = match &state.shell.workspace_mode { - WorkspaceMode::OutputBound => (ConfigMode::OutputBound, None), - WorkspaceMode::Global { ref active, .. } => { - (ConfigMode::Global, Some(*active)) - } - }; - match mode { - (ConfigMode::OutputBound, _) => { - ui.label("Workspaces:"); - for output in state.shell.outputs().cloned().collect::>() { - ui.horizontal(|ui| { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - let mut active_val = active as f64; - ui.label(output.name()); - ui.add( - egui::DragValue::new(&mut active_val) - .clamp_range(0..=(MAX_WORKSPACES - 1)) - .speed(1.0), - ); - if active != active_val as usize { - state.shell.activate( - &state.seats[0], - &output, - active_val as usize, - ); - } - }); - } - } - (ConfigMode::Global, Some(active)) => { - ui.horizontal(|ui| { - let mut active_val = active as f64; - ui.label("Workspace:"); - ui.add( - egui::DragValue::new(&mut active_val) - .clamp_range(0..=(MAX_WORKSPACES - 1)) - .speed(1.0), - ); - if active != active_val as usize { - let output = state.shell.outputs().next().cloned().unwrap(); - state.shell.activate( - &state.seats[0], - &output, - active_val as usize, - ); - } - }); - } - _ => unreachable!(), - } - - // Spaces - for (i, workspace) in state.shell.spaces.iter().enumerate() { - ui.collapsing(format!("Space: {}", i), |ui| { - ui.collapsing(format!("Windows"), |ui| { - for window in workspace.space.windows() { - ui.collapsing(format!("{:?}", window.toplevel()), |ui| { - ui.label(format!("Rect: {:?}", { - let mut geo = window.geometry(); - geo.loc += workspace - .space - .window_location(window) - .unwrap_or((0, 0).into()); - geo - })); - ui.label(format!( - "Bounding box: {:?}", - workspace.space.window_bbox(window) - )); - }); - } - }) - }); - } - }); - - egui::Window::new("Outputs") - .collapsible(true) - .hscroll(true) - .default_pos([300.0, 300.0]) - .show(ctx, |ui| { - ui.label(format!("Global Space: {:?}", state.shell.global_space())); - for output in state - .shell - .outputs() - .cloned() - .collect::>() - .into_iter() - { - ui.separator(); - ui.collapsing(output.name(), |ui| { - ui.label(format!("Mode: {:#?}", output.current_mode())); - ui.label(format!("Scale: {:#?}", output.current_scale())); - ui.label(format!("Transform: {:#?}", output.current_transform())); - ui.label(format!("Geometry: {:?}", output.geometry())); - ui.label(format!( - "Local Geometry: {:?}", - state - .shell - .active_space(&output) - .space - .output_geometry(&output) - )); - ui.label(format!( - "Relative Geometry: {:?}", - state - .shell - .space_relative_output_geometry((0i32, 0i32), &output) - )); - ui.separator(); - ui.collapsing("Layers:", |ui| { - let map = layer_map_for_output(&output); - for layer in map.layers() { - ui.collapsing( - format!( - "{}/{:?}", - layer.wl_surface().id(), - layer.wl_surface().client_id() - ), - |ui| { - ui.label(format!( - "Alive: {:?} {:?} {:?}", - layer.alive(), - layer.layer_surface().alive(), - layer.wl_surface().alive() - )); - ui.label(format!("Layer: {:?}", layer.layer())); - ui.label(format!("Namespace: {:?}", layer.namespace())); - ui.label(format!("Geometry: {:?}", layer.bbox())); - ui.label(format!( - "Anchor: {:?}", - layer.cached_state().anchor - )); - ui.label(format!( - "Margin: {:?}", - layer.cached_state().margin - )); - ui.label(format!( - "Exclusive: {:?}", - layer.cached_state().exclusive_zone - )); - }, - ); - } - ui.label(format!("{:?}", map)); - }); - }); - } - }); - }, - area, - scale, - state.egui.alpha, - &state.start_time, - state.egui.modifiers.clone(), - )) -} - -pub fn log_ui( - state: &mut Common, - area: Rectangle, - scale: f64, - default_width: f32, -) -> Option { - if !state.egui.active { - return None; - } - - Some(state.egui.log_state.run( - |ctx| { - egui::SidePanel::right("Log") - .frame(egui::Frame { - inner_margin: egui::Vec2::new(10.0, 10.0).into(), - outer_margin: egui::Vec2::new(0.0, 0.0).into(), - rounding: 5.0.into(), - shadow: egui::epaint::Shadow { - extrusion: 0.0, - color: egui::Color32::TRANSPARENT, - }, - fill: egui::Color32::from_black_alpha(100), - stroke: egui::Stroke::none(), - }) - .default_width(default_width) - .show(ctx, |ui| { - egui::ScrollArea::vertical() - .always_show_scroll(true) - .stick_to_bottom() - .show(ui, |ui| { - for (_i, record) in state - .log - .debug_buffer - .lock() - .unwrap() - .iter() - .rev() - .enumerate() - { - let mut message = egui::text::LayoutJob::single_section( - record.level.as_short_str().to_string(), - egui::TextFormat::simple( - egui::FontId::monospace(16.0), - match record.level { - slog::Level::Critical => egui::Color32::RED, - slog::Level::Error => egui::Color32::LIGHT_RED, - slog::Level::Warning => egui::Color32::LIGHT_YELLOW, - slog::Level::Info => egui::Color32::LIGHT_BLUE, - slog::Level::Debug => egui::Color32::LIGHT_GREEN, - slog::Level::Trace => egui::Color32::GRAY, - }, - ), - ); - message.append( - &record.message, - 6.0, - egui::TextFormat::simple( - egui::FontId::default(), - egui::Color32::WHITE, - ), - ); - ui.vertical(|ui| { - ui.add(egui::Label::new(message)); - ui.add_space(4.0); - for (k, v) in &record.kv { - ui.horizontal(|ui| { - ui.add( - egui::Label::new(egui::RichText::new(k).code()) - .sense(egui::Sense::click()), - ) - .on_hover_cursor(egui::CursorIcon::PointingHand); - render_value(ui, v); - }); - } - }); - } - }) - }); - }, - area, - scale, - state.egui.alpha, - &state.start_time, - state.egui.modifiers.clone(), - )) -} - -fn render_value(ui: &mut egui::Ui, value: &serde_json::Value) { - use serde_json::Value::*; - - match value { - Null => { - ui.label(egui::RichText::new("null").code()); - } - Bool(val) => { - ui.label(egui::RichText::new(format!("{}", val)).code()); - } - Number(val) => { - ui.label(egui::RichText::new(format!("{}", val)).code()); - } - String(val) => { - ui.label(val); - } - Array(list) => { - ui.vertical(|ui| { - ui.label("["); - for val in list { - ui.horizontal(|ui| { - ui.add_space(4.0); - render_value(ui, val); - }); - } - ui.label("]"); - }); - } - Object(map) => { - ui.vertical(|ui| { - for (k, val) in map { - ui.horizontal(|ui| { - ui.add_space(4.0); - ui.add(egui::Label::new(egui::RichText::new(k).code())); - render_value(ui, val); - }); - } - }); - } - }; -} diff --git a/src/input/mod.rs b/src/input/mod.rs index b599f882..51657b48 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2,26 +2,35 @@ use crate::{ config::{Action, Config}, - shell::{grabs::SeatMoveGrabState, Workspace}, + shell::{ + focus::{target::PointerFocusTarget, FocusDirection}, + layout::{ + floating::SeatMoveGrabState, + tiling::{Direction, FocusResult}, + }, + Workspace, + }, // shell::grabs::SeatMoveGrabState state::Common, utils::prelude::*, + wayland::{handlers::screencopy::ScreencopySessions, protocols::screencopy::Session}, }; +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use smithay::{ backend::input::{ - AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, InputBackend, - InputEvent, KeyState, PointerAxisEvent, + Axis, AxisSource, Device, DeviceCapability, InputBackend, InputEvent, KeyState, + PointerAxisEvent, }, - desktop::{layer_map_for_output, Kind, WindowSurfaceType}, + desktop::{layer_map_for_output, WindowSurfaceType}, input::{ keyboard::{keysyms, FilterResult, KeysymHandle, XkbConfig}, pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent}, Seat, SeatState, }, output::Output, - reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}, - utils::{Buffer, Logical, Point, Rectangle, Size, SERIAL_COUNTER}, + reexports::wayland_server::DisplayHandle, + utils::{Logical, Point, Rectangle, Serial, SERIAL_COUNTER}, wayland::{ - keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, + keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, seat::WaylandFocus, shell::wlr_layer::Layer as WlrLayer, }, }; @@ -103,6 +112,7 @@ impl Devices { pub fn add_seat( dh: &DisplayHandle, seat_state: &mut SeatState, + output: &Output, config: &Config, name: String, ) -> Seat { @@ -112,6 +122,7 @@ pub fn add_seat( userdata.insert_if_missing(Devices::default); userdata.insert_if_missing(SupressedKeys::default); userdata.insert_if_missing(SeatMoveGrabState::default); + userdata.insert_if_missing(|| ActiveOutput(RefCell::new(output.clone()))); userdata.insert_if_missing(|| RefCell::new(CursorImageStatus::Default)); // A lot of clients bind keyboard and pointer unconditionally once on launch.. @@ -143,7 +154,7 @@ impl State { match event { InputEvent::DeviceAdded { device } => { - let seat = &mut self.common.last_active_seat; + let seat = &mut self.common.last_active_seat(); let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); for cap in devices.add_device(&device) { @@ -152,14 +163,9 @@ impl State { _ => {} } } - #[cfg(feature = "debug")] - { - self.common.egui.debug_state.handle_device_added(&device); - self.common.egui.log_state.handle_device_added(&device); - } } InputEvent::DeviceRemoved { device } => { - for seat in &mut self.common.seats { + for seat in &mut self.common.seats() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { @@ -172,26 +178,22 @@ impl State { break; } } - #[cfg(feature = "debug")] - { - self.common.egui.debug_state.handle_device_added(&device); - self.common.egui.log_state.handle_device_added(&device); - } } InputEvent::Keyboard { event, .. } => { use smithay::backend::input::KeyboardKeyEvent; let device = event.device(); - for seat in self.common.seats.clone().iter() { - let current_output = active_output(seat, &self.common); + for seat in self.common.seats().cloned().collect::>().iter() { + let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); let shortcuts_inhibited = workspace - .focus_stack(seat) + .focus_stack + .get(seat) .last() .and_then(|window| { - seat.keyboard_shortcuts_inhibitor_for_surface( - &window.toplevel().wl_surface(), - ) + window.wl_surface().and_then(|surface| { + seat.keyboard_shortcuts_inhibitor_for_surface(&surface) + }) }) .map(|inhibitor| inhibitor.is_active()) .unwrap_or(false); @@ -220,38 +222,6 @@ impl State { { return FilterResult::Intercept(None); } - #[cfg(feature = "debug")] - { - if data.common.seats.iter().position(|x| x == seat).unwrap() - == 0 - && data.common.egui.active - { - if data.common.egui.debug_state.wants_keyboard() { - data.common.egui.debug_state.handle_keyboard( - &handle, - state == KeyState::Pressed, - modifiers.clone(), - ); - userdata - .get::() - .unwrap() - .add(&handle); - return FilterResult::Intercept(None); - } - if data.common.egui.log_state.wants_keyboard() { - data.common.egui.log_state.handle_keyboard( - &handle, - state == KeyState::Pressed, - modifiers.clone(), - ); - userdata - .get::() - .unwrap() - .add(&handle); - return FilterResult::Intercept(None); - } - } - } if state == KeyState::Pressed && (keysyms::KEY_XF86Switch_VT_1..=KEY_XF86Switch_VT_12) @@ -296,192 +266,7 @@ impl State { ) .flatten() { - match action { - Action::Terminate => { - self.common.should_stop = true; - } - #[cfg(feature = "debug")] - Action::Debug => { - self.common.egui.active = !self.common.egui.active; - } - #[cfg(not(feature = "debug"))] - Action::Debug => { - slog_scope::info!("Debug overlay not included in this version") - } - Action::Close => { - let current_output = active_output(seat, &self.common); - let workspace = - self.common.shell.active_space_mut(¤t_output); - if let Some(window) = workspace.focus_stack(seat).last() { - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - xdg.send_close(); - } - } - } - Action::Workspace(key_num) => { - let current_output = active_output(seat, &self.common); - let workspace = match key_num { - 0 => 9, - x => x - 1, - }; - if let Some(motion_event) = self.common.shell.activate( - seat, - ¤t_output, - workspace as usize, - ) { - if let Some(ptr) = seat.get_pointer() { - ptr.motion(self, None, &motion_event); - } - } - } - Action::MoveToWorkspace(key_num) => { - let current_output = active_output(seat, &self.common); - let workspace = match key_num { - 0 => 9, - x => x - 1, - }; - self.common.shell.move_current_window( - seat, - ¤t_output, - workspace as usize, - ); - } - Action::Focus(focus) => { - let current_output = active_output(seat, &self.common); - let workspace = - self.common.shell.active_space_mut(¤t_output); - let focus_stack = workspace.focus_stack(seat); - if let Some(window) = workspace.tiling_layer.move_focus( - focus, - seat, - &mut workspace.space, - focus_stack.iter(), - ) { - std::mem::drop(focus_stack); - Common::set_focus( - self, - Some(window.toplevel().wl_surface()), - seat, - None, - ); - } - } - Action::Fullscreen => { - let current_output = active_output(seat, &self.common); - let workspace = - self.common.shell.active_space_mut(¤t_output); - let focused_window = workspace.focus_stack(seat).last(); - if let Some(window) = focused_window { - workspace.fullscreen_toggle(&window, ¤t_output); - } - } - Action::Orientation(orientation) => { - let output = active_output(seat, &self.common); - let workspace = self.common.shell.active_space_mut(&output); - let focus_stack = workspace.focus_stack(seat); - workspace.tiling_layer.update_orientation( - orientation, - &seat, - &mut workspace.space, - focus_stack.iter(), - ); - } - Action::ToggleTiling => { - let output = active_output(seat, &self.common); - let workspace = self.common.shell.active_space_mut(&output); - workspace.toggle_tiling(seat); - } - Action::ToggleWindowFloating => { - let output = active_output(seat, &self.common); - let workspace = self.common.shell.active_space_mut(&output); - workspace.toggle_floating_window(seat); - } - Action::Spawn(command) => { - if let Err(err) = std::process::Command::new("/bin/sh") - .arg("-c") - .arg(command) - .env("WAYLAND_DISPLAY", &self.common.socket) - .env_remove("COSMIC_SESSION_SOCK") - .spawn() - { - slog_scope::warn!("Failed to spawn: {}", err); - } - } - Action::Screenshot => { - let home = match std::env::var("HOME") { - Ok(home) => home, - Err(err) => { - slog_scope::error!( - "$HOME is not set, can't save screenshots: {}", - err - ); - break; - } - }; - let timestamp = match std::time::SystemTime::UNIX_EPOCH - .elapsed() - { - Ok(duration) => duration.as_secs(), - Err(err) => { - slog_scope::error!("Unable to get timestamp, can't save screenshots: {}", err); - break; - } - }; - for output in self.common.shell.outputs.clone().into_iter() { - match self - .backend - .offscreen_for_output(&output, &mut self.common) - { - Ok((buffer, size)) => { - let mut path = std::path::PathBuf::new(); - path.push(&home); - path.push(format!( - "{}_{}.png", - output.name(), - timestamp - )); - - fn write_png( - path: impl AsRef, - data: Vec, - size: Size, - ) -> anyhow::Result<()> - { - use std::{fs, io}; - - let file = io::BufWriter::new( - fs::File::create(&path)?, - ); - let mut encoder = png::Encoder::new( - file, - size.w as u32, - size.h as u32, - ); - encoder.set_color(png::ColorType::Rgba); - encoder.set_depth(png::BitDepth::Eight); - let mut writer = encoder.write_header()?; - writer.write_image_data(&data)?; - Ok(()) - } - - if let Err(err) = write_png(&path, buffer, size) { - slog_scope::error!( - "Unable to save screenshot at {}: {}", - path.display(), - err - ); - } - } - Err(err) => slog_scope::error!( - "Could not save screenshot for output {}: {}", - output.name(), - err - ), - } - } - } - } + self.handle_action(action, seat, serial, time) } break; } @@ -491,11 +276,11 @@ impl State { use smithay::backend::input::PointerMotionEvent; let device = event.device(); - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { - let current_output = active_output(seat, &self.common); + let current_output = seat.active_output(); let mut position = seat.get_pointer().unwrap().current_location(); position += event.delta(); @@ -508,7 +293,15 @@ impl State { .cloned() .unwrap_or(current_output.clone()); if output != current_output { - set_active_output(seat, &output); + for session in sessions_for_output(&self.common, ¤t_output) { + session.cursor_leave(seat, InputType::Pointer); + } + + for session in sessions_for_output(&self.common, &output) { + session.cursor_enter(seat, InputType::Pointer); + } + + seat.set_active_output(&output); } let output_geometry = output.geometry(); @@ -520,10 +313,7 @@ impl State { .min((output_geometry.loc.y + output_geometry.size.h) as f64); let serial = SERIAL_COUNTER.next_serial(); - let relative_pos = self - .common - .shell - .space_relative_output_geometry(position, &output); + let relative_pos = self.common.shell.map_global_to_space(position, &output); let workspace = self.common.shell.active_space_mut(&output); let under = State::surface_under( position, @@ -532,6 +322,19 @@ impl State { output_geometry, &workspace, ); + + for session in sessions_for_output(&self.common, &output) { + if let Some((geometry, offset)) = seat.cursor_geometry( + position.to_buffer( + output.current_scale().fractional_scale(), + output.current_transform(), + &output.geometry().size.to_f64(), + ), + self.common.clock.now(), + ) { + session.cursor_info(seat, InputType::Pointer, geometry, offset); + } + } seat.get_pointer().unwrap().motion( self, under, @@ -541,36 +344,24 @@ impl State { time: event.time(), }, ); - - #[cfg(feature = "debug")] - if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 { - self.common - .egui - .debug_state - .handle_pointer_motion(position.to_i32_round()); - self.common - .egui - .log_state - .handle_pointer_motion(position.to_i32_round()); - } break; } } } InputEvent::PointerMotionAbsolute { event, .. } => { let device = event.device(); - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { - let output = active_output(seat, &self.common); + let output = seat.active_output(); let geometry = output.geometry(); - let position = - geometry.loc.to_f64() + event.position_transformed(geometry.size); - let relative_pos = self - .common - .shell - .space_relative_output_geometry(position, &output); + let position = geometry.loc.to_f64() + + smithay::backend::input::AbsolutePositionEvent::position_transformed( + &event, + geometry.size, + ); + let relative_pos = self.common.shell.map_global_to_space(position, &output); let workspace = self.common.shell.active_space_mut(&output); let serial = SERIAL_COUNTER.next_serial(); let under = State::surface_under( @@ -590,17 +381,6 @@ impl State { }, ); - #[cfg(feature = "debug")] - if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 { - self.common - .egui - .debug_state - .handle_pointer_motion(position.to_i32_round()); - self.common - .egui - .log_state - .handle_pointer_motion(position.to_i32_round()); - } break; } } @@ -609,36 +389,10 @@ impl State { use smithay::backend::input::{ButtonState, PointerButtonEvent}; let device = event.device(); - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { - #[cfg(feature = "debug")] - if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 - && self.common.egui.active - { - if self.common.egui.debug_state.wants_pointer() { - if let Some(button) = event.button() { - self.common.egui.debug_state.handle_pointer_button( - button, - event.state() == ButtonState::Pressed, - self.common.egui.modifiers.clone(), - ); - } - break; - } - if self.common.egui.log_state.wants_pointer() { - if let Some(button) = event.button() { - self.common.egui.log_state.handle_pointer_button( - button, - event.state() == ButtonState::Pressed, - self.common.egui.modifiers.clone(), - ); - } - break; - } - } - let serial = SERIAL_COUNTER.next_serial(); let button = event.button_code(); if event.state() == ButtonState::Pressed { @@ -649,13 +403,10 @@ impl State { if !seat.get_pointer().unwrap().is_grabbed() && !seat.get_keyboard().map(|k| k.is_grabbed()).unwrap_or(false) { - let output = active_output(seat, &self.common); + let output = seat.active_output(); let pos = seat.get_pointer().unwrap().current_location(); - let output_geo = output.geometry(); - let relative_pos = self - .common - .shell - .space_relative_output_geometry(pos, &output); + let relative_pos = + self.common.shell.map_global_to_space(pos, &output); let workspace = self.common.shell.active_space_mut(&output); let layers = layer_map_for_output(&output); let mut under = None; @@ -664,60 +415,54 @@ impl State { if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos) { - if layer.can_receive_keyboard_focus() { - let layer_loc = - layers.layer_geometry(layer).unwrap().loc; - under = layer + let layer_loc = layers.layer_geometry(layer).unwrap().loc; + if layer.can_receive_keyboard_focus() + && layer .surface_under( - pos - output_geo.loc.to_f64() - - layer_loc.to_f64(), + relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL, ) - .map(|(_, _)| layer.wl_surface().clone()); + .is_some() + { + under = Some(layer.clone().into()); } } else { - under = window - .surface_under( - pos - output_geo.loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(_, _)| window.toplevel().wl_surface().clone()); + under = Some(window.clone().into()); } } else { if let Some(layer) = layers .layer_under(WlrLayer::Overlay, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) { - if layer.can_receive_keyboard_focus() { - let layer_loc = - layers.layer_geometry(layer).unwrap().loc; - under = layer + let layer_loc = layers.layer_geometry(layer).unwrap().loc; + if layer.can_receive_keyboard_focus() + && layer .surface_under( - pos - output_geo.loc.to_f64() - - layer_loc.to_f64(), + relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL, ) - .map(|(_, _)| layer.wl_surface().clone()); + .is_some() + { + under = Some(layer.clone().into()); } - } else if let Some((window, _, _)) = workspace - .space - .surface_under(relative_pos, WindowSurfaceType::ALL) + } else if let Some((window, _)) = + workspace.element_under(relative_pos) { - under = Some(window.toplevel().wl_surface().clone()); + under = Some(window.clone().into()); } else if let Some(layer) = layers .layer_under(WlrLayer::Bottom, pos) .or_else(|| layers.layer_under(WlrLayer::Background, pos)) { - if layer.can_receive_keyboard_focus() { - let layer_loc = - layers.layer_geometry(layer).unwrap().loc; - under = layer + let layer_loc = layers.layer_geometry(layer).unwrap().loc; + if layer.can_receive_keyboard_focus() + && layer .surface_under( - pos - output_geo.loc.to_f64() - - layer_loc.to_f64(), + relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL, ) - .map(|(_, _)| layer.wl_surface().clone()); + .is_some() + { + under = Some(layer.clone().into()); } }; } @@ -740,39 +485,7 @@ impl State { } InputEvent::PointerAxis { event, .. } => { let device = event.device(); - for seat in self.common.seats.clone().iter() { - #[cfg(feature = "debug")] - if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 - && self.common.egui.active - { - if self.common.egui.debug_state.wants_pointer() { - self.common.egui.debug_state.handle_pointer_axis( - event - .amount_discrete(Axis::Horizontal) - .or_else(|| event.amount(Axis::Horizontal).map(|x| x * 3.0)) - .unwrap_or(0.0), - event - .amount_discrete(Axis::Vertical) - .or_else(|| event.amount(Axis::Vertical).map(|x| x * 3.0)) - .unwrap_or(0.0), - ); - break; - } - if self.common.egui.log_state.wants_pointer() { - self.common.egui.log_state.handle_pointer_axis( - event - .amount_discrete(Axis::Horizontal) - .or_else(|| event.amount(Axis::Horizontal).map(|x| x * 3.0)) - .unwrap_or(0.0), - event - .amount_discrete(Axis::Vertical) - .or_else(|| event.amount(Axis::Vertical).map(|x| x * 3.0)) - .unwrap_or(0.0), - ); - break; - } - } - + for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { @@ -814,62 +527,453 @@ impl State { } } + fn handle_action(&mut self, action: Action, seat: &Seat, serial: Serial, time: u32) { + match action { + Action::Terminate => { + self.common.should_stop = true; + } + #[cfg(feature = "debug")] + Action::Debug => { + self.common.egui.active = !self.common.egui.active; + } + #[cfg(not(feature = "debug"))] + Action::Debug => { + slog_scope::info!("Debug overlay not included in this version") + } + Action::Close => { + let current_output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(¤t_output); + if let Some(window) = workspace.focus_stack.get(seat).last() { + window.send_close(); + } + } + Action::Workspace(key_num) => { + let current_output = seat.active_output(); + let workspace = match key_num { + 0 => 9, + x => x - 1, + }; + let _ = self + .common + .shell + .activate(¤t_output, workspace as usize); + } + Action::NextWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .active_num(¤t_output) + .saturating_add(1); + // TODO: Possibly move to next output, if idx to large + let _ = self.common.shell.activate(¤t_output, workspace); + } + Action::PreviousWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .active_num(¤t_output) + .saturating_sub(1); + // TODO: Possibly move to prev output, if idx < 0 + let _ = self.common.shell.activate(¤t_output, workspace); + } + Action::LastWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .len(¤t_output) + .saturating_sub(1); + let _ = self.common.shell.activate(¤t_output, workspace); + } + Action::MoveToWorkspace(key_num) => { + let current_output = seat.active_output(); + let workspace = match key_num { + 0 => 9, + x => x - 1, + }; + Shell::move_current_window( + self, + seat, + ¤t_output, + (¤t_output, Some(workspace as usize)), + ); + } + Action::MoveToNextWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .active_num(¤t_output) + .saturating_add(1); + // TODO: Possibly move to next output, if idx too large + Shell::move_current_window( + self, + seat, + ¤t_output, + (¤t_output, Some(workspace as usize)), + ); + } + Action::MoveToPreviousWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .active_num(¤t_output) + .saturating_sub(1); + // TODO: Possibly move to prev output, if idx < 0 + Shell::move_current_window( + self, + seat, + ¤t_output, + (¤t_output, Some(workspace as usize)), + ); + } + Action::MoveToLastWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .len(¤t_output) + .saturating_sub(1); + Shell::move_current_window( + self, + seat, + ¤t_output, + (¤t_output, Some(workspace as usize)), + ); + } + Action::NextOutput => { + let current_output = seat.active_output(); + if let Some(next_output) = self + .common + .shell + .outputs + .iter() + .skip_while(|o| *o != ¤t_output) + .skip(1) + .next() + .cloned() + { + let idx = self.common.shell.workspaces.active_num(&next_output); + if let Some(new_pos) = self.common.shell.activate(&next_output, idx) { + seat.set_active_output(&next_output); + if let Some(ptr) = seat.get_pointer() { + ptr.motion( + self, + None, + &MotionEvent { + location: new_pos.to_f64(), + serial, + time, + }, + ); + } + } + } + } + Action::PreviousOutput => { + let current_output = seat.active_output(); + if let Some(prev_output) = self + .common + .shell + .outputs + .iter() + .rev() + .skip_while(|o| *o != ¤t_output) + .skip(1) + .next() + .cloned() + { + let idx = self.common.shell.workspaces.active_num(&prev_output); + if let Some(new_pos) = self.common.shell.activate(&prev_output, idx) { + seat.set_active_output(&prev_output); + if let Some(ptr) = seat.get_pointer() { + ptr.motion( + self, + None, + &MotionEvent { + location: new_pos.to_f64(), + serial, + time, + }, + ); + } + } + } + } + Action::MoveToNextOutput => { + let current_output = seat.active_output(); + if let Some(next_output) = self + .common + .shell + .outputs + .iter() + .skip_while(|o| *o != ¤t_output) + .skip(1) + .next() + .cloned() + { + if let Some(new_pos) = Shell::move_current_window( + self, + seat, + ¤t_output, + (&next_output, None), + ) { + if let Some(ptr) = seat.get_pointer() { + ptr.motion( + self, + None, + &MotionEvent { + location: new_pos.to_f64(), + serial, + time, + }, + ); + } + } + } + } + Action::MoveToPreviousOutput => { + let current_output = seat.active_output(); + if let Some(prev_output) = self + .common + .shell + .outputs + .iter() + .rev() + .skip_while(|o| *o != ¤t_output) + .skip(1) + .next() + .cloned() + { + if let Some(new_pos) = Shell::move_current_window( + self, + seat, + ¤t_output, + (&prev_output, None), + ) { + if let Some(ptr) = seat.get_pointer() { + ptr.motion( + self, + None, + &MotionEvent { + location: new_pos.to_f64(), + serial, + time, + }, + ); + } + } + } + } + Action::Focus(focus) => { + let current_output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(¤t_output); + let focus_stack = workspace.focus_stack.get(seat); + match workspace + .tiling_layer + .next_focus(focus, seat, focus_stack.iter()) + { + FocusResult::None => { + // TODO: Handle Workspace orientation + match focus { + FocusDirection::Left => { + self.handle_action(Action::PreviousWorkspace, seat, serial, time) + } + FocusDirection::Right => { + self.handle_action(Action::NextWorkspace, seat, serial, time) + } + FocusDirection::Up => { + self.handle_action(Action::PreviousOutput, seat, serial, time) + } + FocusDirection::Down => { + self.handle_action(Action::NextOutput, seat, serial, time) + } + _ => {} + } + } + FocusResult::Handled => {} + FocusResult::Some(target) => { + std::mem::drop(focus_stack); + Common::set_focus(self, Some(&target), seat, None); + } + } + } + Action::Move(direction) => { + let current_output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(¤t_output); + if let Some(_move_further) = + workspace.tiling_layer.move_current_window(direction, seat) + { + // TODO: Handle Workspace orientation + // TODO: Being able to move Groups (move_further should be KeyboardFocusTarget instead) + match direction { + Direction::Left => { + self.handle_action(Action::MoveToPreviousWorkspace, seat, serial, time) + } + Direction::Right => { + self.handle_action(Action::MoveToNextWorkspace, seat, serial, time) + } + Direction::Up => { + self.handle_action(Action::MoveToPreviousOutput, seat, serial, time) + } + Direction::Down => { + self.handle_action(Action::MoveToNextOutput, seat, serial, time) + } + } + } + } + Action::Maximize => { + let current_output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(¤t_output); + let focus_stack = workspace.focus_stack.get(seat); + let focused_window = focus_stack.last(); + if let Some(window) = focused_window.map(|f| f.active_window()) { + workspace.maximize_toggle(&window, ¤t_output); + } + } + Action::ToggleOrientation => { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + let focus_stack = workspace.focus_stack.get(seat); + workspace + .tiling_layer + .update_orientation(None, &seat, focus_stack.iter()); + } + Action::Orientation(orientation) => { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + let focus_stack = workspace.focus_stack.get(seat); + workspace.tiling_layer.update_orientation( + Some(orientation), + &seat, + focus_stack.iter(), + ); + } + Action::ToggleTiling => { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + workspace.toggle_tiling(seat); + } + Action::ToggleWindowFloating => { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + workspace.toggle_floating_window(seat); + } + Action::Spawn(command) => { + if let Err(err) = std::process::Command::new("/bin/sh") + .arg("-c") + .arg(command) + .env("WAYLAND_DISPLAY", &self.common.socket) + .env_remove("COSMIC_SESSION_SOCK") + .spawn() + { + slog_scope::warn!("Failed to spawn: {}", err); + } + } + } + } + pub fn surface_under( global_pos: Point, relative_pos: Point, output: &Output, output_geo: Rectangle, workspace: &Workspace, - ) -> Option<(WlSurface, Point)> { + ) -> Option<(PointerFocusTarget, Point)> { let layers = layer_map_for_output(output); if let Some(window) = workspace.get_fullscreen(output) { - if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, relative_pos) - .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) - { + if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; - layer - .surface_under( - global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) - } else { - window - .surface_under(global_pos - output_geo.loc.to_f64(), WindowSurfaceType::ALL) - .map(|(s, loc)| (s, loc + output_geo.loc)) + if layer + .surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL) + .is_some() + { + return Some((layer.clone().into(), output_geo.loc + layer_loc)); + } } + Some((window.clone().into(), output_geo.loc)) } else { if let Some(layer) = layers .layer_under(WlrLayer::Overlay, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; - layer - .surface_under( - global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) - } else if let Some((_, surface, loc)) = workspace - .space - .surface_under(relative_pos, WindowSurfaceType::ALL) - { - Some((surface, loc + (global_pos - relative_pos).to_i32_round())) - } else if let Some(layer) = layers + if layer + .surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL) + .is_some() + { + return Some((layer.clone().into(), output_geo.loc + layer_loc)); + } + } + if let Some((mapped, loc)) = workspace.element_under(relative_pos) { + return Some(( + mapped.clone().into(), + loc + (global_pos - relative_pos).to_i32_round(), + )); + } + if let Some(layer) = layers .layer_under(WlrLayer::Bottom, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Background, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; - layer - .surface_under( - global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) - } else { - None + if layer + .surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL) + .is_some() + { + return Some((layer.clone().into(), output_geo.loc + layer_loc)); + } } + None } } } + +fn sessions_for_output(state: &Common, output: &Output) -> impl Iterator { + let workspace = state.shell.active_space(&output); + let maybe_fullscreen = workspace.get_fullscreen(&output); + workspace + .screencopy_sessions + .iter() + .map(|s| (&**s).clone()) + .chain( + maybe_fullscreen + .and_then(|w| w.user_data().get::()) + .map(|sessions| { + sessions + .0 + .borrow() + .iter() + .map(|s| (&**s).clone()) + .collect::>() + }) + .into_iter() + .flatten(), + ) + .chain( + output + .user_data() + .get::() + .map(|sessions| { + sessions + .0 + .borrow() + .iter() + .map(|s| (&**s).clone()) + .collect::>() + }) + .into_iter() + .into_iter() + .flatten(), + ) + .collect::>() + .into_iter() +} diff --git a/src/logger/mod.rs b/src/logger/mod.rs index 4818c7da..14793437 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -1,104 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-only -#[cfg(feature = "debug")] -use std::{ - collections::VecDeque, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, - }, -}; - use anyhow::Result; use slog::Drain; -#[cfg(feature = "debug")] -mod serializer; - -#[cfg(feature = "debug")] -const MAX_RECORDS: usize = 1000; - -#[cfg(feature = "debug")] -pub type LogBuffer = Arc>>; - -#[cfg(feature = "debug")] -#[derive(Clone)] -struct DebugDrain { - buffer: LogBuffer, - dirty_flag: Arc, -} - pub struct LogState { _guard: slog_scope::GlobalLoggerGuard, - #[cfg(feature = "debug")] - pub dirty_flag: Arc, - #[cfg(feature = "debug")] - pub debug_buffer: LogBuffer, -} - -#[cfg(feature = "debug")] -pub struct OwnedRecord { - pub message: String, - pub level: slog::Level, - pub kv: serde_json::map::Map, -} - -#[cfg(feature = "debug")] -impl DebugDrain { - fn new() -> (DebugDrain, LogBuffer, Arc) { - let dirty_flag = Arc::new(AtomicBool::new(false)); - let buffer = Arc::new(Mutex::new(VecDeque::new())); - ( - DebugDrain { - buffer: buffer.clone(), - dirty_flag: dirty_flag.clone(), - }, - buffer, - dirty_flag, - ) - } -} - -#[cfg(feature = "debug")] -impl Drain for DebugDrain { - type Ok = (); - type Err = slog::Error; - - fn log( - &self, - record: &slog::Record<'_>, - values: &slog::OwnedKVList, - ) -> Result { - use serde_json::value::{Serializer as ValueSerializer, Value}; - use serializer::SerdeSerializer; - use slog::KV; - - let mut serializer = SerdeSerializer::start(ValueSerializer, None)?; - values.serialize(record, &mut serializer)?; - record.kv().serialize(record, &mut serializer)?; - let value = match serializer.end().map_err(|_| slog::Error::Other)? { - Value::Object(map) => map, - _ => unreachable!(), - }; - - let mut buffer = self.buffer.lock().unwrap(); - buffer.push_front(OwnedRecord { - message: format!("{}", record.msg()), - level: record.level(), - kv: value, - }); - buffer.truncate(MAX_RECORDS); - self.dirty_flag.store(true, Ordering::SeqCst); - - Ok(()) - } } pub fn init_logger() -> Result { let decorator = slog_term::TermDecorator::new().stderr().build(); // usually we would not want to use a Mutex here, but this is usefull for a prototype, // to make sure we do not miss any in-flight messages, when we crash. - #[cfg(not(feature = "debug"))] let logger = slog::Logger::root( std::sync::Mutex::new( slog_term::CompactFormat::new(decorator) @@ -108,21 +20,6 @@ pub fn init_logger() -> Result { .fuse(), slog::o!(), ); - #[cfg(feature = "debug")] - let (debug_drain, debug_buffer, dirty_flag) = DebugDrain::new(); - #[cfg(feature = "debug")] - let logger = slog::Logger::root( - slog::Duplicate::new( - std::sync::Mutex::new( - slog_term::CompactFormat::new(decorator) - .build() - .ignore_res(), - ), - debug_drain, - ) - .fuse(), - slog::o!(), - ); let _guard = slog_scope::set_global_logger(logger); slog_stdlog::init().unwrap(); @@ -135,11 +32,5 @@ pub fn init_logger() -> Result { ); } - Ok(LogState { - _guard, - #[cfg(feature = "debug")] - debug_buffer, - #[cfg(feature = "debug")] - dirty_flag, - }) + Ok(LogState { _guard }) } diff --git a/src/main.rs b/src/main.rs index 4231f427..088b22ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,7 @@ use smithay::{ }; use anyhow::{Context, Result}; -use std::{ - ffi::OsString, - os::unix::prelude::AsRawFd, - sync::{atomic::Ordering, Arc}, -}; +use std::{ffi::OsString, os::unix::prelude::AsRawFd, sync::Arc}; pub mod backend; pub mod config; @@ -68,18 +64,9 @@ fn main() -> Result<()> { } // trigger routines - data.state.common.shell.refresh(&data.display.handle()); + data.state.common.shell.refresh(); state::Common::refresh_focus(&mut data.state); - // do we need to trigger another render - if data.state.common.dirty_flag.swap(false, Ordering::SeqCst) { - for output in data.state.common.shell.outputs() { - data.state - .backend - .schedule_render(&data.state.common.event_loop_handle, output) - } - } - // send out events let _ = data.display.flush_clients(); })?; diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs new file mode 100644 index 00000000..b2395d73 --- /dev/null +++ b/src/shell/element/mod.rs @@ -0,0 +1,1101 @@ +use crate::{ + backend::render::{ + element::{AsGles2Frame, AsGlowRenderer, CosmicElement}, + GlMultiRenderer, + }, + state::State, + utils::prelude::SeatExt, +}; +use id_tree::NodeId; +use smithay::{ + backend::{ + input::KeyState, + renderer::{ + element::{ + texture::TextureRenderElement, AsRenderElements, Element, RenderElement, + UnderlyingStorage, + }, + gles2::Gles2Texture, + glow::GlowRenderer, + multigpu::Error as MultiError, + ImportAll, Renderer, + }, + }, + desktop::{space::SpaceElement, Kind, PopupManager, Window, WindowSurfaceType}, + input::{ + keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, + pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, + Seat, + }, + output::Output, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState, + wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface}, + }, + space_elements, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, + wayland::{ + compositor::{with_states, with_surface_tree_downward, TraversalAction}, + seat::WaylandFocus, + shell::xdg::XdgToplevelSurfaceRoleAttributes, + }, +}; +use std::{ + collections::HashMap, + fmt, + hash::Hash, + sync::{Arc, Mutex}, +}; + +pub mod stack; +pub use self::stack::CosmicStack; +pub mod window; +pub use self::window::CosmicWindow; + +#[cfg(feature = "debug")] +use smithay::wayland::shell::xdg::XdgToplevelSurfaceData; +#[cfg(feature = "debug")] +use smithay_egui::EguiState; + +use super::{focus::FocusDirection, layout::floating::ResizeState}; + +space_elements! { + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + CosmicMappedInternal; + Window=CosmicWindow, + Stack=CosmicStack, +} + +#[derive(Clone)] +pub struct CosmicMapped { + element: CosmicMappedInternal, + + // associated data + last_cursor_position: Arc>>>, + + //tiling + pub(super) tiling_node_id: Arc>>, + //floating + pub(super) last_geometry: Arc>>>, + pub(super) resize_state: Arc>>, + + #[cfg(feature = "debug")] + debug: Arc>, +} + +impl fmt::Debug for CosmicMapped { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CosmicMapped") + .field("element", &self.element) + .field("last_cursor_position", &self.last_cursor_position) + .field("tiling_node_id", &self.tiling_node_id) + .field("resize_state", &self.resize_state) + .finish() + } +} + +impl PartialEq for CosmicMapped { + fn eq(&self, other: &Self) -> bool { + self.element == other.element + } +} + +impl Eq for CosmicMapped {} + +impl Hash for CosmicMapped { + fn hash(&self, state: &mut H) { + self.element.hash(state) + } +} + +impl CosmicMapped { + pub fn windows(&self) -> impl Iterator)> + '_ { + match &self.element { + CosmicMappedInternal::Stack(stack) => Box::new(stack.windows().map(|w| { + ( + w, + stack + .header + .lock() + .unwrap() + .as_ref() + .map(|header| Point::from((0, header.height() as i32))) + .unwrap_or(Point::from((0, 0))), + ) + })) + as Box)>>, + CosmicMappedInternal::Window(window) => Box::new(std::iter::once(( + window.window.clone(), + window + .header + .lock() + .unwrap() + .as_ref() + .map(|header| Point::from((0, header.height() as i32))) + .unwrap_or(Point::from((0, 0))), + ))), + _ => Box::new(std::iter::empty()), + } + } + + pub fn active_window(&self) -> Window { + match &self.element { + CosmicMappedInternal::Stack(stack) => stack.active(), + CosmicMappedInternal::Window(win) => win.window.clone(), + _ => unreachable!(), + } + } + + pub fn active_window_offset(&self) -> Rectangle { + match &self.element { + CosmicMappedInternal::Stack(stack) => { + let location = ( + 0, + stack + .header + .lock() + .unwrap() + .as_ref() + .map_or(0, |header| header.height()), + ); + let size = stack.active().geometry().size; + Rectangle::from_loc_and_size(location, size) + } + CosmicMappedInternal::Window(win) => { + let location = ( + 0, + win.header + .lock() + .unwrap() + .as_ref() + .map_or(0, |header| header.height()), + ); + let size = win.window.geometry().size; + Rectangle::from_loc_and_size(location, size) + } + _ => unreachable!(), + } + } + + pub fn cursor_position(&self, seat: &Seat) -> Option> { + self.last_cursor_position + .lock() + .unwrap() + .get(&seat.id()) + .cloned() + } + + pub fn set_active(&self, window: &Window) { + if let CosmicMappedInternal::Stack(stack) = &self.element { + stack.set_active(window); + } + } + + pub fn focus_window(&self, window: &Window) { + match &self.element { + CosmicMappedInternal::Stack(stack) => stack.set_active(window), + _ => {} + } + } + + pub fn has_surface(&self, surface: &WlSurface, surface_type: WindowSurfaceType) -> bool { + self.windows().any(|(w, _)| { + let toplevel = w.toplevel().wl_surface(); + + if surface_type.contains(WindowSurfaceType::TOPLEVEL) { + if toplevel == surface { + return true; + } + } + + if surface_type.contains(WindowSurfaceType::SUBSURFACE) { + use std::sync::atomic::{AtomicBool, Ordering}; + + let found = AtomicBool::new(false); + with_surface_tree_downward( + toplevel, + surface, + |_, _, search| TraversalAction::DoChildren(search), + |s, _, search| { + found.fetch_or(s == *search, Ordering::SeqCst); + }, + |_, _, _| !found.load(Ordering::SeqCst), + ); + if found.load(Ordering::SeqCst) { + return true; + } + } + + if surface_type.contains(WindowSurfaceType::POPUP) { + PopupManager::popups_for_surface(toplevel).any(|(p, _)| p.wl_surface() == surface) + } else { + false + } + }) + } + + pub fn handle_focus(&self, direction: FocusDirection) -> bool { + if let CosmicMappedInternal::Stack(stack) = &self.element { + //TODO: stack.handle_focus(direction) + false + } else { + false + } + } + + pub fn set_resizing(&self, resizing: bool) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if resizing { + state.states.set(XdgState::Resizing); + } else { + state.states.unset(XdgState::Resizing); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_resizing(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => { + xdg.current_state().states.contains(XdgState::Resizing) + || xdg.with_pending_state(|states| states.states.contains(XdgState::Resizing)) + } // Kind::X11? + } + } + + pub fn set_tiled(&self, tiled: bool) { + for toplevel in match &self.element { + // we use the tiled state of stack windows anyway to get rid of decorations + CosmicMappedInternal::Stack(_) => None, + CosmicMappedInternal::Window(w) => Some(w.window.toplevel()), + _ => unreachable!(), + } { + match toplevel { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if tiled { + state.states.set(XdgState::TiledLeft); + state.states.set(XdgState::TiledRight); + state.states.set(XdgState::TiledTop); + state.states.set(XdgState::TiledBottom); + } else { + state.states.unset(XdgState::TiledLeft); + state.states.unset(XdgState::TiledRight); + state.states.unset(XdgState::TiledTop); + state.states.unset(XdgState::TiledBottom); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_tiled(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::TiledLeft), + // Kind::X11? + } + } + + pub fn set_fullscreen(&self, fullscreen: bool) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if fullscreen { + state.states.set(XdgState::Fullscreen); + } else { + state.states.unset(XdgState::Fullscreen); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_fullscreen(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => { + xdg.current_state().states.contains(XdgState::Fullscreen) + || xdg.with_pending_state(|states| states.states.contains(XdgState::Fullscreen)) + } // Kind::X11? + } + } + + pub fn set_maximized(&self, maximized: bool) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if maximized { + state.states.set(XdgState::Maximized); + } else { + state.states.unset(XdgState::Maximized); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_maximized(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => { + xdg.current_state().states.contains(XdgState::Maximized) + || xdg.with_pending_state(|states| states.states.contains(XdgState::Maximized)) + } // Kind::X11? + } + } + + pub fn set_activated(&self, activated: bool) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if activated { + state.states.set(XdgState::Activated); + } else { + state.states.unset(XdgState::Activated); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_activated(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => { + xdg.current_state().states.contains(XdgState::Activated) + || xdg.with_pending_state(|states| states.states.contains(XdgState::Activated)) + } // Kind::X11? + } + } + + pub fn set_size(&self, size: Size) { + match &self.element { + CosmicMappedInternal::Stack(s) => s.set_size(size), + CosmicMappedInternal::Window(w) => w.set_size(size), + _ => {} + } + } + + pub fn min_size(&self) -> Size { + match &self.element { + CosmicMappedInternal::Stack(stack) => stack + .windows() + .fold(None, |min_size, window| { + let win_min_size = with_states(window.toplevel().wl_surface(), |states| { + let attrs = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + attrs.min_size + }); + match (min_size, win_min_size) { + (None, x) => Some(x), + (Some(min1), min2) => Some((min1.w.max(min2.w), min1.h.max(min2.h)).into()), + } + }) + .expect("Empty stack?"), + CosmicMappedInternal::Window(window) => { + with_states(window.window.toplevel().wl_surface(), |states| { + let attrs = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + attrs.min_size + }) + } + _ => unreachable!(), + } + } + + pub fn max_size(&self) -> Size { + match &self.element { + CosmicMappedInternal::Stack(stack) => { + let theoretical_max = stack.windows().fold(None, |max_size, window| { + let win_max_size = with_states(window.toplevel().wl_surface(), |states| { + let attrs = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + attrs.max_size + }); + match (max_size, win_max_size) { + (None, x) => Some(x), + (Some(max1), max2) => Some( + ( + if max1.w == 0 { + max2.w + } else if max2.w == 0 { + max1.w + } else { + max1.w.min(max2.w) + }, + if max1.h == 0 { + max2.h + } else if max2.h == 0 { + max1.h + } else { + max1.h.min(max2.h) + }, + ) + .into(), + ), + } + }); + // The problem is, with accumulated sizes, the minimum size could be larger than our maximum... + let min_size = self.min_size(); + match (theoretical_max, min_size) { + (None, _) => (0, 0).into(), + (Some(max), min) => (max.w.max(min.w), max.h.max(min.h)).into(), + } + } + CosmicMappedInternal::Window(window) => { + with_states(window.window.toplevel().wl_surface(), |states| { + let attrs = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + attrs.max_size + }) + } + _ => unreachable!(), + } + } + + pub fn configure(&self) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.send_configure(), + // Kind::X11? + }; + } + } + + pub fn send_close(&self) { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => xdg.send_close(), + // Kind::X11? + }; + } +} + +impl IsAlive for CosmicMapped { + fn alive(&self) -> bool { + self.element.alive() + } +} + +impl SpaceElement for CosmicMapped { + fn bbox(&self) -> Rectangle { + SpaceElement::bbox(&self.element) + } + fn is_in_input_region(&self, point: &Point) -> bool { + SpaceElement::is_in_input_region(&self.element, point) + } + fn set_activate(&self, activated: bool) { + SpaceElement::set_activate(&self.element, activated) + } + fn output_enter(&self, output: &Output, overlap: Rectangle) { + SpaceElement::output_enter(&self.element, output, overlap) + } + fn output_leave(&self, output: &Output) { + SpaceElement::output_leave(&self.element, output) + } + fn geometry(&self) -> Rectangle { + SpaceElement::geometry(&self.element) + } + fn z_index(&self) -> u8 { + SpaceElement::z_index(&self.element) + } + fn refresh(&self) { + SpaceElement::refresh(&self.element) + } +} + +impl KeyboardTarget for CosmicMapped { + fn enter( + &self, + seat: &Seat, + data: &mut State, + keys: Vec>, + serial: Serial, + ) { + match &self.element { + CosmicMappedInternal::Stack(s) => KeyboardTarget::enter(s, seat, data, keys, serial), + CosmicMappedInternal::Window(w) => KeyboardTarget::enter(w, seat, data, keys, serial), + _ => {} + } + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { + match &self.element { + CosmicMappedInternal::Stack(s) => KeyboardTarget::leave(s, seat, data, serial), + CosmicMappedInternal::Window(w) => KeyboardTarget::leave(w, seat, data, serial), + _ => {} + } + } + fn key( + &self, + seat: &Seat, + data: &mut State, + key: KeysymHandle<'_>, + state: KeyState, + serial: Serial, + time: u32, + ) { + match &self.element { + CosmicMappedInternal::Stack(s) => { + KeyboardTarget::key(s, seat, data, key, state, serial, time) + } + CosmicMappedInternal::Window(w) => { + KeyboardTarget::key(w, seat, data, key, state, serial, time) + } + _ => {} + } + } + fn modifiers( + &self, + seat: &Seat, + data: &mut State, + modifiers: ModifiersState, + serial: Serial, + ) { + match &self.element { + CosmicMappedInternal::Stack(s) => { + KeyboardTarget::modifiers(s, seat, data, modifiers, serial) + } + CosmicMappedInternal::Window(w) => { + KeyboardTarget::modifiers(w, seat, data, modifiers, serial) + } + _ => {} + } + } +} + +impl PointerTarget for CosmicMapped { + fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + self.last_cursor_position + .lock() + .unwrap() + .insert(seat.id(), event.location); + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::enter(s, seat, data, event), + CosmicMappedInternal::Window(w) => PointerTarget::enter(w, seat, data, event), + _ => {} + } + } + fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + self.last_cursor_position + .lock() + .unwrap() + .insert(seat.id(), event.location); + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::motion(s, seat, data, event), + CosmicMappedInternal::Window(w) => PointerTarget::motion(w, seat, data, event), + _ => {} + } + } + fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::button(s, seat, data, event), + CosmicMappedInternal::Window(w) => PointerTarget::button(w, seat, data, event), + _ => {} + } + } + fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::axis(s, seat, data, frame), + CosmicMappedInternal::Window(w) => PointerTarget::axis(w, seat, data, frame), + _ => {} + } + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + self.last_cursor_position.lock().unwrap().remove(&seat.id()); + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::leave(s, seat, data, serial, time), + CosmicMappedInternal::Window(w) => PointerTarget::leave(w, seat, data, serial, time), + _ => {} + } + } +} + +impl WaylandFocus for CosmicMapped { + fn wl_surface(&self) -> Option { + match &self.element { + CosmicMappedInternal::Window(w) => w.window.wl_surface().clone(), + CosmicMappedInternal::Stack(s) => s.active().wl_surface().clone(), + _ => None, + } + } + + fn same_client_as(&self, object_id: &ObjectId) -> bool { + match &self.element { + CosmicMappedInternal::Window(w) => w.window.same_client_as(object_id), + CosmicMappedInternal::Stack(s) => s.windows().any(|w| w.same_client_as(object_id)), + _ => false, + } + } +} + +impl From for CosmicMapped { + fn from(w: CosmicWindow) -> Self { + CosmicMapped { + element: CosmicMappedInternal::Window(w), + last_cursor_position: Arc::new(Mutex::new(HashMap::new())), + tiling_node_id: Arc::new(Mutex::new(None)), + last_geometry: Arc::new(Mutex::new(None)), + resize_state: Arc::new(Mutex::new(None)), + #[cfg(feature = "debug")] + debug: Arc::new(Mutex::new(smithay_egui::EguiState::new( + Rectangle::from_loc_and_size((10, 10), (100, 100)), + ))), + } + } +} + +impl From for CosmicMapped { + fn from(s: CosmicStack) -> Self { + CosmicMapped { + element: CosmicMappedInternal::Stack(s), + last_cursor_position: Arc::new(Mutex::new(HashMap::new())), + tiling_node_id: Arc::new(Mutex::new(None)), + last_geometry: Arc::new(Mutex::new(None)), + resize_state: Arc::new(Mutex::new(None)), + #[cfg(feature = "debug")] + debug: Arc::new(Mutex::new(smithay_egui::EguiState::new( + Rectangle::from_loc_and_size((10, 10), (100, 100)), + ))), + } + } +} + +pub enum CosmicMappedRenderElement +where + R: AsGlowRenderer + Renderer + ImportAll, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + Stack(self::stack::CosmicStackRenderElement), + Window(self::window::CosmicWindowRenderElement), + #[cfg(feature = "debug")] + Egui(TextureRenderElement), +} + +impl Element for CosmicMappedRenderElement +where + R: AsGlowRenderer + Renderer + ImportAll, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + fn id(&self) -> &smithay::backend::renderer::element::Id { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.id(), + CosmicMappedRenderElement::Window(elem) => elem.id(), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.id(), + } + } + + fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.current_commit(), + CosmicMappedRenderElement::Window(elem) => elem.current_commit(), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.current_commit(), + } + } + + fn src(&self) -> Rectangle { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.src(), + CosmicMappedRenderElement::Window(elem) => elem.src(), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.src(), + } + } + + fn geometry(&self, scale: Scale) -> Rectangle { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.geometry(scale), + CosmicMappedRenderElement::Window(elem) => elem.geometry(scale), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.geometry(scale), + } + } + + fn location(&self, scale: Scale) -> Point { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.location(scale), + CosmicMappedRenderElement::Window(elem) => elem.location(scale), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.location(scale), + } + } + + fn transform(&self) -> smithay::utils::Transform { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.transform(), + CosmicMappedRenderElement::Window(elem) => elem.transform(), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.transform(), + } + } + + fn damage_since( + &self, + scale: Scale, + commit: Option, + ) -> Vec> { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::Window(elem) => elem.damage_since(scale, commit), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.damage_since(scale, commit), + } + } + + fn opaque_regions(&self, scale: Scale) -> Vec> { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::Window(elem) => elem.opaque_regions(scale), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.opaque_regions(scale), + } + } +} + +impl RenderElement for CosmicMappedRenderElement { + fn draw( + &self, + renderer: &mut GlowRenderer, + frame: &mut ::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), ::Error> { + match self { + CosmicMappedRenderElement::Stack(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + CosmicMappedRenderElement::Window(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + } + } + + fn underlying_storage( + &self, + renderer: &GlowRenderer, + ) -> Option> { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::Window(elem) => elem.underlying_storage(renderer), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.underlying_storage(renderer), + } + } +} + +impl<'a> RenderElement> for CosmicMappedRenderElement> { + fn draw( + &self, + renderer: &mut GlMultiRenderer<'a>, + frame: &mut as Renderer>::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), as Renderer>::Error> { + match self { + CosmicMappedRenderElement::Stack(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + CosmicMappedRenderElement::Window(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => { + let glow_renderer = renderer.glow_renderer_mut(); + let gles2_frame = frame.gles2_frame_mut(); + elem.draw(glow_renderer, gles2_frame, location, scale, damage, log) + .map_err(|err| MultiError::Render(err)) + } + } + } + + fn underlying_storage( + &self, + renderer: &GlMultiRenderer<'a>, + ) -> Option>> { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::Window(elem) => elem.underlying_storage(renderer), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => { + let glow_renderer = renderer.glow_renderer(); + match elem.underlying_storage(glow_renderer) { + Some(UnderlyingStorage::Wayland(buffer)) => { + Some(UnderlyingStorage::Wayland(buffer)) + } + _ => None, + } + } + } + } +} + +impl From> for CosmicMappedRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: stack::CosmicStackRenderElement) -> Self { + CosmicMappedRenderElement::Stack(elem) + } +} +impl From> for CosmicMappedRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: window::CosmicWindowRenderElement) -> Self { + CosmicMappedRenderElement::Window(elem) + } +} +#[cfg(feature = "debug")] +impl From> for CosmicMappedRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: TextureRenderElement) -> Self { + CosmicMappedRenderElement::Egui(elem) + } +} + +impl AsRenderElements for CosmicMapped +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + type RenderElement = CosmicMappedRenderElement; + fn render_elements>( + &self, + location: Point, + scale: Scale, + ) -> Vec { + let mut elements = match &self.element { + CosmicMappedInternal::Stack(s) => AsRenderElements::::render_elements::< + CosmicMappedRenderElement, + >(s, location, scale), + CosmicMappedInternal::Window(w) => AsRenderElements::::render_elements::< + CosmicMappedRenderElement, + >(w, location, scale), + _ => Vec::new(), + }; + + /* + #[cfg(feature = "debug")] + if !elements.is_empty() { + let window = self.active_window(); + let (app_id, title, min_size, max_size, size, states) = with_states(&window.kind().wl_surface(), |states| { + let attributes = states + .data_map + .get::() + .unwrap() + .lock() + .unwrap(); + (attributes.app_id.clone(), attributes.title.clone(), attributes.min_size.clone(), attributes.max_size.clone(), attributes.current.size.clone(), attributes.current.states.clone()) + }); + + let win_geo = self.geometry().size; + let area = { + let size = win_size.clamp((10, 10), (100, 100))}; + let offset = (win_size - (100, 100).into()) + .to_point() + .constain(Rectangle::from_loc_and_size((0, 0), (10, 10))); + Rectangle::from_loc_and_size(offset, size) + }; + + match self.debug.lock().unwrap().render( + |ui| { + egui::Frame::none() + .fill(Color32::DARK_GRAY) + .rounding(0.25) + .show(ui, |ui| { + ui.heading(title.as_deref().unwrap_or("")); + ui.label(app_id.as_deref().unwrap_or("")); + ui.horizontal(|ui| { + ui.label("States: "); + if states.contains(XdgState::Maximized) { + ui.label("🗖"); + } + if states.contains(XdgState::Fullscreen) { + ui.label("⬜") + } + if states.contains(XdgState::Activated) { + ui.label("🖱") + } + if states.contains(XdgState::Resizing) { + ui.label("↔") + } + if states.contains(XdgState::TiledLeft) { + ui.label("⏴") + } + if states.contains(XdgState::TiledRight) { + ui.label("⏵") + } + if states.contains(XdgState::TiledTop) { + ui.label("⏶") + } + if states.contains(XdgState::TiledBottom) { + ui.label("⏷") + } + }); + + let plot = Plot::new("Sizes") + .legend(Legend::default().position(Corner::RightBottom)) + .show_x(false) + .show_y(false) + .data_aspect(0.1); + plot.show(ui, |plot_ui| { + let center = ((max_size.w + 20) / 2, (max_size.h + 20) / 2); + let max_size_rect = Polygon::new(PlotPoints::new(vec![ + (10, 10), + (max_size.w + 10, 10), + (max_size.w + 10, max_size.h + 10), + (10, max_size.h + 10), + (10, 10), + ])); + plot_ui.polygon(max_size_rect.name(format!("{}", max_size))); + + if let Some(size) = size { + let size_rect = Polygon::new(PlotPoints::new(vec![ + (center.0 - size.w / 2, center.1 - size.h / 2), + (center.0 + size.w / 2, center.1 - size.w / 2), + (center.0 + size.w / 2, center.1 + size.w / 2), + (center.0 - size.w / 2, center.1 + size.w / 2), + (center.0 - size.w / 2, center.1 - size.w / 2), + ])); + plot_ui.polygon(size_rect.name(format!("{}", size))); + } + + let min_size_rect = Polygon::new(PlotPoints::new(vec![ + (center.0 - min_size.w / 2, center.1 - min_size.h / 2), + (center.0 + min_size.w / 2, center.1 - min_size.w / 2), + (center.0 + min_size.w / 2, center.1 + min_size.w / 2), + (center.0 - min_size.w / 2, center.1 + min_size.w / 2), + (center.0 - min_size.w / 2, center.1 - min_size.w / 2), + ])); + plot_ui.polygon(min_size_rect.name(format!("{}", min_size))); + }) + }); + }, + renderer, + area, + scale, + if self.last_cursor_position.lock().unwrap().values().any(|p| area.contains(p.to_i32_round())) { + 0.4 + } else { + 1.0 + }, + start_time, + ) { + Ok(element) => elements.push(element), + Err(err) => slog_scope::debug!("Error rendering debug overlay: {}", err), + }; + } + */ + + elements.into_iter().map(C::from).collect() + } +} diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs new file mode 100644 index 00000000..8377ff90 --- /dev/null +++ b/src/shell/element/stack.rs @@ -0,0 +1,400 @@ +use crate::{ + state::State, utils::prelude::SeatExt, wayland::handlers::screencopy::ScreencopySessions, +}; +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; +use smithay::{ + backend::{ + input::KeyState, + renderer::{ + element::{surface::WaylandSurfaceRenderElement, AsRenderElements}, + ImportAll, Renderer, + }, + }, + desktop::{space::SpaceElement, Kind, Window}, + input::{ + keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, + pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, + Seat, + }, + output::Output, + render_elements, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, +}; +use std::{ + hash::Hash, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, +}; + +#[derive(Debug, Clone)] +pub struct CosmicStack { + windows: Arc>>, + active: Arc, + last_location: Arc, Serial, u32)>>>, + previous_keyboard: Arc, + previous_pointer: Arc, + pub(super) header: Arc>>, +} + +impl PartialEq for CosmicStack { + fn eq(&self, other: &Self) -> bool { + *self.windows.lock().unwrap() == *other.windows.lock().unwrap() + } +} + +impl Eq for CosmicStack {} + +impl Hash for CosmicStack { + fn hash(&self, state: &mut H) { + Arc::as_ptr(&self.windows).hash(state) + } +} + +#[derive(Debug)] +pub struct HeaderBar {} + +impl HeaderBar { + pub fn height(&self) -> i32 { + 0 + } +} + +impl CosmicStack { + pub fn active(&self) -> Window { + self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)].clone() + } + + pub fn set_active(&self, window: &Window) { + if let Some(val) = self + .windows + .lock() + .unwrap() + .iter() + .position(|w| w == window) + { + let old = self.active.swap(val, Ordering::SeqCst); + self.previous_keyboard.store(old, Ordering::SeqCst); + self.previous_pointer.store(old, Ordering::SeqCst); + } + } + + pub fn set_size(&self, size: Size) { + let surface_size = ( + size.w, + size.h + - self + .header + .lock() + .unwrap() + .as_ref() + .map(|h| h.height()) + .unwrap_or(0), + ) + .into(); + + for window in self.windows.lock().unwrap().iter() { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| state.size = Some(surface_size)), + }; + } + } + + fn keyboard_leave_if_previous( + &self, + seat: &Seat, + data: &mut State, + serial: Serial, + ) -> usize { + let active = self.active.load(Ordering::SeqCst); + let previous = self.previous_keyboard.swap(active, Ordering::SeqCst); + if previous != active { + KeyboardTarget::leave(&self.windows.lock().unwrap()[previous], seat, data, serial); + // TODO: KeyboardTarget::enter(&self.windows.lock().unwrap()[active], seat, data, serial, seat.keys()) + } + active + } + + fn pointer_leave_if_previous( + &self, + seat: &Seat, + data: &mut State, + serial: Serial, + time: u32, + location: Point, + ) -> usize { + let active = self.active.load(Ordering::SeqCst); + let previous = self.previous_pointer.swap(active, Ordering::SeqCst); + if previous != active { + if let Some(sessions) = self.windows.lock().unwrap()[previous] + .user_data() + .get::() + { + for session in &*sessions.0.borrow() { + session.cursor_leave(seat, InputType::Pointer) + } + } + PointerTarget::leave( + &self.windows.lock().unwrap()[previous], + seat, + data, + serial, + time, + ); + if let Some(sessions) = self.windows.lock().unwrap()[active] + .user_data() + .get::() + { + for session in &*sessions.0.borrow() { + session.cursor_enter(seat, InputType::Pointer) + } + } + PointerTarget::enter( + &self.windows.lock().unwrap()[active], + seat, + data, + &MotionEvent { + location, + serial, + time, + }, + ); + } + active + } +} + +impl IsAlive for CosmicStack { + fn alive(&self) -> bool { + self.windows.lock().unwrap().iter().any(IsAlive::alive) + } +} + +impl SpaceElement for CosmicStack { + fn bbox(&self) -> Rectangle { + SpaceElement::bbox(&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)]) + } + fn is_in_input_region(&self, point: &Point) -> bool { + SpaceElement::is_in_input_region( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + point, + ) + } + fn set_activate(&self, activated: bool) { + self.windows + .lock() + .unwrap() + .iter() + .for_each(|w| SpaceElement::set_activate(w, activated)) + } + fn output_enter(&self, output: &Output, overlap: Rectangle) { + self.windows + .lock() + .unwrap() + .iter() + .for_each(|w| SpaceElement::output_enter(w, output, overlap)) + } + fn output_leave(&self, output: &Output) { + self.windows + .lock() + .unwrap() + .iter() + .for_each(|w| SpaceElement::output_leave(w, output)) + } + fn geometry(&self) -> Rectangle { + SpaceElement::geometry(&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)]) + } + fn z_index(&self) -> u8 { + SpaceElement::z_index(&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)]) + } + fn refresh(&self) { + let mut windows = self.windows.lock().unwrap(); + windows.retain(IsAlive::alive); + let len = windows.len(); + let _ = self + .active + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |active| { + (active > len).then_some(len - 1) + }); + windows.iter().for_each(|w| SpaceElement::refresh(w)) + } +} + +impl KeyboardTarget for CosmicStack { + fn enter( + &self, + seat: &Seat, + data: &mut State, + keys: Vec>, + serial: Serial, + ) { + let active = self.active.load(Ordering::SeqCst); + self.previous_keyboard.store(active, Ordering::SeqCst); + KeyboardTarget::enter( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + keys, + serial, + ) + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { + let active = self.keyboard_leave_if_previous(seat, data, serial); + KeyboardTarget::leave(&self.windows.lock().unwrap()[active], seat, data, serial) + } + fn key( + &self, + seat: &Seat, + data: &mut State, + key: KeysymHandle<'_>, + state: KeyState, + serial: Serial, + time: u32, + ) { + let active = self.keyboard_leave_if_previous(seat, data, serial); + KeyboardTarget::key( + &self.windows.lock().unwrap()[active], + seat, + data, + key, + state, + serial, + time, + ) + } + fn modifiers( + &self, + seat: &Seat, + data: &mut State, + modifiers: ModifiersState, + serial: Serial, + ) { + let active = self.keyboard_leave_if_previous(seat, data, serial); + KeyboardTarget::modifiers( + &self.windows.lock().unwrap()[active], + seat, + data, + modifiers, + serial, + ) + } +} + +impl PointerTarget for CosmicStack { + fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + if let Some(sessions) = self.active().user_data().get::() { + for session in &*sessions.0.borrow() { + session.cursor_enter(seat, InputType::Pointer) + } + } + + *self.last_location.lock().unwrap() = Some((event.location, event.serial, event.time)); + let active = self.active.load(Ordering::SeqCst); + self.previous_pointer.store(active, Ordering::SeqCst); + + PointerTarget::enter( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + event, + ) + } + fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + let active = + self.pointer_leave_if_previous(seat, data, event.serial, event.time, event.location); + + if let Some(sessions) = self.active().user_data().get::() { + for session in &*sessions.0.borrow() { + let buffer_loc = (event.location.x, event.location.y); // we always screencast windows at 1x1 scale + if let Some((geo, hotspot)) = + seat.cursor_geometry(buffer_loc, data.common.clock.now()) + { + session.cursor_info(seat, InputType::Pointer, geo, hotspot); + } + } + } + + PointerTarget::motion(&self.windows.lock().unwrap()[active], seat, data, event) + } + fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { + if let Some((location, _serial, _time)) = self.last_location.lock().unwrap().clone() { + self.pointer_leave_if_previous(seat, data, event.serial, event.time, location); + } + + PointerTarget::button( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + event, + ) + } + fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { + if let Some((location, serial, time)) = self.last_location.lock().unwrap().clone() { + self.pointer_leave_if_previous(seat, data, serial, time, location); + } + + PointerTarget::axis( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + frame, + ) + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + if let Some((location, serial, time)) = self.last_location.lock().unwrap().clone() { + self.pointer_leave_if_previous(seat, data, serial, time, location); + } + if let Some(sessions) = self.active().user_data().get::() { + for session in &*sessions.0.borrow() { + session.cursor_leave(seat, InputType::Pointer) + } + } + + PointerTarget::leave( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + serial, + time, + ) + } +} + +render_elements! { + pub CosmicStackRenderElement where R: ImportAll; + Window=WaylandSurfaceRenderElement, +} + +impl AsRenderElements for CosmicStack +where + R: Renderer + ImportAll, + ::TextureId: 'static, +{ + type RenderElement = CosmicStackRenderElement; + fn render_elements>( + &self, + location: Point, + scale: Scale, + ) -> Vec { + AsRenderElements::::render_elements::>( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + location, + scale, + ) + .into_iter() + .map(C::from) + .collect() + } +} +impl CosmicStack { + pub fn windows(&self) -> impl Iterator { + self.windows + .lock() + .unwrap() + .iter() + .cloned() + .collect::>() + .into_iter() + } +} diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs new file mode 100644 index 00000000..c65ca09c --- /dev/null +++ b/src/shell/element/window.rs @@ -0,0 +1,262 @@ +use crate::{ + state::State, utils::prelude::SeatExt, wayland::handlers::screencopy::ScreencopySessions, +}; +use smithay::{ + backend::{ + input::KeyState, + renderer::{ + element::{surface::WaylandSurfaceRenderElement, AsRenderElements}, + ImportAll, Renderer, + }, + }, + desktop::{space::SpaceElement, Kind, Window}, + input::{ + keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, + pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, + Seat, + }, + output::Output, + reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode, + render_elements, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, + wayland::shell::xdg::ToplevelSurface, +}; +use std::{ + hash::Hash, + sync::{Arc, Mutex}, +}; + +#[derive(Debug, Clone)] +pub struct CosmicWindow { + pub(super) window: Window, + pub(super) header: Arc>>, +} + +impl PartialEq for CosmicWindow { + fn eq(&self, other: &Window) -> bool { + &self.window == other + } +} + +impl PartialEq for Window { + fn eq(&self, other: &CosmicWindow) -> bool { + self == &other.window + } +} + +impl PartialEq for CosmicWindow { + fn eq(&self, other: &Self) -> bool { + self.window == other.window + } +} + +impl Eq for CosmicWindow {} + +impl Hash for CosmicWindow { + fn hash(&self, state: &mut H) { + self.window.hash(state) + } +} + +impl CosmicWindow { + pub fn set_size(&self, size: Size) { + let surface_size = ( + size.w, + size.h + - self + .header + .lock() + .unwrap() + .as_ref() + .map(|h| h.height()) + .unwrap_or(0), + ) + .into(); + match self.window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| state.size = Some(surface_size)), + }; + } +} + +impl From for CosmicWindow { + fn from(window: Window) -> Self { + let is_ssd = matches!( + match window.toplevel() { + Kind::Xdg(xdg) => xdg.current_state().decoration_mode, + }, + Some(DecorationMode::ServerSide) + ); + CosmicWindow { + window, + header: Arc::new(Mutex::new(is_ssd.then_some(HeaderBar::default()))), + } + } +} + +impl From for CosmicWindow { + fn from(surf: ToplevelSurface) -> Self { + let is_ssd = matches!( + surf.current_state().decoration_mode, + Some(DecorationMode::ServerSide) + ); + CosmicWindow { + window: Window::new(Kind::Xdg(surf)), + header: Arc::new(Mutex::new(is_ssd.then_some(HeaderBar::default()))), + } + } +} + +#[derive(Debug, Default, PartialEq)] +pub(super) struct HeaderBar { + pointer_loc: Option>, + close_button_hover: bool, + maximize_button_hover: bool, +} + +impl HeaderBar { + pub fn height(&self) -> i32 { + 0 + } +} + +impl IsAlive for CosmicWindow { + fn alive(&self) -> bool { + self.window.alive() + } +} + +impl SpaceElement for CosmicWindow { + fn bbox(&self) -> Rectangle { + SpaceElement::bbox(&self.window) + } + fn is_in_input_region(&self, point: &Point) -> bool { + SpaceElement::is_in_input_region(&self.window, point) + } + fn set_activate(&self, activated: bool) { + SpaceElement::set_activate(&self.window, activated) + } + fn output_enter(&self, output: &Output, overlap: Rectangle) { + SpaceElement::output_enter(&self.window, output, overlap) + } + fn output_leave(&self, output: &Output) { + SpaceElement::output_leave(&self.window, output) + } + fn geometry(&self) -> Rectangle { + SpaceElement::geometry(&self.window) + } + fn z_index(&self) -> u8 { + SpaceElement::z_index(&self.window) + } + fn refresh(&self) { + SpaceElement::refresh(&self.window) + } +} + +impl KeyboardTarget for CosmicWindow { + fn enter( + &self, + seat: &Seat, + data: &mut State, + keys: Vec>, + serial: Serial, + ) { + KeyboardTarget::enter(&self.window, seat, data, keys, serial) + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { + KeyboardTarget::leave(&self.window, seat, data, serial) + } + fn key( + &self, + seat: &Seat, + data: &mut State, + key: KeysymHandle<'_>, + state: KeyState, + serial: Serial, + time: u32, + ) { + KeyboardTarget::key(&self.window, seat, data, key, state, serial, time) + } + fn modifiers( + &self, + seat: &Seat, + data: &mut State, + modifiers: ModifiersState, + serial: Serial, + ) { + KeyboardTarget::modifiers(&self.window, seat, data, modifiers, serial) + } +} + +impl PointerTarget for CosmicWindow { + fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; + + if let Some(sessions) = self.window.user_data().get::() { + for session in &*sessions.0.borrow() { + session.cursor_enter(seat, InputType::Pointer) + } + } + + PointerTarget::enter(&self.window, seat, data, event) + } + fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; + + if let Some(sessions) = self.window.user_data().get::() { + for session in &*sessions.0.borrow() { + let buffer_loc = (event.location.x, event.location.y); // we always screencast windows at 1x1 scale + if let Some((geo, hotspot)) = + seat.cursor_geometry(buffer_loc, data.common.clock.now()) + { + session.cursor_info(seat, InputType::Pointer, geo, hotspot); + } + } + } + + PointerTarget::motion(&self.window, seat, data, event) + } + fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { + PointerTarget::button(&self.window, seat, data, event) + } + fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { + PointerTarget::axis(&self.window, seat, data, frame) + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; + + if let Some(sessions) = self.window.user_data().get::() { + for session in &*sessions.0.borrow() { + session.cursor_leave(seat, InputType::Pointer) + } + } + + PointerTarget::leave(&self.window, seat, data, serial, time) + } +} + +render_elements! { + pub CosmicWindowRenderElement where R: ImportAll; + Window=WaylandSurfaceRenderElement, +} + +impl AsRenderElements for CosmicWindow +where + R: Renderer + ImportAll, + ::TextureId: 'static, +{ + type RenderElement = CosmicWindowRenderElement; + fn render_elements>( + &self, + location: Point, + scale: Scale, + ) -> Vec { + AsRenderElements::::render_elements::>( + &self.window, + location, + scale, + ) + .into_iter() + .map(C::from) + .collect() + } +} diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index 09886893..611101b3 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -1,24 +1,18 @@ use crate::{ - shell::{OutputBoundState, Shell, Workspace, WorkspaceMode}, + shell::{element::CosmicMapped, Shell, Workspace}, state::Common, utils::prelude::*, wayland::handlers::xdg_shell::PopupGrabData, }; use indexmap::IndexSet; use smithay::{ - desktop::{layer_map_for_output, PopupUngrabStrategy, Window, WindowSurfaceType}, + desktop::{layer_map_for_output, PopupUngrabStrategy}, input::Seat, - reexports::wayland_server::protocol::wl_surface::WlSurface, utils::{IsAlive, Serial, SERIAL_COUNTER}, - wayland::{ - compositor::get_role, - shell::{wlr_layer::LAYER_SURFACE_ROLE, xdg::XDG_TOPLEVEL_ROLE}, - }, -}; -use std::{ - cell::{Ref, RefCell, RefMut}, - collections::HashMap, }; +use std::cell::RefCell; + +use self::target::{KeyboardFocusTarget, WindowGroup}; pub mod target; @@ -32,80 +26,59 @@ pub enum FocusDirection { Out, } -pub struct FocusStack<'a>(Ref<'a, IndexSet>); -pub struct FocusStackMut<'a>(RefMut<'a, IndexSet>); +pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet>); +pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet); impl<'a> FocusStack<'a> { - pub fn last(&self) -> Option { - self.0.iter().rev().find(|w| w.toplevel().alive()).cloned() + pub fn last(&self) -> Option<&CosmicMapped> { + self.0 + .as_ref() + .and_then(|set| set.iter().rev().find(|w| w.alive())) } - pub fn iter(&self) -> impl Iterator { - self.0.iter().rev().filter(|w| w.toplevel().alive()) + pub fn iter(&self) -> impl Iterator { + self.0 + .iter() + .flat_map(|set| set.iter().rev().filter(|w| w.alive())) } } impl<'a> FocusStackMut<'a> { - pub fn append(&mut self, window: &Window) { - self.0.retain(|w| w.toplevel().alive()); + pub fn append(&mut self, window: &CosmicMapped) { + self.0.retain(|w| w.alive()); self.0.shift_remove(window); self.0.insert(window.clone()); } - pub fn last(&self) -> Option { - self.0.iter().rev().find(|w| w.toplevel().alive()).cloned() + pub fn last(&self) -> Option<&CosmicMapped> { + self.0.iter().rev().find(|w| w.alive()) } - pub fn iter(&self) -> impl Iterator { - self.0.iter().rev().filter(|w| w.toplevel().alive()) + pub fn iter(&self) -> impl Iterator { + self.0.iter().rev().filter(|w| w.alive()) } } -type FocusStackData = RefCell<(HashMap>, IndexSet)>; +impl Workspace {} -impl Workspace { - pub fn focus_stack<'a, 'b>(&'b self, seat: &'a Seat) -> FocusStack<'a> { - seat.user_data() - .insert_if_missing(|| FocusStackData::new((HashMap::new(), IndexSet::new()))); - let idx = self.idx; - FocusStack(Ref::map( - seat.user_data().get::().unwrap().borrow(), - |map| map.0.get(&idx).unwrap_or(&map.1), //TODO: workaround until Ref::filter_map goes stable - )) - } - - pub fn focus_stack_mut<'a, 'b>(&'b self, seat: &'a Seat) -> FocusStackMut<'a> { - seat.user_data() - .insert_if_missing(|| FocusStackData::new((HashMap::new(), IndexSet::new()))); - let idx = self.idx; - FocusStackMut(RefMut::map( - seat.user_data() - .get::() - .unwrap() - .borrow_mut(), - |map| map.0.entry(idx).or_insert_with(|| IndexSet::new()), - )) - } -} - -pub struct ActiveFocus(RefCell>); +pub struct ActiveFocus(RefCell>); impl ActiveFocus { - fn set(seat: &Seat, surface: Option) { + fn set(seat: &Seat, target: Option) { if !seat .user_data() - .insert_if_missing(|| ActiveFocus(RefCell::new(surface.clone()))) + .insert_if_missing(|| ActiveFocus(RefCell::new(target.clone()))) { *seat .user_data() .get::() .unwrap() .0 - .borrow_mut() = surface; + .borrow_mut() = target; } } - fn get(seat: &Seat) -> Option { + fn get(seat: &Seat) -> Option { seat.user_data() .get::() .and_then(|a| a.0.borrow().clone()) @@ -115,30 +88,25 @@ impl ActiveFocus { impl Shell { pub fn set_focus<'a>( state: &mut State, - surface: Option<&WlSurface>, + target: Option<&KeyboardFocusTarget>, active_seat: &Seat, serial: Option, ) { // update FocusStack and notify layouts about new focus (if any window) - if let Some(surface) = surface { - if let Some(workspace) = state.common.shell.space_for_window_mut(surface) { - if let Some(window) = workspace - .space - .window_for_surface(surface, WindowSurfaceType::ALL) - { - let mut focus_stack = workspace.focus_stack_mut(active_seat); - if Some(window) != focus_stack.last().as_ref() { - slog_scope::debug!("Focusing window: {:?}", window); - focus_stack.append(window); - // also remove popup grabs, if we are switching focus - if let Some(mut popup_grab) = active_seat - .user_data() - .get::() - .and_then(|x| x.take()) - { - if !popup_grab.has_ended() { - popup_grab.ungrab(PopupUngrabStrategy::All); - } + if let Some(KeyboardFocusTarget::Element(mapped)) = target { + if let Some(workspace) = state.common.shell.space_for_mut(mapped) { + let mut focus_stack = workspace.focus_stack.get_mut(active_seat); + if Some(mapped) != focus_stack.last() { + slog_scope::debug!("Focusing window: {:?}", mapped); + focus_stack.append(mapped); + // also remove popup grabs, if we are switching focus + if let Some(mut popup_grab) = active_seat + .user_data() + .get::() + .and_then(|x| x.take()) + { + if !popup_grab.has_ended() { + popup_grab.ungrab(PopupUngrabStrategy::All); } } } @@ -147,10 +115,10 @@ impl Shell { // update keyboard focus if let Some(keyboard) = active_seat.get_keyboard() { - ActiveFocus::set(active_seat, surface.cloned()); + ActiveFocus::set(active_seat, target.cloned()); keyboard.set_focus( state, - surface.cloned(), + target.cloned(), serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial()), ); } @@ -160,30 +128,23 @@ impl Shell { // update activate status let focused_windows = seats .flat_map(|seat| { - self.outputs - .iter() - .flat_map(|o| self.active_space(o).focus_stack(seat).last().clone()) + self.outputs.iter().flat_map(|o| { + let space = self.active_space(o); + let stack = space.focus_stack.get(seat); + stack.last().cloned() + }) }) .collect::>(); for output in self.outputs.iter() { - let workspace = match &self.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &mut self.spaces[active] - } - WorkspaceMode::Global { active, .. } => &mut self.spaces[*active], - }; + let workspace = self.workspaces.active_mut(output); for focused in focused_windows.iter() { - workspace.space.raise_window(focused, true); + if workspace.floating_layer.mapped().any(|m| m == focused) { + workspace.floating_layer.space.raise_element(focused, true); + } } - for window in workspace.space.windows() { - window.set_activated(focused_windows.contains(window)); + for window in workspace.mapped() { + window.set_activated(focused_windows.contains(&window)); window.configure(); } } @@ -193,55 +154,54 @@ impl Shell { impl Common { pub fn set_focus( state: &mut State, - surface: Option<&WlSurface>, + target: Option<&KeyboardFocusTarget>, active_seat: &Seat, serial: Option, ) { - Shell::set_focus(state, surface, active_seat, serial); - state.common.shell.update_active(state.common.seats.iter()); + Shell::set_focus(state, target, active_seat, serial); + let seats = state.common.seats().cloned().collect::>(); + state.common.shell.update_active(seats.iter()); } pub fn refresh_focus(state: &mut State) { - let seats = state.common.seats.clone(); + let seats = state.common.seats().cloned().collect::>(); for seat in seats { - let output = active_output(&seat, &state.common); + let output = seat.active_output(); + if !state.common.shell.outputs.contains(&output) { + seat.set_active_output(&state.common.shell.outputs[0]); + continue; + } let last_known_focus = ActiveFocus::get(&seat); - if let Some(surface) = last_known_focus { - if surface.alive() { - let is_toplevel = matches!(get_role(&surface), Some(XDG_TOPLEVEL_ROLE)); - let is_layer = matches!(get_role(&surface), Some(LAYER_SURFACE_ROLE)); - - if let Some(popup) = state.common.shell.popups.find_popup(&surface) { - if popup.alive() { - continue; - } - } else if is_layer { - if layer_map_for_output(&output) - .layer_for_surface(&surface, WindowSurfaceType::ALL) - .is_some() - { - continue; // Focus is valid - } - } else if is_toplevel { - let workspace = state.common.shell.active_space(&output); - if let Some(window) = workspace - .space - .window_for_surface(&surface, WindowSurfaceType::ALL) - { - let focus_stack = workspace.focus_stack(&seat); - if !focus_stack.last().map(|w| &w != window).unwrap_or(true) { + if let Some(target) = last_known_focus { + if target.alive() { + match target { + KeyboardFocusTarget::Element(mapped) => { + let workspace = state.common.shell.active_space(&output); + let focus_stack = workspace.focus_stack.get(&seat); + if focus_stack.last().map(|m| m == &mapped).unwrap_or(false) { continue; // Focus is valid } else { slog_scope::debug!("Wrong Window, focus fixup"); } - } else { - slog_scope::debug!("Different workspaces Window, focus fixup"); } - } else { - // unknown surface type, fixup - slog_scope::debug!("Surface unmapped, focus fixup"); - } + KeyboardFocusTarget::LayerSurface(layer) => { + if layer_map_for_output(&output).layers().any(|l| l == &layer) { + continue; // Focus is valid + } + } + KeyboardFocusTarget::Group(WindowGroup { + output: weak_output, + .. + }) => { + if weak_output == output { + continue; // Focus is valid, + } + } + KeyboardFocusTarget::Popup(_) | KeyboardFocusTarget::Fullscreen(_) => { + continue; + } // Focus is valid + }; } else { slog_scope::debug!("Surface dead, focus fixup"); } @@ -263,21 +223,24 @@ impl Common { } // update keyboard focus - let surface = state + let target = state .common .shell .active_space(&output) - .focus_stack(&seat) + .focus_stack + .get(&seat) .last() - .map(|w| w.toplevel().wl_surface().clone()); + .cloned() + .map(KeyboardFocusTarget::from); if let Some(keyboard) = seat.get_keyboard() { - slog_scope::info!("restoring focus to: {:?}", surface.as_ref()); - keyboard.set_focus(state, surface.clone(), SERIAL_COUNTER.next_serial()); - ActiveFocus::set(&seat, surface); + slog_scope::info!("restoring focus to: {:?}", target.as_ref()); + keyboard.set_focus(state, target.clone(), SERIAL_COUNTER.next_serial()); + ActiveFocus::set(&seat, target); } } } - state.common.shell.update_active(state.common.seats.iter()) + let seats = state.common.seats().cloned().collect::>(); + state.common.shell.update_active(seats.iter()) } } diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index 81861424..ef337347 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -1,5 +1,8 @@ -use crate::utils::prelude::*; -pub use smithay::{ +use std::sync::Weak; + +use crate::{shell::element::CosmicMapped, utils::prelude::*}; +use id_tree::NodeId; +use smithay::{ backend::input::KeyState, desktop::{LayerSurface, PopupKind, Window}, input::{ @@ -7,83 +10,131 @@ pub use smithay::{ pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, Seat, }, + output::WeakOutput, reexports::wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface, Resource}, utils::{IsAlive, Serial}, wayland::seat::WaylandFocus, }; #[derive(Debug, Clone, PartialEq)] -pub enum FocusTarget { - Window(Window), +pub enum PointerFocusTarget { + Element(CosmicMapped), + Fullscreen(Window), LayerSurface(LayerSurface), Popup(PopupKind), } -impl IsAlive for FocusTarget { - fn alive(&self) -> bool { - match self { - FocusTarget::Window(w) => w.alive(), - FocusTarget::LayerSurface(l) => l.alive(), - FocusTarget::Popup(p) => p.alive(), +#[derive(Debug, Clone, PartialEq)] +pub enum KeyboardFocusTarget { + Element(CosmicMapped), + Fullscreen(Window), + Group(WindowGroup), + LayerSurface(LayerSurface), + Popup(PopupKind), +} + +impl From for PointerFocusTarget { + fn from(target: KeyboardFocusTarget) -> Self { + match target { + KeyboardFocusTarget::Element(elem) => PointerFocusTarget::Element(elem), + KeyboardFocusTarget::Fullscreen(elem) => PointerFocusTarget::Fullscreen(elem), + KeyboardFocusTarget::LayerSurface(layer) => PointerFocusTarget::LayerSurface(layer), + KeyboardFocusTarget::Popup(popup) => PointerFocusTarget::Popup(popup), + _ => unreachable!("A window grab cannot start a popup grab"), } } } -impl PointerTarget for FocusTarget { +#[derive(Debug, Clone)] +pub struct WindowGroup { + pub(in crate::shell) node: NodeId, + pub(in crate::shell) output: WeakOutput, + pub(in crate::shell) alive: Weak<()>, +} + +impl PartialEq for WindowGroup { + fn eq(&self, other: &Self) -> bool { + self.node == other.node + && self.output == other.output + && Weak::ptr_eq(&self.alive, &other.alive) + } +} + +impl IsAlive for PointerFocusTarget { + fn alive(&self) -> bool { + match self { + PointerFocusTarget::Element(e) => e.alive(), + PointerFocusTarget::Fullscreen(f) => f.alive(), + PointerFocusTarget::LayerSurface(l) => l.alive(), + PointerFocusTarget::Popup(p) => p.alive(), + } + } +} + +impl IsAlive for KeyboardFocusTarget { + fn alive(&self) -> bool { + match self { + KeyboardFocusTarget::Element(e) => e.alive(), + KeyboardFocusTarget::Fullscreen(f) => f.alive(), + KeyboardFocusTarget::Group(g) => g.alive.upgrade().is_some(), + KeyboardFocusTarget::LayerSurface(l) => l.alive(), + KeyboardFocusTarget::Popup(p) => p.alive(), + } + } +} + +impl PointerTarget for PointerFocusTarget { fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { match self { - FocusTarget::Window(w) => { - PointerTarget::enter(w.toplevel().wl_surface(), seat, data, event) - } - FocusTarget::LayerSurface(l) => PointerTarget::enter(l.wl_surface(), seat, data, event), - FocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event), + PointerFocusTarget::Element(w) => PointerTarget::enter(w, seat, data, event), + PointerFocusTarget::Fullscreen(w) => PointerTarget::enter(w, seat, data, event), + PointerFocusTarget::LayerSurface(l) => PointerTarget::enter(l, seat, data, event), + PointerFocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event), } } fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { match self { - FocusTarget::Window(w) => { - PointerTarget::motion(w.toplevel().wl_surface(), seat, data, event) + PointerFocusTarget::Element(w) => PointerTarget::motion(w, seat, data, event), + PointerFocusTarget::Fullscreen(w) => PointerTarget::motion(w, seat, data, event), + PointerFocusTarget::LayerSurface(l) => PointerTarget::motion(l, seat, data, event), + PointerFocusTarget::Popup(p) => { + PointerTarget::motion(p.wl_surface(), seat, data, event) } - FocusTarget::LayerSurface(l) => { - PointerTarget::motion(l.wl_surface(), seat, data, event) - } - FocusTarget::Popup(p) => PointerTarget::motion(p.wl_surface(), seat, data, event), } } fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { match self { - FocusTarget::Window(w) => { - PointerTarget::button(w.toplevel().wl_surface(), seat, data, event) + PointerFocusTarget::Element(w) => PointerTarget::button(w, seat, data, event), + PointerFocusTarget::Fullscreen(w) => PointerTarget::button(w, seat, data, event), + PointerFocusTarget::LayerSurface(l) => PointerTarget::button(l, seat, data, event), + PointerFocusTarget::Popup(p) => { + PointerTarget::button(p.wl_surface(), seat, data, event) } - FocusTarget::LayerSurface(l) => { - PointerTarget::button(l.wl_surface(), seat, data, event) - } - FocusTarget::Popup(p) => PointerTarget::button(p.wl_surface(), seat, data, event), } } fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { match self { - FocusTarget::Window(w) => { - PointerTarget::axis(w.toplevel().wl_surface(), seat, data, frame) - } - FocusTarget::LayerSurface(l) => PointerTarget::axis(l.wl_surface(), seat, data, frame), - FocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame), + PointerFocusTarget::Element(w) => PointerTarget::axis(w, seat, data, frame), + PointerFocusTarget::Fullscreen(w) => PointerTarget::axis(w, seat, data, frame), + PointerFocusTarget::LayerSurface(l) => PointerTarget::axis(l, seat, data, frame), + PointerFocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame), } } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { match self { - FocusTarget::Window(w) => { - PointerTarget::leave(w.toplevel().wl_surface(), seat, data, serial, time) + PointerFocusTarget::Element(w) => PointerTarget::leave(w, seat, data, serial, time), + PointerFocusTarget::Fullscreen(w) => PointerTarget::leave(w, seat, data, serial, time), + PointerFocusTarget::LayerSurface(l) => { + PointerTarget::leave(l, seat, data, serial, time) } - FocusTarget::LayerSurface(l) => { - PointerTarget::leave(l.wl_surface(), seat, data, serial, time) + PointerFocusTarget::Popup(p) => { + PointerTarget::leave(p.wl_surface(), seat, data, serial, time) } - FocusTarget::Popup(p) => PointerTarget::leave(p.wl_surface(), seat, data, serial, time), } } } -impl KeyboardTarget for FocusTarget { +impl KeyboardTarget for KeyboardFocusTarget { fn enter( &self, seat: &Seat, @@ -92,26 +143,28 @@ impl KeyboardTarget for FocusTarget { serial: Serial, ) { match self { - FocusTarget::Window(w) => { - KeyboardTarget::enter(w.toplevel().wl_surface(), seat, data, keys, serial) + KeyboardFocusTarget::Element(w) => KeyboardTarget::enter(w, seat, data, keys, serial), + KeyboardFocusTarget::Fullscreen(w) => { + KeyboardTarget::enter(w, seat, data, keys, serial) } - FocusTarget::LayerSurface(l) => { - KeyboardTarget::enter(l.wl_surface(), seat, data, keys, serial) + KeyboardFocusTarget::Group(_) => {} + KeyboardFocusTarget::LayerSurface(l) => { + KeyboardTarget::enter(l, seat, data, keys, serial) } - FocusTarget::Popup(p) => { + KeyboardFocusTarget::Popup(p) => { KeyboardTarget::enter(p.wl_surface(), seat, data, keys, serial) } } } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { match self { - FocusTarget::Window(w) => { - KeyboardTarget::leave(w.toplevel().wl_surface(), seat, data, serial) + KeyboardFocusTarget::Element(w) => KeyboardTarget::leave(w, seat, data, serial), + KeyboardFocusTarget::Fullscreen(w) => KeyboardTarget::leave(w, seat, data, serial), + KeyboardFocusTarget::Group(_) => {} + KeyboardFocusTarget::LayerSurface(l) => KeyboardTarget::leave(l, seat, data, serial), + KeyboardFocusTarget::Popup(p) => { + KeyboardTarget::leave(p.wl_surface(), seat, data, serial) } - FocusTarget::LayerSurface(l) => { - KeyboardTarget::leave(l.wl_surface(), seat, data, serial) - } - FocusTarget::Popup(p) => KeyboardTarget::leave(p.wl_surface(), seat, data, serial), } } fn key( @@ -124,19 +177,17 @@ impl KeyboardTarget for FocusTarget { time: u32, ) { match self { - FocusTarget::Window(w) => KeyboardTarget::key( - w.toplevel().wl_surface(), - seat, - data, - key, - state, - serial, - time, - ), - FocusTarget::LayerSurface(l) => { - KeyboardTarget::key(l.wl_surface(), seat, data, key, state, serial, time) + KeyboardFocusTarget::Element(w) => { + KeyboardTarget::key(w, seat, data, key, state, serial, time) } - FocusTarget::Popup(p) => { + KeyboardFocusTarget::Fullscreen(w) => { + KeyboardTarget::key(w, seat, data, key, state, serial, time) + } + KeyboardFocusTarget::Group(_) => {} + KeyboardFocusTarget::LayerSurface(l) => { + KeyboardTarget::key(l, seat, data, key, state, serial, time) + } + KeyboardFocusTarget::Popup(p) => { KeyboardTarget::key(p.wl_surface(), seat, data, key, state, serial, time) } } @@ -149,50 +200,113 @@ impl KeyboardTarget for FocusTarget { serial: Serial, ) { match self { - FocusTarget::Window(w) => { - KeyboardTarget::modifiers(w.toplevel().wl_surface(), seat, data, modifiers, serial) + KeyboardFocusTarget::Element(w) => { + KeyboardTarget::modifiers(w, seat, data, modifiers, serial) } - FocusTarget::LayerSurface(l) => { - KeyboardTarget::modifiers(l.wl_surface(), seat, data, modifiers, serial) + KeyboardFocusTarget::Fullscreen(w) => { + KeyboardTarget::modifiers(w, seat, data, modifiers, serial) } - FocusTarget::Popup(p) => { + KeyboardFocusTarget::Group(_) => {} + KeyboardFocusTarget::LayerSurface(l) => { + KeyboardTarget::modifiers(l, seat, data, modifiers, serial) + } + KeyboardFocusTarget::Popup(p) => { KeyboardTarget::modifiers(p.wl_surface(), seat, data, modifiers, serial) } } } } -impl WaylandFocus for FocusTarget { - fn wl_surface(&self) -> Option<&WlSurface> { - Some(match self { - FocusTarget::Window(w) => w.toplevel().wl_surface(), - FocusTarget::LayerSurface(l) => l.wl_surface(), - FocusTarget::Popup(p) => p.wl_surface(), - }) +impl WaylandFocus for KeyboardFocusTarget { + fn wl_surface(&self) -> Option { + match self { + KeyboardFocusTarget::Element(w) => WaylandFocus::wl_surface(w), + KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::wl_surface(w), + KeyboardFocusTarget::Group(_) => None, + KeyboardFocusTarget::LayerSurface(l) => Some(l.wl_surface().clone()), + KeyboardFocusTarget::Popup(p) => Some(p.wl_surface().clone()), + } } fn same_client_as(&self, object_id: &ObjectId) -> bool { match self { - FocusTarget::Window(w) => w.toplevel().wl_surface().id().same_client_as(object_id), - FocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id), - FocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id), + KeyboardFocusTarget::Element(w) => WaylandFocus::same_client_as(w, object_id), + KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::same_client_as(w, object_id), + KeyboardFocusTarget::Group(_) => false, + KeyboardFocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id), + KeyboardFocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id), } } } -impl From for FocusTarget { +impl WaylandFocus for PointerFocusTarget { + fn wl_surface(&self) -> Option { + Some(match self { + PointerFocusTarget::Element(w) => WaylandFocus::wl_surface(w)?, + PointerFocusTarget::Fullscreen(w) => WaylandFocus::wl_surface(w)?, + PointerFocusTarget::LayerSurface(l) => l.wl_surface().clone(), + PointerFocusTarget::Popup(p) => p.wl_surface().clone(), + }) + } + fn same_client_as(&self, object_id: &ObjectId) -> bool { + match self { + PointerFocusTarget::Element(w) => WaylandFocus::same_client_as(w, object_id), + PointerFocusTarget::Fullscreen(w) => WaylandFocus::same_client_as(w, object_id), + PointerFocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id), + PointerFocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id), + } + } +} + +impl From for PointerFocusTarget { + fn from(w: CosmicMapped) -> Self { + PointerFocusTarget::Element(w) + } +} + +impl From for PointerFocusTarget { fn from(w: Window) -> Self { - FocusTarget::Window(w) + PointerFocusTarget::Fullscreen(w) } } -impl From for FocusTarget { +impl From for PointerFocusTarget { fn from(l: LayerSurface) -> Self { - FocusTarget::LayerSurface(l) + PointerFocusTarget::LayerSurface(l) } } -impl From for FocusTarget { +impl From for PointerFocusTarget { fn from(p: PopupKind) -> Self { - FocusTarget::Popup(p) + PointerFocusTarget::Popup(p) + } +} + +impl From for KeyboardFocusTarget { + fn from(w: CosmicMapped) -> Self { + KeyboardFocusTarget::Element(w) + } +} + +impl From for KeyboardFocusTarget { + fn from(w: Window) -> Self { + KeyboardFocusTarget::Fullscreen(w) + } +} + +impl From for KeyboardFocusTarget { + fn from(w: WindowGroup) -> Self { + KeyboardFocusTarget::Group(w) + } +} + +impl From for KeyboardFocusTarget { + fn from(l: LayerSurface) -> Self { + KeyboardFocusTarget::LayerSurface(l) + } +} + +impl From for KeyboardFocusTarget { + fn from(p: PopupKind) -> Self { + KeyboardFocusTarget::Popup(p) } } diff --git a/src/shell/grabs.rs b/src/shell/grabs.rs index cdb06a57..ac5f6877 100644 --- a/src/shell/grabs.rs +++ b/src/shell/grabs.rs @@ -1,367 +1,107 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use super::Shell; -use crate::utils::prelude::*; - -use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; use smithay::{ - backend::renderer::{ImportAll, Renderer}, - desktop::{ - draw_window, - space::{RenderElement, SpaceOutputTuple}, - Kind, Window, + input::pointer::{ + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, + PointerInnerHandle, }, - input::{ - pointer::{ - AxisFrame, ButtonEvent, Focus, GrabStartData as PointerGrabStartData, MotionEvent, - PointerGrab, PointerInnerHandle, - }, - Seat, - }, - output::Output, - reexports::{ - wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState, - wayland_server::protocol::wl_surface::WlSurface, - }, - utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial}, + reexports::wayland_protocols::xdg::shell::server::xdg_toplevel, + utils::{Logical, Point}, }; -use std::cell::RefCell; -impl Shell { - pub fn move_request( - state: &mut State, - window: &Window, - seat: &Seat, - serial: Serial, - start_data: PointerGrabStartData, - ) { - // TODO touch grab - if let Some(pointer) = seat.get_pointer() { - let workspace = state - .common - .shell - .space_for_window_mut(window.toplevel().wl_surface()) - .unwrap(); - if workspace.fullscreen.values().any(|w| w == window) { - return; - } +use crate::state::State; - let pos = pointer.current_location(); - let output = match workspace - .space - .outputs_for_window(&window) - .into_iter() - .find(|o| o.geometry().contains(pos.to_i32_round())) { - Some(o) => o, - None => return, - }; - let mut initial_window_location = workspace.space.window_location(&window).unwrap(); +use super::{ + focus::target::PointerFocusTarget, + layout::{floating::ResizeSurfaceGrab, tiling::ResizeForkGrab}, +}; - match &window.toplevel() { - Kind::Xdg(surface) => { - // If surface is maximized then unmaximize it - let current_state = surface.current_state(); - if current_state.states.contains(XdgState::Maximized) { - workspace - .floating_layer - .unmaximize_request(&mut workspace.space, window); - let new_size = surface.with_pending_state(|state| state.size); - let ratio = pos.x / output.geometry().size.w as f64; +bitflags::bitflags! { + pub struct ResizeEdge: u32 { + const TOP = 0b0001; + const BOTTOM = 0b0010; + const LEFT = 0b0100; + const RIGHT = 0b1000; - initial_window_location = new_size - .map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into()) - .unwrap_or_else(|| pos) - .to_i32_round(); - } - } - }; + const TOP_LEFT = Self::TOP.bits | Self::LEFT.bits; + const BOTTOM_LEFT = Self::BOTTOM.bits | Self::LEFT.bits; - let was_tiled = if workspace.tiling_layer.windows.contains(&window) { - workspace - .tiling_layer - .unmap_window(&mut workspace.space, &window); - true - } else { - workspace - .floating_layer - .unmap_window(&mut workspace.space, &window); - false - }; - - let workspace_handle = workspace.handle; - let workspace_is_empty = workspace.space.windows().next().is_none(); - - if workspace_is_empty { - state - .common - .shell - .workspace_state - .update() - .add_workspace_state(&workspace_handle, WState::Hidden); - } - state - .common - .shell - .toplevel_info_state - .toplevel_leave_workspace(&window, &workspace_handle); - state - .common - .shell - .toplevel_info_state - .toplevel_leave_output(&window, &output); - - let grab_state = MoveGrabState { - window: window.clone(), - was_tiled, - initial_cursor_location: pointer.current_location(), - initial_window_location, - initial_output_location: output.geometry().loc, - }; - let grab = MoveSurfaceGrab::new(start_data, window.clone(), seat); - - *seat - .user_data() - .get::() - .unwrap() - .borrow_mut() = Some(grab_state); - pointer.set_grab(state, grab, serial, Focus::Clear); - } - } - - fn drop_move(state: &mut State, seat: &Seat, output: &Output) { - if let Some(move_state) = seat - .user_data() - .get::() - .unwrap() - .borrow_mut() - .take() - { - let pointer = seat.get_pointer().unwrap(); - let window = move_state.window; - - if window.alive() { - let delta = pointer.current_location() - move_state.initial_cursor_location; - let window_location = (move_state.initial_window_location.to_f64() - + move_state.initial_output_location.to_f64() - - output.geometry().loc.to_f64() - + delta) - .to_i32_round(); - let surface = window.toplevel().wl_surface().clone(); - - let workspace_handle = state.common.shell.active_space(output).handle; - state - .common - .shell - .workspace_state - .update() - .remove_workspace_state(&workspace_handle, WState::Hidden); - state - .common - .shell - .toplevel_info_state - .toplevel_enter_workspace(&window, &workspace_handle); - state - .common - .shell - .toplevel_info_state - .toplevel_enter_output(&window, &output); - - let workspace = state.common.shell.active_space_mut(output); - if move_state.was_tiled { - let focus_stack = workspace.focus_stack(&seat); - workspace.tiling_layer.map_window( - &mut workspace.space, - window, - &seat, - focus_stack.iter(), - ); - } else { - workspace.floating_layer.map_window( - &mut workspace.space, - window, - &seat, - window_location, - ); - } - - Shell::set_focus(state, Some(&surface), &seat, None); - - for window in state.common.shell.active_space(output).space.windows() { - state.common.shell.update_reactive_popups(window); - } - } - } + const TOP_RIGHT = Self::TOP.bits | Self::RIGHT.bits; + const BOTTOM_RIGHT = Self::BOTTOM.bits | Self::RIGHT.bits; } } -pub type SeatMoveGrabState = RefCell>; - -pub struct MoveGrabState { - window: Window, - was_tiled: bool, - initial_cursor_location: Point, - initial_window_location: Point, - initial_output_location: Point, -} - -pub struct MoveGrabRenderElement { - seat_id: usize, - window: Window, - window_location: Point, -} - -impl RenderElement for MoveGrabRenderElement -where - R: Renderer + ImportAll, - ::TextureId: 'static, -{ - fn id(&self) -> usize { - self.seat_id - } - - fn location(&self, scale: impl Into>) -> Point { - (self.window_location - self.window.geometry().loc.to_f64()).to_physical(scale) - } - - fn geometry(&self, scale: impl Into>) -> Rectangle { - let scale = scale.into(); - self.window - .physical_bbox_with_popups(RenderElement::::location(self, scale), scale) - } - - fn accumulated_damage( - &self, - scale: impl Into>, - for_values: Option>, - ) -> Vec> { - let scale = scale.into(); - self.window.accumulated_damage( - RenderElement::::location(self, scale), - scale, - for_values.map(|t| (t.0, t.1)), - ) - } - - fn opaque_regions( - &self, - scale: impl Into>, - ) -> Option>> { - let scale = scale.into(); - self.window - .opaque_regions(RenderElement::::location(self, scale), scale) - } - - fn draw( - &self, - renderer: &mut R, - frame: &mut ::Frame, - scale: impl Into>, - position: Point, - damage: &[Rectangle], - log: &slog::Logger, - ) -> Result<(), ::Error> { - draw_window(renderer, frame, &self.window, scale, position, damage, log) +impl From for ResizeEdge { + #[inline] + fn from(x: xdg_toplevel::ResizeEdge) -> Self { + Self::from_bits(x.into()).unwrap() } } -impl MoveGrabState { - pub fn render(&self, seat: &Seat, output: &Output) -> Option - where - I: From, - { - let cursor_at = seat.get_pointer().unwrap().current_location(); - let delta = cursor_at - self.initial_cursor_location; - let location = - self.initial_output_location.to_f64() + self.initial_window_location.to_f64() + delta; - - let mut window_geo = self.window.bbox(); - window_geo.loc += location.to_i32_round(); - if !output.geometry().intersection(window_geo).is_some() { - return None; - } - - Some(I::from(MoveGrabRenderElement { - seat_id: seat.id(), - window: self.window.clone(), - window_location: location - output.geometry().loc.to_f64(), - })) +impl From for xdg_toplevel::ResizeEdge { + #[inline] + fn from(x: ResizeEdge) -> Self { + Self::try_from(x.bits()).unwrap() } } -pub struct MoveSurfaceGrab { - window: Window, - start_data: PointerGrabStartData, - seat: Seat, +pub enum ResizeGrab { + Floating(ResizeSurfaceGrab), + Tiling(ResizeForkGrab), } -impl PointerGrab for MoveSurfaceGrab { +impl From for ResizeGrab { + fn from(grab: ResizeSurfaceGrab) -> Self { + ResizeGrab::Floating(grab) + } +} + +impl From for ResizeGrab { + fn from(grab: ResizeForkGrab) -> Self { + ResizeGrab::Tiling(grab) + } +} + +impl PointerGrab for ResizeGrab { fn motion( &mut self, - state: &mut State, + data: &mut State, handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(WlSurface, Point)>, + focus: Option<(PointerFocusTarget, Point)>, event: &MotionEvent, ) { - // While the grab is active, no client has pointer focus - handle.motion(state, None, event); - if !self.window.alive() { - self.ungrab(state, handle, event.serial, event.time); + match self { + ResizeGrab::Floating(grab) => grab.motion(data, handle, focus, event), + ResizeGrab::Tiling(grab) => grab.motion(data, handle, focus, event), } } fn button( &mut self, - state: &mut State, + data: &mut State, handle: &mut PointerInnerHandle<'_, State>, event: &ButtonEvent, ) { - handle.button(state, event); - if handle.current_pressed().is_empty() { - self.ungrab(state, handle, event.serial, event.time); + match self { + ResizeGrab::Floating(grab) => grab.button(data, handle, event), + ResizeGrab::Tiling(grab) => grab.button(data, handle, event), } } fn axis( &mut self, - state: &mut State, + data: &mut State, handle: &mut PointerInnerHandle<'_, State>, details: AxisFrame, ) { - handle.axis(state, details); - } - - fn start_data(&self) -> &PointerGrabStartData { - &self.start_data - } -} - -impl MoveSurfaceGrab { - pub fn new( - start_data: PointerGrabStartData, - window: Window, - seat: &Seat, - ) -> MoveSurfaceGrab { - MoveSurfaceGrab { - window, - start_data, - seat: seat.clone(), + match self { + ResizeGrab::Floating(grab) => grab.axis(data, handle, details), + ResizeGrab::Tiling(grab) => grab.axis(data, handle, details), } } - fn ungrab( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - serial: Serial, - time: u32, - ) { - // No more buttons are pressed, release the grab. - let output = active_output(&self.seat, &state.common); - let seat = self.seat.clone(); - - state.common.event_loop_handle.insert_idle(move |data| { - Shell::drop_move(&mut data.state, &seat, &output); - }); - handle.unset_grab(state, serial, time); + fn start_data(&self) -> &PointerGrabStartData { + match self { + ResizeGrab::Floating(grab) => grab.start_data(), + ResizeGrab::Tiling(grab) => grab.start_data(), + } } } diff --git a/src/shell/layout/floating/grabs.rs b/src/shell/layout/floating/grabs.rs deleted file mode 100644 index f1ad8f1f..00000000 --- a/src/shell/layout/floating/grabs.rs +++ /dev/null @@ -1,346 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use crate::utils::prelude::*; -use smithay::{ - desktop::{Kind, Window}, - input::pointer::{ - AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, - PointerInnerHandle, - }, - reexports::{ - wayland_protocols::xdg::shell::server::xdg_toplevel, - wayland_server::protocol::wl_surface::WlSurface, - }, - utils::{IsAlive, Logical, Point, Serial, Size}, - wayland::{ - compositor::with_states, - shell::xdg::{SurfaceCachedState, ToplevelConfigure, XdgToplevelSurfaceRoleAttributes}, - }, -}; -use std::{cell::RefCell, convert::TryFrom, sync::Mutex}; - -bitflags::bitflags! { - struct ResizeEdge: u32 { - const NONE = 0; - const TOP = 1; - const BOTTOM = 2; - const LEFT = 4; - const TOP_LEFT = 5; - const BOTTOM_LEFT = 6; - const RIGHT = 8; - const TOP_RIGHT = 9; - const BOTTOM_RIGHT = 10; - } -} - -impl From for ResizeEdge { - #[inline] - fn from(x: xdg_toplevel::ResizeEdge) -> Self { - Self::from_bits(x.into()).unwrap() - } -} - -impl From for xdg_toplevel::ResizeEdge { - #[inline] - fn from(x: ResizeEdge) -> Self { - Self::try_from(x.bits()).unwrap() - } -} - -/// Information about the resize operation. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -struct ResizeData { - /// The edges the surface is being resized with. - edges: ResizeEdge, - /// The initial window location. - initial_window_location: Point, - /// The initial window size (geometry width and height). - initial_window_size: Size, -} - -/// State of the resize operation. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -enum ResizeState { - /// The surface is not being resized. - NotResizing, - /// The surface is currently being resized. - Resizing(ResizeData), - /// The resize has finished, and the surface needs to ack the final configure. - WaitingForFinalAck(ResizeData, Serial), - /// The resize has finished, and the surface needs to commit its final state. - WaitingForCommit(ResizeData), -} - -impl Default for ResizeState { - fn default() -> Self { - ResizeState::NotResizing - } -} - -pub struct ResizeSurfaceGrab { - start_data: PointerGrabStartData, - window: Window, - edges: ResizeEdge, - initial_window_size: Size, - last_window_size: Size, -} - -impl PointerGrab for ResizeSurfaceGrab { - fn motion( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(WlSurface, Point)>, - event: &MotionEvent, - ) { - // While the grab is active, no client has pointer focus - handle.motion(data, None, event); - - // It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early. - if !self.window.alive() { - handle.unset_grab(data, event.serial, event.time); - return; - } - - let (mut dx, mut dy) = (event.location - self.start_data.location).into(); - - let mut new_window_width = self.initial_window_size.w; - let mut new_window_height = self.initial_window_size.h; - - let left_right = ResizeEdge::LEFT | ResizeEdge::RIGHT; - let top_bottom = ResizeEdge::TOP | ResizeEdge::BOTTOM; - - if self.edges.intersects(left_right) { - if self.edges.intersects(ResizeEdge::LEFT) { - dx = -dx; - } - - new_window_width = (self.initial_window_size.w as f64 + dx) as i32; - } - - if self.edges.intersects(top_bottom) { - if self.edges.intersects(ResizeEdge::TOP) { - dy = -dy; - } - - new_window_height = (self.initial_window_size.h as f64 + dy) as i32; - } - - let (min_size, max_size) = with_states(self.window.toplevel().wl_surface(), |states| { - let data = states.cached_state.current::(); - (data.min_size, data.max_size) - }); - - let min_width = min_size.w.max(1); - let min_height = min_size.h.max(1); - let max_width = if max_size.w == 0 { - i32::max_value() - } else { - max_size.w - }; - let max_height = if max_size.h == 0 { - i32::max_value() - } else { - max_size.h - }; - - new_window_width = new_window_width.max(min_width).min(max_width); - new_window_height = new_window_height.max(min_height).min(max_height); - - self.last_window_size = (new_window_width, new_window_height).into(); - - match &self.window.toplevel() { - Kind::Xdg(xdg) => { - xdg.with_pending_state(|state| { - state.states.set(xdg_toplevel::State::Resizing); - state.size = Some(self.last_window_size); - }); - xdg.send_configure(); - } - } - } - - fn button( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &ButtonEvent, - ) { - handle.button(data, event); - if handle.current_pressed().is_empty() { - // No more buttons are pressed, release the grab. - handle.unset_grab(data, event.serial, event.time); - - // If toplevel is dead, we can't resize it, so we return early. - if !self.window.alive() { - return; - } - - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &self.window.toplevel() { - xdg.with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Resizing); - state.size = Some(self.last_window_size); - }); - xdg.send_configure(); - } - - let mut resize_state = self - .window - .user_data() - .get::>() - .unwrap() - .borrow_mut(); - if let ResizeState::Resizing(resize_data) = *resize_state { - *resize_state = ResizeState::WaitingForFinalAck(resize_data, event.serial); - } else { - panic!("invalid resize state: {:?}", resize_state); - } - } - } - - fn axis( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - details: AxisFrame, - ) { - handle.axis(data, details) - } - - fn start_data(&self) -> &PointerGrabStartData { - &self.start_data - } -} - -impl ResizeSurfaceGrab { - pub fn new( - start_data: PointerGrabStartData, - window: Window, - edges: xdg_toplevel::ResizeEdge, - initial_window_location: Point, - initial_window_size: Size, - ) -> ResizeSurfaceGrab { - let resize_state = ResizeState::Resizing(ResizeData { - edges: edges.into(), - initial_window_location, - initial_window_size, - }); - - window - .user_data() - .insert_if_missing(|| RefCell::new(ResizeState::default())); - *window - .user_data() - .get::>() - .unwrap() - .borrow_mut() = resize_state; - - ResizeSurfaceGrab { - start_data, - window, - edges: edges.into(), - initial_window_size, - last_window_size: initial_window_size, - } - } - - pub fn ack_configure(window: &Window, configure: ToplevelConfigure) { - let surface = window.toplevel().wl_surface(); - - let waiting_for_serial = - if let Some(data) = window.user_data().get::>() { - if let ResizeState::WaitingForFinalAck(_, serial) = *data.borrow() { - Some(serial) - } else { - None - } - } else { - None - }; - - if let Some(serial) = waiting_for_serial { - // When the resize grab is released the surface - // resize state will be set to WaitingForFinalAck - // and the client will receive a configure request - // without the resize state to inform the client - // resizing has finished. Here we will wait for - // the client to acknowledge the end of the - // resizing. To check if the surface was resizing - // before sending the configure we need to use - // the current state as the received acknowledge - // will no longer have the resize state set - let is_resizing = with_states(&surface, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .current - .states - .contains(xdg_toplevel::State::Resizing) - }); - - if configure.serial >= serial && is_resizing { - let mut resize_state = window - .user_data() - .get::>() - .unwrap() - .borrow_mut(); - if let ResizeState::WaitingForFinalAck(resize_data, _) = *resize_state { - *resize_state = ResizeState::WaitingForCommit(resize_data); - } else { - unreachable!() - } - } - } - } - - pub fn apply_resize_state( - window: &Window, - mut location: Point, - size: Size, - ) -> Option> { - let mut new_location = None; - - if let Some(resize_state) = window.user_data().get::>() { - let mut resize_state = resize_state.borrow_mut(); - - // If the window is being resized by top or left, its location must be adjusted - // accordingly. - match *resize_state { - ResizeState::Resizing(resize_data) - | ResizeState::WaitingForFinalAck(resize_data, _) - | ResizeState::WaitingForCommit(resize_data) => { - let ResizeData { - edges, - initial_window_location, - initial_window_size, - } = resize_data; - - if edges.intersects(ResizeEdge::TOP_LEFT) { - if edges.intersects(ResizeEdge::LEFT) { - location.x = - initial_window_location.x + (initial_window_size.w - size.w); - } - if edges.intersects(ResizeEdge::TOP) { - location.y = - initial_window_location.y + (initial_window_size.h - size.h); - } - - new_location = Some(location); - } - } - ResizeState::NotResizing => (), - } - - // Finish resizing. - if let ResizeState::WaitingForCommit(_) = *resize_state { - *resize_state = ResizeState::NotResizing; - } - } - - new_location - } -} diff --git a/src/shell/layout/floating/grabs/mod.rs b/src/shell/layout/floating/grabs/mod.rs new file mode 100644 index 00000000..65700de7 --- /dev/null +++ b/src/shell/layout/floating/grabs/mod.rs @@ -0,0 +1,5 @@ +mod moving; +mod resize; + +pub use self::moving::*; +pub use self::resize::*; diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs new file mode 100644 index 00000000..5b5ef2aa --- /dev/null +++ b/src/shell/layout/floating/grabs/moving.rs @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + backend::render::element::{AsGles2Frame, AsGlowRenderer}, + shell::{ + element::{CosmicMapped, CosmicMappedRenderElement}, + focus::target::{KeyboardFocusTarget, PointerFocusTarget}, + }, + utils::prelude::*, +}; + +use smithay::{ + backend::renderer::{ + element::{AsRenderElements, RenderElement}, + ImportAll, Renderer, + }, + desktop::space::SpaceElement, + input::{ + pointer::{ + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, + PointerGrab, PointerInnerHandle, + }, + Seat, + }, + output::Output, + utils::{IsAlive, Logical, Point, Serial}, +}; +use std::cell::RefCell; + +pub type SeatMoveGrabState = RefCell>; + +pub struct MoveGrabState { + window: CosmicMapped, + initial_cursor_location: Point, + initial_window_location: Point, +} + +impl MoveGrabState { + pub fn render(&self, seat: &Seat, output: &Output) -> Vec + where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, + I: From>, + { + let cursor_at = seat.get_pointer().unwrap().current_location(); + let delta = cursor_at - self.initial_cursor_location; + let location = self.initial_window_location.to_f64() + delta; + + let mut window_geo = self.window.geometry(); + window_geo.loc += location.to_i32_round(); + if !output.geometry().intersection(window_geo).is_some() { + return Vec::new(); + } + + let scale = output.current_scale().fractional_scale().into(); + AsRenderElements::::render_elements::( + &self.window, + (location.to_i32_round() - output.geometry().loc - self.window.geometry().loc) + .to_physical_precise_round(scale), + scale, + ) + } +} + +pub struct MoveSurfaceGrab { + window: CosmicMapped, + start_data: PointerGrabStartData, + seat: Seat, +} + +impl PointerGrab for MoveSurfaceGrab { + fn motion( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(PointerFocusTarget, Point)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(state, None, event); + if !self.window.alive() { + self.ungrab(state, handle, event.serial, event.time); + } + } + + fn button( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(state, event); + if handle.current_pressed().is_empty() { + self.ungrab(state, handle, event.serial, event.time); + } + } + + fn axis( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(state, details); + } + + fn start_data(&self) -> &PointerGrabStartData { + &self.start_data + } +} + +impl MoveSurfaceGrab { + pub fn new( + start_data: PointerGrabStartData, + window: CosmicMapped, + seat: &Seat, + initial_cursor_location: Point, + initial_window_location: Point, + ) -> MoveSurfaceGrab { + let grab_state = MoveGrabState { + window: window.clone(), + initial_cursor_location, + initial_window_location, + }; + + *seat + .user_data() + .get::() + .unwrap() + .borrow_mut() = Some(grab_state); + + MoveSurfaceGrab { + window, + start_data, + seat: seat.clone(), + } + } + + fn ungrab( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + serial: Serial, + time: u32, + ) { + // No more buttons are pressed, release the grab. + let output = self.seat.active_output(); + + if let Some(grab_state) = self + .seat + .user_data() + .get::() + .and_then(|s| s.borrow_mut().take()) + { + if grab_state.window.alive() { + let delta = handle.current_location() - grab_state.initial_cursor_location; + let window_location = (grab_state.initial_window_location.to_f64() + delta) + .to_i32_round() + - output.geometry().loc; + + let workspace_handle = state.common.shell.active_space(&output).handle; + for (window, _) in grab_state.window.windows() { + state + .common + .shell + .toplevel_info_state + .toplevel_enter_workspace(&window, &workspace_handle); + state + .common + .shell + .toplevel_info_state + .toplevel_enter_output(&window, &output); + } + + let offset = state + .common + .shell + .active_space(&output) + .floating_layer + .space + .output_geometry(&output) + .unwrap() + .loc; + state + .common + .shell + .active_space_mut(&output) + .floating_layer + .map_internal(grab_state.window, &output, Some(window_location + offset)); + } + } + + handle.unset_grab(state, serial, time); + if self.window.alive() { + Common::set_focus( + state, + Some(&KeyboardFocusTarget::from(self.window.clone())), + &self.seat, + Some(serial), + ) + } + } +} diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs new file mode 100644 index 00000000..d228d0ee --- /dev/null +++ b/src/shell/layout/floating/grabs/resize.rs @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + shell::{element::CosmicMapped, focus::target::PointerFocusTarget, grabs::ResizeEdge}, + utils::prelude::*, +}; +use smithay::{ + desktop::space::SpaceElement, + input::pointer::{ + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, + PointerInnerHandle, + }, + utils::{IsAlive, Logical, Point, Size}, +}; + +/// Information about the resize operation. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct ResizeData { + /// The edges the surface is being resized with. + edges: ResizeEdge, + /// The initial window location. + initial_window_location: Point, + /// The initial window size (geometry width and height). + initial_window_size: Size, +} + +/// State of the resize operation. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum ResizeState { + /// The surface is currently being resized. + Resizing(ResizeData), + /// The resize has finished, and the surface needs to commit its final state. + WaitingForCommit(ResizeData), +} + +pub struct ResizeSurfaceGrab { + start_data: PointerGrabStartData, + window: CosmicMapped, + edges: ResizeEdge, + initial_window_size: Size, + last_window_size: Size, +} + +impl PointerGrab for ResizeSurfaceGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(PointerFocusTarget, Point)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(data, None, event); + + // It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early. + if !self.window.alive() { + handle.unset_grab(data, event.serial, event.time); + return; + } + + let (mut dx, mut dy) = (event.location - self.start_data.location).into(); + + let mut new_window_width = self.initial_window_size.w; + let mut new_window_height = self.initial_window_size.h; + + let left_right = ResizeEdge::LEFT | ResizeEdge::RIGHT; + let top_bottom = ResizeEdge::TOP | ResizeEdge::BOTTOM; + + if self.edges.intersects(left_right) { + if self.edges.intersects(ResizeEdge::LEFT) { + dx = -dx; + } + + new_window_width = (self.initial_window_size.w as f64 + dx) as i32; + } + + if self.edges.intersects(top_bottom) { + if self.edges.intersects(ResizeEdge::TOP) { + dy = -dy; + } + + new_window_height = (self.initial_window_size.h as f64 + dy) as i32; + } + + let (min_size, max_size) = (self.window.min_size(), self.window.max_size()); + + let min_width = min_size.w.max(1); + let min_height = min_size.h.max(1); + let max_width = if max_size.w == 0 { + i32::max_value() + } else { + max_size.w + }; + let max_height = if max_size.h == 0 { + i32::max_value() + } else { + max_size.h + }; + + new_window_width = new_window_width.max(min_width).min(max_width); + new_window_height = new_window_height.max(min_height).min(max_height); + + self.last_window_size = (new_window_width, new_window_height).into(); + + self.window.set_resizing(true); + self.window.set_size(self.last_window_size); + self.window.configure(); + } + + fn button( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(data, event); + if handle.current_pressed().is_empty() { + // No more buttons are pressed, release the grab. + handle.unset_grab(data, event.serial, event.time); + + // If toplevel is dead, we can't resize it, so we return early. + if !self.window.alive() { + return; + } + + self.window.set_resizing(false); + self.window.set_size(self.last_window_size); + self.window.configure(); + + let mut resize_state = self.window.resize_state.lock().unwrap(); + if let Some(ResizeState::Resizing(resize_data)) = *resize_state { + *resize_state = Some(ResizeState::WaitingForCommit(resize_data)); + } else { + panic!("invalid resize state: {:?}", resize_state); + } + } + } + + fn axis( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(data, details) + } + + fn start_data(&self) -> &PointerGrabStartData { + &self.start_data + } +} + +impl ResizeSurfaceGrab { + pub fn new( + start_data: PointerGrabStartData, + mapped: CosmicMapped, + edges: ResizeEdge, + initial_window_location: Point, + initial_window_size: Size, + ) -> ResizeSurfaceGrab { + let resize_state = ResizeState::Resizing(ResizeData { + edges, + initial_window_location, + initial_window_size, + }); + + *mapped.resize_state.lock().unwrap() = Some(resize_state); + + ResizeSurfaceGrab { + start_data, + window: mapped, + edges, + initial_window_size, + last_window_size: initial_window_size, + } + } + + pub fn apply_resize_to_location(window: CosmicMapped, space: &mut Workspace) { + if let Some(mut location) = space.floating_layer.space.element_location(&window) { + let mut new_location = None; + + let mut resize_state = window.resize_state.lock().unwrap(); + // If the window is being resized by top or left, its location must be adjusted + // accordingly. + match *resize_state { + Some(ResizeState::Resizing(resize_data)) + | Some(ResizeState::WaitingForCommit(resize_data)) => { + let ResizeData { + edges, + initial_window_location, + initial_window_size, + } = resize_data; + + if edges.intersects(ResizeEdge::TOP_LEFT) { + let size = window.geometry().size; + if edges.intersects(ResizeEdge::LEFT) { + location.x = + initial_window_location.x + (initial_window_size.w - size.w); + } + if edges.intersects(ResizeEdge::TOP) { + location.y = + initial_window_location.y + (initial_window_size.h - size.h); + } + + new_location = Some(location); + } + } + _ => {} + }; + + // Finish resizing. + if let Some(ResizeState::WaitingForCommit(_)) = *resize_state { + if !window.is_resizing() { + *resize_state = None; + } + } + std::mem::drop(resize_state); + + if let Some(new_location) = new_location { + for (window, offset) in window.windows() { + update_reactive_popups( + &window, + new_location + offset, + space.floating_layer.space.outputs(), + ); + } + space + .floating_layer + .space + .map_element(window, new_location, false); + } + } + } +} diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 7cd99d0a..e512ef6a 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -1,99 +1,87 @@ // SPDX-License-Identifier: GPL-3.0-only use smithay::{ - desktop::{layer_map_for_output, space::RenderZindex, Kind, Space, Window}, - input::{ - pointer::{Focus, GrabStartData as PointerGrabStartData}, - Seat, - }, + backend::renderer::{element::RenderElement, ImportAll, Renderer}, + desktop::{layer_map_for_output, space::SpaceElement, Space, Window}, + input::{pointer::GrabStartData as PointerGrabStartData, Seat}, output::Output, - reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{ - ResizeEdge, State as XdgState, - }, - utils::{IsAlive, Logical, Point, Rectangle, Serial}, - wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes}, + utils::{Logical, Point, Rectangle, Serial}, }; -use std::{collections::HashSet, sync::Mutex}; +use std::collections::HashMap; -use crate::state::State; +use crate::{ + backend::render::element::{AsGles2Frame, AsGlowRenderer}, + shell::{ + element::{CosmicMapped, CosmicMappedRenderElement}, + grabs::ResizeEdge, + OutputNotMapped, + }, + state::State, + utils::prelude::*, +}; mod grabs; pub use self::grabs::*; -pub const FLOATING_INDEX: u8 = RenderZindex::Shell as u8 + 1; - -#[derive(Debug, Default)] +#[derive(Debug)] pub struct FloatingLayout { - pending_windows: Vec, - pub windows: HashSet, + pub(in crate::shell) space: Space, } -#[derive(Default)] -pub struct WindowUserDataInner { - last_geometry: Rectangle, +impl Default for FloatingLayout { + fn default() -> Self { + FloatingLayout { + space: Space::new(None), + } + } } -pub type WindowUserData = Mutex; impl FloatingLayout { pub fn new() -> FloatingLayout { Default::default() } - pub fn map_window( + pub fn map_output(&mut self, output: &Output, location: Point) { + self.space.map_output(output, location) + } + + pub fn unmap_output(&mut self, output: &Output) { + self.space.unmap_output(output); + self.refresh(); + } + + pub fn map( &mut self, - space: &mut Space, - window: Window, + mapped: impl Into, seat: &Seat, position: impl Into>>, ) { - if let Some(output) = super::output_from_seat(Some(seat), space) { - self.map_window_internal(space, window, &output, position.into()); - } else { - self.pending_windows.push(window); - } + let mapped = mapped.into(); + let output = seat.active_output(); + let position = position.into(); + + self.map_internal(mapped, &output, position) } - pub fn refresh(&mut self, space: &mut Space) { - self.pending_windows.retain(|w| w.toplevel().alive()); - if let Some(output) = super::output_from_seat(None, space) { - for window in std::mem::take(&mut self.pending_windows).into_iter() { - self.map_window_internal(space, window, &output, None); - } - } - // TODO make sure all windows are still visible on any output or move them - } - - fn map_window_internal( + pub(in crate::shell) fn map_internal( &mut self, - space: &mut Space, - window: Window, + mapped: CosmicMapped, output: &Output, position: Option>, ) { - let last_geometry = window - .user_data() - .get::() - .map(|u| u.lock().unwrap().last_geometry); - let mut win_geo = window.geometry(); + let mut win_geo = mapped.geometry(); let layers = layer_map_for_output(&output); let geometry = layers.non_exclusive_zone(); + let last_geometry = mapped.last_geometry.lock().unwrap().clone(); let mut geo_updated = false; - if let Some(size) = last_geometry.clone().map(|g| g.size) { - geo_updated = win_geo.size == size; + if let Some(size) = last_geometry.map(|g| g.size) { + geo_updated = win_geo.size != size; win_geo.size = size; } { - let (min_size, max_size) = with_states(window.toplevel().wl_surface(), |states| { - let attrs = states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap(); - (attrs.min_size, attrs.max_size) - }); + let (min_size, max_size) = (mapped.min_size(), mapped.max_size()); if win_geo.size.w > geometry.size.w / 3 * 2 { // try a more reasonable size let mut width = geometry.size.w / 3 * 2; @@ -136,124 +124,188 @@ impl FloatingLayout { .into() }); - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - xdg.with_pending_state(|state| { - state.states.unset(XdgState::TiledLeft); - state.states.unset(XdgState::TiledRight); - state.states.unset(XdgState::TiledTop); - state.states.unset(XdgState::TiledBottom); - if geo_updated { - state.size = Some(win_geo.size); - } - }); - xdg.send_configure(); + mapped.set_tiled(false); + if geo_updated { + mapped.set_size(win_geo.size); } + mapped.configure(); - space.map_window(&window, position, FLOATING_INDEX, false); - self.windows.insert(window); + self.space.map_element(mapped, position, false); } - pub fn unmap_window(&mut self, space: &mut Space, window: &Window) { + pub fn unmap(&mut self, window: &CosmicMapped) -> bool { #[allow(irrefutable_let_patterns)] - let is_maximized = match &window.toplevel() { - Kind::Xdg(surface) => { - surface.with_pending_state(|state| state.states.contains(XdgState::Maximized)) - } - }; + let is_maximized = window.is_maximized(); if !is_maximized { - if let Some(location) = space.window_location(window) { - let user_data = window.user_data(); - user_data.insert_if_missing(|| WindowUserData::default()); - user_data - .get::() - .unwrap() - .lock() - .unwrap() - .last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size); + if let Some(location) = self.space.element_location(window) { + *window.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size( + location, + window.geometry().size, + )); } } - space.unmap_window(window); - self.pending_windows.retain(|w| w != window); - self.windows.remove(window); + let was_unmaped = self.space.elements().any(|e| e == window); + self.space.unmap_elem(&window); + was_unmaped } - pub fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) { - let layers = layer_map_for_output(&output); - let geometry = layers.non_exclusive_zone(); - - if let Some(location) = space.window_location(window) { - let user_data = window.user_data(); - user_data.insert_if_missing(|| WindowUserData::default()); - user_data - .get::() - .unwrap() - .lock() - .unwrap() - .last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size); - } - - space.map_window( - &window, - (geometry.loc.x, geometry.loc.y), - FLOATING_INDEX, - true, - ); - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(surface) = &window.toplevel() { - surface.with_pending_state(|state| { - state.states.set(XdgState::Maximized); - state.size = Some(geometry.size); - }); - window.configure(); - } + pub fn element_geometry(&self, elem: &CosmicMapped) -> Option> { + self.space.element_geometry(elem) } - pub fn unmaximize_request(&mut self, space: &mut Space, window: &Window) { - let last_geometry = window - .user_data() - .get::() - .map(|u| u.lock().unwrap().last_geometry); - match window.toplevel() { - Kind::Xdg(toplevel) => { - toplevel.with_pending_state(|state| { - state.states.unset(XdgState::Maximized); - state.size = last_geometry.map(|g| g.size); - }); - toplevel.send_configure(); + pub fn maximize_request(&mut self, window: &Window) { + if let Some(mapped) = self + .space + .elements() + .find(|m| m.windows().any(|(w, _)| &w == window)) + { + if let Some(location) = self.space.element_location(mapped) { + *mapped.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size( + location, + mapped.geometry().size, + )); } } - if let Some(last_location) = last_geometry.map(|g| g.loc) { - space.map_window(&window, last_location, FLOATING_INDEX, true); + } + + pub fn unmaximize_request(&mut self, window: &Window) { + let maybe_mapped = self + .space + .elements() + .find(|m| m.windows().any(|(w, _)| &w == window)) + .cloned(); + + if let Some(mapped) = maybe_mapped { + let last_geometry = mapped.last_geometry.lock().unwrap().clone(); + mapped.set_size(last_geometry.map(|g| g.size).expect("No previous size?")); + let last_location = last_geometry.map(|g| g.loc).expect("No previous location?"); + self.space.map_element(mapped, last_location, true); } } pub fn resize_request( - state: &mut State, - window: &Window, + &mut self, + mapped: &CosmicMapped, seat: &Seat, - serial: Serial, + _serial: Serial, start_data: PointerGrabStartData, edges: ResizeEdge, - ) { - // it is so stupid, that we have to do this here. TODO: Refactor grabs - let workspace = state - .common - .shell - .space_for_window_mut(window.toplevel().wl_surface()) - .unwrap(); - let space = &mut workspace.space; + ) -> Option { + if seat.get_pointer().is_some() { + let location = self.space.element_location(&mapped).unwrap(); + let size = mapped.geometry().size; - if let Some(pointer) = seat.get_pointer() { - let location = space.window_location(&window).unwrap(); - let size = window.geometry().size; - - let grab = - grabs::ResizeSurfaceGrab::new(start_data, window.clone(), edges, location, size); - - pointer.set_grab(state, grab, serial, Focus::Clear); + Some(grabs::ResizeSurfaceGrab::new( + start_data, + mapped.clone(), + edges, + location, + size, + )) + } else { + None } } + + pub fn mapped(&self) -> impl Iterator { + self.space.elements() + } + + pub fn windows(&self) -> impl Iterator + '_ { + self.mapped().flat_map(|e| e.windows().map(|(w, _)| w)) + } + + pub fn refresh(&mut self) { + self.space.refresh(); + for element in self + .space + .elements() + .filter(|e| self.space.outputs_for_element(e).is_empty()) + .cloned() + .collect::>() + .into_iter() + { + // TODO what about windows leaving to the top with no headerbar to drag? can that happen? (Probably if the user is moving outputs down) + *element.last_geometry.lock().unwrap() = None; + let output = self.space.outputs().next().unwrap().clone(); + self.map_internal(element, &output, None); + } + } + + pub fn most_overlapped_output_for_element(&self, elem: &CosmicMapped) -> Option { + let elem_geo = self.space.element_geometry(elem)?; + + if self.space.outputs().nth(1).is_none() { + return self.space.outputs().next().cloned(); + } + + Some( + self.space + .outputs_for_element(elem) + .into_iter() + .max_by_key(|o| { + let output_geo = self.space.output_geometry(o).unwrap(); + if let Some(intersection) = output_geo.intersection(elem_geo) { + intersection.size.w * intersection.size.h + } else { + 0 + } + }) + .unwrap_or(self.space.outputs().next().unwrap().clone()), + ) + } + + pub fn merge(&mut self, other: FloatingLayout) { + let mut output_pos_map = HashMap::new(); + for output in self.space.outputs() { + output_pos_map.insert( + output.clone(), + self.space.output_geometry(output).unwrap().loc + - other + .space + .output_geometry(output) + .map(|geo| geo.loc) + .unwrap_or_else(|| (0, 0).into()), + ); + } + for element in other.space.elements() { + let mut elem_geo = other.space.element_geometry(element).unwrap(); + let output = other + .space + .outputs_for_element(element) + .into_iter() + .filter(|o| self.space.outputs().any(|o2| o == o2)) + .max_by_key(|o| { + let output_geo = other.space.output_geometry(o).unwrap(); + let intersection = output_geo.intersection(elem_geo).unwrap(); + intersection.size.w * intersection.size.h + }) + .unwrap_or(self.space.outputs().next().unwrap().clone()); + elem_geo.loc += output_pos_map + .get(&output) + .copied() + .unwrap_or_else(|| (0, 0).into()); + self.space.map_element(element.clone(), elem_geo.loc, false); + } + self.refresh(); //fixup any out of bounds elements + } + + pub fn render_output( + &self, + output: &Output, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, + { + let output_scale = output.current_scale().fractional_scale(); + let output_geo = self.space.output_geometry(output).ok_or(OutputNotMapped)?; + Ok(self + .space + .render_elements_for_region::(&output_geo, output_scale)) + } } diff --git a/src/shell/layout/mod.rs b/src/shell/layout/mod.rs index 8f002037..83083d9f 100644 --- a/src/shell/layout/mod.rs +++ b/src/shell/layout/mod.rs @@ -1,11 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{input::ActiveOutput, state::State}; use regex::RegexSet; use smithay::{ - desktop::{Space, Window}, - input::Seat, - output::Output, + desktop::Window, wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes}, }; use std::sync::Mutex; @@ -19,6 +16,16 @@ pub enum Orientation { Vertical, } +impl std::ops::Not for Orientation { + type Output = Self; + fn not(self) -> Self::Output { + match self { + Orientation::Horizontal => Orientation::Vertical, + Orientation::Vertical => Orientation::Horizontal, + } + } +} + lazy_static::lazy_static! { static ref EXCEPTIONS_APPID: RegexSet = RegexSet::new(&[ r"Authy Desktop", @@ -118,17 +125,3 @@ pub fn should_be_floating(window: &Window) -> bool { false }) } - -fn output_from_seat(seat: Option<&Seat>, space: &Space) -> Option { - seat.and_then(|seat| { - seat.user_data() - .get::() - .map(|active| active.0.borrow().clone()) - .or_else(|| { - seat.get_pointer() - .map(|ptr| space.output_under(ptr.current_location()).next().unwrap()) - .cloned() - }) - }) - .or_else(|| space.outputs().next().cloned()) -} diff --git a/src/shell/layout/tiling/grabs.rs b/src/shell/layout/tiling/grabs.rs index a12f5a8e..b23c4965 100644 --- a/src/shell/layout/tiling/grabs.rs +++ b/src/shell/layout/tiling/grabs.rs @@ -1,23 +1,52 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{shell::layout::Orientation, utils::prelude::*}; -use atomic_float::AtomicF64; +use crate::{ + shell::{focus::target::PointerFocusTarget, layout::Orientation}, + utils::prelude::*, +}; +use id_tree::NodeId; use smithay::{ input::pointer::{ AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, }, - reexports::wayland_server::protocol::wl_surface::WlSurface, - utils::{Logical, Point, Size}, + output::{Output, WeakOutput}, + utils::{Logical, Point}, }; -use std::sync::{atomic::Ordering, Arc}; + +use super::Data; pub struct ResizeForkGrab { - pub start_data: PointerGrabStartData, - pub orientation: Orientation, - pub initial_size: Size, - pub initial_ratio: f64, - pub ratio: Arc, + start_data: PointerGrabStartData, + idx: usize, + initial_size_upleft: i32, + initial_size_downright: i32, + node: NodeId, + output: WeakOutput, +} + +impl ResizeForkGrab { + pub fn new( + start_data: PointerGrabStartData, + node: NodeId, + output: &Output, + data: &Data, + idx: usize, + ) -> ResizeForkGrab { + let sizes = match data { + Data::Group { ref sizes, .. } => sizes, + _ => panic!("Resizing without a group?!?"), + }; + + ResizeForkGrab { + start_data, + idx, + initial_size_upleft: sizes.iter().take(idx + 1).sum(), + initial_size_downright: sizes.iter().skip(idx + 1).sum(), + node, + output: output.downgrade(), + } + } } impl PointerGrab for ResizeForkGrab { @@ -25,21 +54,139 @@ impl PointerGrab for ResizeForkGrab { &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(WlSurface, Point)>, + _focus: Option<(PointerFocusTarget, Point)>, event: &MotionEvent, ) { // While the grab is active, no client has pointer focus handle.motion(data, None, event); let delta = event.location - self.start_data.location; - let delta = match self.orientation { - Orientation::Vertical => delta.x / self.initial_size.w as f64, - Orientation::Horizontal => delta.y / self.initial_size.h as f64, - }; - self.ratio.store( - 0.9f64.min(0.1f64.max(self.initial_ratio + delta)), - Ordering::SeqCst, - ); + + if let Some(output) = self.output.upgrade() { + let tiling_layer = &mut data.common.shell.active_space_mut(&output).tiling_layer; + if let Some(tree) = tiling_layer.trees.get_mut(&output) { + if tree.get(&self.node).is_ok() { + let orientation = tree.get(&self.node).unwrap().data().orientation(); + let delta = match orientation { + Orientation::Vertical => delta.x, + Orientation::Horizontal => delta.y, + } + .round() as i32; + + let upleft_node_id = + match tree.children_ids(&self.node).unwrap().skip(self.idx).next() { + Some(elem) => elem, + None => { + return; + } + }; + let downright_node_id = match tree + .children_ids(&self.node) + .unwrap() + .skip(self.idx + 1) + .next() + { + Some(elem) => elem, + None => { + return; + } + }; + + let next_mapped = |mut node| loop { + if let Some(node_id) = node { + match tree.get(&node_id).unwrap().data() { + Data::Group { orientation: o, .. } if o == &orientation => { + node = tree.children_ids(&node_id).unwrap().last().cloned(); + } + _ => { + break node_id; + } + } + } else { + unreachable!() + } + }; + let upleft_mapped_id = next_mapped(Some(upleft_node_id.clone())); + let downright_mapped_id = next_mapped(Some(downright_node_id.clone())); + + let new_upleft_size = self.initial_size_upleft + delta; + let new_downright_size = self.initial_size_downright - delta; + let new_upleft_mapped_size = match orientation { + Orientation::Horizontal => { + tree.get(&upleft_mapped_id) + .unwrap() + .data() + .geometry() + .size + .h + } + Orientation::Vertical => { + tree.get(&upleft_mapped_id) + .unwrap() + .data() + .geometry() + .size + .w + } + } + delta; + let new_downright_mapped_size = match orientation { + Orientation::Horizontal => { + tree.get(&downright_mapped_id) + .unwrap() + .data() + .geometry() + .size + .h + } + Orientation::Vertical => { + tree.get(&downright_mapped_id) + .unwrap() + .data() + .geometry() + .size + .w + } + } - delta; + + if new_upleft_mapped_size > 100 && new_downright_mapped_size > 100 { + // lets update + { + let node = tree.get_mut(&self.node).unwrap(); + let data = node.data_mut(); + match data { + Data::Group { sizes, .. } => { + sizes[self.idx] = new_upleft_size; + sizes[self.idx + 1] = new_downright_size; + } + _ => unreachable!(), + }; + } + for (mapped_id, mapped_size) in &[ + (upleft_mapped_id, new_upleft_mapped_size), + (downright_mapped_id, new_downright_mapped_size), + ] { + let parent = tree.get(mapped_id).unwrap().parent().cloned().unwrap(); + if parent != self.node { + let idx = tree + .children_ids(&parent) + .unwrap() + .position(|id| id == mapped_id) + .unwrap(); + let node = tree.get_mut(&parent).unwrap(); + let data = node.data_mut(); + match data { + Data::Group { sizes, .. } => { + sizes[idx] = *mapped_size; + } + _ => unreachable!(), + }; + } + } + return tiling_layer.refresh(); + } + } + } + } } fn button( diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 5496e4e8..0ba18ad3 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1,63 +1,244 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - shell::{focus::FocusDirection, layout::Orientation}, + backend::render::element::{AsGles2Frame, AsGlowRenderer}, + shell::{ + element::{CosmicMapped, CosmicMappedRenderElement}, + focus::{ + target::{KeyboardFocusTarget, WindowGroup}, + FocusDirection, + }, + grabs::ResizeEdge, + layout::Orientation, + OutputNotMapped, + }, utils::prelude::*, + wayland::handlers::xdg_shell::popup::get_popup_toplevel, }; -use atomic_float::AtomicF64; use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; use smithay::{ - desktop::{layer_map_for_output, Kind, Space, Window}, - input::{ - pointer::{Focus, GrabStartData as PointerGrabStartData}, - Seat, + backend::renderer::{ + element::{AsRenderElements, RenderElement}, + ImportAll, Renderer, }, - reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{ - ResizeEdge, State as XdgState, - }, - utils::{IsAlive, Rectangle, Serial}, -}; -use std::{ - cell::RefCell, - collections::HashSet, - sync::{atomic::Ordering, Arc}, + desktop::{layer_map_for_output, space::SpaceElement, PopupKind, Window}, + input::{pointer::GrabStartData as PointerGrabStartData, Seat}, + output::Output, + utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial}, }; +use std::{borrow::Borrow, collections::HashMap, hash::Hash, sync::Arc}; mod grabs; pub use self::grabs::*; -#[derive(Debug)] -pub struct TilingLayout { - gaps: (i32, i32), - trees: Vec>, - pub windows: HashSet, +#[derive(Debug, Clone)] +struct OutputData { + output: Output, + location: Point, } -#[derive(Debug)] -pub enum Data { - Fork { - orientation: Orientation, - ratio: Arc, - }, - Stack { - active: usize, - len: usize, - }, - Window(Window), +impl Borrow for OutputData { + fn borrow(&self) -> &Output { + &self.output + } +} + +impl PartialEq for OutputData { + fn eq(&self, other: &Self) -> bool { + self.output == other.output + } +} + +impl Eq for OutputData {} + +impl PartialEq for OutputData { + fn eq(&self, other: &Output) -> bool { + &self.output == other + } +} + +impl Hash for OutputData { + fn hash(&self, state: &mut H) { + self.output.hash(state) + } +} + +#[derive(Debug, serde::Deserialize, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Left, + Right, + Up, + Down, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum FocusResult { + None, + Handled, + Some(KeyboardFocusTarget), } #[derive(Debug, Clone)] -pub struct WindowInfo { - node: NodeId, - output: usize, +pub struct TilingLayout { + gaps: (i32, i32), + trees: HashMap>, +} + +#[derive(Debug, Clone)] +pub enum Data { + Group { + orientation: Orientation, + sizes: Vec, + last_geometry: Rectangle, + alive: Arc<()>, + }, + Mapped { + mapped: CosmicMapped, + last_geometry: Rectangle, + }, } impl Data { - fn fork() -> Data { - Data::Fork { - orientation: Orientation::Vertical, - ratio: Arc::new(AtomicF64::new(0.5)), + fn new_group(orientation: Orientation, geo: Rectangle) -> Data { + Data::Group { + orientation, + sizes: vec![ + match orientation { + Orientation::Vertical => geo.size.w / 2, + Orientation::Horizontal => geo.size.h / 2, + }; + 2 + ], + last_geometry: geo, + alive: Arc::new(()), + } + } + + fn is_group(&self) -> bool { + matches!(self, Data::Group { .. }) + } + fn is_mapped(&self, mapped: Option<&CosmicMapped>) -> bool { + match mapped { + Some(m) => matches!(self, Data::Mapped { mapped, .. } if m == mapped), + None => matches!(self, Data::Mapped { .. }), + } + } + + fn orientation(&self) -> Orientation { + match self { + Data::Group { orientation, .. } => *orientation, + _ => panic!("Not a group"), + } + } + + fn add_window(&mut self, idx: usize) { + match self { + Data::Group { + sizes, + last_geometry, + orientation, + .. + } => { + let last_length = match orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + let equal_sizing = last_length / (sizes.len() as i32 + 1); // new window size + let remainder = last_length - equal_sizing; // size for the rest of the windowns + + for size in sizes.iter_mut() { + *size = ((*size as f64 / last_length as f64) * remainder as f64).round() as i32; + } + let used_size: i32 = sizes.iter().sum(); + let new_size = last_length - used_size; + + sizes.insert(idx, new_size); + } + Data::Mapped { .. } => panic!("Adding window to leaf?"), + } + } + + fn swap_windows(&mut self, i: usize, j: usize) { + match self { + Data::Group { sizes, .. } => { + sizes.swap(i, j); + } + Data::Mapped { .. } => panic!("Swapping windows to a leaf?"), + } + } + + fn remove_window(&mut self, idx: usize) { + match self { + Data::Group { + sizes, + last_geometry, + orientation, + .. + } => { + let last_length = match orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + let old_size = sizes.remove(idx); + for size in sizes.iter_mut() { + *size += + ((old_size as f64 / last_length as f64) * (*size as f64)).round() as i32; + } + let used_size: i32 = sizes.iter().sum(); + let overflow = last_length - used_size; + if overflow != 0 { + *sizes.last_mut().unwrap() += overflow; + } + } + Data::Mapped { .. } => panic!("Added window to leaf?"), + } + } + + fn geometry(&self) -> &Rectangle { + match self { + Data::Group { last_geometry, .. } => last_geometry, + Data::Mapped { last_geometry, .. } => last_geometry, + } + } + + fn update_geometry(&mut self, geo: Rectangle) { + match self { + Data::Group { + orientation, + sizes, + last_geometry, + .. + } => { + let previous_length = match orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + let new_length = match orientation { + Orientation::Horizontal => geo.size.h, + Orientation::Vertical => geo.size.w, + }; + + sizes.iter_mut().for_each(|len| { + *len = (((*len as f64) / (previous_length as f64)) * (new_length as f64)) + .round() as i32; + }); + let sum: i32 = sizes.iter().sum(); + if sum < new_length { + *sizes.last_mut().unwrap() += new_length - sum; + } + *last_geometry = geo; + } + Data::Mapped { last_geometry, .. } => { + *last_geometry = geo; + } + } + } + + fn len(&self) -> usize { + match self { + Data::Group { sizes, .. } => sizes.len(), + Data::Mapped { .. } => 1, } } } @@ -66,406 +247,805 @@ impl TilingLayout { pub fn new() -> TilingLayout { TilingLayout { gaps: (0, 4), - trees: Vec::new(), - windows: HashSet::new(), + trees: HashMap::new(), } } } impl TilingLayout { - pub fn map_window<'a>( - &mut self, - space: &mut Space, - window: Window, - seat: &Seat, - focus_stack: impl Iterator + 'a, - ) { - self.map_window_internal(space, &window, Some(seat), Some(focus_stack)); - self.windows.insert(window); - self.refresh(space); + pub fn map_output(&mut self, output: &Output, location: Point) { + if !self.trees.contains_key(output) { + self.trees.insert( + OutputData { + output: output.clone(), + location, + }, + Tree::new(), + ); + } else { + let tree = self.trees.remove(output).unwrap(); + self.trees.insert( + OutputData { + output: output.clone(), + location, + }, + tree, + ); + } } - pub fn move_focus<'a>( + pub fn unmap_output(&mut self, output: &Output) { + if let Some(src) = self.trees.remove(output) { + // TODO: expects last remaining output + let Some((output, dst)) = self.trees.iter_mut().next() else { return; }; + let orientation = match output.output.geometry().size { + x if x.w >= x.h => Orientation::Vertical, + _ => Orientation::Horizontal, + }; + TilingLayout::merge_trees(src, dst, orientation); + self.refresh() + } + } + + pub fn map<'a>( + &mut self, + window: CosmicMapped, + seat: &Seat, + focus_stack: impl Iterator + 'a, + ) { + let output = seat.active_output(); + window.output_enter(&output, window.bbox()); + self.map_internal(window, &output, Some(focus_stack)); + self.refresh(); + } + + fn map_internal<'a>( + &mut self, + window: impl Into, + output: &Output, + focus_stack: Option + 'a>, + ) { + let tree = self.trees.get_mut(output).expect("Output not mapped?"); + let window = window.into(); + let new_window = Node::new(Data::Mapped { + mapped: window.clone(), + last_geometry: Rectangle::from_loc_and_size((0, 0), (100, 100)), + }); + + let last_active = + focus_stack.and_then(|focus_stack| TilingLayout::last_active_window(tree, focus_stack)); + + let window_id = if let Some((_last_active_window, ref node_id)) = last_active { + let orientation = { + let window_size = tree.get(node_id).unwrap().data().geometry().size; + if window_size.w > window_size.h { + Orientation::Vertical + } else { + Orientation::Horizontal + } + }; + let new_id = tree.insert(new_window, InsertBehavior::AsRoot).unwrap(); + TilingLayout::new_group(tree, &node_id, &new_id, orientation).unwrap(); + new_id + } else { + // nothing? then we add to the root + if let Some(root_id) = tree.root_node_id().cloned() { + let orientation = { + let output_size = output.geometry().size; + if output_size.w > output_size.h { + Orientation::Vertical + } else { + Orientation::Horizontal + } + }; + let new_id = tree.insert(new_window, InsertBehavior::AsRoot).unwrap(); + TilingLayout::new_group(tree, &root_id, &new_id, orientation).unwrap(); + new_id + } else { + tree.insert(new_window, InsertBehavior::AsRoot).unwrap() + } + }; + + *window.tiling_node_id.lock().unwrap() = Some(window_id); + } + + pub fn unmap(&mut self, window: &CosmicMapped) -> Option { + let output = { + let node_id = window.tiling_node_id.lock().unwrap().clone()?; + self.trees + .iter() + .find(|(_, tree)| { + tree.get(&node_id) + .map(|node| node.data().is_mapped(Some(window))) + .unwrap_or(false) + }) + .map(|(o, _)| o.output.clone())? + }; + + self.unmap_window_internal(window); + window.output_leave(&output); + window.set_tiled(false); + self.refresh(); + Some(output) + } + + fn unmap_window_internal(&mut self, mapped: &CosmicMapped) { + if let Some(node_id) = mapped.tiling_node_id.lock().unwrap().as_ref() { + if let Some(tree) = self.trees.values_mut().find(|tree| { + tree.get(node_id) + .map(|node| node.data().is_mapped(Some(mapped))) + .unwrap_or(false) + }) { + let parent_id = tree + .get(&node_id) + .ok() + .and_then(|node| node.parent()) + .cloned(); + let position = parent_id.as_ref().and_then(|parent_id| { + tree.children_ids(&parent_id) + .unwrap() + .position(|id| id == node_id) + }); + let parent_parent_id = parent_id.as_ref().and_then(|parent_id| { + tree.get(parent_id) + .ok() + .and_then(|node| node.parent()) + .cloned() + }); + + // remove self + slog_scope::debug!("Remove window {:?}", mapped); + let _ = tree.remove_node(node_id.clone(), RemoveBehavior::DropChildren); + + // fixup parent node + match parent_id { + Some(id) => { + let position = position.unwrap(); + let group = tree.get_mut(&id).unwrap().data_mut(); + assert!(group.is_group()); + + if group.len() > 2 { + group.remove_window(position); + } else { + slog_scope::debug!("Removing Group"); + let other_child = + tree.children_ids(&id).unwrap().cloned().next().unwrap(); + let fork_pos = parent_parent_id.as_ref().and_then(|parent_id| { + tree.children_ids(parent_id).unwrap().position(|i| i == &id) + }); + let _ = tree.remove_node(id.clone(), RemoveBehavior::OrphanChildren); + tree.move_node( + &other_child, + parent_parent_id + .as_ref() + .map(|parent_id| MoveBehavior::ToParent(parent_id)) + .unwrap_or(MoveBehavior::ToRoot), + ) + .unwrap(); + if let Some(old_pos) = fork_pos { + tree.make_nth_sibling(&other_child, old_pos).unwrap(); + } + } + } + None => {} // root + } + } + } + } + + pub fn output_for_element(&self, elem: &CosmicMapped) -> Option<&Output> { + self.mapped().find_map(|(o, m, _)| (m == elem).then_some(o)) + } + + pub fn element_geometry(&self, elem: &CosmicMapped) -> Option> { + if let Some(id) = elem.tiling_node_id.lock().unwrap().as_ref() { + if let Some(output) = self.output_for_element(elem) { + let (output_data, tree) = self.trees.get_key_value(output).unwrap(); + let node = tree.get(id).ok()?; + let data = node.data(); + assert!(data.is_mapped(Some(elem))); + let mut geo = *data.geometry(); + geo.loc += output_data.location; + return Some(geo); + } + } + None + } + + pub fn move_current_window<'a>( + &mut self, + direction: Direction, + seat: &Seat, + ) -> Option { + let output = seat.active_output(); + let tree = self.trees.get_mut(&output).unwrap(); + + let node_id = match TilingLayout::currently_focused_node(tree, seat) { + Some(node_id) => node_id, + None => { + return None; + } + }; + + let mut child_id = node_id.clone(); + // Without a parent to start with, just return + let Some(og_parent) = tree.get(&node_id).unwrap().parent().cloned() else { + let data = tree.get(&node_id).unwrap().data(); + assert!(data.is_mapped(None)); + return match data { + Data::Mapped { mapped, .. } => Some(mapped.clone()), + _ => unreachable!(), + }; + }; + let og_idx = tree + .children_ids(&og_parent) + .unwrap() + .position(|id| id == &child_id) + .unwrap(); + let mut maybe_parent = Some(og_parent.clone()); + + while let Some(parent) = maybe_parent { + let parent_data = tree.get(&parent).unwrap().data(); + let orientation = parent_data.orientation(); + let len = parent_data.len(); + + // which child are we? + let idx = tree + .children_ids(&parent) + .unwrap() + .position(|id| id == &child_id) + .unwrap(); + + // if the orientation does not match, we want to create a new group with our parent. + if matches!( + (orientation, direction), + (Orientation::Horizontal, Direction::Right) + | (Orientation::Horizontal, Direction::Left) + | (Orientation::Vertical, Direction::Up) + | (Orientation::Vertical, Direction::Down) + ) { + TilingLayout::new_group( + tree, + &parent, + &node_id, + match direction { + Direction::Left | Direction::Right => Orientation::Vertical, + Direction::Up | Direction::Down => Orientation::Horizontal, + }, + ) + .unwrap(); + tree.make_nth_sibling( + &node_id, + if direction == Direction::Left || direction == Direction::Up { + 0 + } else { + 1 + }, + ) + .unwrap(); + + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .remove_window(og_idx); + self.refresh(); + return None; + } + + // now if the orientation matches + + // if we are not already in this group, we just move into it (up) + if child_id != node_id { + tree.move_node(&node_id, MoveBehavior::ToParent(&parent)) + .unwrap(); + tree.make_nth_sibling( + &node_id, + if direction == Direction::Left || direction == Direction::Up { + idx + } else { + idx + 1 + }, + ) + .unwrap(); + tree.get_mut(&parent).unwrap().data_mut().add_window(idx); + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .remove_window(og_idx); + self.refresh(); + return None; + } + + // we can maybe move inside the group, if we don't run out of elements + if let Some(next_idx) = match (orientation, direction) { + (Orientation::Horizontal, Direction::Down) + | (Orientation::Vertical, Direction::Right) + if idx < (len - 1) => + { + Some(idx + 1) + } + (Orientation::Horizontal, Direction::Up) + | (Orientation::Vertical, Direction::Left) + if idx > 0 => + { + Some(idx - 1) + } + _ => None, + } { + // if we can, we need to check the next element and move "into" it (down) + let next_child_id = tree + .children_ids(&parent) + .unwrap() + .nth(next_idx) + .unwrap() + .clone(); + if tree.get(&next_child_id).unwrap().data().is_group() { + // if it is a group, we want to move into the group + tree.move_node(&node_id, MoveBehavior::ToParent(&next_child_id)) + .unwrap(); + let group_orientation = tree.get(&next_child_id).unwrap().data().orientation(); + match (group_orientation, direction) { + (Orientation::Horizontal, Direction::Down) + | (Orientation::Vertical, Direction::Right) => { + tree.make_first_sibling(&node_id).unwrap(); + tree.get_mut(&next_child_id) + .unwrap() + .data_mut() + .add_window(0); + } + (Orientation::Horizontal, Direction::Up) + | (Orientation::Vertical, Direction::Left) => { + tree.make_last_sibling(&node_id).unwrap(); + let group = tree.get_mut(&next_child_id).unwrap().data_mut(); + group.add_window(group.len()); + } + _ => { + // we want the middle + let group_len = tree.get(&next_child_id).unwrap().data().len(); + if group_len % 2 == 0 { + tree.make_nth_sibling(&node_id, group_len / 2).unwrap(); + tree.get_mut(&next_child_id) + .unwrap() + .data_mut() + .add_window(group_len / 2); + } else { + // we move again by making a new fork + let old_id = tree + .children_ids(&next_child_id) + .unwrap() + .skip(group_len / 2) + .next() + .unwrap() + .clone(); + TilingLayout::new_group( + tree, + &old_id, + &node_id, + !group_orientation, + ) + .unwrap(); + tree.make_nth_sibling( + &node_id, + if direction == Direction::Left || direction == Direction::Up { + 0 + } else { + 1 + }, + ) + .unwrap(); + } + } + }; + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .remove_window(og_idx); + } else if len == 2 && child_id == node_id { + // if we are just us two in the group, lets swap + tree.make_nth_sibling(&node_id, next_idx).unwrap(); + // also swap sizes + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .swap_windows(idx, next_idx); + } else { + // else we make a new fork + TilingLayout::new_group(tree, &next_child_id, &node_id, orientation).unwrap(); + tree.make_nth_sibling( + &node_id, + if direction == Direction::Left || direction == Direction::Up { + 1 + } else { + 0 + }, + ) + .unwrap(); + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .remove_window(og_idx); + } + self.refresh(); + return None; + } + + // We have reached the end of our parent group, try to move out even higher. + maybe_parent = tree.get(&parent).unwrap().parent().cloned(); + child_id = parent.clone(); + } + + match tree.get(&node_id).unwrap().data() { + Data::Mapped { mapped, .. } => Some(mapped.clone()), + Data::Group { .. } => None, // TODO move groups to other screens + } + } + + pub fn next_focus<'a>( &mut self, direction: FocusDirection, seat: &Seat, - space: &mut Space, - focus_stack: impl Iterator + 'a, - ) -> Option { - let output = super::output_from_seat(Some(seat), space); - let idx = space - .outputs() - .position(|o| Some(o) == output.as_ref()) - .unwrap_or(0); - let tree = TilingLayout::active_tree(&mut self.trees, idx); - if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { - let mut node_id = last_active; - while let Some((fork, child)) = TilingLayout::find_fork(tree, node_id) { - if let &Data::Fork { - ref orientation, .. - } = tree.get(&fork).unwrap().data() - { - // found a fork - // which child are we? - let first = tree.children_ids(&fork).unwrap().next() == Some(&child); - let focus_subtree = match (first, orientation, direction) { - (true, Orientation::Horizontal, FocusDirection::Down) - | (true, Orientation::Vertical, FocusDirection::Right) => { - tree.children_ids(&fork).unwrap().skip(1).next() - } - (false, Orientation::Horizontal, FocusDirection::Up) - | (false, Orientation::Vertical, FocusDirection::Left) => { - tree.children_ids(&fork).unwrap().next() - } - _ => None, // continue iterating - }; + focus_stack: impl Iterator + 'a, + ) -> FocusResult { + let output = seat.active_output(); + let tree = self.trees.get_mut(&output).unwrap(); - if focus_subtree.is_some() { - let mut node_id = focus_subtree; - while node_id.is_some() - && !matches!( - tree.get(node_id.as_ref().unwrap()).unwrap().data(), - Data::Window(_) - ) - { - // TODO, if ndoe_id is a stack, we want to assign the active node instead of the first - node_id = tree.children_ids(node_id.as_ref().unwrap()).unwrap().next(); + // TODO: Rather use something like seat.current_keyboard_focus + // TODO https://github.com/Smithay/smithay/pull/777 + if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { + let (last_window, last_node_id) = last_active; + + // stacks may handle focus internally + if last_window.handle_focus(direction) { + return FocusResult::Handled; + } + + let mut node_id = last_node_id.clone(); + while let Some(group) = tree.get(&node_id).unwrap().parent() { + let child = node_id.clone(); + let group_data = tree.get(&group).unwrap().data(); + let main_orientation = group_data.orientation(); + assert!(group_data.is_group()); + + if direction == FocusDirection::Out { + return FocusResult::Some( + WindowGroup { + node: group.clone(), + output: output.downgrade(), + alive: match group_data { + &Data::Group { ref alive, .. } => Arc::downgrade(alive), + _ => unreachable!(), + }, } - if let Some(Data::Window(window)) = - node_id.and_then(|i| tree.get(&i).ok()).map(|n| n.data()) - { - return Some(window.clone()); + .into(), + ); + } + + // which child are we? + let idx = tree + .children_ids(&group) + .unwrap() + .position(|id| id == &child) + .unwrap(); + let len = group_data.len(); + + let focus_subtree = match (main_orientation, direction) { + (Orientation::Horizontal, FocusDirection::Down) + | (Orientation::Vertical, FocusDirection::Right) + if idx < (len - 1) => + { + tree.children_ids(&group).unwrap().skip(idx + 1).next() + } + (Orientation::Horizontal, FocusDirection::Up) + | (Orientation::Vertical, FocusDirection::Left) + if idx > 0 => + { + tree.children_ids(&group).unwrap().skip(idx - 1).next() + } + _ => None, // continue iterating + }; + + if focus_subtree.is_some() { + let mut node_id = focus_subtree; + while node_id.is_some() { + match tree.get(node_id.unwrap()).unwrap().data() { + Data::Group { orientation, .. } if orientation == &main_orientation => { + // if the group is layed out in the direction we care about, + // we can just use the first or last element (depending on the direction) + match direction { + FocusDirection::Down | FocusDirection::Right => { + node_id = tree + .children_ids(node_id.as_ref().unwrap()) + .unwrap() + .next(); + } + FocusDirection::Up | FocusDirection::Left => { + node_id = tree + .children_ids(node_id.as_ref().unwrap()) + .unwrap() + .last(); + } + _ => unreachable!(), + } + } + Data::Group { .. } => { + let center = { + let geo = tree.get(&last_node_id).unwrap().data().geometry(); + let mut point = geo.loc; + match direction { + FocusDirection::Down => { + point += Point::from((geo.size.w / 2 - 1, geo.size.h)) + } + FocusDirection::Up => point.x += geo.size.w / 2 - 1, + FocusDirection::Left => point.y += geo.size.h / 2 - 1, + FocusDirection::Right => { + point += Point::from((geo.size.w, geo.size.h / 2 - 1)) + } + _ => unreachable!(), + }; + point.to_f64() + }; + + let distance = |candidate: &&NodeId| -> f64 { + let geo = tree.get(candidate).unwrap().data().geometry(); + let mut point = geo.loc; + match direction { + FocusDirection::Up => { + point += Point::from((geo.size.w / 2, geo.size.h)) + } + FocusDirection::Down => point.x += geo.size.w, + FocusDirection::Right => point.y += geo.size.h / 2, + FocusDirection::Left => { + point += Point::from((geo.size.w, geo.size.h / 2)) + } + _ => unreachable!(), + }; + let point = point.to_f64(); + ((point.x - center.x).powi(2) + (point.y - center.y).powi(2)) + .sqrt() + }; + + node_id = tree + .children_ids(node_id.as_ref().unwrap()) + .unwrap() + .min_by(|node1, node2| { + distance(node1).abs().total_cmp(&distance(node2).abs()) + }); + } + Data::Mapped { mapped, .. } => { + return FocusResult::Some(mapped.clone().into()); + } } } + } else { + node_id = group.clone(); } - node_id = fork; } } - None + FocusResult::None } pub fn update_orientation<'a>( &mut self, - new_orientation: Orientation, + new_orientation: Option, seat: &Seat, - space: &mut Space, - focus_stack: impl Iterator + 'a, + focus_stack: impl Iterator + 'a, ) { - let output = super::output_from_seat(Some(seat), space); - let idx = space - .outputs() - .position(|o| Some(o) == output.as_ref()) - .unwrap_or(0); - let tree = TilingLayout::active_tree(&mut self.trees, idx); - if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { - if let Some((fork, _child)) = TilingLayout::find_fork(tree, last_active) { - if let &mut Data::Fork { + let output = seat.active_output(); + let tree = self.trees.get_mut(&output).unwrap(); + if let Some((_, last_active)) = TilingLayout::last_active_window(tree, focus_stack) { + if let Some(group) = tree.get(&last_active).unwrap().parent().cloned() { + if let &mut Data::Group { ref mut orientation, + ref mut sizes, + ref last_geometry, .. - } = tree.get_mut(&fork).unwrap().data_mut() + } = tree.get_mut(&group).unwrap().data_mut() { + let previous_length = match orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + let new_orientation = new_orientation.unwrap_or(!*orientation); + let new_length = match new_orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + + sizes.iter_mut().for_each(|len| { + *len = (((*len as f64) / (previous_length as f64)) * (new_length as f64)) + .round() as i32; + }); + let sum: i32 = sizes.iter().sum(); + if sum < new_length { + *sizes.last_mut().unwrap() += new_length - sum; + } + *orientation = new_orientation; } } } - self.refresh(space); + self.refresh(); } - pub fn refresh<'a>(&mut self, space: &mut Space) { - let active_outputs = space.outputs().count(); - if self.trees.len() > active_outputs { - for tree in self - .trees - .drain(active_outputs..self.trees.len()) + pub fn refresh<'a>(&mut self) { + let dead_windows = self + .mapped() + .map(|(_, w, _)| w.clone()) + .filter(|w| !w.alive()) + .collect::>(); + for dead_window in dead_windows.iter() { + self.unmap_window_internal(&dead_window); + } + // flatten trees + for tree in self.trees.values_mut() { + let root_id = match tree.root_node_id() { + Some(root) => root, + None => { + continue; + } + }; + for node_id in tree + .traverse_pre_order_ids(root_id) + .unwrap() .collect::>() .into_iter() { - if let Some(root_id) = tree.root_node_id() { - for node in tree.traverse_pre_order(root_id).unwrap() { - if let Data::Window(window) = node.data() { - self.map_window_internal( - space, - window, - None, - Option::>::None, - ); - } + let node = tree.get(&node_id).unwrap(); + let data = node.data(); + if data.is_group() && data.len() == 1 { + // RemoveBehavior::LiftChildren sadly does not what we want: lifting them into the same place. + // So we need to fix that manually.. + let child_id = tree + .children_ids(&node_id) + .unwrap() + .cloned() + .next() + .unwrap(); + let idx = node.parent().map(|parent_id| { + tree.children_ids(&parent_id) + .unwrap() + .position(|id| id == &node_id) + .unwrap() + }); + tree.remove_node(node_id, RemoveBehavior::LiftChildren) + .unwrap(); + if let Some(idx) = idx { + tree.make_nth_sibling(&child_id, idx).unwrap(); + } else { + // additionally `RemoveBehavior::LiftChildren` doesn't work, when removing the root-node, + // even with just one child. *sigh* + tree.move_node(&child_id, MoveBehavior::ToRoot).unwrap(); } } } } - - let mut changed = false; - while let Some(dead_windows) = Some(TilingLayout::update_space_positions( - &mut self.trees, - space, - self.gaps, - )) - .filter(|v| !v.is_empty()) - { - for window in dead_windows { - self.unmap_window_internal(&window); - } - changed = true; - } - if changed { - for window in &self.windows { - update_reactive_popups(space, window); - } - } - } - - pub fn unmap_window(&mut self, space: &mut Space, window: &Window) { - self.unmap_window_internal(window); - space.unmap_window(window); - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - xdg.with_pending_state(|state| { - state.states.unset(XdgState::TiledLeft); - state.states.unset(XdgState::TiledRight); - state.states.unset(XdgState::TiledTop); - state.states.unset(XdgState::TiledBottom); - }); - } - self.windows.remove(window); - self.refresh(space); + TilingLayout::update_space_positions(&mut self.trees, self.gaps); } pub fn resize_request( - state: &mut State, - window: &Window, - seat: &Seat, - serial: Serial, + &self, + mapped: &CosmicMapped, + _seat: &Seat, + _serial: Serial, start_data: PointerGrabStartData, edges: ResizeEdge, - ) { - // it is so stupid, that we have to do this here. TODO: Refactor grabs - let workspace = state - .common - .shell - .space_for_window_mut(window.toplevel().wl_surface()) - .unwrap(); - let space = &mut workspace.space; - let trees = &mut workspace.tiling_layer.trees; + ) -> Option { + let (output, mut node_id) = self.trees.iter().find_map(|(output, tree)| { + let root_id = tree.root_node_id()?; + tree.traverse_pre_order_ids(root_id) + .unwrap() + .find(|id| tree.get(id).unwrap().data().is_mapped(Some(mapped))) + .map(|id| (&output.output, id)) + })?; - if let Some(pointer) = seat.get_pointer() { - if let Some(info) = window.user_data().get::>() { - let output = info.borrow().output; - let tree = TilingLayout::active_tree(trees, output); - let mut node_id = info.borrow().node.clone(); - - while let Some((fork, child)) = TilingLayout::find_fork(tree, node_id) { - if let &Data::Fork { - ref orientation, - ref ratio, - } = tree.get(&fork).unwrap().data() - { - // found a fork - // which child are we? - let first = tree.children_ids(&fork).unwrap().next() == Some(&child); - match (first, orientation, edges) { - (true, Orientation::Horizontal, ResizeEdge::Bottom) - | (false, Orientation::Horizontal, ResizeEdge::Top) - | (true, Orientation::Vertical, ResizeEdge::Right) - | (false, Orientation::Vertical, ResizeEdge::Left) => { - let output = space.outputs().nth(output).cloned(); - if let Some(output) = output { - let grab = ResizeForkGrab { - start_data, - orientation: *orientation, - initial_ratio: ratio.load(Ordering::SeqCst), - initial_size: layer_map_for_output(&output) - .non_exclusive_zone() - .size, - ratio: ratio.clone(), - }; - - pointer.set_grab(state, grab, serial, Focus::Clear); - } - return; - } - _ => {} // continue iterating - } - } - node_id = fork; - } + let tree = self.trees.get(output).unwrap(); + while let Some(group_id) = tree.get(&node_id).unwrap().parent() { + let orientation = tree.get(group_id).unwrap().data().orientation(); + if !((orientation == Orientation::Vertical + && (edges.contains(ResizeEdge::LEFT) || edges.contains(ResizeEdge::RIGHT))) + || (orientation == Orientation::Horizontal + && (edges.contains(ResizeEdge::TOP) || edges.contains(ResizeEdge::BOTTOM)))) + { + node_id = group_id.clone(); + continue; } - } - } - fn active_tree<'a>(trees: &'a mut Vec>, output: usize) -> &'a mut Tree { - while trees.len() <= output { - trees.push(Tree::new()) + let node_idx = tree + .children_ids(group_id) + .unwrap() + .position(|id| id == &node_id) + .unwrap(); + let idx = match edges { + x if x.intersects(ResizeEdge::TOP_LEFT) => node_idx - 1, + _ => node_idx, + }; + if idx > tree.get(&group_id).unwrap().data().len() { + return None; + } + + return Some(ResizeForkGrab::new( + start_data, + group_id.clone(), + output, + tree.get(&group_id).unwrap().data(), + idx, + )); } - &mut trees[output] + + None } fn last_active_window<'a>( tree: &mut Tree, - mut focus_stack: impl Iterator, - ) -> Option { - let last_active = focus_stack - .find_map(|window| tree.root_node_id() + mut focus_stack: impl Iterator, + ) -> Option<(CosmicMapped, NodeId)> { + focus_stack + .find_map(|mapped| tree.root_node_id() .and_then(|root| tree.traverse_pre_order_ids(root).unwrap() - .find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Window(w)) if w == window)) - ) - ); - - last_active + .find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Mapped { mapped: m, .. }) if m == mapped)) + ).map(|id| (mapped.clone(), id)) + ) } - fn find_fork(tree: &mut Tree, mut node_id: NodeId) -> Option<(NodeId, NodeId)> { - while let Some(parent_id) = tree.get(&node_id).unwrap().parent().cloned() { - if let &Data::Fork { .. } = tree.get(&parent_id).unwrap().data() { - return Some((parent_id, node_id)); - } - node_id = parent_id; + fn currently_focused_node(tree: &mut Tree, seat: &Seat) -> Option { + let mut target = seat.get_keyboard().unwrap().current_focus()?; + + // if the focus is currently on a popup, treat it's toplevel as the target + if let KeyboardFocusTarget::Popup(popup) = target { + let toplevel_surface = match popup { + PopupKind::Xdg(xdg) => get_popup_toplevel(&xdg), + }?; + let root_id = tree.root_node_id()?; + let node = + tree.traverse_pre_order(root_id) + .unwrap() + .find(|node| match node.data() { + Data::Mapped { mapped, .. } => mapped + .windows() + .any(|(w, _)| w.toplevel().wl_surface() == &toplevel_surface), + _ => false, + })?; + + target = KeyboardFocusTarget::Element(match node.data() { + Data::Mapped { mapped, .. } => mapped.clone(), + _ => unreachable!(), + }); } + + match target { + KeyboardFocusTarget::Element(mapped) => { + let node_id = mapped.tiling_node_id.lock().unwrap().clone()?; + let node = tree.get(&node_id).ok()?; + let data = node.data(); + if data.is_mapped(Some(&mapped)) { + return Some(node_id); + } + } + KeyboardFocusTarget::Group(window_group) => { + if window_group.output == seat.active_output() { + let node = tree.get(&window_group.node).ok()?; + if node.data().is_group() { + return Some(window_group.node); + } + } + } + _ => {} + }; + None } - fn map_window_internal<'a>( - &mut self, - space: &mut Space, - window: &Window, - seat: Option<&Seat>, - focus_stack: Option + 'a>, - ) { - let output = super::output_from_seat(seat, space); - let idx = space - .outputs() - .position(|o| Some(o) == output.as_ref()) - .unwrap_or(0); - let tree = TilingLayout::active_tree(&mut self.trees, idx); - let new_window = Node::new(Data::Window(window.clone())); - - let last_active = focus_stack.and_then(|mut iter| - iter.find_map(|window| tree.root_node_id() - .and_then(|root| tree.traverse_pre_order_ids(root).unwrap() - .find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Window(w)) if w == window)) - ) - ) - ); - let window_id = if let Some(ref node_id) = last_active { - let parent_id = tree.get(node_id).unwrap().parent().cloned(); - if let Some(stack_id) = - parent_id.filter(|id| matches!(tree.get(id).unwrap().data(), Data::Stack { .. })) - { - // we add to the stack - let window_id = tree - .insert(new_window, InsertBehavior::UnderNode(&stack_id)) - .unwrap(); - if let Data::Stack { - ref mut len, - ref mut active, - } = tree.get_mut(&stack_id).unwrap().data_mut() - { - *active = *len; - *len += 1; - } - Ok(window_id) - } else { - // we create a new fork - TilingLayout::new_fork(tree, node_id, new_window) - } - } else { - // nothing? then we add to the root - if let Some(root_id) = tree.root_node_id().cloned() { - TilingLayout::new_fork(tree, &root_id, new_window) - } else { - tree.insert(new_window, InsertBehavior::AsRoot) - } - } - .unwrap(); - - { - let user_data = window.user_data(); - let window_info = WindowInfo { - node: window_id.clone(), - output: idx, - }; - // insert or update - if !user_data.insert_if_missing(|| RefCell::new(window_info.clone())) { - *user_data.get::>().unwrap().borrow_mut() = window_info; - } - } - } - - fn unmap_window_internal(&mut self, window: &Window) { - if let Some(info) = window.user_data().get::>() { - let output = info.borrow().output; - let tree = TilingLayout::active_tree(&mut self.trees, output); - - let node_id = info.borrow().node.clone(); - let parent_id = tree - .get(&node_id) - .ok() - .and_then(|node| node.parent()) - .cloned(); - let parent_parent_id = parent_id.as_ref().and_then(|parent_id| { - tree.get(parent_id) - .ok() - .and_then(|node| node.parent()) - .cloned() - }); - - // remove self - slog_scope::debug!("Remove window {:?}", window); - let _ = tree.remove_node(node_id.clone(), RemoveBehavior::DropChildren); - - // fixup parent node - match parent_id { - Some(id) if matches!(tree.get(&id).unwrap().data(), Data::Fork { .. }) => { - slog_scope::debug!("Removing Fork"); - let other_child = tree.children_ids(&id).unwrap().cloned().next().unwrap(); - let fork_pos = parent_parent_id.as_ref().and_then(|parent_id| { - tree.children_ids(parent_id).unwrap().position(|i| i == &id) - }); - let _ = tree.remove_node(id.clone(), RemoveBehavior::OrphanChildren); - tree.move_node( - &other_child, - parent_parent_id - .as_ref() - .map(|parent_id| MoveBehavior::ToParent(parent_id)) - .unwrap_or(MoveBehavior::ToRoot), - ) - .unwrap(); - if let Some(old_pos) = fork_pos { - tree.make_nth_sibling(&other_child, old_pos).unwrap(); - } - } - Some(id) if matches!(tree.get(&id).unwrap().data(), Data::Stack { .. }) => { - if tree.children_ids(&id).unwrap().count() == 0 { - slog_scope::debug!("Removing Stack"); - // remove stack - let _ = tree.remove_node(id.clone(), RemoveBehavior::DropChildren); - // TODO now we need to untangle the parent_parent - // So we should REFACTOR this unmap function to not only work with windows - } else { - // fixup stack values - if let Data::Stack { - ref mut active, - ref mut len, - } = tree.get_mut(&id).unwrap().data_mut() - { - *len -= 1; - *active = std::cmp::max(*active, *len - 1); - } - } - } - None => {} // root - _ => unreachable!(), - } - } - } - - fn new_fork( + fn new_group( tree: &mut Tree, old_id: &NodeId, - new: Node, + new_id: &NodeId, + orientation: Orientation, ) -> Result { - let new_fork = Node::new(Data::fork()); + let new_group = Node::new(Data::new_group( + orientation, + Rectangle::from_loc_and_size((0, 0), (100, 100)), + )); let old = tree.get(old_id)?; let parent_id = old.parent().cloned(); let pos = parent_id.as_ref().and_then(|parent_id| { @@ -474,9 +1054,9 @@ impl TilingLayout { .position(|id| id == old_id) }); - let fork_id = tree + let group_id = tree .insert( - new_fork, + new_group, if let Some(parent) = parent_id.as_ref() { InsertBehavior::UnderNode(parent) } else { @@ -485,132 +1065,225 @@ impl TilingLayout { ) .unwrap(); - tree.move_node(old_id, MoveBehavior::ToParent(&fork_id)) + tree.move_node(old_id, MoveBehavior::ToParent(&group_id)) .unwrap(); // keep position if let Some(old_pos) = pos { - tree.make_nth_sibling(&fork_id, old_pos).unwrap(); + tree.make_nth_sibling(&group_id, old_pos).unwrap(); } - tree.insert(new, InsertBehavior::UnderNode(&fork_id)) + tree.move_node(new_id, MoveBehavior::ToParent(&group_id)) + .unwrap(); + + Ok(group_id) } - fn update_space_positions( - trees: &mut Vec>, - space: &mut Space, - gaps: (i32, i32), - ) -> Vec { - let mut dead_windows = Vec::new(); + fn update_space_positions(trees: &mut HashMap>, gaps: (i32, i32)) { let (outer, inner) = gaps; - for (idx, output) in space - .outputs() - .cloned() - .enumerate() - .collect::>() - .into_iter() + for (output, tree) in trees + .iter_mut() + .map(|(output_data, tree)| (&output_data.output, tree)) { - let tree = TilingLayout::active_tree(trees, idx); if let Some(root) = tree.root_node_id() { - let mut stack = Vec::new(); + let mut geo = layer_map_for_output(&output).non_exclusive_zone(); + geo.loc.x += outer; + geo.loc.y += outer; + geo.size.w -= outer * 2; + geo.size.h -= outer * 2; + let mut stack = vec![geo]; - let mut geo = Some(layer_map_for_output(&output).non_exclusive_zone()); - // TODO saturate? minimum? - if let Some(mut geo) = geo.as_mut() { - geo.loc.x += outer; - geo.loc.y += outer; - geo.size.w -= outer * 2; - geo.size.h -= outer * 2; - } - - for node in tree.traverse_pre_order(root).unwrap() { - let geo = stack.pop().unwrap_or(geo); - match node.data() { - Data::Fork { orientation, ratio } => { - if let Some(geo) = geo { + for node_id in tree + .traverse_pre_order_ids(root) + .unwrap() + .collect::>() + .into_iter() + { + let node = tree.get_mut(&node_id).unwrap(); + if let Some(mut geo) = stack.pop() { + let data = node.data_mut(); + match data { + Data::Group { + orientation, sizes, .. + } => { match orientation { Orientation::Horizontal => { - let top_size = ( - geo.size.w, - ((geo.size.h as f64) * ratio.load(Ordering::SeqCst)) - .ceil() - as i32, - ); - stack.push(Some(Rectangle::from_loc_and_size( - (geo.loc.x, geo.loc.y + top_size.1), - (geo.size.w, geo.size.h - top_size.1), - ))); - stack.push(Some(Rectangle::from_loc_and_size( - geo.loc, top_size, - ))); + let mut previous: i32 = sizes.iter().sum(); + for size in sizes.iter().rev() { + previous -= *size; + stack.push(Rectangle::from_loc_and_size( + (geo.loc.x, geo.loc.y + previous), + (geo.size.w, *size), + )); + } } Orientation::Vertical => { - let left_size = ( - ((geo.size.w as f64) * ratio.load(Ordering::SeqCst)) - .ceil() - as i32, - geo.size.h, - ); - stack.push(Some(Rectangle::from_loc_and_size( - (geo.loc.x + left_size.0, geo.loc.y), - (geo.size.w - left_size.0, geo.size.h), - ))); - stack.push(Some(Rectangle::from_loc_and_size( - geo.loc, left_size, - ))); - } - } - } else { - stack.push(None); - stack.push(None); - } - } - Data::Stack { active, len } => { - for i in 0..*len { - if i == *active { - stack.push(geo); - } else { - stack.push(None); - } - } - } - Data::Window(window) => { - if window.alive() { - if let Some(geo) = geo { - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - if xdg.current_state().states.contains(XdgState::Fullscreen) - || xdg.with_pending_state(|pending| { - pending.states.contains(XdgState::Fullscreen) - }) - { - continue; + let mut previous: i32 = sizes.iter().sum(); + for size in sizes.iter().rev() { + previous -= *size; + stack.push(Rectangle::from_loc_and_size( + (geo.loc.x + previous, geo.loc.y), + (*size, geo.size.h), + )); } - xdg.with_pending_state(|state| { - state.size = Some( - (geo.size.w - inner * 2, geo.size.h - inner * 2) - .into(), - ); - state.states.set(XdgState::TiledLeft); - state.states.set(XdgState::TiledRight); - state.states.set(XdgState::TiledTop); - state.states.set(XdgState::TiledBottom); - }); - xdg.send_configure(); } - space.map_window( - &window, - (geo.loc.x + inner, geo.loc.y + inner), - None, - false, - ); } - } else { - dead_windows.push(window.clone()); + data.update_geometry(geo); + } + Data::Mapped { mapped, .. } => { + if !(mapped.is_fullscreen() || mapped.is_maximized()) { + mapped.set_tiled(true); + mapped.set_size( + (geo.size.w - inner * 2, geo.size.h - inner * 2).into(), + ); + mapped.configure(); + } + geo.loc += (inner, inner).into(); + data.update_geometry(geo); } } } } } } - dead_windows + } + + pub fn mapped(&self) -> impl Iterator)> { + self.trees + .iter() + .flat_map(|(output_data, tree)| { + if let Some(root) = tree.root_node_id() { + Some( + tree.traverse_pre_order(root) + .unwrap() + .filter(|node| node.data().is_mapped(None)) + .map(|node| match node.data() { + Data::Mapped { + mapped, + last_geometry, + .. + } => ( + &output_data.output, + mapped, + output_data.location + last_geometry.loc, + ), + _ => unreachable!(), + }), + ) + } else { + None + } + }) + .flatten() + } + + pub fn windows(&self) -> impl Iterator)> + '_ { + self.mapped().flat_map(|(output, mapped, loc)| { + mapped + .windows() + .map(move |(w, p)| (output.clone(), w, p + loc)) + }) + } + + pub fn merge(&mut self, other: TilingLayout) { + for (output_data, src) in other.trees { + let mut dst = self.trees.entry(output_data.clone()).or_default(); + let orientation = match output_data.output.geometry().size { + x if x.w >= x.h => Orientation::Vertical, + _ => Orientation::Horizontal, + }; + TilingLayout::merge_trees(src, &mut dst, orientation); + } + self.refresh(); + } + + fn merge_trees(src: Tree, dst: &mut Tree, orientation: Orientation) { + if let Some(dst_root_id) = dst.root_node_id().cloned() { + let mut stack = Vec::new(); + + if let Some(src_root_id) = src.root_node_id() { + let root_node = src.get(src_root_id).unwrap(); + let new_node = Node::new(root_node.data().clone()); + let new_id = dst + .insert(new_node, InsertBehavior::UnderNode(&dst_root_id)) + .unwrap(); + if let &mut Data::Mapped { ref mut mapped, .. } = + dst.get_mut(&new_id).unwrap().data_mut() + { + *mapped.tiling_node_id.lock().unwrap() = Some(new_id.clone()); + } + TilingLayout::new_group(dst, &dst_root_id, &new_id, orientation).unwrap(); + stack.push((src_root_id.clone(), new_id)); + } + + while let Some((src_id, dst_id)) = stack.pop() { + for child_id in src.children_ids(&src_id).unwrap() { + let src_node = src.get(&child_id).unwrap(); + let new_node = Node::new(src_node.data().clone()); + let new_child_id = dst + .insert(new_node, InsertBehavior::UnderNode(&dst_id)) + .unwrap(); + if let &mut Data::Mapped { ref mut mapped, .. } = + dst.get_mut(&new_child_id).unwrap().data_mut() + { + *mapped.tiling_node_id.lock().unwrap() = Some(new_child_id.clone()); + } + stack.push((child_id.clone(), new_child_id)); + } + } + } else { + *dst = src; + } + } + + pub fn render_output( + &self, + output: &Output, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, + { + let output_scale = output.current_scale().fractional_scale(); + + if !self.trees.contains_key(output) { + return Err(OutputNotMapped); + } + + Ok(self + .trees + .iter() + .flat_map(|(output_data, tree)| { + if &output_data.output != output { + return None; + } + let root = tree.root_node_id()?; + Some( + tree.traverse_pre_order(root) + .unwrap() + .filter(|node| node.data().is_mapped(None)) + .map(|node| match node.data() { + Data::Mapped { + mapped, + last_geometry, + .. + } => (mapped, last_geometry.loc), + _ => unreachable!(), + }), + ) + }) + .flatten() + .flat_map(|(mapped, loc)| { + AsRenderElements::::render_elements::>( + mapped, + loc.to_physical_precise_round(output_scale) + - mapped + .geometry() + .loc + .to_physical_precise_round(output_scale), + Scale::from(output_scale), + ) + }) + .collect::>()) } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index b4f7671f..7cad816a 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,11 +1,13 @@ -use std::{cell::Cell, mem::MaybeUninit}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, cell::RefCell}; +use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; use smithay::{ desktop::{layer_map_for_output, LayerSurface, PopupManager, Window, WindowSurfaceType}, - input::{pointer::MotionEvent, Seat}, + input::Seat, output::Output, reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}, - utils::{Logical, Point, Rectangle, SERIAL_COUNTER}, + utils::{Logical, Point, Rectangle}, wayland::{ compositor::with_states, shell::{ @@ -17,11 +19,8 @@ use smithay::{ }, }; -use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; - use crate::{ - config::{Config, WorkspaceMode as ConfigMode}, - //state::ClientState, + config::{Config, WorkspaceMode as ConfigMode, OutputConfig}, utils::prelude::*, wayland::protocols::{ toplevel_info::ToplevelInfoState, @@ -33,19 +32,23 @@ use crate::{ }, }; -pub const MAX_WORKSPACES: usize = 10; +mod element; pub mod focus; pub mod grabs; pub mod layout; mod workspace; +pub use self::element::CosmicMappedRenderElement; pub use self::workspace::*; +use self::{ + element::{CosmicMapped, CosmicWindow}, + focus::target::KeyboardFocusTarget, + layout::{floating::FloatingLayout, tiling::TilingLayout}, +}; pub struct Shell { pub popups: PopupManager, - pub spaces: [Workspace; MAX_WORKSPACES], pub outputs: Vec, - pub workspace_mode: WorkspaceMode, - pub shell_mode: ShellMode, + pub workspaces: WorkspaceMode, pub floating_default: bool, pub pending_windows: Vec<(Window, Seat)>, pub pending_layers: Vec<(LayerSurface, Output, Seat)>, @@ -58,28 +61,329 @@ pub struct Shell { pub workspace_state: WorkspaceState, } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug)] +pub struct WorkspaceSet { + active: usize, + amount: WorkspaceAmount, + group: WorkspaceGroupHandle, + idx: usize, + pub(crate) workspaces: Vec, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum WorkspaceAmount { + Dynamic, + Static(u8), +} + +fn create_workspace( + state: &mut WorkspaceUpdateGuard<'_, State>, + group_handle: &WorkspaceGroupHandle, + active: bool, +) -> Workspace { + let workspace_handle = state.create_workspace(&group_handle).unwrap(); + if active { + state.add_workspace_state(&workspace_handle, WState::Active); + } + state.set_workspace_capabilities( + &workspace_handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + Workspace::new(workspace_handle) +} + +impl WorkspaceSet { + fn new( + state: &mut WorkspaceUpdateGuard<'_, State>, + amount: WorkspaceAmount, + idx: usize, + ) -> WorkspaceSet { + let group_handle = state.create_workspace_group(); + + let workspaces = match amount { + WorkspaceAmount::Dynamic => { + let workspace = create_workspace(state, &group_handle, true); + workspace_set_idx(state, 1, idx, &workspace.handle); + state.set_workspace_capabilities( + &workspace.handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + vec![workspace] + } + WorkspaceAmount::Static(len) => (0..len) + .map(|i| { + let workspace = create_workspace(state, &group_handle, i == 0); + workspace_set_idx(state, i + 1, idx, &workspace.handle); + state.set_workspace_capabilities( + &workspace.handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + workspace + }) + .collect(), + }; + + WorkspaceSet { + active: 0, + amount, + group: group_handle, + idx, + workspaces, + } + } + + fn activate(&mut self, idx: usize, state: &mut WorkspaceUpdateGuard<'_, State>) { + if idx < self.workspaces.len() && self.active != idx { + let old_active = self.active; + state.remove_workspace_state(&self.workspaces[old_active].handle, WState::Active); + state.add_workspace_state(&self.workspaces[idx].handle, WState::Active); + self.active = idx; + } + } + + fn refresh<'a>( + &mut self, + state: &mut WorkspaceState, + toplevel_info: &mut ToplevelInfoState, + outputs: impl Iterator)>, + ) { + match self.amount { + WorkspaceAmount::Dynamic => self.ensure_last_empty(state, outputs), + WorkspaceAmount::Static(len) => { + self.ensure_static(len as usize, state, toplevel_info, outputs) + } + } + self.workspaces[self.active].refresh(); + } + + fn ensure_last_empty<'a>( + &mut self, + state: &mut WorkspaceState, + outputs: impl Iterator)>, + ) { + let mut state = state.update(); + + // add empty at the end, if necessary + if self.workspaces.last().unwrap().windows().next().is_some() { + let mut workspace = create_workspace(&mut state, &self.group, false); + state.set_workspace_capabilities( + &workspace.handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + for (output, location) in outputs { + workspace.map_output(output, location); + } + self.workspaces.push(workspace); + } + + let len = self.workspaces.len(); + let mut keep = vec![true; len]; + // remove empty workspaces in between, if they are not active + for (i, workspace) in self.workspaces.iter().enumerate() { + let has_windows = workspace.windows().next().is_some(); + + if !has_windows && i != self.active && i != len - 1 { + state.remove_workspace(workspace.handle); + keep[i] = false; + } + } + + let mut iter = keep.iter(); + self.workspaces.retain(|_| *iter.next().unwrap()); + self.active -= keep + .iter() + .take(self.active + 1) + .filter(|keep| !**keep) + .count(); + + if keep.iter().any(|val| *val == false) { + for (i, workspace) in self.workspaces.iter().enumerate() { + workspace_set_idx(&mut state, i as u8 + 1, self.idx, &workspace.handle); + } + } + } + + fn ensure_static<'a>( + &mut self, + amount: usize, + state: &mut WorkspaceState, + toplevel_info: &mut ToplevelInfoState, + outputs: impl Iterator)>, + ) { + if amount < self.workspaces.len() { + let mut state = state.update(); + // merge last ones + let overflow = self.workspaces.split_off(amount); + if self.active >= self.workspaces.len() { + self.active = self.workspaces.len() - 1; + state.add_workspace_state(&self.workspaces[self.active].handle, WState::Active); + } + let last_space = self.workspaces.last_mut().unwrap(); + + for workspace in overflow { + for element in workspace.mapped() { + // fixup toplevel state + for (toplevel, _) in element.windows() { + toplevel_info.toplevel_leave_workspace(&toplevel, &workspace.handle); + toplevel_info.toplevel_enter_workspace(&toplevel, &last_space.handle); + } + } + last_space.tiling_layer.merge(workspace.tiling_layer); + last_space.floating_layer.merge(workspace.floating_layer); + last_space + .fullscreen + .extend(workspace.fullscreen.into_iter()); + state.remove_workspace(workspace.handle); + } + + last_space.refresh(); + } else if amount > self.workspaces.len() { + let mut state = state.update(); + // add empty ones + let outputs = outputs.collect::>(); + while amount > self.workspaces.len() { + let mut workspace = create_workspace(&mut state, &self.group, false); + workspace_set_idx( + &mut state, + self.workspaces.len() as u8 + 1, + self.idx, + &workspace.handle, + ); + state.set_workspace_capabilities( + &workspace.handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + for &(output, location) in outputs.iter() { + workspace.map_output(output, location); + } + self.workspaces.push(workspace); + } + } + } + + fn update_idx(&mut self, state: &mut WorkspaceUpdateGuard<'_, State>, idx: usize) { + self.idx = idx; + for (i, workspace) in self.workspaces.iter().enumerate() { + workspace_set_idx(state, i as u8, idx, &workspace.handle); + } + } +} + +#[derive(Debug)] pub enum WorkspaceMode { - OutputBound, - Global { - active: usize, - group: WorkspaceGroupHandle, - }, + OutputBound(HashMap, WorkspaceAmount), + Global(WorkspaceSet), } -pub enum ShellMode { - Normal, - Resize, - Adjust, -} +impl WorkspaceMode { + pub fn new( + config: crate::config::WorkspaceMode, + amount: WorkspaceAmount, + state: &mut WorkspaceUpdateGuard<'_, State>, + ) -> WorkspaceMode { + match config { + crate::config::WorkspaceMode::Global => { + WorkspaceMode::Global(WorkspaceSet::new(state, amount, 0)) + } + crate::config::WorkspaceMode::OutputBound => { + WorkspaceMode::OutputBound(HashMap::new(), amount) + } + } + } -#[derive(Debug, Clone)] -pub struct OutputBoundState { - pub active: Cell, - group: Cell, -} + pub fn get(&self, num: usize, output: &Output) -> Option<&Workspace> { + match self { + WorkspaceMode::Global(set) => set.workspaces.get(num), + WorkspaceMode::OutputBound(sets, _) => { + sets.get(output).and_then(|set| set.workspaces.get(num)) + } + } + } -const UNINIT_SPACE: MaybeUninit = MaybeUninit::uninit(); + pub fn get_mut(&mut self, num: usize, output: &Output) -> Option<&mut Workspace> { + match self { + WorkspaceMode::Global(set) => set.workspaces.get_mut(num), + WorkspaceMode::OutputBound(sets, _) => sets + .get_mut(output) + .and_then(|set| set.workspaces.get_mut(num)), + } + } + + pub fn active(&self, output: &Output) -> &Workspace { + match self { + WorkspaceMode::Global(set) => &set.workspaces[set.active], + WorkspaceMode::OutputBound(sets, _) => { + let set = sets.get(output).unwrap(); + &set.workspaces[set.active] + } + } + } + + pub fn active_mut(&mut self, output: &Output) -> &mut Workspace { + match self { + WorkspaceMode::Global(set) => &mut set.workspaces[set.active], + WorkspaceMode::OutputBound(sets, _) => { + let set = sets.get_mut(output).unwrap(); + &mut set.workspaces[set.active] + } + } + } + + pub fn active_num(&self, output: &Output) -> usize { + match self { + WorkspaceMode::Global(set) => set.active, + WorkspaceMode::OutputBound(sets, _) => { + let set = sets.get(output).unwrap(); + set.active + } + } + } + + pub fn len(&self, output: &Output) -> usize { + match self { + WorkspaceMode::Global(set) => set.workspaces.len(), + WorkspaceMode::OutputBound(sets, _) => { + let set = sets.get(output).unwrap(); + set.workspaces.len() + } + } + } + + pub fn spaces(&self) -> impl Iterator { + match self { + WorkspaceMode::Global(set) => { + Box::new(set.workspaces.iter()) as Box> + } + WorkspaceMode::OutputBound(sets, _) => { + Box::new(sets.values().flat_map(|set| set.workspaces.iter())) + } + } + } + + pub fn spaces_for_output(&self, output: &Output) -> impl Iterator { + match self { + WorkspaceMode::Global(set) => { + Box::new(set.workspaces.iter()) as Box> + } + WorkspaceMode::OutputBound(sets, _) => Box::new( + sets.get(output) + .into_iter() + .flat_map(|set| set.workspaces.iter()), + ), + } + } + + pub fn spaces_mut(&mut self) -> impl Iterator { + match self { + WorkspaceMode::Global(set) => { + Box::new(set.workspaces.iter_mut()) as Box> + } + WorkspaceMode::OutputBound(sets, _) => { + Box::new(sets.values_mut().flat_map(|set| set.workspaces.iter_mut())) + } + } + } +} impl Shell { pub fn new(config: &Config, dh: &DisplayHandle) -> Self { @@ -106,32 +410,18 @@ impl Shell { |_| true, ); - let mut spaces = unsafe { - let mut spaces = [UNINIT_SPACE; MAX_WORKSPACES]; - for (idx, space) in spaces.iter_mut().enumerate() { - *space = MaybeUninit::new(Workspace::new( - idx as u8, - std::mem::zeroed(), /* Will be initialized by init_mode */ - )); - } - std::mem::transmute(spaces) - }; - let mode = init_mode( - &config.static_conf.workspace_mode, - None, - &[], - &mut workspace_state, - &mut spaces, + let mode = WorkspaceMode::new( + config.static_conf.workspace_mode, + config.static_conf.workspace_amount, + &mut workspace_state.update(), ); let floating_default = config.static_conf.floating_default; Shell { popups: PopupManager::new(None), - spaces, outputs: Vec::new(), - workspace_mode: mode, + workspaces: mode, floating_default, - shell_mode: ShellMode::Normal, pending_windows: Vec::new(), pending_layers: Vec::new(), @@ -145,325 +435,367 @@ impl Shell { } pub fn add_output(&mut self, output: &Output) { - let was_empty = self.outputs.is_empty(); + if self.outputs.contains(output) { + return; + } + self.outputs.push(output.clone()); let mut state = self.workspace_state.update(); - match self.workspace_mode { - WorkspaceMode::OutputBound => { - let idx = self - .spaces - .iter() - .position(|x| x.space.outputs().next().is_none()) - .expect("More then 10 outputs?"); - - remap_output( - output, - &mut self.spaces, - None, - idx, - Point::from((0, 0)), - &mut self.toplevel_info_state, - ); - let mut workspace = &mut self.spaces[idx]; - - let group = state.create_workspace_group(); - state.add_group_output(&group, output); - state.remove_workspace(workspace.handle); - let handle = init_workspace_handle(&mut state, &group, &mut workspace); - state.add_workspace_state(&handle, WState::Active); - - let output_state = OutputBoundState { - active: Cell::new(workspace.idx as usize), - group: Cell::new(group), - }; - - if was_empty { - for workspace in self.spaces.iter_mut().skip(1) { - init_workspace_handle(&mut state, &group, workspace); - } + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets, amount) => { + // TODO: Restore previously assigned workspaces, if possible! + if !sets.contains_key(output) { + sets.insert( + output.clone(), + WorkspaceSet::new(&mut state, *amount, sets.len()), + ); } - - if !output - .user_data() - .insert_if_missing(|| output_state.clone()) - { - let existing_state = output.user_data().get::().unwrap(); - existing_state.active.set(output_state.active.get()); - existing_state.group.set(output_state.group.get()); + for workspace in &mut sets.get_mut(output).unwrap().workspaces { + workspace.map_output(output, (0, 0).into()); } } - WorkspaceMode::Global { active, group } => { - state.add_group_output(&group, output); - - remap_output( - output, - &mut self.spaces, - None, - active, - output.current_location(), - &mut self.toplevel_info_state, - ); + WorkspaceMode::Global(set) => { + // TODO: Restore any window positions from previous outputs ??? + state.add_group_output(&set.group, output); + for workspace in &mut set.workspaces { + workspace.map_output(output, output.user_data() + .get::>() + .unwrap() + .borrow() + .position + .into() + ); + } } } } - pub fn remove_output(&mut self, output: &Output) { + pub fn remove_output(&mut self, output: &Output, seats: impl Iterator>) { + if let Some(first_output) = self.outputs.get(0) { + for seat in seats { + if &seat.active_output() == output { + seat.set_active_output(first_output); + } + } + } + + if !self.outputs.contains(output) { + return; + } + let mut state = self.workspace_state.update(); self.outputs.retain(|o| o != output); - match self.workspace_mode { - WorkspaceMode::OutputBound => { - let output_state = output.user_data().get::().unwrap(); - remap_output( - output, - &mut self.spaces, - output_state.active.get(), - None, - None, - &mut self.toplevel_info_state, - ); + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets, _) => { + if let Some(set) = sets.remove(output) { + // TODO: Heuristic which output to move to. + // It is supposed to be the *most* internal, we just pick the first one for now + // and hope enumeration order works in our favor. + if let Some(new_output) = self.outputs.get(0) { + let new_set = sets.get_mut(new_output).unwrap(); + let workspace_group = new_set.group; + for mut workspace in set.workspaces { + // update workspace protocol state + state.remove_workspace(workspace.handle); + let workspace_handle = + state.create_workspace(&workspace_group).unwrap(); + state.set_workspace_capabilities( + &workspace_handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + workspace.handle = workspace_handle; - // reassign workspaces to a different output - let new_group = self - .outputs - .iter() - .next() - .map(|o| o.user_data().get::().unwrap().group.get()); - for mut workspace in self.spaces.iter_mut() { - if state - .workspace_belongs_to_group(&output_state.group.get(), &workspace.handle) - { - state.remove_workspace(workspace.handle); - if let Some(new_group) = new_group { - init_workspace_handle(&mut state, &new_group, &mut workspace); + // update mapping + workspace.map_output(new_output, (0, 0).into()); + workspace.unmap_output(output); + workspace.refresh(); + + new_set.workspaces.push(workspace); } + state.remove_workspace_group(set.group); } + // if there is no output, we are going to quit anyway, just drop the workspace set } - - // destroy workspace group - state.remove_workspace_group(output_state.group.get()); + for (i, set) in sets.values_mut().enumerate() { + set.update_idx(&mut state, i); + } + std::mem::drop(state); + self.refresh(); // cleans up excess of workspaces and empty workspaces } - WorkspaceMode::Global { active, group } => { - state.remove_group_output(&group, output); - - remap_output( - output, - &mut self.spaces, - active, - None, - None, - &mut self.toplevel_info_state, - ); + WorkspaceMode::Global(set) => { + state.remove_group_output(&set.group, output); + for workspace in &mut set.workspaces { + workspace.unmap_output(output); + workspace.refresh(); + } } }; } pub fn refresh_outputs(&mut self) { - if let WorkspaceMode::Global { active, .. } = self.workspace_mode { - let workspace = &mut self.spaces[active]; - for output in self.outputs.iter() { - workspace - .space - .map_output(output, output.current_location()); - } - } else { - for output in self.outputs.iter() { - let active = output - .user_data() - .get::() + if let WorkspaceMode::Global(set) = &mut self.workspaces { + for workspace in &mut set.workspaces { + for output in self.outputs.iter() { + workspace.map_output(output, output.user_data() + .get::>() .unwrap() - .active - .get(); - let workspace = &mut self.spaces[active]; - workspace.space.map_output(output, (0, 0)); + .borrow() + .position + .into()); + } } } } pub fn set_mode(&mut self, mode: ConfigMode) { - match (&mut self.workspace_mode, mode) { - (WorkspaceMode::OutputBound, ConfigMode::Global) => { - let new_active = 0; - init_mode( - &mode, - Some(&WorkspaceMode::OutputBound), - &self.outputs, - &mut self.workspace_state, - &mut self.spaces, - ); - for output in &self.outputs { - let old_active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - remap_output( - output, - &mut self.spaces, - old_active, - new_active, - output.current_location(), - &mut self.toplevel_info_state, + let mut state = self.workspace_state.update(); + + match (&mut self.workspaces, mode) { + (dst @ WorkspaceMode::OutputBound(_, _), ConfigMode::Global) => { + // rustc should really be able to infer that this doesn't need an if. + let (sets, amount) = + if let &mut WorkspaceMode::OutputBound(ref mut sets, ref amount) = dst { + (sets, *amount) + } else { + unreachable!() + }; + + // in this case we have to merge our sets, preserving placing of windows as nicely as possible + let mut new_set = WorkspaceSet::new(&mut state, WorkspaceAmount::Static(0), 0); + + // lets construct an iterator of all the pairs of workspaces we have to merge + // we first split of the part of the workspaces that contain the currently active one + let mut second_half = sets + .iter_mut() + .map(|(output, set)| (output.clone(), set.workspaces.split_off(set.active))) + .collect::>(); + + let mut first_half = std::iter::repeat(()) + // we continuously pop the last elements from the first half and group them together. + .map(|_| { + sets.iter_mut() + .flat_map(|(o, w)| w.workspaces.pop().map(|w| (o.clone(), w))) + .collect::>() + }) + // we stop once there is no workspace anymore in the entire set + .filter(|vec| !vec.is_empty()) + .fuse() + .collect::>(); + // we reverse those then to get the proper order + first_half.reverse(); + + let mergers = first_half + .into_iter() + // we need to know, which is supposed to be active and we loose that info by chaining, so lets add a bool + .map(|w| (w, false)) + .chain( + (0..) + // here we continuously remove the first element + .map(|i| { + ( + second_half + .iter_mut() + .flat_map(|&mut (ref o, ref mut w)| { + if !w.is_empty() { + Some((o.clone(), w.remove(0))) + } else { + None + } + }) + .collect::>(), + i == 0, + ) + }) + .filter(|(vec, _)| !vec.is_empty()) + .fuse(), + ); + + for (i, (workspaces, active)) in mergers.into_iter().enumerate() { + // and then we can merge each vector into one and put that into our new set. + let workspace_handle = state.create_workspace(&new_set.group).unwrap(); + state.set_workspace_capabilities( + &workspace_handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + workspace_set_idx(&mut state, i as u8 + 1, 0, &workspace_handle); + + let mut new_workspace = Workspace::new(workspace_handle); + for output in self.outputs.iter() { + new_workspace.map_output(output, output.current_location()); + } + new_workspace.tiling_enabled = workspaces.iter().any(|(_, w)| w.tiling_enabled); + + for (_output, workspace) in workspaces.into_iter() { + for toplevel in workspace.windows() { + self.toplevel_info_state + .toplevel_leave_workspace(&toplevel, &workspace.handle); + self.toplevel_info_state + .toplevel_enter_workspace(&toplevel, &new_workspace.handle); + } + new_workspace.tiling_layer.merge(workspace.tiling_layer); + new_workspace.floating_layer.merge(workspace.floating_layer); + new_workspace + .fullscreen + .extend(workspace.fullscreen.into_iter()); + state.remove_workspace(workspace.handle); + } + + if active { + new_set.active = new_set.workspaces.len(); + } + new_set.workspaces.push(new_workspace); + } + + for group in sets.values().map(|set| set.group) { + state.remove_workspace_group(group); + } + + new_set.amount = amount; + *dst = WorkspaceMode::Global(new_set); + } + (dst @ WorkspaceMode::Global(_), ConfigMode::OutputBound) => { + // rustc should really be able to infer that this doesn't need an if. + let set = if let &mut WorkspaceMode::Global(ref mut set) = dst { + set + } else { + unreachable!() + }; + + // split workspaces apart, preserving window positions relative to their outputs + let mut sets = HashMap::new(); + for (i, output) in self.outputs.iter().enumerate() { + sets.insert( + output.clone(), + WorkspaceSet::new(&mut state, WorkspaceAmount::Static(0), i), ); } - } - (x @ WorkspaceMode::Global { .. }, ConfigMode::OutputBound) => { - // inits OutputBoundState if it not exists - init_mode( - &mode, - Some(x), - &self.outputs, - &mut self.workspace_state, - &mut self.spaces, - ); - if let WorkspaceMode::Global { ref active, .. } = x { - for output in &self.outputs { - let new_active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - remap_output( - output, - &mut self.spaces, - *active, - new_active, - Point::from((0, 0)), - &mut self.toplevel_info_state, + for (i, workspace) in set.workspaces.drain(..).enumerate() { + for (idx, output) in self.outputs.iter().enumerate() { + // copy over everything and then remove other outputs to preserve state + let new_set = sets.get_mut(output).unwrap(); + let new_workspace_handle = state.create_workspace(&new_set.group).unwrap(); + state.set_workspace_capabilities( + &new_workspace_handle, + [WorkspaceCapabilities::Activate].into_iter(), ); + workspace_set_idx(&mut state, i as u8 + 1, idx, &new_workspace_handle); + + let mut old_tiling_layer = workspace.tiling_layer.clone(); + let mut new_floating_layer = FloatingLayout::new(); + let mut new_tiling_layer = TilingLayout::new(); + + for element in workspace.mapped() { + for (toplevel, _) in element.windows() { + self.toplevel_info_state + .toplevel_leave_workspace(&toplevel, &workspace.handle); + } + + if workspace + .floating_layer + .most_overlapped_output_for_element(element) + .as_ref() + == Some(output) + { + if let Some(mut old_mapped_loc) = + workspace.floating_layer.space.element_location(element) + { + let old_output_geo = workspace + .floating_layer + .space + .output_geometry(output) + .unwrap(); + old_mapped_loc -= old_output_geo.loc; + new_floating_layer.map_internal( + element.clone(), + output, + Some(old_mapped_loc), + ); + } + } else { + old_tiling_layer.unmap(element); + } + } + + new_floating_layer.map_output(output, (0, 0).into()); + new_tiling_layer.map_output(output, (0, 0).into()); + new_tiling_layer.merge(old_tiling_layer); + + let mut new_workspace = Workspace { + tiling_layer: new_tiling_layer, + floating_layer: new_floating_layer, + tiling_enabled: workspace.tiling_enabled, + fullscreen: workspace + .fullscreen + .iter() + .filter(|(key, _)| *key == output) + .map(|(o, w)| (o.clone(), w.clone())) + .collect(), + ..Workspace::new(new_workspace_handle) + }; + for toplevel in new_workspace.windows() { + self.toplevel_info_state + .toplevel_enter_workspace(&toplevel, &new_workspace_handle); + } + new_workspace.refresh(); + + new_set.workspaces.push(new_workspace); + new_set.active = set.active; } + state.remove_workspace(workspace.handle); } + state.remove_workspace_group(set.group); + + for new_set in sets.values_mut() { + new_set.amount = set.amount; + } + *dst = WorkspaceMode::OutputBound(sets, set.amount); } _ => {} } + + std::mem::drop(state); + self.refresh(); // get rid of empty workspaces and enforce potential maximum } - pub fn activate( - &mut self, - seat: &Seat, - output: &Output, - idx: usize, - ) -> Option { - if idx > MAX_WORKSPACES { - return None; - } - - match self.workspace_mode { - WorkspaceMode::OutputBound => { - // if the workspace is active on a different output, move the cursor over - for output in self.outputs.iter().filter(|o| o != &output) { - if output - .user_data() - .get::() - .unwrap() - .active - .get() - == idx - { - let geometry = output.geometry(); - set_active_output(seat, output); - return Some(MotionEvent { - location: Point::::from(( - geometry.loc.x + (geometry.size.w / 2), - geometry.loc.y + (geometry.size.h / 2), - )) - .to_f64(), - serial: SERIAL_COUNTER.next_serial(), - time: 0, - }); - } - } - - // else we exchange the workspace on the current output - let output_state = output.user_data().get::().unwrap(); - let old_active = output_state.active.get(); - if idx != old_active { - let mut state = self.workspace_state.update(); - output_state.active.set(idx); - - if !state.workspace_belongs_to_group( - &output_state.group.get(), - &self.spaces[idx].handle, - ) { - state.remove_workspace(self.spaces[idx].handle); - init_workspace_handle( - &mut state, - &output_state.group.get(), - &mut self.spaces[idx], - ); - } - - state.remove_workspace_state(&self.spaces[old_active].handle, WState::Active); - state.add_workspace_state(&self.spaces[idx].handle, WState::Active); - - std::mem::drop(state); - remap_output( - output, - &mut self.spaces, - old_active, - idx, - Point::from((0, 0)), - &mut self.toplevel_info_state, - ); + pub fn activate(&mut self, output: &Output, idx: usize) -> Option> { + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets, _) => { + if let Some(set) = sets.get_mut(output) { + set.activate(idx, &mut self.workspace_state.update()); } } - WorkspaceMode::Global { ref mut active, .. } => { - let old = *active; - *active = idx; - - let mut state = self.workspace_state.update(); - for output in &self.outputs { - remap_output( - output, - &mut self.spaces, - old, - idx, - output.current_location(), - &mut self.toplevel_info_state, - ); - } - state.remove_workspace_state(&self.spaces[old].handle, WState::Active); - state.add_workspace_state(&self.spaces[idx].handle, WState::Active); + WorkspaceMode::Global(set) => { + set.activate(idx, &mut self.workspace_state.update()); } } - None + let output_geo = output.geometry(); + Some(output_geo.loc + Point::from((output_geo.size.w / 2, output_geo.size.h / 2))) } pub fn active_space(&self, output: &Output) -> &Workspace { - match &self.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &self.spaces[active] + match &self.workspaces { + WorkspaceMode::OutputBound(sets, _) => { + let set = sets.get(output).unwrap(); + &set.workspaces[set.active] } - WorkspaceMode::Global { active, .. } => &self.spaces[*active], + WorkspaceMode::Global(set) => &set.workspaces[set.active], } } pub fn active_space_mut(&mut self, output: &Output) -> &mut Workspace { - match &self.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &mut self.spaces[active] + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets, _) => { + let set = sets.get_mut(output).unwrap(); + &mut set.workspaces[set.active] } - WorkspaceMode::Global { active, .. } => &mut self.spaces[*active], + WorkspaceMode::Global(set) => &mut set.workspaces[set.active], } } - pub fn outputs_for_surface<'a>( + pub fn visible_outputs_for_surface<'a>( &'a self, surface: &'a WlSurface, ) -> impl Iterator + 'a { @@ -475,39 +807,80 @@ impl Shell { Some(output) => { Box::new(std::iter::once(output.clone())) as Box> } - None => Box::new( - self.spaces - .iter() - .filter_map(|w| { - if let Some(window) = - w.space.window_for_surface(surface, WindowSurfaceType::ALL) - { - Some(w.space.outputs_for_window(&window).into_iter()) - } else { - None - } - }) - .flatten(), - ), + None => Box::new(self.outputs().map(|o| self.active_space(o)).flat_map(|w| { + w.mapped() + .find(|e| e.has_surface(surface, WindowSurfaceType::ALL)) + .into_iter() + .flat_map(|e| w.outputs_for_element(e)) + })), } } - pub fn space_for_window(&self, surface: &WlSurface) -> Option<&Workspace> { - self.spaces.iter().find(|workspace| { - workspace - .space - .window_for_surface(surface, WindowSurfaceType::ALL) + pub fn workspaces_for_surface( + &self, + surface: &WlSurface, + ) -> impl Iterator { + match self.outputs.iter().find(|o| { + let map = layer_map_for_output(o); + map.layer_for_surface(surface, WindowSurfaceType::ALL) .is_some() - }) + }) { + Some(output) => self + .workspaces + .spaces() + .filter(move |workspace| { + workspace + .floating_layer + .space + .outputs() + .any(|o| o == output) + }) + .map(|w| (w.handle.clone(), output.clone())) + .collect::>(), + None => self + .workspaces + .spaces() + .filter_map(|w| { + if let Some(mapped) = w + .mapped() + .find(|e| e.has_surface(surface, WindowSurfaceType::ALL)) + { + let outputs = w.outputs_for_element(mapped); + Some(std::iter::repeat(w.handle.clone()).zip(outputs).fuse()) + } else { + None + } + }) + .flatten() + .collect::>(), + } + .into_iter() } - pub fn space_for_window_mut(&mut self, surface: &WlSurface) -> Option<&mut Workspace> { - self.spaces.iter_mut().find(|workspace| { - workspace - .space - .window_for_surface(surface, WindowSurfaceType::ALL) - .is_some() - }) + pub fn element_for_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> { + self.workspaces + .spaces() + .find_map(|w| w.element_for_surface(surface)) + } + + pub fn space_for(&self, mapped: &CosmicMapped) -> Option<&Workspace> { + self.workspaces + .spaces() + .find(|workspace| workspace.mapped().any(|m| m == mapped)) + } + + pub fn space_for_mut(&mut self, mapped: &CosmicMapped) -> Option<&mut Workspace> { + self.workspaces + .spaces_mut() + .find(|workspace| workspace.mapped().any(|m| m == mapped)) + } + + pub fn space_for_handle(&self, handle: &WorkspaceHandle) -> Option<&Workspace> { + self.workspaces.spaces().find(|w| &w.handle == handle) + } + + pub fn space_for_handle_mut(&mut self, handle: &WorkspaceHandle) -> Option<&mut Workspace> { + self.workspaces.spaces_mut().find(|w| &w.handle == handle) } pub fn outputs(&self) -> impl Iterator { @@ -527,66 +900,45 @@ impl Shell { .unwrap_or_else(|| Rectangle::from_loc_and_size((0, 0), (0, 0))) } - pub fn space_relative_output_geometry( + pub fn map_global_to_space( &self, global_loc: impl Into>, output: &Output, ) -> Point { - match self.workspace_mode { - WorkspaceMode::Global { .. } => global_loc.into(), - WorkspaceMode::OutputBound => { + match self.workspaces { + WorkspaceMode::Global(_) => global_loc.into(), + WorkspaceMode::OutputBound(_, _) => { let p = global_loc.into().to_f64() - output.current_location().to_f64(); (C::from_f64(p.x), C::from_f64(p.y)).into() } } } - pub fn refresh(&mut self, dh: &DisplayHandle) { + pub fn refresh(&mut self) { self.popups.cleanup(); - match &self.workspace_mode { - WorkspaceMode::OutputBound => { - for output in &self.outputs { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - let workspace = &mut self.spaces[active]; - workspace.refresh(dh); - if workspace.space.windows().next().is_none() - && !self - .workspace_state - .workspace_states(&workspace.handle) - .map(|mut i| i.any(|s| s == &WState::Hidden)) - .unwrap_or(true) - { - self.workspace_state - .update() - .add_workspace_state(&workspace.handle, WState::Hidden); - } + + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets, _) => { + for (output, set) in sets.iter_mut() { + set.refresh( + &mut self.workspace_state, + &mut self.toplevel_info_state, + std::iter::once((output, (0, 0).into())), + ); } } - WorkspaceMode::Global { active, .. } => { - let workspace = &mut self.spaces[*active]; - workspace.refresh(dh); - if workspace.space.windows().next().is_none() - && !self - .workspace_state - .workspace_states(&workspace.handle) - .map(|mut i| i.any(|s| s == &WState::Hidden)) - .unwrap_or(true) - { - self.workspace_state - .update() - .add_workspace_state(&workspace.handle, WState::Hidden); - } - } - }; + WorkspaceMode::Global(set) => set.refresh( + &mut self.workspace_state, + &mut self.toplevel_info_state, + self.outputs.iter().map(|o| (o, o.current_location())), + ), + } + for output in &self.outputs { let mut map = layer_map_for_output(output); - map.cleanup(dh); + map.cleanup(); } + self.toplevel_info_state .refresh(Some(&self.workspace_state)); } @@ -600,54 +952,34 @@ impl Shell { .position(|(w, _)| w == window) .unwrap(); let (window, seat) = state.common.shell.pending_windows.remove(pos); - let surface = window.toplevel().wl_surface().clone(); - let workspace = match &state.common.shell.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &mut state.common.shell.spaces[active] - } - WorkspaceMode::Global { active, .. } => &mut state.common.shell.spaces[*active], - }; - state - .common - .shell - .workspace_state - .update() - .remove_workspace_state(&workspace.handle, WState::Hidden); - state - .common - .shell - .toplevel_info_state - .toplevel_enter_workspace(&window, &workspace.handle); + let workspace = state.common.shell.workspaces.active_mut(output); state .common .shell .toplevel_info_state .toplevel_enter_output(&window, &output); + state + .common + .shell + .toplevel_info_state + .toplevel_enter_workspace(&window, &workspace.handle); + + let mapped = CosmicMapped::from(CosmicWindow::from(window.clone())); if layout::should_be_floating(&window) || state.common.shell.floating_default { - workspace - .floating_layer - .map_window(&mut workspace.space, window, &seat, None); + workspace.floating_layer.map(mapped.clone(), &seat, None); } else { - let focus_stack = workspace.focus_stack(&seat); - workspace.tiling_layer.map_window( - &mut workspace.space, - window, - &seat, - focus_stack.iter(), - ); + let focus_stack = workspace.focus_stack.get(&seat); + workspace + .tiling_layer + .map(mapped.clone(), &seat, focus_stack.iter()); } - Shell::set_focus(state, Some(&surface), &seat, None); + Shell::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); - for window in state.common.shell.active_space(output).space.windows() { - state.common.shell.update_reactive_popups(window); + let active_space = state.common.shell.active_space(output); + for mapped in active_space.mapped() { + state.common.shell.update_reactive_popups(mapped); } } @@ -661,9 +993,8 @@ impl Shell { .unwrap(); let (layer_surface, output, seat) = state.common.shell.pending_layers.remove(pos); - let surface = layer_surface.wl_surface(); let wants_focus = { - with_states(surface, |states| { + with_states(layer_surface.wl_surface(), |states| { let state = states.cached_state.current::(); matches!(state.layer, Layer::Top | Layer::Overlay) && state.keyboard_interactivity != KeyboardInteractivity::None @@ -671,194 +1002,105 @@ impl Shell { }; let mut map = layer_map_for_output(&output); - map.map_layer(&state.common.display_handle, &layer_surface) - .unwrap(); + map.map_layer(&layer_surface).unwrap(); if wants_focus { - Shell::set_focus(state, Some(surface), &seat, None) + Shell::set_focus(state, Some(&layer_surface.into()), &seat, None) } } - pub fn move_current_window(&mut self, seat: &Seat, output: &Output, idx: usize) { - if idx > MAX_WORKSPACES { - return; + pub fn move_current_window( + state: &mut State, + seat: &Seat, + from_output: &Output, + to: (&Output, Option), + ) -> Option> { + let (to_output, to_idx) = to; + let to_idx = to_idx.unwrap_or(state.common.shell.workspaces.active_num(to_output)); + + if from_output == to_output + && to_idx == state.common.shell.workspaces.active_num(from_output) + { + return None; } - let workspace = match &self.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &mut self.spaces[active] - } - WorkspaceMode::Global { active, .. } => &mut self.spaces[*active], - }; - if idx == workspace.idx as usize { - return; + let from_workspace = state.common.shell.workspaces.active_mut(from_output); + let maybe_window = from_workspace.focus_stack.get(seat).last().cloned(); + + let Some(mapped) = maybe_window else { return None; }; + let Some(window_state) = from_workspace.unmap(&mapped) else { return None; }; + + for (toplevel, _) in mapped.windows() { + state + .common + .shell + .toplevel_info_state + .toplevel_leave_workspace(&toplevel, &from_workspace.handle); + } + let elements = from_workspace.mapped().cloned().collect::>(); + std::mem::drop(from_workspace); + for mapped in elements.into_iter() { + state.common.shell.update_reactive_popups(&mapped); } - let maybe_window = workspace.focus_stack(seat).last(); - if let Some(window) = maybe_window { - let mut workspace_state = self.workspace_state.update(); - workspace - .floating_layer - .unmap_window(&mut workspace.space, &window); - workspace + seat.set_active_output(&to_output); + let new_pos = state.common.shell.activate(to_output, to_idx); + + let to_workspace = state + .common + .shell + .workspaces + .get_mut(to_idx, to_output) + .unwrap(); // checked above + let focus_stack = to_workspace.focus_stack.get(&seat); + if window_state == ManagedState::Floating { + to_workspace.floating_layer.map(mapped.clone(), &seat, None); + } else { + to_workspace .tiling_layer - .unmap_window(&mut workspace.space, &window); - self.toplevel_info_state - .toplevel_leave_workspace(&window, &workspace.handle); - if workspace.space.windows().next().is_none() { - workspace_state.add_workspace_state(&workspace.handle, WState::Hidden); - } - - let new_workspace = &mut self.spaces[idx]; - workspace_state.remove_workspace_state(&new_workspace.handle, WState::Hidden); - self.toplevel_info_state - .toplevel_enter_workspace(&window, &new_workspace.handle); - let focus_stack = new_workspace.focus_stack(&seat); - if layout::should_be_floating(&window) { - new_workspace.floating_layer.map_window( - &mut new_workspace.space, - window, - &seat, - None, - ); - } else { - new_workspace.tiling_layer.map_window( - &mut new_workspace.space, - window, - &seat, - focus_stack.iter(), - ); - } + .map(mapped.clone(), &seat, focus_stack.iter()); + } + for (toplevel, _) in mapped.windows() { + state + .common + .shell + .toplevel_info_state + .toplevel_enter_workspace(&toplevel, &to_workspace.handle); + } + for mapped in to_workspace + .mapped() + .cloned() + .collect::>() + .into_iter() + { + state.common.shell.update_reactive_popups(&mapped); } - for window in self.active_space(output).space.windows() { - self.update_reactive_popups(window); - } - for window in self.spaces[idx].space.windows() { - self.update_reactive_popups(window); + Common::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); + new_pos + } + + pub fn update_reactive_popups(&self, mapped: &CosmicMapped) { + if let Some(workspace) = self.space_for(mapped) { + let element_loc = workspace.element_geometry(mapped).unwrap().loc; + for (toplevel, offset) in mapped.windows() { + let window_geo_offset = toplevel.geometry().loc; + update_reactive_popups( + &toplevel, + element_loc + offset + window_geo_offset, + self.outputs.iter(), + ); + } } } } -fn init_mode( - config_mode: &ConfigMode, - old_mode: Option<&WorkspaceMode>, - outputs: &[Output], - state: &mut WorkspaceState, - workspaces: &mut [Workspace; MAX_WORKSPACES], -) -> WorkspaceMode { - let mut state = state.update(); - - // cleanup - for workspace in workspaces.iter_mut() { - state.remove_workspace(workspace.handle); - } - - match old_mode { - Some(WorkspaceMode::Global { group, .. }) => state.remove_workspace_group(group.clone()), - Some(WorkspaceMode::OutputBound) => { - for output in outputs { - if let Some(old_state) = output.user_data().get::() { - state.remove_workspace_group(old_state.group.get()); - } - } - } - _ => {} - }; - - // set the new state (especially cosmic_workspace state) - match config_mode { - ConfigMode::Global => { - let group = state.create_workspace_group(); - for output in outputs { - state.add_group_output(&group, output) - } - for workspace in workspaces.iter_mut() { - init_workspace_handle(&mut state, &group, workspace); - } - state.add_workspace_state(&workspaces[0].handle, WState::Active); - state.remove_workspace_state(&workspaces[0].handle, WState::Hidden); - WorkspaceMode::Global { active: 0, group } - } - ConfigMode::OutputBound => { - for (i, output) in outputs.iter().enumerate() { - let group = state.create_workspace_group(); - state.add_group_output(&group, output); - - let workspace = workspaces.get_mut(i).expect("More then ten workspaces?!?"); - let handle = init_workspace_handle(&mut state, &group, workspace); - state.add_workspace_state(&handle, WState::Active); - state.remove_workspace_state(&handle, WState::Hidden); - - let output_state = OutputBoundState { - active: Cell::new(i), - group: Cell::new(group), - }; - let map = output.user_data(); - if !map.insert_if_missing(|| output_state) { - let old_state = map.get::().unwrap(); - old_state.active.set(i); - old_state.group.set(group); - } - } - if !outputs.is_empty() { - for workspace in workspaces.iter_mut().skip(outputs.iter().count()) { - let group = outputs[0] - .user_data() - .get::() - .unwrap() - .group - .get(); - init_workspace_handle(&mut state, &group, workspace); - } - } - WorkspaceMode::OutputBound - } - } -} - -fn init_workspace_handle<'a>( +fn workspace_set_idx<'a>( state: &mut WorkspaceUpdateGuard<'a, State>, - group: &WorkspaceGroupHandle, - workspace: &mut Workspace, -) -> WorkspaceHandle { - let handle = state.create_workspace(&group).unwrap(); - state.set_workspace_capabilities(&handle, [WorkspaceCapabilities::Activate].into_iter()); - state.set_workspace_name(&handle, format!("{}", workspace.idx + 1)); - state.set_workspace_coordinates(&handle, [Some(workspace.idx as u32), None, None]); - if workspace.space.windows().next().is_none() { - state.add_workspace_state(&handle, WState::Hidden); - } - workspace.handle = handle.clone(); - handle -} - -fn remap_output( - output: &Output, - spaces: &mut [Workspace], - old: impl Into>, - new: impl Into>, - pos: impl Into>>, - info_state: &mut ToplevelInfoState, + idx: u8, + output_pos: usize, + handle: &WorkspaceHandle, ) { - if let Some(old) = old.into() { - let old_space = &mut spaces[old].space; - old_space.unmap_output(output); - for window in old_space.windows() { - info_state.toplevel_leave_output(window, output); - } - } - if let Some(new) = new.into() { - let new_space = &mut spaces[new].space; - new_space.map_output(output, pos.into().expect("new requires pos")); - for window in new_space.windows() { - info_state.toplevel_enter_output(window, output); - } - } + state.set_workspace_name(&handle, format!("{}", idx)); + state.set_workspace_coordinates(&handle, [Some(idx as u32), Some(output_pos as u32), None]); } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 0fbe52d7..b8b610ca 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,109 +1,221 @@ use crate::{ - shell::layout::{floating::FloatingLayout, tiling::TilingLayout}, + backend::render::element::{AsGles2Frame, AsGlowRenderer}, + shell::layout::{ + floating::{FloatingLayout, MoveSurfaceGrab}, + tiling::TilingLayout, + }, state::State, - wayland::protocols::workspace::WorkspaceHandle, + utils::prelude::*, + wayland::{ + handlers::screencopy::DropableSession, + protocols::{ + screencopy::{BufferParams, Session as ScreencopySession}, + workspace::WorkspaceHandle, + }, + }, }; +use indexmap::IndexSet; use smithay::{ - desktop::{Kind, Space, Window, WindowSurfaceType}, + backend::renderer::{ + element::{surface::WaylandSurfaceRenderElement, AsRenderElements, Element, RenderElement}, + ImportAll, Renderer, + }, + desktop::{layer_map_for_output, space::SpaceElement, Kind, LayerSurface, Window}, input::{pointer::GrabStartData as PointerGrabStartData, Seat}, output::Output, reexports::{ wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}, - wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}, + wayland_server::protocol::wl_surface::WlSurface, }, - utils::{IsAlive, Serial}, + utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial}, + wayland::shell::wlr_layer::Layer, }; use std::collections::HashMap; +use super::{ + element::CosmicMapped, + focus::{FocusStack, FocusStackMut}, + grabs::ResizeGrab, + CosmicMappedRenderElement, +}; + +#[derive(Debug)] pub struct Workspace { - pub idx: u8, - pub space: Space, pub tiling_layer: TilingLayout, pub floating_layer: FloatingLayout, - tiling_enabled: bool, - pub fullscreen: HashMap, + pub tiling_enabled: bool, + pub fullscreen: HashMap, pub handle: WorkspaceHandle, + pub focus_stack: FocusStacks, + pub pending_buffers: Vec<(ScreencopySession, BufferParams)>, + pub screencopy_sessions: Vec, +} + +#[derive(Debug, Default)] +pub struct FocusStacks(HashMap, IndexSet>); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ManagedState { + Tiling, + Floating, } impl Workspace { - pub fn new(idx: u8, handle: WorkspaceHandle) -> Workspace { + pub fn new(handle: WorkspaceHandle) -> Workspace { Workspace { - idx, - space: Space::new(None), tiling_layer: TilingLayout::new(), floating_layer: FloatingLayout::new(), tiling_enabled: true, fullscreen: HashMap::new(), handle, + focus_stack: FocusStacks::default(), + pending_buffers: Vec::new(), + screencopy_sessions: Vec::new(), } } - pub fn refresh(&mut self, dh: &DisplayHandle) { - let outputs = self.space.outputs().collect::>(); - let dead_output_windows = self - .fullscreen - .iter() - .filter(|(name, _)| !outputs.iter().any(|o| o.name() == **name)) - .map(|(_, w)| w) - .cloned() - .collect::>(); - for window in dead_output_windows { - self.unfullscreen_request(&window); - } + pub fn refresh(&mut self) { self.fullscreen.retain(|_, w| w.alive()); - self.floating_layer.refresh(&mut self.space); - self.tiling_layer.refresh(&mut self.space); - self.space.refresh(dh); + self.floating_layer.refresh(); + self.tiling_layer.refresh(); + } + + pub fn commit(&mut self, surface: &WlSurface) { + if let Some(mapped) = self.element_for_surface(surface) { + mapped + .windows() + .find(|(w, _)| w.toplevel().wl_surface() == surface) + .unwrap() + .0 + .on_commit(); + } + } + + pub fn map_output(&mut self, output: &Output, position: Point) { + self.tiling_layer.map_output(output, position); + self.floating_layer.map_output(output, position); + } + + pub fn unmap_output(&mut self, output: &Output) { + if let Some(dead_output_window) = self.fullscreen.remove(output) { + self.unfullscreen_request(&dead_output_window); + } + self.tiling_layer.unmap_output(output); + self.floating_layer.unmap_output(output); + self.refresh(); + } + + pub fn unmap(&mut self, mapped: &CosmicMapped) -> Option { + let was_floating = self.floating_layer.unmap(&mapped); + let was_tiling = self.tiling_layer.unmap(&mapped).is_some(); + if was_floating || was_tiling { + assert!(was_floating != was_tiling); + } + self.focus_stack + .0 + .values_mut() + .for_each(|set| set.retain(|m| m != mapped)); + if was_floating { + Some(ManagedState::Floating) + } else if was_tiling { + Some(ManagedState::Tiling) + } else { + None + } + } + + pub fn element_for_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> { + self.floating_layer + .mapped() + .chain(self.tiling_layer.mapped().map(|(_, w, _)| w)) + .find(|e| { + e.windows() + .any(|(w, _)| w.toplevel().wl_surface() == surface) + }) + } + + pub fn outputs_for_element(&self, elem: &CosmicMapped) -> impl Iterator { + self.floating_layer + .space + .outputs_for_element(elem) + .into_iter() + .chain(self.tiling_layer.output_for_element(elem).cloned()) + } + + pub fn output_under(&self, point: Point) -> Option<&Output> { + let space = &self.floating_layer.space; + space.outputs().find(|o| { + let internal_output_geo = space.output_geometry(o).unwrap(); + let external_output_geo = o.geometry(); + internal_output_geo.contains(point - external_output_geo.loc + internal_output_geo.loc) + }) + } + + pub fn element_under( + &self, + location: Point, + ) -> Option<(&CosmicMapped, Point)> { + self.floating_layer + .space + .element_under(location) + .or_else(|| { + self.tiling_layer.mapped().find_map(|(_, mapped, loc)| { + let test_point = location - loc.to_f64() + mapped.geometry().loc.to_f64(); + mapped + .is_in_input_region(&test_point) + .then_some((mapped, loc - mapped.geometry().loc)) + }) + }) + } + + pub fn element_geometry(&self, elem: &CosmicMapped) -> Option> { + let space = &self.floating_layer.space; + let outputs = space.outputs().collect::>(); + let offset = if outputs.len() == 1 + && space.output_geometry(&outputs[0]).unwrap().loc == Point::from((0, 0)) + { + outputs[0].geometry().loc + } else { + (0, 0).into() + }; + + self.floating_layer + .space + .element_geometry(elem) + .or_else(|| self.tiling_layer.element_geometry(elem)) + .map(|mut geo| { + geo.loc += offset; + geo + }) } pub fn maximize_request(&mut self, window: &Window, output: &Output) { - if self.fullscreen.values().any(|w| w == window) { + if self.fullscreen.contains_key(output) { return; } - if self.floating_layer.windows.contains(window) { - self.floating_layer - .maximize_request(&mut self.space, window, output); - } - } + self.floating_layer.maximize_request(window); + + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + xdg.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Maximized); + state.states.unset(xdg_toplevel::State::Fullscreen); + }); + } + + self.set_fullscreen(window, output) + } pub fn unmaximize_request(&mut self, window: &Window) { if self.fullscreen.values().any(|w| w == window) { - return self.unfullscreen_request(window); - } - if self.floating_layer.windows.contains(window) { - self.floating_layer - .unmaximize_request(&mut self.space, window); - } - } - - pub fn resize_request( - state: &mut State, - surface: &WlSurface, - seat: &Seat, - serial: Serial, - start_data: PointerGrabStartData, - edges: ResizeEdge, - ) { - let workspace = state.common.shell.space_for_window_mut(surface).unwrap(); - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - - if workspace.fullscreen.values().any(|w| w == &window) { - return; - } - if workspace.floating_layer.windows.contains(&window) { - FloatingLayout::resize_request(state, &window, seat, serial, start_data.clone(), edges) - } else if workspace.tiling_layer.windows.contains(&window) { - TilingLayout::resize_request(state, &window, seat, serial, start_data, edges) + self.unfullscreen_request(window); + self.floating_layer.unmaximize_request(window); } } pub fn fullscreen_request(&mut self, window: &Window, output: &Output) { - if self.fullscreen.contains_key(&output.name()) { + if self.fullscreen.contains_key(output) { return; } @@ -111,6 +223,24 @@ impl Workspace { if let Kind::Xdg(xdg) = &window.toplevel() { xdg.with_pending_state(|state| { state.states.set(xdg_toplevel::State::Fullscreen); + state.states.unset(xdg_toplevel::State::Maximized); + }); + } + + self.set_fullscreen(window, output) + } + + fn set_fullscreen(&mut self, window: &Window, output: &Output) { + if let Some(mapped) = self + .mapped() + .find(|m| m.windows().any(|(w, _)| &w == window)) + { + mapped.set_active(window); + } + + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + xdg.with_pending_state(|state| { state.size = Some( output .current_mode() @@ -123,8 +253,8 @@ impl Workspace { }); xdg.send_configure(); - self.fullscreen.insert(output.name(), window.clone()); } + self.fullscreen.insert(output.clone(), window.clone()); } pub fn unfullscreen_request(&mut self, window: &Window) { @@ -133,45 +263,127 @@ impl Workspace { if let Kind::Xdg(xdg) = &window.toplevel() { xdg.with_pending_state(|state| { state.states.unset(xdg_toplevel::State::Fullscreen); + state.states.unset(xdg_toplevel::State::Maximized); state.size = None; }); - self.floating_layer.refresh(&mut self.space); - self.tiling_layer.refresh(&mut self.space); + self.floating_layer.refresh(); + self.tiling_layer.refresh(); xdg.send_configure(); } self.fullscreen.retain(|_, w| w != window); } } - pub fn fullscreen_toggle(&mut self, window: &Window, output: &Output) { - if self.fullscreen.contains_key(&output.name()) { - self.unfullscreen_request(window) + pub fn maximize_toggle(&mut self, window: &Window, output: &Output) { + if self.fullscreen.contains_key(output) { + self.unmaximize_request(window) } else { - self.fullscreen_request(window, output) + self.maximize_request(window, output) } } pub fn get_fullscreen(&self, output: &Output) -> Option<&Window> { - if !self.space.outputs().any(|o| o == output) { + self.fullscreen.get(output).filter(|w| w.alive()) + } + + pub fn resize_request( + &mut self, + mapped: &CosmicMapped, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + edges: ResizeEdge, + ) -> Option { + if mapped.is_fullscreen() || mapped.is_maximized() { return None; } - self.fullscreen.get(&output.name()).filter(|w| w.alive()) + + let edges = edges.into(); + if self.floating_layer.mapped().any(|m| m == mapped) { + self.floating_layer + .resize_request(mapped, seat, serial, start_data.clone(), edges) + .map(Into::into) + } else if self.tiling_layer.mapped().any(|(_, m, _)| m == mapped) { + self.tiling_layer + .resize_request(mapped, seat, serial, start_data, edges) + .map(Into::into) + } else { + None + } + } + + pub fn move_request( + &mut self, + window: &Window, + seat: &Seat, + output: &Output, + _serial: Serial, + start_data: PointerGrabStartData, + ) -> Option { + let pointer = seat.get_pointer().unwrap(); + let pos = pointer.current_location(); + + let mapped = self + .element_for_surface(window.toplevel().wl_surface())? + .clone(); + let mut initial_window_location = self.element_geometry(&mapped).unwrap().loc; + + if mapped.is_fullscreen() || mapped.is_maximized() { + // If surface is maximized then unmaximize it + self.unmaximize_request(window); + let new_size = match window.toplevel() { + Kind::Xdg(toplevel) => toplevel.with_pending_state(|state| state.size), + //_ => unreachable!(), // TODO x11 + }; + let ratio = pos.x / output.geometry().size.w as f64; + + initial_window_location = new_size + .map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into()) + .unwrap_or_else(|| pos) + .to_i32_round(); + } + + let was_floating = self.floating_layer.unmap(&mapped); + //let was_tiled = self.tiling_layer.unmap(&mapped); + //assert!(was_floating != was_tiled); + + if was_floating { + Some(MoveSurfaceGrab::new( + start_data, + mapped, + seat, + pos, + initial_window_location, + )) + } else { + None // TODO + } } pub fn toggle_tiling(&mut self, seat: &Seat) { if self.tiling_enabled { - for window in self.tiling_layer.windows.clone().into_iter() { - self.tiling_layer.unmap_window(&mut self.space, &window); - self.floating_layer - .map_window(&mut self.space, window, seat, None); + for window in self + .tiling_layer + .mapped() + .map(|(_, m, _)| m.clone()) + .collect::>() + .into_iter() + { + self.tiling_layer.unmap(&window); + self.floating_layer.map(window, seat, None); } self.tiling_enabled = false; } else { - let focus_stack = self.focus_stack(seat); - for window in self.floating_layer.windows.clone().into_iter() { - self.floating_layer.unmap_window(&mut self.space, &window); - self.tiling_layer - .map_window(&mut self.space, window, seat, focus_stack.iter()) + let focus_stack = self.focus_stack.get(seat); + for window in self + .floating_layer + .mapped() + .cloned() + .collect::>() + .into_iter() + { + self.floating_layer.unmap(&window); + self.tiling_layer.map(window, seat, focus_stack.iter()) } self.tiling_enabled = true; } @@ -179,18 +391,290 @@ impl Workspace { pub fn toggle_floating_window(&mut self, seat: &Seat) { if self.tiling_enabled { - if let Some(window) = self.focus_stack(seat).iter().next().cloned() { - if self.tiling_layer.windows.contains(&window) { - self.tiling_layer.unmap_window(&mut self.space, &window); - self.floating_layer - .map_window(&mut self.space, window, seat, None); - } else if self.floating_layer.windows.contains(&window) { - let focus_stack = self.focus_stack(seat); - self.floating_layer.unmap_window(&mut self.space, &window); - self.tiling_layer - .map_window(&mut self.space, window, seat, focus_stack.iter()) + if let Some(window) = self.focus_stack.get(seat).iter().next().cloned() { + if self.tiling_layer.mapped().any(|(_, m, _)| m == &window) { + self.tiling_layer.unmap(&window); + self.floating_layer.map(window, seat, None); + } else if self.floating_layer.mapped().any(|w| w == &window) { + let focus_stack = self.focus_stack.get(seat); + self.floating_layer.unmap(&window); + self.tiling_layer.map(window, seat, focus_stack.iter()) } } } } + + pub fn mapped(&self) -> impl Iterator { + self.floating_layer + .mapped() + .chain(self.tiling_layer.mapped().map(|(_, w, _)| w)) + } + + pub fn windows(&self) -> impl Iterator + '_ { + self.floating_layer + .windows() + .chain(self.tiling_layer.windows().map(|(_, w, _)| w)) + } + + pub fn render_output( + &self, + output: &Output, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, + { + let mut render_elements = Vec::new(); + + let output_scale = output.current_scale().fractional_scale(); + let layer_map = layer_map_for_output(output); + + if let Some(fullscreen) = self.fullscreen.get(output) { + // overlay layer surfaces + render_elements.extend( + layer_map + .layers() + .rev() + .filter(|s| s.layer() == Layer::Overlay) + .filter_map(|surface| { + layer_map + .layer_geometry(surface) + .map(|geo| (geo.loc, surface)) + }) + .flat_map(|(loc, surface)| { + AsRenderElements::::render_elements::>( + surface, + loc.to_physical_precise_round(output_scale), + Scale::from(output_scale), + ) + }), + ); + + // fullscreen window + render_elements.extend(AsRenderElements::::render_elements::< + WorkspaceRenderElement, + >(fullscreen, (0, 0).into(), output_scale.into())); + } else { + // TODO: Handle modes like + // - keyboard window swapping + // - resizing / moving in tiling + + // overlay and top layer surfaces + let lower = { + let (lower, upper): (Vec<&LayerSurface>, Vec<&LayerSurface>) = layer_map + .layers() + .rev() + .partition(|s| matches!(s.layer(), Layer::Background | Layer::Bottom)); + + render_elements.extend( + upper + .into_iter() + .filter_map(|surface| { + layer_map + .layer_geometry(surface) + .map(|geo| (geo.loc, surface)) + }) + .flat_map(|(loc, surface)| { + AsRenderElements::::render_elements::>( + surface, + loc.to_physical_precise_round(output_scale), + Scale::from(output_scale), + ) + }), + ); + + lower + }; + + // floating surfaces + render_elements.extend( + self.floating_layer + .render_output::(output)? + .into_iter() + .map(WorkspaceRenderElement::from), + ); + + //tiling surfaces + render_elements.extend( + self.tiling_layer + .render_output::(output)? + .into_iter() + .map(WorkspaceRenderElement::from), + ); + + // bottom and background layer surfaces + { + render_elements.extend( + lower + .into_iter() + .filter_map(|surface| { + layer_map + .layer_geometry(surface) + .map(|geo| (geo.loc, surface)) + }) + .flat_map(|(loc, surface)| { + AsRenderElements::::render_elements::>( + surface, + loc.to_physical_precise_round(output_scale), + Scale::from(output_scale), + ) + }), + ); + } + } + + Ok(render_elements) + } +} + +impl FocusStacks { + pub fn get<'a>(&'a self, seat: &Seat) -> FocusStack<'a> { + FocusStack(self.0.get(seat)) + } + + pub fn get_mut<'a>(&'a mut self, seat: &Seat) -> FocusStackMut<'a> { + FocusStackMut(self.0.entry(seat.clone()).or_default()) + } +} + +pub struct OutputNotMapped; + +pub enum WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + Wayland(WaylandSurfaceRenderElement), + Window(CosmicMappedRenderElement), +} + +impl Element for WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + fn id(&self) -> &smithay::backend::renderer::element::Id { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.id(), + WorkspaceRenderElement::Window(elem) => elem.id(), + } + } + + fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.current_commit(), + WorkspaceRenderElement::Window(elem) => elem.current_commit(), + } + } + + fn src(&self) -> Rectangle { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.src(), + WorkspaceRenderElement::Window(elem) => elem.src(), + } + } + + fn geometry(&self, scale: Scale) -> Rectangle { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.geometry(scale), + WorkspaceRenderElement::Window(elem) => elem.geometry(scale), + } + } + + fn location(&self, scale: Scale) -> Point { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.location(scale), + WorkspaceRenderElement::Window(elem) => elem.location(scale), + } + } + + fn transform(&self) -> smithay::utils::Transform { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.transform(), + WorkspaceRenderElement::Window(elem) => elem.transform(), + } + } + + fn damage_since( + &self, + scale: Scale, + commit: Option, + ) -> Vec> { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.damage_since(scale, commit), + WorkspaceRenderElement::Window(elem) => elem.damage_since(scale, commit), + } + } + + fn opaque_regions(&self, scale: Scale) -> Vec> { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.opaque_regions(scale), + WorkspaceRenderElement::Window(elem) => elem.opaque_regions(scale), + } + } +} + +impl RenderElement for WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn draw( + &self, + renderer: &mut R, + frame: &mut ::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), ::Error> { + match self { + WorkspaceRenderElement::Wayland(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + WorkspaceRenderElement::Window(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + } + } + + fn underlying_storage( + &self, + renderer: &R, + ) -> Option> { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.underlying_storage(renderer), + WorkspaceRenderElement::Window(elem) => elem.underlying_storage(renderer), + } + } +} + +impl From for WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: WaylandSurfaceRenderElement) -> Self { + WorkspaceRenderElement::Wayland(elem) + } +} + +impl From> for WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: CosmicMappedRenderElement) -> Self { + WorkspaceRenderElement::Window(elem) + } } diff --git a/src/state.rs b/src/state.rs index 3eaf3bbe..9deee434 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,37 +7,46 @@ use crate::{ shell::Shell, utils::prelude::*, wayland::protocols::{ - drm::WlDrmState, export_dmabuf::ExportDmabufState, - output_configuration::OutputConfigurationState, workspace::WorkspaceClientState, + drm::WlDrmState, + output_configuration::OutputConfigurationState, + screencopy::{BufferParams, ScreencopyState, Session as ScreencopySession}, + workspace::WorkspaceClientState, }, }; +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::CursorMode; use smithay::{ - backend::drm::DrmNode, + backend::{ + drm::DrmNode, + renderer::{ + element::{default_primary_scanout_output_compare, RenderElementStates}, + glow::GlowRenderer, + }, + }, + desktop::utils::{ + surface_presentation_feedback_flags_from_states, surface_primary_scanout_output, + update_surface_primary_scanout_output, OutputPresentationFeedback, + }, input::{Seat, SeatState}, output::{Mode as OutputMode, Output, Scale}, reexports::{ calloop::{LoopHandle, LoopSignal}, wayland_server::{ backend::{ClientData, ClientId, DisconnectReason}, + protocol::wl_shm, Display, DisplayHandle, }, }, - utils::{Buffer, Size}, + utils::{Clock, Monotonic}, wayland::{ compositor::CompositorState, data_device::DataDeviceState, dmabuf::DmabufState, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState, - primary_selection::PrimarySelectionState, shm::ShmState, viewporter::ViewporterState, + presentation::PresentationState, primary_selection::PrimarySelectionState, shm::ShmState, + viewporter::ViewporterState, }, }; -use std::{ - cell::RefCell, - ffi::OsString, - sync::{atomic::AtomicBool, Arc}, - time::Instant, -}; -#[cfg(feature = "debug")] -use std::{collections::VecDeque, time::Duration}; +use std::{cell::RefCell, ffi::OsString, time::Duration}; +use std::{collections::VecDeque, time::Instant}; pub struct ClientState { pub workspace_client_state: WorkspaceClientState, @@ -69,12 +78,11 @@ pub struct Common { //pub output_conf: ConfigurationManager, pub shell: Shell, - pub dirty_flag: Arc, - pub seats: Vec>, - pub last_active_seat: Seat, + seats: Vec>, + last_active_seat: Option>, - pub start_time: Instant, + pub clock: Clock, pub should_stop: bool, pub log: LogState, @@ -85,34 +93,18 @@ pub struct Common { pub compositor_state: CompositorState, pub data_device_state: DataDeviceState, pub dmabuf_state: DmabufState, - pub export_dmabuf_state: ExportDmabufState, pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub output_state: OutputManagerState, pub output_configuration_state: OutputConfigurationState, + pub presentation_state: PresentationState, pub primary_selection_state: PrimarySelectionState, + pub screencopy_state: ScreencopyState, pub seat_state: SeatState, pub shm_state: ShmState, pub wl_drm_state: WlDrmState, pub viewporter_state: ViewporterState, } -#[cfg(feature = "debug")] -pub struct Egui { - pub debug_state: smithay_egui::EguiState, - pub log_state: smithay_egui::EguiState, - pub modifiers: smithay::wayland::seat::ModifiersState, - pub active: bool, - pub alpha: f32, -} - -#[cfg(feature = "debug")] -pub struct Fps { - pub state: smithay_egui::EguiState, - pub modifiers: smithay::wayland::seat::ModifiersState, - pub frames: VecDeque<(Instant, Duration)>, - pub start: Instant, -} - pub enum BackendData { X11(X11State), Winit(WinitState), @@ -149,11 +141,12 @@ impl BackendData { output: &Output, test_only: bool, shell: &mut Shell, + seats: impl Iterator>, loop_handle: &LoopHandle<'_, Data>, ) -> Result<(), anyhow::Error> { let result = match self { BackendData::Kms(ref mut state) => { - state.apply_config_for_output(output, shell, test_only, loop_handle) + state.apply_config_for_output(output, seats, shell, test_only, loop_handle) } BackendData::Winit(ref mut state) => state.apply_config_for_output(output, test_only), BackendData::X11(ref mut state) => state.apply_config_for_output(output, test_only), @@ -187,102 +180,25 @@ impl BackendData { result } - pub fn schedule_render(&mut self, loop_handle: &LoopHandle<'_, Data>, output: &Output) { + pub fn schedule_render( + &mut self, + loop_handle: &LoopHandle<'_, Data>, + output: &Output, + screencopy: Option>, + ) { match self { - BackendData::Winit(_) => {} // We cannot do this on the winit backend. + BackendData::Winit(ref mut state) => state.pending_screencopy(screencopy), // We cannot do this on the winit backend. // Winit has a very strict render-loop and skipping frames breaks atleast the wayland winit-backend. // Swapping with damage (which should be empty on these frames) is likely good enough anyway. - BackendData::X11(ref mut state) => state.schedule_render(output), + BackendData::X11(ref mut state) => state.schedule_render(output, screencopy), BackendData::Kms(ref mut state) => { - if let Err(err) = state.schedule_render(loop_handle, output) { + if let Err(err) = state.schedule_render(loop_handle, output, None, screencopy) { slog_scope::crit!("Failed to schedule event, are we shutting down? {:?}", err); } } _ => unreachable!("No backend was initialized"), } } - - pub fn offscreen_for_output( - &mut self, - output: &Output, - state: &mut Common, - ) -> anyhow::Result<(Vec, Size)> { - use crate::backend::render::{render_output, AsGles2Renderer, CustomElem}; - use anyhow::Context; - use smithay::backend::renderer::{ImportAll, Renderer}; - use smithay::desktop::space::RenderElement; - use smithay::{ - backend::{ - drm::NodeType, - renderer::{gles2::Gles2Renderbuffer, Bind, ExportMem, Offscreen}, - }, - utils::Rectangle, - }; - - fn capture( - gpu: Option, - renderer: &mut R, - output: &Output, - state: &mut Common, - ) -> anyhow::Result<(Vec, Size)> - where - E: std::error::Error + Send + Sync + 'static, - T: Clone + 'static, - R: Renderer - + ImportAll - + AsGles2Renderer - + Offscreen - + Bind - + ExportMem, - CustomElem: RenderElement, - { - let size = output - .geometry() - .size - .to_f64() - .to_buffer( - output.current_scale().fractional_scale(), - output.current_transform().into(), - ) - .to_i32_round(); - let buffer = Offscreen::::create_buffer(renderer, size)?; - renderer.bind(buffer)?; - render_output( - gpu.as_ref(), - renderer, - 0, - state, - output, - false, - #[cfg(feature = "debug")] - None, - ) - .map_err(|err| anyhow::anyhow!("Failed to render output: {:?}", err))?; // lifetime issue, grrr - let mapping = renderer.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), size))?; - let data = Vec::from(renderer.map_texture(&mapping)?); - - Ok((data, size)) - } - - match self { - BackendData::Winit(winit) => capture(None, winit.backend.renderer(), output, state), - BackendData::X11(x11) => capture(None, &mut x11.renderer, output, state), - BackendData::Kms(kms) => { - let node = kms - .target_node_for_output(output) - .unwrap_or(kms.primary) - .node_with_type(NodeType::Render) - .with_context(|| "Unable to find node")??; - capture( - Some(node), - &mut kms.api.renderer::(&node, &node)?, - output, - state, - ) - } - BackendData::Unset => unreachable!(), - } - } } impl State { @@ -293,31 +209,31 @@ impl State { signal: LoopSignal, log: LogState, ) -> State { + let clock = Clock::new().expect("Failed to initialize clock"); let config = Config::load(); let compositor_state = CompositorState::new::(dh, None); let data_device_state = DataDeviceState::new::(dh, None); let dmabuf_state = DmabufState::new(); - let export_dmabuf_state = ExportDmabufState::new::( - dh, - //|client| client.get_data::().unwrap().privileged, - |_| true, - ); let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(dh); let output_state = OutputManagerState::new_with_xdg_output::(dh); let output_configuration_state = OutputConfigurationState::new(dh, |_| true); + let presentation_state = PresentationState::new::(dh, clock.id() as u32); let primary_selection_state = PrimarySelectionState::new::(dh, None); - let shm_state = ShmState::new::(dh, vec![], None); - let mut seat_state = SeatState::::new(); + let screencopy_state = ScreencopyState::new::( + dh, + vec![CursorMode::Embedded, CursorMode::Hidden], + |_| true, + ); // TODO: privileged + let shm_state = ShmState::new::( + dh, + vec![wl_shm::Format::Xbgr8888, wl_shm::Format::Abgr8888], + None, + ); + let seat_state = SeatState::::new(); let viewporter_state = ViewporterState::new::(dh, None); let wl_drm_state = WlDrmState; let shell = Shell::new(&config, dh); - let initial_seat = crate::input::add_seat(dh, &mut seat_state, &config, "seat-0".into()); - - #[cfg(not(feature = "debug"))] - let dirty_flag = Arc::new(AtomicBool::new(false)); - #[cfg(feature = "debug")] - let dirty_flag = log.dirty_flag.clone(); State { common: Common { @@ -328,38 +244,27 @@ impl State { event_loop_signal: signal, shell, - dirty_flag, - seats: vec![initial_seat.clone()], - last_active_seat: initial_seat, + seats: Vec::new(), + last_active_seat: None, - start_time: Instant::now(), + clock, should_stop: false, log, #[cfg(feature = "debug")] - egui: Egui { - debug_state: smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous), - log_state: { - let mut state = - smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous); - state.set_zindex(0); - state - }, - modifiers: Default::default(), - active: false, - alpha: 1.0, - }, + egui: Egui { active: false }, compositor_state, data_device_state, dmabuf_state, - export_dmabuf_state, + screencopy_state, shm_state, seat_state, keyboard_shortcuts_inhibit_state, output_state, output_configuration_state, + presentation_state, primary_selection_state, viewporter_state, wl_drm_state, @@ -376,10 +281,9 @@ impl State { match std::env::var("COSMIC_RENDER_AUTO_ASSIGN").map(|val| val.to_lowercase()) { Ok(val) if val == "y" || val == "yes" || val == "true" => Some( kms_state - .target_node_for_output(&active_output( - &self.common.last_active_seat, - &self.common, - )) + .target_node_for_output( + &self.common.last_active_seat().active_output(), + ) .unwrap_or(kms_state.primary), ), _ => Some(kms_state.primary), @@ -415,49 +319,275 @@ impl State { } } -#[cfg(feature = "debug")] -impl Fps { - const WINDOW_SIZE: usize = 100; - - pub fn start(&mut self) { - self.start = Instant::now(); +impl Common { + pub fn add_seat(&mut self, seat: Seat) { + if self.seats.is_empty() { + self.last_active_seat = Some(seat.clone()); + } + self.seats.push(seat); } - pub fn end(&mut self) { - let frame_time = Instant::now().duration_since(self.start); - - self.frames.push_back((self.start, frame_time)); - if self.frames.len() > Fps::WINDOW_SIZE { - self.frames.pop_front(); + pub fn remove_seat(&mut self, seat: &Seat) { + self.seats.retain(|s| s != seat); + if self.seats.is_empty() { + self.last_active_seat = None; + } else if self.last_active_seat() == seat { + self.last_active_seat = Some(self.seats[0].clone()); } } - pub fn max_frametime(&self) -> &Duration { - self.frames - .iter() - .map(|(_, f)| f) - .max() - .unwrap_or(&Duration::ZERO) + pub fn seats(&self) -> impl Iterator> { + self.seats.iter() } - pub fn min_frametime(&self) -> &Duration { + pub fn last_active_seat(&self) -> &Seat { + self.last_active_seat.as_ref().expect("No seat?") + } + + pub fn send_frames(&self, output: &Output, render_element_states: &RenderElementStates) { + let time = self.clock.now(); + let throttle = Some(Duration::from_secs(1)); + + let active = self.shell.active_space(output); + active.mapped().for_each(|mapped| { + if active.outputs_for_element(mapped).any(|o| &o == output) { + let window = mapped.active_window(); + window.with_surfaces(|surface, states| { + update_surface_primary_scanout_output( + surface, + output, + states, + render_element_states, + default_primary_scanout_output_compare, + ) + }); + window.send_frame(output, time, throttle, surface_primary_scanout_output); + } + }); + + for space in self + .shell + .workspaces + .spaces() + .filter(|w| w.handle != active.handle) + { + space.mapped().for_each(|mapped| { + if space.outputs_for_element(mapped).any(|o| &o == output) { + let window = mapped.active_window(); + window.send_frame(output, time, throttle, |_, _| None); + } + }); + } + + let map = smithay::desktop::layer_map_for_output(output); + for layer_surface in map.layers() { + layer_surface.with_surfaces(|surface, states| { + update_surface_primary_scanout_output( + surface, + output, + states, + render_element_states, + default_primary_scanout_output_compare, + ) + }); + layer_surface.send_frame(output, time, throttle, surface_primary_scanout_output); + } + } + + pub fn take_presentation_feedback( + &self, + output: &Output, + render_element_states: &RenderElementStates, + ) -> OutputPresentationFeedback { + let mut output_presentation_feedback = OutputPresentationFeedback::new(output); + + let active = self.shell.active_space(output); + active.mapped().for_each(|mapped| { + mapped.active_window().take_presentation_feedback( + &mut output_presentation_feedback, + surface_primary_scanout_output, + |surface, _| { + surface_presentation_feedback_flags_from_states(surface, render_element_states) + }, + ); + }); + + let map = smithay::desktop::layer_map_for_output(output); + for layer_surface in map.layers() { + layer_surface.take_presentation_feedback( + &mut output_presentation_feedback, + surface_primary_scanout_output, + |surface, _| { + surface_presentation_feedback_flags_from_states(surface, render_element_states) + }, + ); + } + + output_presentation_feedback + } +} + +#[cfg(feature = "debug")] +pub struct Egui { + pub active: bool, +} + +pub struct Fps { + #[cfg(feature = "debug")] + pub state: smithay_egui::EguiState, + pending_frame: Option, + pub frames: VecDeque, +} + +#[derive(Debug)] +struct PendingFrame { + start: Instant, + duration_elements: Option, + duration_render: Option, + duration_screencopy: Option, + duration_displayed: Option, +} + +#[derive(Debug)] +pub struct Frame { + pub start: Instant, + pub duration_elements: Duration, + pub duration_render: Duration, + pub duration_screencopy: Option, + pub duration_displayed: Duration, +} + +impl Frame { + fn render_time(&self) -> Duration { + self.duration_elements + self.duration_render + } + + fn frame_time(&self) -> Duration { + self.duration_elements + + self.duration_render + + self.duration_screencopy.clone().unwrap_or(Duration::ZERO) + } + + fn time_to_display(&self) -> Duration { + self.duration_elements + + self.duration_render + + self.duration_screencopy.clone().unwrap_or(Duration::ZERO) + + self.duration_displayed + } +} + +impl From for Frame { + fn from(pending: PendingFrame) -> Self { + Frame { + start: pending.start, + duration_elements: pending.duration_elements.unwrap_or(Duration::ZERO), + duration_render: pending.duration_render.unwrap_or(Duration::ZERO), + duration_screencopy: pending.duration_screencopy, + duration_displayed: pending.duration_displayed.unwrap_or(Duration::ZERO), + } + } +} + +impl Fps { + const WINDOW_SIZE: usize = 360; + + pub fn start(&mut self) { + self.pending_frame = Some(PendingFrame { + start: Instant::now(), + duration_elements: None, + duration_render: None, + duration_screencopy: None, + duration_displayed: None, + }); + } + + pub fn elements(&mut self) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.duration_elements = Some(Instant::now().duration_since(frame.start)); + } + } + + pub fn render(&mut self) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.duration_render = Some( + Instant::now().duration_since(frame.start) + - frame.duration_elements.clone().unwrap_or(Duration::ZERO), + ); + } + } + + pub fn screencopy(&mut self) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.duration_screencopy = Some( + Instant::now().duration_since(frame.start) + - frame.duration_elements.clone().unwrap_or(Duration::ZERO) + - frame.duration_render.clone().unwrap_or(Duration::ZERO), + ); + } + } + + pub fn displayed(&mut self) { + if let Some(mut frame) = self.pending_frame.take() { + frame.duration_displayed = Some( + Instant::now().duration_since(frame.start) + - frame.duration_elements.clone().unwrap_or(Duration::ZERO) + - frame.duration_render.clone().unwrap_or(Duration::ZERO) + - frame.duration_screencopy.clone().unwrap_or(Duration::ZERO), + ); + + self.frames.push_back(frame.into()); + while self.frames.len() > Fps::WINDOW_SIZE { + self.frames.pop_front(); + } + } + } + + pub fn max_frametime(&self) -> Duration { self.frames .iter() - .map(|(_, f)| f) + .map(|f| f.frame_time()) + .max() + .unwrap_or(Duration::ZERO) + } + + pub fn min_frametime(&self) -> Duration { + self.frames + .iter() + .map(|f| f.frame_time()) .min() - .unwrap_or(&Duration::ZERO) + .unwrap_or(Duration::ZERO) + } + + pub fn max_time_to_display(&self) -> Duration { + self.frames + .iter() + .map(|f| f.time_to_display()) + .max() + .unwrap_or(Duration::ZERO) + } + + pub fn min_time_to_display(&self) -> Duration { + self.frames + .iter() + .map(|f| f.time_to_display()) + .min() + .unwrap_or(Duration::ZERO) } pub fn avg_frametime(&self) -> Duration { if self.frames.is_empty() { return Duration::ZERO; } + self.frames.iter().map(|f| f.frame_time()).sum::() / (self.frames.len() as u32) + } + + pub fn avg_rendertime(&self, window: usize) -> Duration { self.frames .iter() - .map(|(_, f)| f) - .cloned() + .take(window) + .map(|f| f.render_time()) .sum::() - / (self.frames.len() as u32) + / window as u32 } pub fn avg_fps(&self) -> f64 { @@ -465,7 +595,9 @@ impl Fps { return 0.0; } let secs = match (self.frames.front(), self.frames.back()) { - (Some((start, _)), Some((end, dur))) => end.duration_since(*start) + *dur, + (Some(Frame { start, .. }), Some(end_frame)) => { + end_frame.start.duration_since(*start) + end_frame.frame_time() + } _ => Duration::ZERO, } .as_secs_f64(); @@ -474,25 +606,44 @@ impl Fps { } #[cfg(feature = "debug")] -impl Default for Fps { - fn default() -> Fps { +static INTEL_LOGO: &'static [u8] = include_bytes!("../resources/icons/intel.svg"); +#[cfg(feature = "debug")] +static AMD_LOGO: &'static [u8] = include_bytes!("../resources/icons/amd.svg"); +#[cfg(feature = "debug")] +static NVIDIA_LOGO: &'static [u8] = include_bytes!("../resources/icons/nvidia.svg"); + +impl Fps { + pub fn new(_renderer: &mut GlowRenderer) -> Fps { + #[cfg(feature = "debug")] + let state = { + let state = smithay_egui::EguiState::new(smithay::utils::Rectangle::from_loc_and_size( + (0, 0), + (400, 800), + )); + let mut visuals: egui::style::Visuals = Default::default(); + visuals.window_shadow.extrusion = 0.0; + state.context().set_visuals(visuals); + state + .load_svg(_renderer, String::from("intel"), INTEL_LOGO) + .unwrap(); + state + .load_svg(_renderer, String::from("amd"), AMD_LOGO) + .unwrap(); + state + .load_svg(_renderer, String::from("nvidia"), NVIDIA_LOGO) + .unwrap(); + state + }; + Fps { - state: { - let mut state = smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous); - let mut visuals: egui::style::Visuals = Default::default(); - visuals.window_shadow.extrusion = 0.0; - state.context().set_visuals(visuals); - state.set_zindex(110); // always render on top - state - }, - modifiers: Default::default(), + #[cfg(feature = "debug")] + state, + pending_frame: None, frames: VecDeque::with_capacity(Fps::WINDOW_SIZE + 1), - start: Instant::now(), } } } -#[cfg(feature = "debug")] pub fn avg_fps<'a>(iter: impl Iterator) -> f64 { let sum_secs = iter.map(|d| d.as_secs_f64()).sum::(); 1.0 / (sum_secs / Fps::WINDOW_SIZE as f64) diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 55936e98..57348025 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -1,10 +1,19 @@ -use crate::input::{ActiveOutput, SeatId}; -use smithay::{ - input::Seat, - output::Output, - utils::{Logical, Rectangle, Transform}, +use std::{cell::RefCell, sync::Mutex, time::Duration}; + +use crate::{ + backend::render::cursor::CursorState, + input::{ActiveOutput, SeatId}, +}; +use smithay::{ + desktop::utils::bbox_from_surface_tree, + input::{ + pointer::{CursorImageAttributes, CursorImageStatus}, + Seat, + }, + output::Output, + utils::{Buffer, IsAlive, Logical, Monotonic, Point, Rectangle, Time, Transform}, + wayland::compositor::with_states, }; -use std::cell::RefCell; pub use crate::shell::{Shell, Workspace}; pub use crate::state::{Common, State}; @@ -32,38 +41,93 @@ impl OutputExt for Output { pub trait SeatExt { fn id(&self) -> usize; + + fn active_output(&self) -> Output; + fn set_active_output(&self, output: &Output); + fn cursor_geometry( + &self, + loc: impl Into>, + time: Time, + ) -> Option<(Rectangle, Point)>; } impl SeatExt for Seat { fn id(&self) -> usize { self.user_data().get::().unwrap().0 } -} -pub fn active_output(seat: &Seat, state: &Common) -> Output { - seat.user_data() - .get::() - .map(|x| x.0.borrow().clone()) - .unwrap_or_else(|| { - state - .shell - .outputs() - .next() - .cloned() - .expect("Backend has no outputs?") - }) -} + fn active_output(&self) -> Output { + self.user_data() + .get::() + .map(|x| x.0.borrow().clone()) + .unwrap() + } -pub fn set_active_output(seat: &Seat, output: &Output) { - if !seat - .user_data() - .insert_if_missing(|| ActiveOutput(RefCell::new(output.clone()))) - { - *seat + fn set_active_output(&self, output: &Output) { + *self .user_data() .get::() .unwrap() .0 .borrow_mut() = output.clone(); } + + fn cursor_geometry( + &self, + loc: impl Into>, + time: Time, + ) -> Option<(Rectangle, Point)> { + let location = loc.into().to_i32_round(); + + let cursor_status = self + .user_data() + .get::>() + .map(|cell| { + let mut cursor_status = cell.borrow_mut(); + if let CursorImageStatus::Surface(ref surface) = *cursor_status { + if !surface.alive() { + *cursor_status = CursorImageStatus::Default; + } + } + cursor_status.clone() + }) + .unwrap_or(CursorImageStatus::Default); + + match cursor_status { + CursorImageStatus::Surface(surface) => { + let hotspot = with_states(&surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .hotspot + }); + let geo = bbox_from_surface_tree(&surface, (location.x, location.y)); + let buffer_geo = Rectangle::from_loc_and_size( + (geo.loc.x, geo.loc.y), + geo.size.to_buffer(1, Transform::Normal), + ); + Some((buffer_geo, (hotspot.x, hotspot.y).into())) + } + CursorImageStatus::Default => { + let seat_userdata = self.user_data(); + seat_userdata.insert_if_missing(CursorState::default); + let state = seat_userdata.get::().unwrap(); + let frame = state + .cursor + .get_image(1, Into::::into(time).as_millis() as u32); + + Some(( + Rectangle::from_loc_and_size( + location, + (frame.width as i32, frame.height as i32), + ), + (frame.xhot as i32, frame.yhot as i32).into(), + )) + } + CursorImageStatus::Hidden => None, + } + } } diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index 3e5c766c..f7b2f81a 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{state::BackendData, utils::prelude::*}; +use crate::{state::BackendData, utils::prelude::*, wayland::protocols::screencopy::SessionType}; use smithay::{ backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state}, delegate_compositor, @@ -18,11 +18,13 @@ use smithay::{ }; use std::sync::Mutex; +use super::screencopy::PendingScreencopyBuffers; + impl State { fn early_import_surface(&mut self, surface: &WlSurface) { let mut import_nodes = std::collections::HashSet::new(); let dh = &self.common.display_handle; - for output in self.common.shell.outputs_for_surface(&surface) { + for output in self.common.shell.visible_outputs_for_surface(&surface) { if let BackendData::Kms(ref mut kms_state) = &mut self.backend { if let Some(target) = kms_state.target_node_for_output(&output) { if import_nodes.insert(target) { @@ -90,7 +92,7 @@ impl State { if !initial_configure_sent { // compute initial dimensions by mapping Shell::map_layer(self, &surface); - // this will also send a configure + surface.layer_surface().send_configure(); } initial_configure_sent } @@ -121,7 +123,7 @@ impl CompositorHandler for State { state.wl_buffer().is_some() }) { - let output = active_output(&seat, &self.common); + let output = seat.active_output(); Shell::map_window(self, &window, &output); } else { return; @@ -152,46 +154,25 @@ impl CompositorHandler for State { // If we would re-position the window inside the grab we would get a weird jittery animation. // We only want to resize once the client has acknoledged & commited the new size, // so we need to carefully track the state through different handlers. - if let Some((space, window)) = - self.common - .shell - .space_for_window_mut(surface) - .and_then(|workspace| { - workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .cloned() - .map(|window| (&mut workspace.space, window)) - }) - { - let new_location = - crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_state( - &window, - space.window_location(&window).unwrap(), - window.geometry().size, + if let Some(element) = self.common.shell.element_for_surface(surface).cloned() { + if let Some(workspace) = self.common.shell.space_for_mut(&element) { + crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_to_location( + element.clone(), + workspace, ); - if let Some(location) = new_location { - space.map_window( - &window, - location, - crate::shell::layout::floating::FLOATING_INDEX, - true, - ); - for window in space.windows() { - update_reactive_popups(space, window); - } + workspace.commit(surface); } } + //handle window screencopy sessions + self.schedule_window_session(surface); + // We need to know every potential output for importing to the right gpu and scheduling a render, // so call this only after every potential surface map operation has been done. self.early_import_surface(surface); // and refresh smithays internal state self.common.shell.popups.commit(surface); - for workspace in &self.common.shell.spaces { - workspace.space.commit(surface); - } // re-arrange layer-surfaces (commits may change size and positioning) if let Some(output) = self.common.shell.outputs().find(|o| { @@ -199,14 +180,37 @@ impl CompositorHandler for State { map.layer_for_surface(surface, WindowSurfaceType::ALL) .is_some() }) { - let dh = &self.common.display_handle; - layer_map_for_output(output).arrange(dh); + layer_map_for_output(output).arrange(); } + let mut scheduled_sessions = self.schedule_workspace_sessions(surface); + // schedule a new render - for output in self.common.shell.outputs_for_surface(surface) { - self.backend - .schedule_render(&self.common.event_loop_handle, &output); + for output in self.common.shell.visible_outputs_for_surface(surface) { + if let Some(sessions) = output.user_data().get::() { + scheduled_sessions + .get_or_insert_with(Vec::new) + .extend(sessions.borrow_mut().drain(..)); + } + + self.backend.schedule_render( + &self.common.event_loop_handle, + &output, + scheduled_sessions.as_ref().map(|sessions| { + sessions + .iter() + .filter(|(s, _)| match s.session_type() { + SessionType::Output(o) | SessionType::Workspace(o, _) + if o == output => + { + true + } + _ => false, + }) + .cloned() + .collect::>() + }), + ); } } } diff --git a/src/wayland/handlers/export_dmabuf.rs b/src/wayland/handlers/export_dmabuf.rs deleted file mode 100644 index 5a492a11..00000000 --- a/src/wayland/handlers/export_dmabuf.rs +++ /dev/null @@ -1,424 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use anyhow::{anyhow, Context, Result}; - -use std::{cell::RefCell, time::Instant}; - -use smithay::{ - backend::{ - drm::{DrmNode, NodeType}, - egl::EGLDevice, - renderer::{ - gles2::{Gles2Error, Gles2Renderbuffer, Gles2Renderer}, - utils::with_renderer_surface_state, - Bind, ExportDma, ImportAll, Offscreen, Renderer, - }, - }, - desktop::{draw_window, draw_window_popups, space::RenderElement, Kind, Window}, - input::pointer::CursorImageStatus, - output::Output, - reexports::wayland_server::{protocol::wl_output::WlOutput, DisplayHandle, Resource}, - utils::{IsAlive, Size, Transform}, - wayland::{ - compositor::{get_children, with_states, SurfaceAttributes}, - dmabuf::get_dmabuf, - }, -}; - -use crate::{ - backend::render::{ - cursor::draw_cursor, render_output, render_workspace, AsGles2Renderer, CustomElem, - }, - state::{BackendData, ClientState, Common}, - utils::prelude::*, - wayland::protocols::{ - export_dmabuf::{delegate_export_dmabuf, Capture, CaptureError, ExportDmabufHandler}, - workspace::WorkspaceHandle, - }, -}; - -impl ExportDmabufHandler for State { - fn capture_output( - &mut self, - _dh: &DisplayHandle, - output: WlOutput, - overlay_cursor: bool, - ) -> Result { - let output = Output::from_resource(&output) - .ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?; - - let renderer = match self.backend { - BackendData::Kms(ref mut kms) => { - // the kms backend just keeps its dmabufs easily accessible for capture. - return kms - .capture_output(&output) - .map(|(device, dmabuf, presentation_time)| Capture { - device, - dmabuf, - presentation_time, - }) - .ok_or(CaptureError::Temporary( - anyhow!("Surface not initialized yet").into(), - )); - } - BackendData::Winit(ref mut winit) => winit.backend.renderer(), - BackendData::X11(ref mut x11) => &mut x11.renderer, - _ => unreachable!(), - }; - let device = device_from_renderer(renderer) - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - - let size = output - .geometry() - .size - .to_f64() - .to_buffer( - output.current_scale().fractional_scale(), - output.current_transform().into(), - ) - .to_i32_round(); - let buffer = Offscreen::::create_buffer(renderer, size) - .context("Failed to create render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .bind(buffer) - .context("Failed to bind render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - render_output( - None, - renderer, - 0, - &mut self.common, - &output, - !overlay_cursor, - #[cfg(feature = "debug")] - None, - ) - .context("Failed to render desktop for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - let dmabuf = renderer - .export_framebuffer(size) - .context("Failed to export buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - Ok(Capture { - device, - dmabuf, - presentation_time: Instant::now(), - }) - } - - fn capture_workspace( - &mut self, - _dh: &DisplayHandle, - workspace: WorkspaceHandle, - wl_output: WlOutput, - overlay_cursor: bool, - ) -> Result { - let output = Output::from_resource(&wl_output) - .ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?; - let workspace = self - .common - .shell - .spaces - .iter() - .find(|w| w.handle == workspace) - .ok_or(CaptureError::Permanent(anyhow!("Workspace is gone").into()))? - .idx; - if self.common.shell.active_space(&output).idx == workspace { - self.capture_output(_dh, wl_output, overlay_cursor) - } else { - match self.backend { - BackendData::Winit(ref mut winit) => { - let device = device_from_renderer(winit.backend.renderer()) - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - capture_workspace( - device, - winit.backend.renderer(), - &output, - workspace, - &mut self.common, - ) - } - BackendData::X11(ref mut x11) => { - let device = device_from_renderer(&x11.renderer) - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - capture_workspace( - device, - &mut x11.renderer, - &output, - workspace, - &mut self.common, - ) - } - BackendData::Kms(ref mut kms) => { - let node = kms - .target_node_for_output(&output) - .unwrap_or(kms.primary) - .node_with_type(NodeType::Render) - .with_context(|| "Unable to find node") - .map_err(|x| CaptureError::Permanent(x.into()))? - .map_err(|x| CaptureError::Permanent(x.into()))?; - let mut renderer = kms - .api - .renderer::(&node, &node) - .with_context(|| format!("Failed to optain renderer for {:?}", node)) - .map_err(|x| CaptureError::Permanent(x.into()))?; - capture_workspace(node, &mut renderer, &output, workspace, &mut self.common) - } - BackendData::Unset => unreachable!(), - } - } - } - - fn capture_toplevel( - &mut self, - dh: &DisplayHandle, - window: Window, - overlay_cursor: bool, - ) -> Result { - let Kind::Xdg(xdg) = window.toplevel(); - let surface = xdg.wl_surface(); - let window_transform = with_states(surface, |states| { - states - .cached_state - .current::() - .buffer_transform - .into() - }); - - let workspace = self.common.shell.space_for_window(surface); - let pointers = if overlay_cursor && workspace.is_some() { - self.common - .seats - .iter() - .filter_map(|seat| { - let cursor_status = seat - .user_data() - .get::>() - .map(|cell| { - let mut cursor_status = cell.borrow_mut(); - if let CursorImageStatus::Surface(ref surface) = *cursor_status { - if !surface.alive() { - *cursor_status = CursorImageStatus::Default; - } - } - cursor_status.clone() - }) - .unwrap_or(CursorImageStatus::Default); - - if cursor_status != CursorImageStatus::Hidden { - let workspace = workspace.as_deref()?; - let loc = seat.get_pointer().map(|ptr| ptr.current_location())?; - let output = active_output(seat, &self.common); - - if self.common.shell.active_space(&output).idx == workspace.idx { - let relative = self - .common - .shell - .space_relative_output_geometry(loc, &output); - // unwrap is safe, because we got this workspace from `space_for_window`. It has to contain the window. - let bbox = workspace.space.window_bbox(&window).unwrap(); - bbox.contains(relative.to_i32_round()) - .then_some((seat, (relative - bbox.loc.to_f64()).to_i32_round())) - } else { - None - } - } else { - None - } - }) - .collect::>() - } else { - Vec::with_capacity(0) - }; - - let device = match self.backend { - BackendData::Winit(ref mut winit) => device_from_renderer(winit.backend.renderer()), - BackendData::X11(ref x11) => device_from_renderer(&x11.renderer), - BackendData::Kms(ref kms) => Ok(dh - .get_client(window.toplevel().wl_surface().id()) - .ok() - .with_context(|| "Unable to find matching wayland client") - .map_err(|x| CaptureError::Permanent(x.into()))? - .get_data::() - .unwrap() - .drm_node - .clone() - .unwrap_or_else(|| kms.primary.clone())), - _ => unreachable!(), - } - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - - // first lets check, if we can just send a dmabuf from the client directly - if pointers.is_empty() - && window_transform == Transform::Normal - && get_children(surface).is_empty() - && self.common.shell.popups.find_popup(surface).is_none() - { - let dmabuf = with_renderer_surface_state(surface, |state| { - state.wl_buffer().and_then(|buf| get_dmabuf(buf).ok()) - }); - if let Some(dmabuf) = dmabuf { - return Ok(Capture { - device, - dmabuf, - presentation_time: std::time::Instant::now(), - }); - } - } - - // we need to composite - let mut _tmp_multirenderer = None; - let renderer = match self.backend { - BackendData::Winit(ref mut winit) => winit.backend.renderer(), - BackendData::X11(ref mut x11) => &mut x11.renderer, - BackendData::Kms(ref mut kms) => { - _tmp_multirenderer = Some( - kms.api - .renderer::(&device, &device) - .with_context(|| format!("Failed to optain renderer for {:?}", device)) - .map_err(|x| CaptureError::Permanent(x.into()))?, - ); - _tmp_multirenderer.as_mut().unwrap().as_gles2() - } - BackendData::Unset => unreachable!(), - }; - - let bbox = window.bbox_with_popups(); - let size = bbox.size + Size::from((-bbox.loc.x, -bbox.loc.y)); - let buffer = Offscreen::::create_buffer( - renderer, - size.to_buffer(1, window_transform), - ) - .context("Failed to create render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .bind(buffer) - .context("Failed to bind render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .render(size.to_physical(1), Transform::Normal, |renderer, frame| { - let log = slog_scope::logger(); - let damage = &[window.physical_bbox_with_popups((0.0, 0.0), 1.0)]; - draw_window(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?; - draw_window_popups(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?; - for (seat, loc) in pointers.into_iter() { - if let Some(cursor_elem) = draw_cursor::<_, CustomElem>( - renderer, - seat, - loc, - &self.common.start_time, - true, - ) { - let damage = RenderElement::::accumulated_damage( - &cursor_elem, - 1.0, - None, - ); - cursor_elem.draw( - renderer, - frame, - 1.0, - loc.to_physical(1.0), - &damage, - &log, - )?; - } - } - Result::<(), Gles2Error>::Ok(()) - }) - .context("Failed to render window for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))? - .context("Failed to render window for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - let dmabuf = renderer - .export_framebuffer(size.to_buffer(1, window_transform)) - .context("Failed to export buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - Ok(Capture { - device, - dmabuf, - presentation_time: Instant::now(), - }) - } - - fn start_time(&mut self) -> Instant { - self.common.start_time - } -} - -fn capture_workspace( - gpu: DrmNode, - renderer: &mut R, - output: &Output, - idx: u8, - state: &mut Common, -) -> Result -where - E: std::error::Error + Send + Sync + 'static, - T: Clone + 'static, - R: Renderer - + ImportAll - + AsGles2Renderer - + Offscreen - + Bind - + ExportDma, - CustomElem: RenderElement, -{ - let size = output - .geometry() - .size - .to_f64() - .to_buffer( - output.current_scale().fractional_scale(), - output.current_transform().into(), - ) - .to_i32_round(); - let buffer = Offscreen::::create_buffer(renderer, size) - .context("Failed to create render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .bind(buffer) - .context("Failed to bind render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - render_workspace( - Some(&gpu), - renderer, - 0, - state, - idx, - &output, - true, - #[cfg(feature = "debug")] - None, - ) - .map_err(|err| anyhow!("Failed to render desktop for offscreen capture: {:?}", err)) // meh.. - .map_err(|err| CaptureError::Temporary(err.into()))?; - let dmabuf = renderer - .export_framebuffer(size) - .context("Failed to export buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - Ok(Capture { - device: gpu, - dmabuf, - presentation_time: Instant::now(), - }) -} - -fn device_from_renderer(renderer: &Gles2Renderer) -> Result { - EGLDevice::device_for_display(renderer.egl_context().display())? - .try_get_render_node()? - .ok_or(anyhow!( - "No node associated with context (software context?)" - )) -} - -delegate_export_dmabuf!(State); diff --git a/src/wayland/handlers/layer_shell.rs b/src/wayland/handlers/layer_shell.rs index b5c73350..2083d541 100644 --- a/src/wayland/handlers/layer_shell.rs +++ b/src/wayland/handlers/layer_shell.rs @@ -3,7 +3,7 @@ use crate::utils::prelude::*; use smithay::{ delegate_layer_shell, - desktop::{LayerSurface, PopupKind}, + desktop::{layer_map_for_output, LayerSurface, PopupKind, WindowSurfaceType}, output::Output, reexports::wayland_server::protocol::wl_output::WlOutput, wayland::shell::{ @@ -14,6 +14,8 @@ use smithay::{ }, }; +use super::screencopy::PendingScreencopyBuffers; + impl WlrLayerShellHandler for State { fn shell_state(&mut self) -> &mut WlrLayerShellState { &mut self.common.shell.layer_shell_state @@ -26,12 +28,11 @@ impl WlrLayerShellHandler for State { _layer: Layer, namespace: String, ) { - super::mark_dirty_on_drop(&self.common, surface.wl_surface()); - let seat = self.common.last_active_seat.clone(); + let seat = self.common.last_active_seat().clone(); let output = wl_output .as_ref() .and_then(Output::from_resource) - .unwrap_or_else(|| active_output(&seat, &self.common)); + .unwrap_or_else(|| seat.active_output()); self.common.shell.pending_layers.push(( LayerSurface::new(surface, namespace), output, @@ -51,6 +52,44 @@ impl WlrLayerShellHandler for State { .unwrap(); } } + + fn layer_destroyed(&mut self, surface: WlrLayerSurface) { + let maybe_output = self + .common + .shell + .outputs() + .find(|o| { + let map = layer_map_for_output(o); + map.layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) + .is_some() + }) + .cloned(); + + if let Some(output) = maybe_output { + { + let mut map = layer_map_for_output(&output); + let layer = map + .layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) + .unwrap() + .clone(); + map.unmap_layer(&layer); + } + + // collect screencopy sessions needing an update + let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface()); + if let Some(sessions) = output.user_data().get::() { + scheduled_sessions + .get_or_insert_with(Vec::new) + .extend(sessions.borrow_mut().drain(..)); + } + + self.backend.schedule_render( + &self.common.event_loop_handle, + &output, + scheduled_sessions, + ); + } + } } delegate_layer_shell!(State); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 06590066..6b6028d5 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -4,12 +4,13 @@ pub mod buffer; pub mod compositor; pub mod data_device; pub mod dmabuf; -pub mod export_dmabuf; pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; pub mod output_configuration; +pub mod presentation; pub mod primary_selection; +pub mod screencopy; pub mod seat; pub mod shm; pub mod toplevel_info; @@ -18,28 +19,3 @@ pub mod viewporter; pub mod wl_drm; pub mod workspace; pub mod xdg_shell; - -use crate::state::Common; -use smithay::{ - reexports::wayland_server::protocol::wl_surface::WlSurface, - wayland::compositor::{add_destruction_hook, with_states}, -}; - -fn mark_dirty_on_drop(state: &Common, wl_surface: &WlSurface) { - use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }; - - let dirty = state.dirty_flag.clone(); - struct DirtyFlag(Arc); - - with_states(wl_surface, |data| { - data.data_map.insert_if_missing(|| DirtyFlag(dirty)); - }); - add_destruction_hook(wl_surface, |data| { - if let Some(DirtyFlag(dirty)) = data.data_map.get::() { - dirty.store(true, Ordering::SeqCst); - } - }) -} diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs index 48594321..92981de0 100644 --- a/src/wayland/handlers/output_configuration.rs +++ b/src/wayland/handlers/output_configuration.rs @@ -81,10 +81,12 @@ impl State { } } + let seats = self.common.seats().cloned().collect::>(); if let Err(err) = self.backend.apply_config_for_output( output, test_only, &mut self.common.shell, + seats.iter().cloned(), &self.common.event_loop_handle, ) { slog_scope::warn!( @@ -106,6 +108,7 @@ impl State { output, false, &mut self.common.shell, + seats.iter().cloned(), &self.common.event_loop_handle, ) { slog_scope::error!( diff --git a/src/wayland/handlers/presentation.rs b/src/wayland/handlers/presentation.rs new file mode 100644 index 00000000..96dd14e7 --- /dev/null +++ b/src/wayland/handlers/presentation.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use smithay::delegate_presentation; + +delegate_presentation!(State); diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs new file mode 100644 index 00000000..d16d65dd --- /dev/null +++ b/src/wayland/handlers/screencopy.rs @@ -0,0 +1,1052 @@ +use std::{ + cell::RefCell, + collections::HashSet, + ops::{Deref, DerefMut}, +}; + +use anyhow::anyhow; +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::{ + FailureReason, InputType, +}; +use smithay::{ + backend::{ + allocator::dmabuf::Dmabuf, + drm::DrmNode, + egl::EGLDevice, + renderer::{ + buffer_dimensions, buffer_type, + damage::{DamageTrackedRenderer, DamageTrackedRendererError}, + element::{ + surface::WaylandSurfaceRenderElement, AsRenderElements, RenderElementStates, + }, + gles2::Gles2Renderbuffer, + glow::GlowRenderer, + Bind, BufferType, ExportMem, ImportAll, Offscreen, Renderer, + }, + }, + desktop::Window, + output::Output, + reexports::wayland_server::{ + protocol::{wl_buffer::WlBuffer, wl_shm::Format as ShmFormat, wl_surface::WlSurface}, + Resource, + }, + utils::{IsAlive, Physical, Rectangle, Scale, Transform}, + wayland::{ + dmabuf::get_dmabuf, + shm::{with_buffer_contents, with_buffer_contents_mut}, + }, +}; + +use crate::{ + backend::render::{cursor, render_output, render_workspace, CursorMode, CLEAR_COLOR}, + state::{BackendData, ClientState, Common, State}, + utils::prelude::OutputExt, + wayland::protocols::{ + screencopy::{ + delegate_screencopy, BufferInfo, BufferParams, CursorMode as ScreencopyCursorMode, + CursorSession, ScreencopyHandler, Session, SessionType, + }, + workspace::WorkspaceHandle, + }, +}; + +use super::data_device::get_dnd_icon; + +pub type PendingScreencopyBuffers = RefCell>; + +#[derive(Debug, Default)] +pub struct ScreencopySessions(pub RefCell>); + +#[derive(Debug)] +pub struct DropableSession(Session, FailureReason); +impl Deref for DropableSession { + type Target = Session; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for DropableSession { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl Drop for DropableSession { + fn drop(&mut self) { + self.0.failed(self.1); + } +} +impl PartialEq for DropableSession { + fn eq(&self, other: &Session) -> bool { + &self.0 == other + } +} + +pub type SessionDTR = RefCell; + +impl ScreencopyHandler for State { + fn capture_output(&mut self, output: Output, session: Session) -> Vec { + let formats = match formats_for_output(&output, &mut self.backend) { + Ok(formats) => formats, + Err(reason) => { + session.failed(reason); + return Vec::new(); + } + }; + + for seat in self.common.seats() { + if let Some(pointer) = seat.get_pointer() { + if output + .geometry() + .contains(pointer.current_location().to_i32_round()) + { + session.cursor_enter(seat, InputType::Pointer); + } + } + } + + session + .user_data() + .insert_if_missing(|| SessionDTR::new(DamageTrackedRenderer::from_output(&output))); + output + .user_data() + .insert_if_missing(ScreencopySessions::default); + output + .user_data() + .get::() + .unwrap() + .0 + .borrow_mut() + .push(DropableSession(session, FailureReason::InvalidOutput)); + + formats + } + + fn capture_workspace( + &mut self, + handle: WorkspaceHandle, + output: Output, + session: Session, + ) -> Vec { + let formats = match formats_for_output(&output, &mut self.backend) { + Ok(formats) => formats, + Err(reason) => { + session.failed(reason); + return Vec::new(); + } + }; + + let workspace = match self.common.shell.space_for_handle_mut(&handle) { + Some(workspace) => workspace, + None => { + session.failed(FailureReason::InvalidWorkspace); + return Vec::new(); + } + }; + + session + .user_data() + .insert_if_missing(|| SessionDTR::new(DamageTrackedRenderer::from_output(&output))); + + workspace + .screencopy_sessions + .push(DropableSession(session, FailureReason::InvalidWorkspace)); + + formats + } + + fn capture_toplevel(&mut self, toplevel: Window, session: Session) -> Vec { + let surface = toplevel.toplevel().wl_surface(); + let size = toplevel + .bbox_with_popups() + .size + .to_buffer(1, Transform::Normal); + + let mut _kms_renderer = None; + let renderer = match self.backend { + BackendData::Kms(ref mut kms) => { + let node = self + .common + .display_handle + .get_client(surface.id()) + .ok() + .and_then(|client| client.get_data::().unwrap().drm_node.clone()) + .unwrap_or(kms.primary.clone()); + _kms_renderer = Some(kms.api.renderer::(&node, &node).unwrap()); + _kms_renderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(ref mut winit) => winit.backend.renderer(), + BackendData::X11(ref mut x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + let mut formats = vec![ + BufferInfo::Shm { + format: ShmFormat::Abgr8888, + size, + stride: size.w as u32 * 4, + }, + BufferInfo::Shm { + format: ShmFormat::Xbgr8888, + size, + stride: size.w as u32 * 4, + }, + ]; + + if let Some(node) = EGLDevice::device_for_display(renderer.egl_context().display()) + .ok() + .and_then(|device| device.try_get_render_node().ok().flatten()) + { + formats.extend( + renderer + .egl_context() + .dmabuf_render_formats() + .iter() + .map(|format| format.code) + .collect::>() + .into_iter() + .map(|format| BufferInfo::Dmabuf { node, format, size }), + ); + } + + let size = toplevel.geometry().size.to_physical(1); + session.user_data().insert_if_missing(|| { + SessionDTR::new(DamageTrackedRenderer::new(size, 1.0, Transform::Normal)) + }); + toplevel + .user_data() + .insert_if_missing(ScreencopySessions::default); + toplevel + .user_data() + .get::() + .unwrap() + .0 + .borrow_mut() + .push(DropableSession(session, FailureReason::InvalidToplevel)); + + formats + } + + fn capture_cursor(&mut self, _session: CursorSession) -> Vec { + unimplemented!("We don't advertise the capture cursor mode") + } + + fn buffer_attached(&mut self, session: Session, params: BufferParams, on_damage: bool) { + // verify buffer size + let buffer_size = match buffer_dimensions(¶ms.buffer) { + Some(size) => size.to_logical(1, Transform::Normal), + None => { + slog_scope::warn!("Error during screencopy session: Buffer has no size"); + session.failed(FailureReason::InvalidBuffer); + return; + } + }; + match session.session_type() { + SessionType::Output(output) | SessionType::Workspace(output, _) => { + let mode = match output.current_mode() { + Some(mode) => mode, + None => { + slog_scope::warn!("Error during screencopy session: Output has no mode"); + session.failed(FailureReason::InvalidOutput); + return; + } + } + .size; + + if buffer_size.to_physical(1) != mode { + slog_scope::warn!("Error during screencopy session: Buffer size doesn't match"); + session.failed(FailureReason::InvalidSize); + return; + } + } + SessionType::Window(window) => { + let geometry = window.geometry(); + if buffer_size != geometry.size { + slog_scope::warn!("Error during screencopy session: Buffer size doesn't match"); + session.failed(FailureReason::InvalidSize); + return; + } + } + _ => {} + }; + + if !matches!( + buffer_type(¶ms.buffer), + Some(BufferType::Shm) | Some(BufferType::Dma) + ) { + slog_scope::warn!("Error during screencopy session: Buffer is neither shm or dma"); + session.failed(FailureReason::InvalidBuffer); + return; + } + + if let Some(BufferType::Shm) = buffer_type(¶ms.buffer) { + if with_buffer_contents(¶ms.buffer, |_, info| { + info.format != ShmFormat::Abgr8888 && info.format != ShmFormat::Xbgr8888 + }) + .unwrap() + { + slog_scope::warn!("Error during screencopy session: Invalid shm buffer format"); + session.failed(FailureReason::InvalidBuffer); + return; + } + } + + if on_damage { + match session.session_type() { + SessionType::Output(output) => { + output + .user_data() + .insert_if_missing(PendingScreencopyBuffers::default); + output + .user_data() + .get::() + .unwrap() + .borrow_mut() + .push((session, params)); + } + SessionType::Workspace(_output, handle) => { + match self.common.shell.space_for_handle_mut(&handle) { + Some(workspace) => workspace.pending_buffers.push((session, params)), + None => session.failed(FailureReason::InvalidWorkspace), + }; + } + SessionType::Window(window) => { + window + .user_data() + .insert_if_missing(PendingScreencopyBuffers::default); + window + .user_data() + .get::() + .unwrap() + .borrow_mut() + .push((session, params)); + } + _ => unreachable!(), + }; + } else { + let result = match session.session_type() { + SessionType::Output(output) => { + render_output_to_buffer(self, &session, params, &output) + } + SessionType::Workspace(output, handle) => { + render_workspace_to_buffer(self, &session, params, &output, &handle) + } + SessionType::Window(window) => { + render_window_to_buffer(self, &session, params, &window) + } + _ => unreachable!("Session types not supported"), + }; + + match result { + Ok(false) => { + // client didn't wanna wait for damage, so it gets empty damage + session.commit_buffer( + match session.session_type() { + SessionType::Output(output) | SessionType::Workspace(output, _) => { + output.current_transform() + } + _ => Transform::Normal, + }, + Vec::new(), + None, + ); + } + Ok(true) => {} // success + Err((reason, error)) => { + slog_scope::warn!("Error during screencopy session: {}", error); + session.failed(reason); + } + } + } + } + + fn cursor_session_destroyed(&mut self, _session: CursorSession) { + unreachable!("We currently don't support cursor sessions"); + } + + fn session_destroyed(&mut self, session: Session) { + match session.session_type() { + SessionType::Output(output) => { + if let Some(pending_buffers) = output.user_data().get::() + { + pending_buffers.borrow_mut().retain(|(s, _)| s != &session); + } + if let Some(sessions) = output.user_data().get::() { + sessions.0.borrow_mut().retain(|s| s != &session); + } + } + SessionType::Workspace(_, handle) => { + if let Some(workspace) = self.common.shell.space_for_handle_mut(&handle) { + workspace.pending_buffers.retain(|(s, _)| s != &session); + workspace.screencopy_sessions.retain(|s| s != &session); + } + } + SessionType::Window(window) => { + if let Some(pending_buffers) = window.user_data().get::() + { + pending_buffers.borrow_mut().retain(|(s, _)| s != &session); + } + if let Some(sessions) = window.user_data().get::() { + sessions.0.borrow_mut().retain(|s| s != &session); + } + } + _ => {} + } + } +} + +fn formats_for_output( + output: &Output, + backend: &mut BackendData, +) -> Result, FailureReason> { + let mode = match output.current_mode() { + Some(mode) => mode.size.to_logical(1).to_buffer(1, Transform::Normal), + None => { + return Err(FailureReason::InvalidOutput); + } + }; + + let mut _kms_renderer = None; + let renderer = match backend { + BackendData::Kms(ref mut kms) => { + let node = kms.target_node_for_output(&output).unwrap_or(kms.primary); + _kms_renderer = Some(kms.api.renderer::(&node, &node).unwrap()); + _kms_renderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(ref mut winit) => winit.backend.renderer(), + BackendData::X11(ref mut x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + let mut formats = vec![ + BufferInfo::Shm { + format: ShmFormat::Abgr8888, + size: mode, + stride: mode.w as u32 * 4, + }, + BufferInfo::Shm { + format: ShmFormat::Xbgr8888, + size: mode, + stride: mode.w as u32 * 4, + }, + ]; + + if let Some(node) = EGLDevice::device_for_display(renderer.egl_context().display()) + .ok() + .and_then(|device| device.try_get_render_node().ok().flatten()) + { + formats.extend( + renderer + .egl_context() + .dmabuf_render_formats() + .iter() + .map(|format| format.code) + .collect::>() + .into_iter() + .map(|format| BufferInfo::Dmabuf { + node, + format, + size: mode, + }), + ); + } + + Ok(formats) +} + +fn node_from_params( + params: &BufferParams, + backend: &BackendData, + output: Option<&Output>, +) -> Option { + match buffer_type(¶ms.buffer) { + Some(BufferType::Dma) if params.node.is_some() => params.node.clone(), + Some(BufferType::Shm) | Some(BufferType::Dma) => match backend { + BackendData::Kms(kms) => Some( + output + .and_then(|output| kms.target_node_for_output(output)) + .unwrap_or(kms.primary), + ), + _ => None, + }, + _ => unreachable!(), + } +} + +fn submit_buffer( + session: &Session, + buffer: &WlBuffer, + renderer: &mut R, + transform: Transform, + damage: Vec>, +) -> Result<(), ::Error> +where + R: ExportMem, +{ + if matches!(buffer_type(buffer), Some(BufferType::Shm)) { + let buffer_size = buffer_dimensions(buffer).unwrap(); + with_buffer_contents_mut(buffer, |slice, data| { + let offset = data.offset as i32; + let width = data.width as i32; + let height = data.height as i32; + let stride = data.stride as i32; + + // number of bytes per pixel + // TODO: compute from data.format + let pixelsize = 4i32; + + // ensure consistency, the SHM handler of smithay should ensure this + assert!((offset + (height - 1) * stride + width * pixelsize) as usize <= slice.len()); + + let mapping = + renderer.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), buffer_size))?; + let gl_data = renderer.map_texture(&mapping)?; + assert!((width * height * pixelsize) as usize <= gl_data.len()); + + for i in 0..height { + unsafe { + std::ptr::copy_nonoverlapping::( + gl_data.as_ptr().offset((width * pixelsize * i) as isize), + slice.as_mut_ptr().offset((offset + stride * i) as isize), + (width * pixelsize) as usize, + ); + } + } + Ok(()) + }) + .unwrap()?; + } + + session.commit_buffer(transform, damage, None); + + Ok(()) +} + +pub fn render_session( + node: Option, + renderer: &mut R, + session: &Session, + params: &BufferParams, + transform: Transform, + render_fn: F, +) -> Result> +where + R: ExportMem, + F: FnOnce( + Option<&DrmNode>, + &WlBuffer, + &mut R, + &mut DamageTrackedRenderer, + usize, + ) -> Result< + (Option>>, RenderElementStates), + DamageTrackedRendererError, + >, +{ + let mut dtr = session + .user_data() + .get::() + .unwrap() + .borrow_mut(); + + let res = render_fn( + node.as_ref(), + ¶ms.buffer, + renderer, + &mut *dtr, + params.age as usize, + )?; + + if let (Some(damage), _) = res { + submit_buffer(session, ¶ms.buffer, renderer, transform, damage) + .map_err(DamageTrackedRendererError::Rendering)?; + Ok(true) + } else { + Ok(false) + } +} + +pub fn render_output_to_buffer( + state: &mut State, + session: &Session, + params: BufferParams, + output: &Output, +) -> Result { + let mode = output + .current_mode() + .map(|mode| mode.size.to_logical(1).to_buffer(1, Transform::Normal)); + let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); + if mode != Some(buffer_size) { + return Err((FailureReason::InvalidSize, anyhow!("Output changed mode"))); + } + + let node = node_from_params(¶ms, &mut state.backend, Some(output)); + let mut _tmp_multirenderer = None; + let renderer = match &mut state.backend { + BackendData::Kms(kms) => { + _tmp_multirenderer = Some( + kms.api + .renderer::(node.as_ref().unwrap(), node.as_ref().unwrap()) + .map_err(|err| (FailureReason::Unspec, err.into()))?, + ); + _tmp_multirenderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(winit) => winit.backend.renderer(), + BackendData::X11(x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + let common = &mut state.common; + render_session::<_, _>( + node, + renderer, + session, + ¶ms, + output.current_transform(), + |node, buffer, renderer, dtr, age| { + let cursor_mode = match session.cursor_mode() { + ScreencopyCursorMode::Embedded => CursorMode::All, + ScreencopyCursorMode::Captured(_) | ScreencopyCursorMode::None => CursorMode::None, + }; + + if let Ok(dmabuf) = get_dmabuf(buffer) { + render_output::<_, _, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + dmabuf, + dtr, + age, + common, + &output, + cursor_mode, + None, + None, + ) + } else { + let size = buffer_dimensions(buffer).unwrap(); + let render_buffer = Offscreen::::create_buffer(renderer, size) + .map_err(DamageTrackedRendererError::Rendering)?; + render_output::<_, _, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + render_buffer, + dtr, + age, + common, + &output, + cursor_mode, + None, + None, + ) + } + }, + ) + .map_err(|err| (FailureReason::Unspec, err.into())) +} + +pub fn render_workspace_to_buffer( + state: &mut State, + session: &Session, + params: BufferParams, + output: &Output, + handle: &WorkspaceHandle, +) -> Result { + let mode = output + .current_mode() + .map(|mode| mode.size.to_logical(1).to_buffer(1, Transform::Normal)); + let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); + if mode != Some(buffer_size) { + return Err((FailureReason::InvalidSize, anyhow!("Output changed mode"))); + } + + let node = node_from_params(¶ms, &mut state.backend, Some(output)); + let mut _tmp_multirenderer = None; + let renderer = match &mut state.backend { + BackendData::Kms(kms) => { + _tmp_multirenderer = Some( + kms.api + .renderer::(node.as_ref().unwrap(), node.as_ref().unwrap()) + .map_err(|err| (FailureReason::Unspec, err.into()))?, + ); + _tmp_multirenderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(winit) => winit.backend.renderer(), + BackendData::X11(x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + let common = &mut state.common; + render_session::<_, _>( + node, + renderer, + session, + ¶ms, + output.current_transform(), + |node, buffer, renderer, dtr, age| { + let cursor_mode = match session.cursor_mode() { + ScreencopyCursorMode::Embedded => CursorMode::All, + ScreencopyCursorMode::Captured(_) | ScreencopyCursorMode::None => CursorMode::None, + }; + if let Ok(dmabuf) = get_dmabuf(buffer) { + render_workspace::<_, _, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + dmabuf, + dtr, + age, + common, + &output, + handle, + cursor_mode, + None, + None, + ) + } else { + let size = buffer_dimensions(buffer).unwrap(); + let render_buffer = Offscreen::::create_buffer(renderer, size) + .map_err(DamageTrackedRendererError::Rendering)?; + render_workspace::<_, _, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + render_buffer, + dtr, + age, + common, + &output, + handle, + cursor_mode, + None, + None, + ) + } + }, + ) + .map_err(|err| (FailureReason::Unspec, err.into())) +} + +smithay::render_elements! { + pub WindowCaptureElement where R: ImportAll; + WaylandElement=WaylandSurfaceRenderElement, + CursorElement=cursor::CursorRenderElement, +} + +pub fn render_window_to_buffer( + state: &mut State, + session: &Session, + params: BufferParams, + window: &Window, +) -> Result { + let geometry = window.geometry(); + let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); + if buffer_size != geometry.size.to_buffer(1, Transform::Normal) { + return Err((FailureReason::InvalidSize, anyhow!("Window changed size"))); + } + + let node = node_from_params(¶ms, &mut state.backend, None); + let mut _tmp_multirenderer = None; + let renderer = match &mut state.backend { + BackendData::Kms(kms) => { + _tmp_multirenderer = Some( + kms.api + .renderer::(node.as_ref().unwrap(), node.as_ref().unwrap()) + .map_err(|err| (FailureReason::Unspec, err.into()))?, + ); + _tmp_multirenderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(winit) => winit.backend.renderer(), + BackendData::X11(x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + render_session::<_, _>( + node, + renderer, + session, + ¶ms, + Transform::Normal, + |_node, buffer, renderer, dtr, age| { + // TODO cursor elements! + let mut elements = AsRenderElements::::render_elements::< + WindowCaptureElement, + >( + window, + (-geometry.loc.x, -geometry.loc.y).into(), + Scale::from(1.0), + ); + + for seat in state.common.seats() { + if let Some(location) = { + // we need to find the mapped element in that case + if let Some(mapped) = state + .common + .shell + .element_for_surface(window.toplevel().wl_surface()) + { + mapped.cursor_position(seat).and_then(|mut p| { + p -= mapped.active_window_offset().loc.to_f64(); + if p.x < 0. || p.y < 0. { + None + } else { + Some(p) + } + }) + } else { + None + } + } { + if session.cursor_mode() == ScreencopyCursorMode::Embedded { + elements.extend( + cursor::draw_cursor( + renderer, + seat, + location, + 1.0.into(), + state.common.clock.now(), + true, + ) + .into_iter() + .map(WindowCaptureElement::from), + ); + } + + if let Some(wl_surface) = get_dnd_icon(seat) { + elements.extend( + cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), 1.0) + .into_iter() + .map(WindowCaptureElement::from), + ); + } + } + } + + if let Ok(dmabuf) = get_dmabuf(buffer) { + renderer + .bind(dmabuf) + .map_err(DamageTrackedRendererError::Rendering)?; + } else { + let size = buffer_dimensions(buffer).unwrap(); + let render_buffer = Offscreen::::create_buffer(renderer, size) + .map_err(DamageTrackedRendererError::Rendering)?; + renderer + .bind(render_buffer) + .map_err(DamageTrackedRendererError::Rendering)?; + } + + dtr.render_output(renderer, age, &elements, CLEAR_COLOR, None) + }, + ) + .map_err(|err| (FailureReason::Unspec, err.into())) +} + +impl Common { + pub fn still_pending(&mut self, session: Session, params: BufferParams) { + match session.session_type() { + SessionType::Output(output) => { + if output + .user_data() + .get::() + .map_or(false, |sessions| { + sessions.0.borrow().iter().any(|s| &*s == &session) + }) + { + output + .user_data() + .get::() + .unwrap() + .borrow_mut() + .push((session, params)); + } + } + SessionType::Workspace(_output, handle) => { + if let Some(space) = self.shell.space_for_handle_mut(&handle) { + if space.screencopy_sessions.iter().any(|s| s == &session) { + space.pending_buffers.push((session, params)); + } + } + } + SessionType::Window(window) => { + if window + .user_data() + .get::() + .map_or(false, |sessions| { + sessions.0.borrow().iter().any(|s| &*s == &session) + }) + { + window + .user_data() + .get::() + .unwrap() + .borrow_mut() + .push((session, params)); + } + } + _ => {} + } + } +} + +pub trait UserdataExt { + fn sessions(&self) -> Vec; + fn pending_buffers( + &self, + ) -> std::iter::Flatten>>; +} + +impl UserdataExt for Output { + fn sessions(&self) -> Vec { + self.user_data() + .get::() + .map_or(Vec::new(), |sessions| { + sessions.0.borrow().iter().map(|s| s.0.clone()).collect() + }) + } + + fn pending_buffers( + &self, + ) -> std::iter::Flatten>> + { + self.user_data() + .get::() + .map(|pending| pending.borrow_mut().split_off(0).into_iter()) + .into_iter() + .flatten() + } +} + +impl UserdataExt for Window { + fn sessions(&self) -> Vec { + self.user_data() + .get::() + .map_or(Vec::new(), |sessions| { + sessions.0.borrow().iter().map(|s| s.0.clone()).collect() + }) + } + + fn pending_buffers( + &self, + ) -> std::iter::Flatten>> + { + self.user_data() + .get::() + .map(|pending| pending.borrow_mut().split_off(0).into_iter()) + .into_iter() + .flatten() + } +} + +impl State { + pub fn schedule_window_session(&mut self, surface: &WlSurface) { + if let Some(element) = self.common.shell.element_for_surface(surface).cloned() { + let active = element.active_window(); + if active.toplevel().wl_surface() == surface { + for (session, params) in active.pending_buffers() { + let window = active.clone(); + self.common.event_loop_handle.insert_idle(move |data| { + if !session.alive() { + return; + } + + match render_window_to_buffer( + &mut data.state, + &session, + params.clone(), + &window, + ) { + // rendering yielded no damage, buffer is still pending + Ok(false) => data.state.common.still_pending(session, params), + Ok(true) => {} // success + Err((reason, err)) => { + slog_scope::warn!("Screencopy session failed: {}", err); + session.failed(reason); + } + } + }); + } + } + } + } + + pub fn workspace_session_for_output( + &mut self, + output: &Output, + ) -> Option> { + let workspace = self.common.shell.active_space_mut(output); + if !workspace.pending_buffers.is_empty() { + Some(std::mem::take(&mut workspace.pending_buffers)) + } else { + None + } + } + + pub fn schedule_workspace_sessions( + &mut self, + surface: &WlSurface, + ) -> Option> { + // here we store additional workspace_sessions, we should handle, when rendering the corresponding output anyway + let mut output_sessions: Option> = None; + + // lets check which workspaces this surface belongs to + let active_spaces = self + .common + .shell + .outputs() + .map(|o| (o.clone(), self.common.shell.active_space(o).handle.clone())) + .collect::>(); + for (handle, output) in self.common.shell.workspaces_for_surface(surface) { + let workspace = self.common.shell.space_for_handle_mut(&handle).unwrap(); + if !workspace.pending_buffers.is_empty() { + // TODO: replace with drain_filter.... + let mut i = 0; + while i < workspace.pending_buffers.len() { + if let SessionType::Workspace(o, w) = + workspace.pending_buffers[i].0.session_type() + { + if active_spaces.contains(&(o.clone(), w)) { + // surface is on an active workspace/output combo, add to workspace_sessions + let (session, params) = workspace.pending_buffers.remove(i); + output_sessions + .get_or_insert_with(Vec::new) + .push((session, params)); + } else if handle == w && output == o { + // surface is visible on an offscreen workspace session, schedule a new render + let (session, params) = workspace.pending_buffers.remove(i); + let output = output.clone(); + self.common.event_loop_handle.insert_idle(move |data| { + if !session.alive() { + return; + } + if !data.state.common.shell.outputs.contains(&output) { + return; + } + match render_workspace_to_buffer( + &mut data.state, + &session, + params.clone(), + &output, + &handle, + ) { + Ok(false) => { + // rendering yielded no new damage, buffer still pending + data.state.common.still_pending(session, params); + } + Ok(true) => {} + Err((reason, err)) => { + slog_scope::warn!("Screencopy session failed: {}", err); + session.failed(reason); + } + } + }); + } else { + i += 1; + } + } else { + unreachable!(); + } + } + } + } + + output_sessions + } +} + +delegate_screencopy!(State); diff --git a/src/wayland/handlers/seat.rs b/src/wayland/handlers/seat.rs index 47e87444..1af64bb7 100644 --- a/src/wayland/handlers/seat.rs +++ b/src/wayland/handlers/seat.rs @@ -1,17 +1,23 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::state::State; +use crate::{ + shell::focus::target::{KeyboardFocusTarget, PointerFocusTarget}, + state::State, +}; use smithay::{ delegate_seat, input::{pointer::CursorImageStatus, SeatHandler, SeatState}, - reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource}, - wayland::{data_device::set_data_device_focus, primary_selection::set_primary_focus}, + reexports::wayland_server::Resource, + wayland::{ + data_device::set_data_device_focus, primary_selection::set_primary_focus, + seat::WaylandFocus, + }, }; use std::cell::RefCell; impl SeatHandler for State { - type KeyboardFocus = WlSurface; - type PointerFocus = WlSurface; + type KeyboardFocus = KeyboardFocusTarget; + type PointerFocus = PointerFocusTarget; fn seat_state(&mut self) -> &mut SeatState { &mut self.common.seat_state @@ -35,10 +41,12 @@ impl SeatHandler for State { focused: Option<&Self::KeyboardFocus>, ) { let dh = &self.common.display_handle; - if let Some(client) = focused.and_then(|s| dh.get_client(s.id()).ok()) { - set_data_device_focus(dh, seat, Some(client)); - let client2 = focused.and_then(|s| dh.get_client(s.id()).ok()).unwrap(); - set_primary_focus(dh, seat, Some(client2)) + if let Some(client) = focused + .and_then(|t| t.wl_surface()) + .and_then(|s| dh.get_client(s.id()).ok()) + { + set_data_device_focus(dh, seat, Some(client.clone())); + set_primary_focus(dh, seat, Some(client)) } } } diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs index ae6bcbe2..68a02331 100644 --- a/src/wayland/handlers/toplevel_management.rs +++ b/src/wayland/handlers/toplevel_management.rs @@ -19,18 +19,35 @@ impl ToplevelManagementHandler for State { } fn activate(&mut self, _dh: &DisplayHandle, window: &Window, seat: Option>) { - if let Some(idx) = self + for output in self .common .shell - .space_for_window(window.toplevel().wl_surface()) - .map(|w| w.idx) + .outputs() + .cloned() + .collect::>() + .iter() { - let seat = seat.unwrap_or(self.common.last_active_seat.clone()); - let output = active_output(&seat, &self.common); - if self.common.shell.active_space(&output).idx != idx { - self.common.shell.activate(&seat, &output, idx as usize); + let maybe = self + .common + .shell + .workspaces + .spaces_for_output(output) + .enumerate() + .find(|(_, w)| w.windows().any(|w| &w == window)); + if let Some((idx, workspace)) = maybe { + let seat = seat.unwrap_or(self.common.last_active_seat().clone()); + let mapped = workspace + .mapped() + .find(|m| m.windows().any(|(w, _)| &w == window)) + .unwrap() + .clone(); + + std::mem::drop(workspace); + self.common.shell.activate(&output, idx as usize); + mapped.focus_window(window); + Common::set_focus(self, Some(&mapped.clone().into()), &seat, None); + return; } - Common::set_focus(self, Some(window.toplevel().wl_surface()), &seat, None); } } diff --git a/src/wayland/handlers/workspace.rs b/src/wayland/handlers/workspace.rs index 41344e1b..eecc24c6 100644 --- a/src/wayland/handlers/workspace.rs +++ b/src/wayland/handlers/workspace.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ + shell::WorkspaceMode, state::ClientState, utils::prelude::*, wayland::protocols::workspace::{ @@ -29,16 +30,22 @@ impl WorkspaceHandler for State { for request in requests.into_iter() { match request { Request::Activate(handle) => { - if let Some(idx) = self - .common - .shell - .spaces - .iter() - .position(|w| w.handle == handle) - { - let seat = &self.common.last_active_seat; - let output = active_output(seat, &self.common); - self.common.shell.activate(seat, &output, idx); + let maybe = match &self.common.shell.workspaces { + WorkspaceMode::Global(set) => set + .workspaces + .iter() + .position(|w| w.handle == handle) + .map(|i| (self.common.last_active_seat().active_output(), i)), + WorkspaceMode::OutputBound(sets, _) => sets.iter().find_map(|(o, set)| { + set.workspaces + .iter() + .position(|w| w.handle == handle) + .map(|i| (o.clone(), i)) + }), + }; + + if let Some((output, idx)) = maybe { + self.common.shell.activate(&output, idx); } } _ => {} diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index a43edc8f..a87a7435 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::utils::prelude::*; +use crate::{utils::prelude::*, wayland::protocols::screencopy::SessionType}; use smithay::{ delegate_xdg_shell, desktop::{ find_popup_root_surface, Kind, PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab, - PopupUngrabStrategy, Window, WindowSurfaceType, + PopupUngrabStrategy, Window, }, input::{ pointer::{Focus, GrabStartData as PointerGrabStartData}, @@ -17,12 +17,17 @@ use smithay::{ wayland_server::protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface}, }, utils::Serial, - wayland::shell::xdg::{ - Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, + wayland::{ + seat::WaylandFocus, + shell::xdg::{ + PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, + }, }, }; use std::cell::Cell; +use super::screencopy::PendingScreencopyBuffers; + pub mod popup; pub type PopupGrabData = Cell>>; @@ -33,21 +38,14 @@ impl XdgShellHandler for State { } fn new_toplevel(&mut self, surface: ToplevelSurface) { - super::mark_dirty_on_drop(&self.common, surface.wl_surface()); - - let seat = &self.common.last_active_seat; + let seat = self.common.last_active_seat().clone(); let window = Window::new(Kind::Xdg(surface)); self.common.shell.toplevel_info_state.new_toplevel(&window); - self.common - .shell - .pending_windows - .push((window, seat.clone())); + self.common.shell.pending_windows.push((window, seat)); // We will position the window after the first commit, when we know its size hints } fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) { - super::mark_dirty_on_drop(&self.common, surface.wl_surface()); - surface.with_pending_state(|state| { state.geometry = positioner.get_geometry(); state.positioner = positioner; @@ -67,35 +65,19 @@ impl XdgShellHandler for State { } } - fn ack_configure(&mut self, surface: WlSurface, configure: Configure) { - if let Configure::Toplevel(configure) = configure { - // If we would re-position the window inside the grab we would get a weird jittery animation. - // We only want to resize once the client has acknoledged & commited the new size, - // so we need to carefully track the state through different handlers. - if let Some(window) = - self.common - .shell - .space_for_window(&surface) - .and_then(|workspace| { - workspace - .space - .window_for_surface(&surface, WindowSurfaceType::TOPLEVEL) - }) - { - crate::shell::layout::floating::ResizeSurfaceGrab::ack_configure(window, configure) - } - } - } - fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) { let seat = Seat::from_resource(&seat).unwrap(); let kind = PopupKind::Xdg(surface); - if let Ok(root) = find_popup_root_surface(&kind) { + if let Some(root) = find_popup_root_surface(&kind) + .ok() + .and_then(|root| self.common.shell.element_for_surface(&root)) + { + let target = root.clone().into(); let ret = self .common .shell .popups - .grab_popup(root, kind, &seat, serial); + .grab_popup(target, kind, &seat, serial); if let Ok(mut grab) = ret { if let Some(keyboard) = seat.get_keyboard() { @@ -157,18 +139,36 @@ impl XdgShellHandler for State { fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) { let seat = Seat::from_resource(&seat).unwrap(); if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) { - let workspace = self + if let Some(mapped) = self .common .shell - .space_for_window_mut(surface.wl_surface()) - .unwrap(); - let window = workspace - .space - .window_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - - Shell::move_request(self, &window, &seat, serial, start_data); + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + let output = seat.active_output(); + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + if let Some(grab) = + workspace.move_request(&window, &seat, &output, serial, start_data) + { + let handle = workspace.handle; + self.common + .shell + .toplevel_info_state + .toplevel_leave_workspace(&window, &handle); + self.common + .shell + .toplevel_info_state + .toplevel_leave_output(&window, &output); + seat.get_pointer() + .unwrap() + .set_grab(self, grab, serial, Focus::Clear); + } + } + } } } @@ -181,35 +181,59 @@ impl XdgShellHandler for State { ) { let seat = Seat::from_resource(&seat).unwrap(); if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) { - Workspace::resize_request(self, surface.wl_surface(), &seat, serial, start_data, edges); + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + if let Some(grab) = + workspace.resize_request(&mapped, &seat, serial, start_data, edges) + { + seat.get_pointer() + .unwrap() + .set_grab(self, grab, serial, Focus::Clear); + } + } + } } } fn maximize_request(&mut self, surface: ToplevelSurface) { - let surface = surface.wl_surface(); - let seat = &self.common.last_active_seat; - let output = active_output(seat, &self.common); + let seat = self.common.last_active_seat(); + let output = seat.active_output(); - if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - workspace.maximize_request(&window, &output) + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + workspace.maximize_request(&window, &output) + } } } fn unmaximize_request(&mut self, surface: ToplevelSurface) { - let surface = surface.wl_surface(); - - if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - workspace.unmaximize_request(&window) + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + workspace.unmaximize_request(&window) + } } } @@ -218,30 +242,79 @@ impl XdgShellHandler for State { .as_ref() .and_then(Output::from_resource) .unwrap_or_else(|| { - let seat = &self.common.last_active_seat; - active_output(seat, &self.common) + let seat = self.common.last_active_seat(); + seat.active_output() }); - let surface = surface.wl_surface(); - if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - workspace.fullscreen_request(&window, &output) + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + workspace.fullscreen_request(&window, &output) + } } } fn unfullscreen_request(&mut self, surface: ToplevelSurface) { - let surface = surface.wl_surface(); - if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - workspace.unfullscreen_request(&window) + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + workspace.unfullscreen_request(&window) + } + } + } + + fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { + let outputs = self + .common + .shell + .visible_outputs_for_surface(surface.wl_surface()) + .collect::>(); + for output in outputs.iter() { + self.common.shell.active_space_mut(output).refresh(); + } + + // screencopy + let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface()); + for output in outputs.into_iter() { + if let Some(sessions) = output.user_data().get::() { + scheduled_sessions + .get_or_insert_with(Vec::new) + .extend(sessions.borrow_mut().drain(..)); + } + self.backend.schedule_render( + &self.common.event_loop_handle, + &output, + scheduled_sessions.as_ref().map(|sessions| { + sessions + .iter() + .filter(|(s, _)| match s.session_type() { + SessionType::Output(o) | SessionType::Workspace(o, _) + if o == output => + { + true + } + _ => false, + }) + .cloned() + .collect::>() + }), + ); } } } @@ -270,7 +343,6 @@ fn check_grab_preconditions( .as_ref() .unwrap() .0 - .id() .same_client_as(&surface.id()) { return None; diff --git a/src/wayland/handlers/xdg_shell/popup.rs b/src/wayland/handlers/xdg_shell/popup.rs index ef0aed53..a677ea24 100644 --- a/src/wayland/handlers/xdg_shell/popup.rs +++ b/src/wayland/handlers/xdg_shell/popup.rs @@ -3,8 +3,7 @@ use crate::{shell::Shell, utils::prelude::*}; use smithay::{ desktop::{ - layer_map_for_output, LayerSurface, PopupKind, PopupManager, Space, Window, - WindowSurfaceType, + layer_map_for_output, LayerSurface, PopupKind, PopupManager, Window, WindowSurfaceType, }, output::Output, reexports::{ @@ -27,12 +26,19 @@ use std::sync::Mutex; impl Shell { pub fn unconstrain_popup(&self, surface: &PopupSurface, positioner: &PositionerState) { if let Some(parent) = get_popup_toplevel(&surface) { - if let Some(workspace) = self.space_for_window(&parent) { - let window = workspace - .space - .window_for_surface(&parent, WindowSurfaceType::ALL) + if let Some(elem) = self.element_for_surface(&parent) { + let workspace = self.space_for(elem).unwrap(); + let element_loc = workspace.element_geometry(elem).unwrap().loc; + let (window, offset) = elem + .windows() + .find(|(w, _)| w.toplevel().wl_surface() == &parent) .unwrap(); - unconstrain_xdg_popup(surface, positioner, &workspace.space, window); + let window_geo_offset = window.geometry().loc; + let window_loc = element_loc + offset + window_geo_offset; + let anchor_point = get_anchor_point(&positioner) + window_loc; + if let Some(output) = workspace.output_under(anchor_point) { + unconstrain_xdg_popup(surface, positioner, window_loc, output.geometry()); + } } else if let Some((output, layer_surface)) = self.outputs().find_map(|o| { let map = layer_map_for_output(o); map.layer_for_surface(&parent, WindowSurfaceType::ALL) @@ -42,15 +48,14 @@ impl Shell { } } } - - pub fn update_reactive_popups(&self, window: &Window) { - if let Some(workspace) = self.space_for_window(window.toplevel().wl_surface()) { - update_reactive_popups(&workspace.space, window); - } - } } -pub fn update_reactive_popups(space: &Space, window: &Window) { +pub fn update_reactive_popups<'a>( + window: &Window, + loc: Point, + outputs: impl Iterator, +) { + let output_geo = outputs.map(|o| o.geometry()).collect::>(); for (popup, _) in PopupManager::popups_for_surface(window.toplevel().wl_surface()) { match popup { PopupKind::Xdg(surface) => { @@ -64,12 +69,19 @@ pub fn update_reactive_popups(space: &Space, window: &Window) { attributes.current.positioner.clone() }); if positioner.reactive { - unconstrain_xdg_popup(&surface, &positioner, space, window); - if let Err(err) = surface.send_configure() { - slog_scope::warn!( - "Compositor bug: Unable to re-configure reactive popup: {}", - err - ); + let anchor_point = get_anchor_point(&positioner) + loc; + if let Some(rect) = output_geo + .iter() + .find(|geo| geo.contains(anchor_point)) + .copied() + { + unconstrain_xdg_popup(&surface, &positioner, loc, rect); + if let Err(err) = surface.send_configure() { + slog_scope::warn!( + "Compositor bug: Unable to re-configure reactive popup: {}", + err + ); + } } } } @@ -80,32 +92,18 @@ pub fn update_reactive_popups(space: &Space, window: &Window) { fn unconstrain_xdg_popup( surface: &PopupSurface, positioner: &PositionerState, - space: &Space, - window: &Window, + window_loc: Point, + rect: Rectangle, ) { - let anchor_point = get_anchor_point(&positioner) + space.window_location(&window).unwrap(); - if let Some(output_rect) = space - .outputs_for_window(window) - .into_iter() - .find(|o| { - space - .output_geometry(o) - .map(|rect| rect.contains(anchor_point)) - .unwrap_or(false) - }) - .map(|o| space.output_geometry(&o).unwrap()) - { - // the output_rect represented relative to the parents coordinate system - let mut relative = output_rect; - relative.loc -= space.window_location(&window).unwrap(); - let offset = check_constrained(&surface, positioner.get_geometry(), relative); + let mut relative = rect; + relative.loc -= window_loc; + let offset = check_constrained(&surface, positioner.get_geometry(), relative); - if offset.x != 0 || offset.y != 0 { - slog_scope::debug!("Unconstraining popup: {:?}", surface); - if !unconstrain_flip(&surface, &positioner, relative) { - if !unconstrain_slide(&surface, &positioner, relative) { - unconstrain_resize(&surface, &positioner, relative); - } + if offset.x != 0 || offset.y != 0 { + slog_scope::debug!("Unconstraining popup: {:?}", surface); + if !unconstrain_flip(&surface, &positioner, relative) { + if !unconstrain_slide(&surface, &positioner, relative) { + unconstrain_resize(&surface, &positioner, relative); } } } @@ -319,7 +317,7 @@ fn get_anchor_point(positioner: &PositionerState) -> Point { .into() } -fn get_popup_toplevel(popup: &PopupSurface) -> Option { +pub fn get_popup_toplevel(popup: &PopupSurface) -> Option { let mut parent = popup.get_parent_surface()?; while get_role(&parent) == Some(XDG_POPUP_ROLE) { parent = with_states(&parent, |states| { diff --git a/src/wayland/protocols/export_dmabuf.rs b/src/wayland/protocols/export_dmabuf.rs deleted file mode 100644 index 0288eebf..00000000 --- a/src/wayland/protocols/export_dmabuf.rs +++ /dev/null @@ -1,308 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use smithay::{ - backend::{ - allocator::{dmabuf::Dmabuf, Buffer}, - drm::DrmNode, - }, - desktop::Window, - reexports::wayland_server::{ - self, backend::GlobalId, protocol::wl_output::WlOutput, Client, Dispatch, DisplayHandle, - GlobalDispatch, - }, -}; -use std::{ - fs::File, - io::{Seek, SeekFrom}, - os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}, - time::Instant, -}; - -use cosmic_protocols::export_dmabuf::v1::server::{ - zcosmic_export_dmabuf_frame_v1::{self, CancelReason, Flags, ZcosmicExportDmabufFrameV1}, - zcosmic_export_dmabuf_manager_v1::{self, ZcosmicExportDmabufManagerV1}, -}; - -use crate::wayland::protocols::{ - toplevel_info::{window_from_handle, ToplevelInfoHandler}, - workspace::{WorkspaceHandle, WorkspaceHandler}, -}; - -/// Export Dmabuf global state -#[derive(Debug)] -pub struct ExportDmabufState { - global: GlobalId, -} - -pub struct ExportDmabufGlobalData { - filter: Box Fn(&'a Client) -> bool + Send + Sync>, -} - -impl ExportDmabufState { - /// Create a new dmabuf global - pub fn new(display: &DisplayHandle, client_filter: F) -> ExportDmabufState - where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler - + 'static, - F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, - { - ExportDmabufState { - global: display.create_global::( - 1, - ExportDmabufGlobalData { - filter: Box::new(client_filter), - }, - ), - } - } - - /// Returns the export dmabuf global. - pub fn global(&self) -> GlobalId { - self.global.clone() - } -} - -pub enum CaptureError { - Temporary(Box), - Permanent(Box), - Resizing, -} - -pub struct Capture { - pub device: DrmNode, - pub dmabuf: Dmabuf, - pub presentation_time: Instant, -} - -pub trait ExportDmabufHandler { - fn capture_output( - &mut self, - dh: &DisplayHandle, - output: WlOutput, - overlay_cursor: bool, - ) -> Result; - fn capture_workspace( - &mut self, - dh: &DisplayHandle, - workspace: WorkspaceHandle, - output: WlOutput, - overlay_cursor: bool, - ) -> Result; - fn capture_toplevel( - &mut self, - dh: &DisplayHandle, - toplevel: Window, - overlay_cursor: bool, - ) -> Result; - fn start_time(&mut self) -> Instant; -} - -impl GlobalDispatch - for ExportDmabufState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler, -{ - fn bind( - _state: &mut D, - _handle: &DisplayHandle, - _client: &Client, - resource: wayland_server::New, - _global_data: &ExportDmabufGlobalData, - data_init: &mut wayland_server::DataInit<'_, D>, - ) { - data_init.init(resource, ()); - } - - fn can_view(client: Client, global_data: &ExportDmabufGlobalData) -> bool { - (global_data.filter)(&client) - } -} - -impl Dispatch for ExportDmabufState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler - + WorkspaceHandler - + ToplevelInfoHandler, -{ - fn request( - state: &mut D, - _client: &wayland_server::Client, - _resource: &ZcosmicExportDmabufManagerV1, - request: ::Request, - _data: &(), - dhandle: &DisplayHandle, - data_init: &mut wayland_server::DataInit<'_, D>, - ) { - let start_time = state.start_time(); - match request { - zcosmic_export_dmabuf_manager_v1::Request::CaptureOutput { - frame, - overlay_cursor, - output, - } => { - let frame = data_init.init(frame, ()); - match state.capture_output(dhandle, output, overlay_cursor != 0) { - Ok(capture) => handle_capture(capture, frame, start_time), - Err(err) => frame.cancel(err.into()), - } - } - zcosmic_export_dmabuf_manager_v1::Request::CaptureWorkspace { - frame, - overlay_cursor, - workspace, - output, - } => { - let frame = data_init.init(frame, ()); - match state.workspace_state().workspace_handle(&workspace) { - Some(workspace) => { - match state.capture_workspace( - dhandle, - workspace, - output, - overlay_cursor != 0, - ) { - Ok(capture) => handle_capture(capture, frame, start_time), - Err(err) => frame.cancel(err.into()), - } - } - None => frame.cancel(CancelReason::Permanent), - } - } - zcosmic_export_dmabuf_manager_v1::Request::CaptureToplevel { - frame, - overlay_cursor, - toplevel, - } => { - let frame = data_init.init(frame, ()); - match window_from_handle(toplevel) { - Some(window) => { - match state.capture_toplevel(dhandle, window, overlay_cursor != 0) { - Ok(capture) => handle_capture(capture, frame, start_time), - Err(err) => frame.cancel(err.into()), - } - } - None => frame.cancel(CancelReason::Permanent), - } - } - zcosmic_export_dmabuf_manager_v1::Request::Destroy => {} - _ => {} - } - } -} - -impl From for CancelReason { - fn from(err: CaptureError) -> Self { - match err { - CaptureError::Temporary(err) => { - slog_scope::debug!("Temporary Capture Error: {}", err); - CancelReason::Temporary - } - CaptureError::Permanent(err) => { - slog_scope::warn!("Permanent Capture Error: {}", err); - CancelReason::Permanent - } - CaptureError::Resizing => CancelReason::Resizing, - } - } -} - -fn handle_capture(capture: Capture, frame: ZcosmicExportDmabufFrameV1, start_time: Instant) { - let Capture { - device, - dmabuf, - presentation_time, - } = capture; - let format = dmabuf.format(); - let modifier: u64 = format.modifier.into(); - - frame.device(Vec::from(device.dev_id().to_ne_bytes())); - frame.frame( - dmabuf.width(), - dmabuf.height(), - 0, - 0, - if dmabuf.y_inverted() { 1 } else { 0 }, - Flags::Transient, - format.code as u32, - (modifier >> 32) as u32, - (modifier & 0xFFFFFFFF) as u32, - dmabuf.num_planes() as u32, - ); - - for (i, (handle, (offset, stride))) in dmabuf - .handles() - .zip(dmabuf.offsets().zip(dmabuf.strides())) - .enumerate() - { - // SAFETY: BorrowedFd is used for seeking - let mut file = unsafe { File::from_raw_fd(handle.as_raw_fd()) }; - let size = match file.seek(SeekFrom::End(0)) { - Ok(size) => size, - Err(err) => { - slog_scope::debug!("Temporary Capture Error: {}", err); - frame.cancel(zcosmic_export_dmabuf_frame_v1::CancelReason::Temporary); - return; - } - }; - if let Err(err) = file.rewind() { - slog_scope::debug!("Temporary Capture Error: {}", err); - frame.cancel(zcosmic_export_dmabuf_frame_v1::CancelReason::Temporary); - return; - } - // SAFETY: Converted back to raw_fd, no chance in ownership - let handle = file.into_raw_fd(); - // FDs are dup'ed by wayland-rs before sending them - frame.object(i as u32, handle, size as u32, offset, stride, i as u32); - } - - let duration = presentation_time.duration_since(start_time); - let (tv_sec, tv_nsec) = (duration.as_secs(), duration.subsec_nanos()); - frame.ready((tv_sec >> 32) as u32, (tv_sec & 0xFFFFFFFF) as u32, tv_nsec); -} - -impl Dispatch for ExportDmabufState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler, -{ - fn request( - _state: &mut D, - _client: &wayland_server::Client, - _resource: &ZcosmicExportDmabufFrameV1, - request: ::Request, - _data: &(), - _dhandle: &DisplayHandle, - _data_init: &mut wayland_server::DataInit<'_, D>, - ) { - match request { - zcosmic_export_dmabuf_frame_v1::Request::Destroy => {} - _ => {} - } - } -} - -macro_rules! delegate_export_dmabuf { - ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { - smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_manager_v1::ZcosmicExportDmabufManagerV1: $crate::wayland::protocols::export_dmabuf::ExportDmabufGlobalData - ] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_manager_v1::ZcosmicExportDmabufManagerV1: () - ] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_frame_v1::ZcosmicExportDmabufFrameV1: () - ] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState); - }; -} -pub(crate) use delegate_export_dmabuf; diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 2129bd98..e84f5ad1 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod drm; -pub mod export_dmabuf; +//pub mod export_dmabuf; pub mod output_configuration; +pub mod screencopy; pub mod toplevel_info; pub mod toplevel_management; pub mod workspace; diff --git a/src/wayland/protocols/output_configuration.rs b/src/wayland/protocols/output_configuration.rs index 45cb341d..9a98c52d 100644 --- a/src/wayland/protocols/output_configuration.rs +++ b/src/wayland/protocols/output_configuration.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use smithay::{ - output::{Mode, Output, OutputData}, + output::{Mode, Output}, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, @@ -17,6 +17,7 @@ use smithay::{ }, }, utils::{Logical, Physical, Point, Size, Transform}, + wayland::output::WlOutputData, }; use std::{ convert::{TryFrom, TryInto}, @@ -498,7 +499,7 @@ where impl OutputConfigurationState where D: GlobalDispatch - + GlobalDispatch + + GlobalDispatch + Dispatch + Dispatch + Dispatch diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs new file mode 100644 index 00000000..fe1b8ac8 --- /dev/null +++ b/src/wayland/protocols/screencopy.rs @@ -0,0 +1,946 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; + +use cosmic_protocols::screencopy::v1::server::{ + zcosmic_screencopy_manager_v1::{self, CursorMode as WlCursorMode, ZcosmicScreencopyManagerV1}, + zcosmic_screencopy_session_v1::{ + self, BufferType, FailureReason, InputType, ZcosmicScreencopySessionV1, + }, +}; +use smithay::{ + backend::{ + allocator::Fourcc as DrmFourcc, + drm::{DrmNode, NodeType}, + }, + desktop::Window, + input::{Seat, SeatHandler}, + output::Output, + reexports::wayland_server::{ + protocol::{wl_buffer::WlBuffer, wl_output, wl_seat::WlSeat, wl_shm::Format as ShmFormat}, + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + }, + utils::{user_data::UserDataMap, Buffer, IsAlive, Physical, Point, Rectangle, Size, Transform}, +}; +use wayland_backend::{ + protocol::WEnum, + server::{GlobalId, ObjectId}, +}; + +use crate::state::State; + +use super::{ + toplevel_info::window_from_handle, + workspace::{WorkspaceHandle, WorkspaceHandler}, +}; + +/// Screencopy global state +pub struct ScreencopyState { + global: GlobalId, +} + +pub struct ScreencopyGlobalData { + cursor_modes: Vec, + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +impl ScreencopyState { + /// Create a new screencopy global + pub fn new( + display: &DisplayHandle, + cursor_modes: I, + client_filter: F, + ) -> ScreencopyState + where + D: GlobalDispatch + + Dispatch> + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, + I: IntoIterator, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + ScreencopyState { + global: display.create_global::( + 1, + ScreencopyGlobalData { + cursor_modes: Vec::from_iter(cursor_modes), + filter: Box::new(client_filter), + }, + ), + } + } + + /// Returns the screencopy global id + pub fn global(&self) -> GlobalId { + self.global.clone() + } +} + +#[derive(Debug)] +pub enum BufferInfo { + Shm { + format: ShmFormat, + size: Size, + stride: u32, + }, + Dmabuf { + node: DrmNode, + format: DrmFourcc, + size: Size, + }, +} + +#[derive(Debug)] +pub struct SessionDataInnerInner { + gone: bool, + pending_buffer: Option, + _type: SessionType, + aux: AuxData, +} + +impl SessionDataInnerInner { + pub fn is_cursor(&self) -> bool { + match self.aux { + AuxData::Cursor { .. } => true, + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SessionType { + Output(Output), + Workspace(Output, WorkspaceHandle), + Window(Window), + Cursor(Seat), + #[doc(hidden)] + Unknown, +} + +#[derive(Debug)] +enum AuxData { + Normal { cursor: CursorMode }, + Cursor { seat: WlSeat }, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum CursorMode { + Captured(Vec), + Embedded, + None, +} + +impl AuxData { + pub fn seat(&self) -> &WlSeat { + match self { + AuxData::Cursor { seat } => seat, + _ => unreachable!("Unwrapped seat from aux data"), + } + } + + pub fn cursor(&self) -> &CursorMode { + match self { + AuxData::Normal { cursor } => &cursor, + _ => unreachable!("Unwrapped cursor from aux data"), + } + } +} + +impl CursorMode { + pub fn sessions<'a>(&'a self) -> impl Iterator { + match self { + CursorMode::Captured(sessions) => Some(sessions.iter()).into_iter().flatten(), + _ => None.into_iter().flatten(), + } + } +} + +#[derive(Debug)] +pub struct SessionDataInner { + inner: Mutex, + user_data: UserDataMap, +} + +pub type SessionData = Arc; + +#[derive(Debug, Clone)] +pub struct Session { + obj: SessionResource, + data: SessionData, +} + +#[derive(Debug, Clone)] +enum SessionResource { + Alive(ZcosmicScreencopySessionV1), + Destroyed(ObjectId), +} + +impl SessionResource { + fn client(&self) -> Option { + match self { + SessionResource::Alive(obj) => obj.client(), + _ => None, + } + } + + fn buffer_info( + &self, + _type: BufferType, + node: Option, + format: u32, + width: u32, + height: u32, + stride: u32, + ) { + if let SessionResource::Alive(obj) = self { + obj.buffer_info(_type, node, format, width, height, stride) + } + } + + fn init_done(&self) { + if let SessionResource::Alive(obj) = self { + obj.init_done() + } + } + + fn transform(&self, transform: wl_output::Transform) { + if let SessionResource::Alive(obj) = self { + obj.transform(transform) + } + } + + fn damage(&self, x: u32, y: u32, w: u32, h: u32) { + if let SessionResource::Alive(obj) = self { + obj.damage(x, y, w, h) + } + } + + fn commit_time(&self, time_sec_hi: u32, time_sec_lo: u32, time_nsec: u32) { + if let SessionResource::Alive(obj) = self { + obj.commit_time(time_sec_hi, time_sec_lo, time_nsec) + } + } + + fn ready(&self) { + if let SessionResource::Alive(obj) = self { + obj.ready() + } + } + + fn failed(&self, reason: FailureReason) { + if let SessionResource::Alive(obj) = self { + obj.failed(reason) + } + } + + fn cursor_enter(&self, wl_seat: &WlSeat, input_type: InputType) { + if let SessionResource::Alive(obj) = self { + obj.cursor_enter(wl_seat, input_type) + } + } + + fn cursor_info( + &self, + wl_seat: &WlSeat, + input_type: InputType, + x: i32, + y: i32, + w: i32, + h: i32, + dx: i32, + dy: i32, + ) { + if let SessionResource::Alive(obj) = self { + obj.cursor_info(wl_seat, input_type, x, y, w, h, dx, dy) + } + } + + fn cursor_leave(&self, wl_seat: &WlSeat, input_type: InputType) { + if let SessionResource::Alive(obj) = self { + obj.cursor_leave(wl_seat, input_type) + } + } +} + +impl PartialEq for SessionResource { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (SessionResource::Alive(obj1), SessionResource::Alive(obj2)) => obj1 == obj2, + (SessionResource::Alive(obj), SessionResource::Destroyed(id)) + | (SessionResource::Destroyed(id), SessionResource::Alive(obj)) => obj.id() == *id, + (SessionResource::Destroyed(id1), SessionResource::Destroyed(id2)) => id1 == id2, + } + } +} + +impl PartialEq for Session { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } +} + +// TODO: Handle Alive + +// TODO: Better errors + +impl Session { + pub fn cursor_enter(&self, seat: &Seat, input_type: InputType) { + if !self.alive() { + return; + } + if let Some(client) = self.obj.client() { + for wl_seat in seat.client_seats(&client) { + self.obj.cursor_enter(&wl_seat, input_type) + } + } + } + + pub fn cursor_info( + &self, + seat: &Seat, + input_type: InputType, + geometry: Rectangle, + offset: Point, + ) { + if !self.alive() { + return; + } + if let Some(client) = self.obj.client() { + for wl_seat in seat.client_seats(&client) { + self.obj.cursor_info( + &wl_seat, + input_type, + geometry.loc.x, + geometry.loc.y, + geometry.size.w, + geometry.size.h, + offset.x, + offset.y, + ); + let data = self.data.inner.lock().unwrap(); + for cursor_session in data.aux.cursor().sessions() { + cursor_session.obj.cursor_info( + &wl_seat, + input_type, + geometry.loc.x, + geometry.loc.y, + geometry.size.w, + geometry.size.h, + offset.x, + offset.y, + ); + } + } + } + } + + pub fn cursor_leave(&self, seat: &Seat, input_type: InputType) { + if !self.alive() { + return; + } + if let Some(client) = self.obj.client() { + for wl_seat in seat.client_seats(&client) { + self.obj.cursor_leave(&wl_seat, input_type) + } + } + } + + pub fn cursor_sessions(&self) -> impl Iterator { + if !self.alive() { + return Vec::new().into_iter(); + } + self.data + .inner + .lock() + .unwrap() + .aux + .cursor() + .sessions() + .cloned() + .collect::>() + .into_iter() + } + + pub fn commit_buffer( + &self, + transform: Transform, + damage: Vec>, + time: Option, + ) { + if !self.alive() { + return; + } + self.obj.transform(transform.into()); + for rect in damage { + self.obj.damage( + rect.loc.x as u32, + rect.loc.y as u32, + rect.size.w as u32, + rect.size.h as u32, + ); + } + if let Some(time) = time { + let tv_sec_hi = (time.as_secs() >> 32) as u32; + let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32; + self.obj + .commit_time(tv_sec_hi, tv_sec_lo, time.subsec_nanos()); + } + self.obj.ready() + } + + pub fn failed(&self, reason: FailureReason) { + if !self.alive() { + return; + } + self.obj.failed(reason); + self.data.inner.lock().unwrap().gone = true; + } + + pub fn user_data(&self) -> &UserDataMap { + &self.data.user_data + } + + pub fn session_type(&self) -> SessionType { + self.data.inner.lock().unwrap()._type.clone() + } + + pub fn cursor_mode(&self) -> CursorMode { + self.data.inner.lock().unwrap().aux.cursor().clone() + } +} + +impl IsAlive for Session { + fn alive(&self) -> bool { + !self.data.inner.lock().unwrap().gone + } +} + +#[derive(Debug, Clone)] +pub struct CursorSession { + obj: SessionResource, + data: SessionData, +} + +impl PartialEq for CursorSession { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } +} + +impl CursorSession { + pub fn seat(&self) -> WlSeat { + self.data.inner.lock().unwrap().aux.seat().clone() + } + + pub fn buffer_waiting(&self) -> Option { + self.data.inner.lock().unwrap().pending_buffer.take() + } + + pub fn commit_buffer<'a>( + &self, + transform: Transform, + damage: impl Iterator> + 'a, + ) { + self.obj.transform(transform.into()); + for rect in damage { + self.obj.damage( + rect.loc.x as u32, + rect.loc.y as u32, + rect.size.w as u32, + rect.size.h as u32, + ); + } + } + + pub fn failed(&self, reason: FailureReason) { + self.obj.failed(reason); + self.data.inner.lock().unwrap().gone = true; + } + + pub fn user_data(&self) -> &UserDataMap { + &self.data.user_data + } +} + +impl IsAlive for CursorSession { + fn alive(&self) -> bool { + !self.data.inner.lock().unwrap().gone + } +} + +#[derive(Debug, Clone)] +pub struct BufferParams { + pub buffer: WlBuffer, + pub node: Option, + pub age: u32, +} + +pub trait ScreencopyHandler { + fn capture_output(&mut self, output: Output, session: Session) -> Vec; + + fn capture_workspace( + &mut self, + workspace: WorkspaceHandle, + output: Output, + session: Session, + ) -> Vec; + + fn capture_toplevel(&mut self, toplevel: Window, session: Session) -> Vec; + + fn capture_cursor(&mut self, session: CursorSession) -> Vec; + + fn buffer_attached(&mut self, session: Session, buffer: BufferParams, on_damage: bool); + + fn cursor_session_destroyed(&mut self, session: CursorSession) { + let _ = session; + } + + fn session_destroyed(&mut self, session: Session) { + let _ = session; + } +} + +impl GlobalDispatch for ScreencopyState +where + D: GlobalDispatch + + Dispatch> + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + global_data: &ScreencopyGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let global = data_init.init(resource, global_data.cursor_modes.clone()); + for mode in &global_data.cursor_modes { + global.supported_cursor_mode(*mode); + } + } + + fn can_view(client: Client, global_data: &ScreencopyGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +fn check_cursor( + cursor: WEnum, + supported: &[WlCursorMode], + resource: &ZcosmicScreencopyManagerV1, +) -> Option { + match cursor.into_result() { + Ok(mode) => { + if !supported.contains(&mode) { + slog_scope::warn!("Client did send unsupported cursor mode: {:?}", mode); + resource.post_error( + zcosmic_screencopy_manager_v1::Error::InvalidCursorMode, + "Unsupported cursor mode", + ); + return None; + } + Some(mode) + } + Err(err) => { + slog_scope::warn!("Client did send unknown cursor mode: {}", err); + resource.post_error( + zcosmic_screencopy_manager_v1::Error::InvalidCursorMode, + "Unknown cursor mode, wrong protocol version?", + ); + None + } + } +} + +fn init_session( + data_init: &mut DataInit<'_, D>, + session: New, + cursor: WlCursorMode, + _type: SessionType, +) -> Option +where + D: GlobalDispatch + + Dispatch> + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + let data = Arc::new(SessionDataInner { + inner: Mutex::new(SessionDataInnerInner { + gone: false, + pending_buffer: None, + aux: AuxData::Normal { + cursor: match cursor { + WlCursorMode::Capture => CursorMode::Captured(Vec::new()), + WlCursorMode::Embedded => CursorMode::Embedded, + _ => CursorMode::None, + }, + }, + _type, + }), + user_data: UserDataMap::new(), + }); + let session = data_init.init(session, data.clone()); + + let session = Session { + obj: SessionResource::Alive(session), + data, + }; + + Some(session) +} + +impl Dispatch, D> for ScreencopyState +where + D: GlobalDispatch + + Dispatch> + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ZcosmicScreencopyManagerV1, + request: ::Request, + data: &Vec, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_screencopy_manager_v1::Request::CaptureOutput { + session, + output, + cursor, + } => { + let Some(cursor) = check_cursor(cursor, &data, resource) else { return; }; + + match Output::from_resource(&output) { + Some(output) => { + let session = match init_session( + data_init, + session, + cursor, + SessionType::Output(output.clone()), + ) { + Some(result) => result, + None => { + return; + } + }; + let formats = state.capture_output(output, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); + } + } + None => { + let session = + match init_session(data_init, session, cursor, SessionType::Unknown) { + Some(result) => result, + None => { + return; + } + }; + session.failed(FailureReason::InvalidOutput); + return; + } + } + } + zcosmic_screencopy_manager_v1::Request::CaptureToplevel { + session, + toplevel, + cursor, + } => { + let Some(cursor) = check_cursor(cursor, &data, resource) else { return; }; + + match window_from_handle(toplevel) { + Some(window) => { + let session = match init_session( + data_init, + session, + cursor, + SessionType::Window(window.clone()), + ) { + Some(result) => result, + None => { + return; + } + }; + + let formats = state.capture_toplevel(window, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); + } + } + None => { + let session = + match init_session(data_init, session, cursor, SessionType::Unknown) { + Some(result) => result, + None => { + return; + } + }; + session.obj.failed(FailureReason::InvalidToplevel); + return; + } + } + } + zcosmic_screencopy_manager_v1::Request::CaptureWorkspace { + session, + workspace, + output, + cursor, + } => { + let Some(cursor) = check_cursor(cursor, &data, resource) else { return; }; + + match Output::from_resource(&output) { + Some(output) => match state.workspace_state().workspace_handle(&workspace) { + Some(handle) => { + let session = match init_session( + data_init, + session, + cursor, + SessionType::Workspace(output.clone(), handle.clone()), + ) { + Some(result) => result, + None => { + return; + } + }; + let formats = state.capture_workspace(handle, output, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); + } + } + None => { + let session = match init_session( + data_init, + session, + cursor, + SessionType::Unknown, + ) { + Some(result) => result, + None => { + return; + } + }; + session.failed(FailureReason::InvalidWorkspace); + return; + } + }, + None => { + let session = + match init_session(data_init, session, cursor, SessionType::Unknown) { + Some(result) => result, + None => { + return; + } + }; + session.failed(FailureReason::InvalidOutput); + return; + } + } + } + _ => {} + } + } +} + +impl Dispatch for ScreencopyState +where + D: GlobalDispatch + + Dispatch> + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ZcosmicScreencopySessionV1, + request: ::Request, + data: &SessionData, + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_screencopy_session_v1::Request::CaptureCursor { + session, + seat: wl_seat, + } => match Seat::from_resource(&wl_seat) { + Some(seat) => { + { + let resource_data = data.inner.lock().unwrap(); + if resource_data.is_cursor() || resource_data.gone { + resource.failed(FailureReason::Unspec); + return; + } + } + + let data = Arc::new(SessionDataInner { + inner: Mutex::new(SessionDataInnerInner { + gone: false, + pending_buffer: None, + aux: AuxData::Cursor { seat: wl_seat }, + _type: SessionType::Cursor(seat), + }), + user_data: UserDataMap::new(), + }); + let session = data_init.init(session, data.clone()); + + let cursor_session = CursorSession { + obj: SessionResource::Alive(session), + data, + }; + let formats = state.capture_cursor(cursor_session.clone()); + if !cursor_session.data.inner.lock().unwrap().gone { + send_formats(&cursor_session.obj, formats); + } + } + None => { + let session = match init_session( + data_init, + session, + WlCursorMode::Capture, + SessionType::Unknown, + ) { + Some(result) => result, + None => { + return; + } + }; + session.failed(FailureReason::InvalidSeat); + return; + } + }, + zcosmic_screencopy_session_v1::Request::AttachBuffer { buffer, node, age } => { + if data.inner.lock().unwrap().gone { + resource.failed(FailureReason::Unspec); + return; + } + let params = BufferParams { + buffer, + node: node.and_then(|p| DrmNode::from_path(p).ok()), + age, + }; + data.inner.lock().unwrap().pending_buffer = Some(params); + } + zcosmic_screencopy_session_v1::Request::Commit { options } => { + let buffer = { + let mut resource_data = data.inner.lock().unwrap(); + if resource_data.is_cursor() || resource_data.gone { + resource.failed(FailureReason::Unspec); + return; + } + resource_data.pending_buffer.take() + }; + + if let Some(buffer) = buffer { + let session = Session { + obj: SessionResource::Alive(resource.clone()), + data: data.clone(), + }; + state.buffer_attached( + session, + buffer, + options + .into_result() + .ok() + .map(|v| v.contains(zcosmic_screencopy_session_v1::Options::OnDamage)) + .unwrap_or(false), + ); + } else { + resource.failed(FailureReason::InvalidBuffer); + } + } + zcosmic_screencopy_session_v1::Request::Destroy => { + data.inner.lock().unwrap().gone = true; + } + _ => {} + } + } + + fn destroyed( + state: &mut D, + _client: wayland_backend::server::ClientId, + resource: wayland_backend::server::ObjectId, + data: &SessionData, + ) { + if data.inner.lock().unwrap().is_cursor() { + let session = CursorSession { + obj: SessionResource::Destroyed(resource), + data: data.clone(), + }; + state.cursor_session_destroyed(session) + } else { + let session = Session { + obj: SessionResource::Destroyed(resource), + data: data.clone(), + }; + state.session_destroyed(session) + } + } +} + +fn send_formats(session: &SessionResource, formats: Vec) { + for format in formats { + match format { + BufferInfo::Dmabuf { node, format, size } => { + if let Some(node_path) = node + .dev_path_with_type(NodeType::Render) + .or_else(|| node.dev_path()) + { + session.buffer_info( + zcosmic_screencopy_session_v1::BufferType::Dmabuf, + Some(node_path.as_os_str().to_string_lossy().into_owned()), + format as u32, + size.w as u32, + size.h as u32, + 0, + ); + } + } + BufferInfo::Shm { + format, + size, + stride, + } => session.buffer_info( + zcosmic_screencopy_session_v1::BufferType::WlShm, + None, + format as u32, + size.w as u32, + size.h as u32, + stride, + ), + } + } + + session.init_done(); +} + +macro_rules! delegate_screencopy { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: $crate::wayland::protocols::screencopy::ScreencopyGlobalData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: std::vec::Vec + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1: $crate::wayland::protocols::screencopy::SessionData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + }; +} +pub(crate) use delegate_screencopy; diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 6a4c9b25..79cc8848 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -413,9 +413,9 @@ fn send_toplevel_to_client( .iter() .filter(|o| !handle_state.outputs.contains(o)) { - new_output.with_client_outputs(dh, &client, |_dh, wl_output| { - instance.output_enter(wl_output); - }); + for wl_output in new_output.client_outputs(&client) { + instance.output_enter(&wl_output); + } changed = true; } for old_output in handle_state @@ -423,9 +423,9 @@ fn send_toplevel_to_client( .iter() .filter(|o| !state.outputs.contains(o)) { - old_output.with_client_outputs(dh, &client, |_dh, wl_output| { - instance.output_leave(wl_output); - }); + for wl_output in old_output.client_outputs(&client) { + instance.output_leave(&wl_output); + } changed = true; } handle_state.outputs = state.outputs.clone(); diff --git a/src/wayland/protocols/toplevel_management.rs b/src/wayland/protocols/toplevel_management.rs index 46b95153..4dbad39f 100644 --- a/src/wayland/protocols/toplevel_management.rs +++ b/src/wayland/protocols/toplevel_management.rs @@ -182,12 +182,12 @@ where let window = window_from_handle(toplevel).unwrap(); if let Some(toplevel_state) = window.user_data().get::() { let mut toplevel_state = toplevel_state.lock().unwrap(); - if let Some(client) = surface.client_id() { + if let Some(client) = surface.client() { if width == 0 && height == 0 { - toplevel_state.rectangles.remove(&client); + toplevel_state.rectangles.remove(&client.id()); } else { toplevel_state.rectangles.insert( - client, + client.id(), ( surface, Rectangle::from_loc_and_size((x, y), (width, height)), @@ -207,7 +207,7 @@ where if !mng_state .instances .iter() - .any(|i| i.client_id().map(|c| c == client).unwrap_or(false)) + .any(|i| i.client().map(|c| c.id() == client).unwrap_or(false)) { for toplevel in state.toplevel_info_state_mut().toplevels.iter() { if let Some(toplevel_state) = toplevel.user_data().get::() { diff --git a/src/wayland/protocols/workspace.rs b/src/wayland/protocols/workspace.rs index 07cfdff2..6853b7eb 100644 --- a/src/wayland/protocols/workspace.rs +++ b/src/wayland/protocols/workspace.rs @@ -821,9 +821,9 @@ where .iter() .filter(|o| !handle_state.outputs.contains(o)) { - new_output.with_client_outputs(dh, &client, |_dh, wl_output| { - instance.output_enter(wl_output); - }); + for wl_output in new_output.client_outputs(&client) { + instance.output_enter(&wl_output); + } changed = true; } for old_output in handle_state @@ -831,9 +831,9 @@ where .iter() .filter(|o| !group.outputs.contains(o)) { - old_output.with_client_outputs(dh, &client, |_dh, wl_output| { - instance.output_leave(wl_output); - }); + for wl_output in old_output.client_outputs(&client) { + instance.output_leave(&wl_output); + } changed = true; } handle_state.outputs = group.outputs.clone();