diff --git a/Cargo.lock b/Cargo.lock index d09895c8c..6b843e32d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,7 +93,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -109,26 +109,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" +name = "async-channel" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", ] [[package]] @@ -222,7 +211,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -245,27 +234,25 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cairo-rs" -version = "0.18.5" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +checksum = "ae50b5510d86cf96ac2370e66d8dc960882f3df179d6a5a1e52bd94a1416c0f7" dependencies = [ "bitflags 2.8.0", "cairo-sys-rs", "glib", "libc", - "once_cell", - "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.18.2" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +checksum = "f18b6bb8e43c7eb0f2aac7976afe0c61b6f5fc2ab7bc4c139537ea56c92290df" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -295,6 +282,16 @@ dependencies = [ "target-lexicon", ] +[[package]] +name = "cfg-expr" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -329,6 +326,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -444,7 +450,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -476,7 +482,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -578,6 +584,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "exr" version = "1.73.0" @@ -646,7 +673,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -704,7 +731,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -727,52 +754,51 @@ dependencies = [ "slab", ] -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - [[package]] name = "gdk-pixbuf" -version = "0.18.5" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +checksum = "7563afd6ff0a221edfbb70a78add5075b8d9cb48e637a40a24c3ece3fea414d0" dependencies = [ "gdk-pixbuf-sys", "gio", "glib", "libc", - "once_cell", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.18.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +checksum = "67f2587c9202bf997476bbba6aaed4f78a11538a2567df002a5f57f5331d0b5c" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 7.0.3", +] + +[[package]] +name = "gdk4" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", ] [[package]] -name = "gdk-sys" -version = "0.18.2" +name = "gdk4-sys" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -782,34 +808,58 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps", + "system-deps 7.0.3", +] + +[[package]] +name = "gdk4-wayland" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd34518488cd624a85e75e82540bc24c72cfeb0aea6bad7faed683ca3977dba0" +dependencies = [ + "gdk4", + "gdk4-wayland-sys", + "gio", + "glib", + "libc", + "wayland-backend", + "wayland-client", ] [[package]] -name = "gdkwayland-sys" -version = "0.18.2" +name = "gdk4-wayland-sys" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +checksum = "7c7a0f2332c531d62ee3f14f5e839ac1abac59e9b052adf1495124c00d89a34b" dependencies = [ - "gdk-sys", "glib-sys", - "gobject-sys", "libc", - "pkg-config", - "system-deps", + "system-deps 7.0.3", ] [[package]] -name = "gdkx11-sys" -version = "0.18.2" +name = "gdk4-x11" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +checksum = "5c3e7380a9a206b170e1b52b5f25581406db816c68f4e7140dbef89a9e5b52ac" dependencies = [ - "gdk-sys", + "gdk4", + "gdk4-x11-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk4-x11-sys" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "070bd50a053f90d7fdf6be1d75672ea0f97c0e5da3a10dc6d02e5defcb0db32f" +dependencies = [ + "gdk4-sys", "glib-sys", "libc", - "system-deps", - "x11", + "system-deps 7.0.3", ] [[package]] @@ -845,9 +895,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.18.4" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +checksum = "a4f00c70f8029d84ea7572dd0e1aaa79e5329667b4c17f329d79ffb1e6277487" dependencies = [ "futures-channel", "futures-core", @@ -856,30 +906,28 @@ dependencies = [ "gio-sys", "glib", "libc", - "once_cell", "pin-project-lite", "smallvec", - "thiserror", ] [[package]] name = "gio-sys" -version = "0.18.1" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +checksum = "160eb5250a26998c3e1b54e6a3d4ea15c6c7762a6062a19a7b63eff6e2b33f9e" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", - "winapi", + "system-deps 7.0.3", + "windows-sys 0.59.0", ] [[package]] name = "glib" -version = "0.18.5" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +checksum = "707b819af8059ee5395a2de9f2317d87a53dbad8846a2f089f0bb44703f37686" dependencies = [ "bitflags 2.8.0", "futures-channel", @@ -893,96 +941,147 @@ dependencies = [ "gobject-sys", "libc", "memchr", - "once_cell", "smallvec", - "thiserror", ] [[package]] name = "glib-macros" -version = "0.18.5" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68" dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", + "heck", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] name = "glib-sys" -version = "0.18.1" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +checksum = "a8928869a44cfdd1fccb17d6746e4ff82c8f82e41ce705aa026a52ca8dc3aefb" dependencies = [ "libc", - "system-deps", + "system-deps 7.0.3", ] [[package]] name = "gobject-sys" -version = "0.18.0" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +checksum = "c773a3cb38a419ad9c26c81d177d96b4b08980e8bdbbf32dace883e96e96e7e3" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 7.0.3", ] [[package]] -name = "gtk" -version = "0.18.2" +name = "graphene-rs" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +checksum = "3cbc5911bfb32d68dcfa92c9510c462696c2f715548fcd7f3f1be424c739de19" +dependencies = [ + "glib", + "graphene-sys", + "libc", +] + +[[package]] +name = "graphene-sys" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a68d39515bf340e879b72cecd4a25c1332557757ada6e8aba8654b4b81d23a" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", + "system-deps 7.0.3", +] + +[[package]] +name = "gsk4" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855" dependencies = [ - "atk", "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", + "gdk4", "glib", - "gtk-sys", - "gtk3-macros", + "graphene-rs", + "gsk4-sys", "libc", "pango", - "pkg-config", ] [[package]] -name = "gtk-sys" -version = "0.18.2" +name = "gsk4-sys" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc" dependencies = [ - "atk-sys", "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", + "gdk4-sys", "glib-sys", "gobject-sys", + "graphene-sys", "libc", "pango-sys", - "system-deps", + "system-deps 7.0.3", +] + +[[package]] +name = "gtk4" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1c491051f030994fd0cde6f3c44f3f5640210308cff1298c7673c47408091d" +dependencies = [ + "cairo-rs", + "field-offset", + "futures-channel", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "graphene-rs", + "gsk4", + "gtk4-macros", + "gtk4-sys", + "libc", + "pango", ] [[package]] -name = "gtk3-macros" -version = "0.18.2" +name = "gtk4-macros" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.96", + "syn", +] + +[[package]] +name = "gtk4-sys" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "gsk4-sys", + "libc", + "pango-sys", + "system-deps 7.0.3", ] [[package]] @@ -1001,12 +1100,6 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -1134,7 +1227,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -1215,7 +1308,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -1481,7 +1574,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -1528,10 +1621,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -1643,29 +1736,34 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pango" -version = "0.18.3" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +checksum = "6b1f5dc1b8cf9bc08bfc0843a04ee0fa2e78f1e1fa4b126844a383af4f25f0ec" dependencies = [ "gio", "glib", "libc", - "once_cell", "pango-sys", ] [[package]] name = "pango-sys" -version = "0.18.0" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +checksum = "0dbb9b751673bd8fe49eb78620547973a1e719ed431372122b20abd12445bab5" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 7.0.3", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1743,46 +1841,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit 0.22.24", ] [[package]] @@ -1810,7 +1873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -1905,7 +1968,7 @@ dependencies = [ "rand", "rand_chacha", "simd_helpers", - "system-deps", + "system-deps 6.2.2", "thiserror", "v_frame", "wasm-bindgen", @@ -2083,7 +2146,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -2169,16 +2232,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.96" @@ -2198,7 +2251,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -2207,8 +2260,21 @@ version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "cfg-expr", - "heck 0.5.0", + "cfg-expr 0.15.8", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "system-deps" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" +dependencies = [ + "cfg-expr 0.17.2", + "heck", "pkg-config", "toml", "version-compare", @@ -2218,6 +2284,7 @@ dependencies = [ name = "tao" version = "0.32.8" dependencies = [ + "async-channel", "bitflags 2.8.0", "core-foundation", "core-graphics", @@ -2226,9 +2293,9 @@ dependencies = [ "dlopen2", "dpi", "env_logger", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", + "gdk4-wayland", + "gdk4-x11", + "gtk4", "image", "jni", "lazy_static", @@ -2263,7 +2330,7 @@ version = "0.1.3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -2289,7 +2356,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -2340,35 +2407,35 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.7.4", ] [[package]] @@ -2445,12 +2512,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "walkdir" version = "2.5.0" @@ -2489,7 +2550,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.96", + "syn", "wasm-bindgen-shared", ] @@ -2511,7 +2572,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2674,7 +2735,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -2685,7 +2746,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -2936,6 +2997,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +dependencies = [ + "memchr", +] + [[package]] name = "write16" version = "1.0.0" @@ -2948,16 +3018,6 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "x11-dl" version = "2.21.0" @@ -3010,7 +3070,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", "synstructure", ] @@ -3032,7 +3092,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -3052,7 +3112,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", "synstructure", ] @@ -3075,7 +3135,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 165bda447..2b91620f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ rwh_05 = { package = "raw-window-handle", version = "0.5", features = [ "std" ], rwh_06 = { package = "raw-window-handle", version = "0.6", features = [ "std" ], optional = true } bitflags = "2" crossbeam-channel = "0.5" +async-channel = "2.3" url = "2" dpi = "0.1" @@ -146,9 +147,9 @@ dispatch = "0.2" scopeguard = "1.2" [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] -gtk = "0.18" -gdkx11-sys = "0.18" -gdkwayland-sys = "0.18.0" +gtk = { package = "gtk4", version = "0.9", features = ["v4_6"] } +gdk-x11 = { package = "gdk4-x11", version = "0.9" } +gdk-wayland = { package = "gdk4-wayland", version = "0.9", features = ["wayland_crate"]} x11-dl = "2.21" parking_lot = "0.12" dlopen2 = "0.7.0" diff --git a/README.md b/README.md index a7d504619..a76ee6a88 100644 --- a/README.md +++ b/README.md @@ -49,13 +49,13 @@ Gtk and its related libraries are used to build the support of Linux. Be sure to #### Arch Linux / Manjaro: ```bash -sudo pacman -S gtk3 +sudo pacman -S gtk4 ``` #### Debian / Ubuntu: ```bash -sudo apt install libgtk-3-dev +sudo apt install libgtk-4-dev ``` ### Acknowledgement diff --git a/examples/window.rs b/examples/window.rs index c4804fc4c..44d3d0bcc 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -10,6 +10,7 @@ use tao::{ #[allow(clippy::single_match)] fn main() { + env_logger::init(); let event_loop = EventLoop::new(); let mut window = Some( diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 2113220ac..c54ea30bd 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -21,7 +21,9 @@ use crate::{ error::{ExternalError, OsError}, event_loop::{EventLoopBuilder, EventLoopWindowTarget}, monitor::MonitorHandle, - platform_impl::{x11::xdisplay::XError, Parent, Window as UnixWindow}, + platform_impl::{ + gtk_window::ApplicationWindow, x11::xdisplay::XError, Parent, Window as UnixWindow, + }, window::{Window, WindowBuilder}, }; @@ -67,11 +69,11 @@ pub trait WindowExtUnix { /// and know what they're doing. fn new_from_gtk_window( event_loop_window_target: &EventLoopWindowTarget, - window: gtk::ApplicationWindow, + window: ApplicationWindow, ) -> Result; /// Returns the `gtk::ApplicatonWindow` from gtk crate that is used by this window. - fn gtk_window(&self) -> >k::ApplicationWindow; + fn gtk_window(&self) -> &ApplicationWindow; /// Returns the vertical `gtk::Box` that is added by default as the sole child of this window. /// Returns `None` if the default vertical `gtk::Box` creation was disabled by [`WindowBuilderExtUnix::with_default_vbox`]. @@ -84,7 +86,7 @@ pub trait WindowExtUnix { } impl WindowExtUnix for Window { - fn gtk_window(&self) -> >k::ApplicationWindow { + fn gtk_window(&self) -> &ApplicationWindow { &self.window.window } @@ -98,10 +100,10 @@ impl WindowExtUnix for Window { fn new_from_gtk_window( event_loop_window_target: &EventLoopWindowTarget, - window: gtk::ApplicationWindow, + window: ApplicationWindow, ) -> Result { let window = UnixWindow::new_from_gtk_window(&event_loop_window_target.p, window)?; - Ok(Window { window: window }) + Ok(Window { window }) } fn set_badge_count(&self, count: Option, desktop_filename: Option) { @@ -114,7 +116,7 @@ pub trait WindowBuilderExtUnix { fn with_skip_taskbar(self, skip: bool) -> WindowBuilder; /// Set this window as a transient dialog for `parent` /// - fn with_transient_for(self, parent: &impl gtk::glib::IsA) -> WindowBuilder; + fn with_transient_for(self, parent: &impl gtk::prelude::IsA) -> WindowBuilder; /// Whether to enable or disable the internal draw for transparent window. /// @@ -158,8 +160,8 @@ impl WindowBuilderExtUnix for WindowBuilder { self } - fn with_transient_for(mut self, parent: &impl gtk::glib::IsA) -> WindowBuilder { - use gtk::glib::Cast; + fn with_transient_for(mut self, parent: &impl gtk::prelude::IsA) -> WindowBuilder { + use gtk::prelude::Cast; self.platform_specific.parent = Parent::ChildOf(parent.clone().upcast()); self } diff --git a/src/platform_impl/linux/device.rs b/src/platform_impl/linux/device.rs index 860860379..7909dd97e 100644 --- a/src/platform_impl/linux/device.rs +++ b/src/platform_impl/linux/device.rs @@ -3,7 +3,6 @@ use std::{ ptr, }; -use gtk::glib; use x11_dl::{xinput2, xlib}; use crate::event::{DeviceEvent, ElementState, RawKeyEvent}; @@ -11,8 +10,8 @@ use crate::event::{DeviceEvent, ElementState, RawKeyEvent}; use super::keycode_from_scancode; /// Spawn Device event thread. Only works on x11 since wayland doesn't have such global events. -pub fn spawn(device_tx: glib::Sender) { - std::thread::spawn(move || unsafe { +pub fn spawn(device_tx: async_channel::Sender) { + std::thread::spawn(async move || unsafe { let xlib = xlib::Xlib::open().unwrap(); let xinput2 = xinput2::XInput2::open().unwrap(); let display = (xlib.XOpenDisplay)(ptr::null()); @@ -45,35 +44,32 @@ pub fn spawn(device_tx: glib::Sender) { } let event_type = event.get_type(); - match event_type { - xlib::GenericEvent => { - let mut xev = event.generic_event_cookie; - if (xlib.XGetEventData)(display, &mut xev) == xlib::True { - match xev.evtype { - xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => { - let xev: &xinput2::XIRawEvent = &*(xev.data as *const _); - let physical_key = keycode_from_scancode(xev.detail as u32); - let state = match xev.evtype { - xinput2::XI_RawKeyPress => ElementState::Pressed, - xinput2::XI_RawKeyRelease => ElementState::Released, - _ => unreachable!(), - }; + if event_type == xlib::GenericEvent { + let mut xev = event.generic_event_cookie; + if (xlib.XGetEventData)(display, &mut xev) == xlib::True { + match xev.evtype { + xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => { + let xev: &xinput2::XIRawEvent = &*(xev.data as *const _); + let physical_key = keycode_from_scancode(xev.detail as u32); + let state = match xev.evtype { + xinput2::XI_RawKeyPress => ElementState::Pressed, + xinput2::XI_RawKeyRelease => ElementState::Released, + _ => unreachable!(), + }; - let event = RawKeyEvent { - physical_key, - state, - }; + let event = RawKeyEvent { + physical_key, + state, + }; - if let Err(e) = device_tx.send(DeviceEvent::Key(event)) { - log::info!("Failed to send device event {} since receiver is closed. Closing x11 thread along with it", e); - break; - } + if let Err(e) = device_tx.send_blocking(DeviceEvent::Key(event)) { + log::info!("Failed to send device event {} since receiver is closed. Closing x11 thread along with it", e); + break; } - _ => {} } + _ => {} } } - _ => {} } } }); diff --git a/src/platform_impl/linux/event_loop.rs b/src/platform_impl/linux/event_loop.rs index 39bb014d3..bba13f0b7 100644 --- a/src/platform_impl/linux/event_loop.rs +++ b/src/platform_impl/linux/event_loop.rs @@ -12,16 +12,13 @@ use std::{ time::Instant, }; -use cairo::{RectangleInt, Region}; -use crossbeam_channel::SendError; -use gdk::{Cursor, CursorType, EventKey, EventMask, ScrollDirection, WindowEdge, WindowState}; -use gio::Cancellable; -use glib::{source::Priority, MainContext}; use gtk::{ - cairo, gdk, gio, - glib::{self}, + cairo::{RectangleInt, Region}, + gdk::{self, Cursor, ScrollDirection, SurfaceEdge}, + glib::{self, closure_local, MainContext}, prelude::*, - Settings, + Application, EventControllerFocus, EventControllerKey, EventControllerMotion, + EventControllerScroll, EventControllerScrollFlags, GestureClick, Settings, }; use crate::{ @@ -33,17 +30,20 @@ use crate::{ event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, keyboard::ModifiersState, monitor::MonitorHandle as RootMonitorHandle, - platform_impl::platform::{device, DEVICE_ID}, window::{ CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, WindowId as RootWindowId, }, }; use super::{ + device, + gtk_window::ApplicationWindow, keyboard, monitor::{self, MonitorHandle}, - taskbar, util, + taskbar, + util::{self}, window::{WindowId, WindowRequest}, + DEVICE_ID, }; use taskbar::TaskbarIndicator; @@ -53,87 +53,86 @@ pub struct EventLoopWindowTarget { /// Gdk display pub(crate) display: gdk::Display, /// Gtk application - pub(crate) app: gtk::Application, + pub(crate) app: Application, /// Window Ids of the application pub(crate) windows: Rc>>, /// Window requests sender - pub(crate) window_requests_tx: glib::Sender<(WindowId, WindowRequest)>, + pub(crate) window_requests_tx: async_channel::Sender<(WindowId, WindowRequest)>, /// Draw event sender - pub(crate) draw_tx: crossbeam_channel::Sender, + pub(crate) draw_tx: async_channel::Sender, _marker: std::marker::PhantomData, } impl EventLoopWindowTarget { #[inline] - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - monitor::from_point(&self.display, x, y) + pub fn monitor_from_point(&self, _: f64, _: f64) -> Option { + None } + #[inline] pub fn available_monitors(&self) -> VecDeque { - let mut handles = VecDeque::new(); - let display = &self.display; - let numbers = display.n_monitors(); - - for i in 0..numbers { - let monitor = MonitorHandle::new(display, i); - handles.push_back(monitor); - } - - handles + monitor::available_monitors(&self.display) } #[inline] pub fn primary_monitor(&self) -> Option { - let monitor = self.display.primary_monitor(); - monitor.and_then(|monitor| { - let handle = MonitorHandle { monitor }; - Some(RootMonitorHandle { inner: handle }) - }) + monitor::primary_monitor(&self.display) } #[cfg(feature = "rwh_05")] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + let display = self.display.clone(); if self.is_wayland() { + use gdk_wayland::wayland_client::Proxy; + let display = display + .downcast::() + .unwrap() + .wl_display() + .unwrap() + .id() + .as_ptr(); + let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); - display_handle.display = unsafe { - gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.display.as_ptr() as *mut _) - }; - rwh_05::RawDisplayHandle::Wayland(display_handle) + display_handle.display = display as *mut _; + display_handle.into() } else { - let mut display_handle = rwh_05::XlibDisplayHandle::empty(); - unsafe { - if let Ok(xlib) = x11_dl::xlib::Xlib::open() { - let display = (xlib.XOpenDisplay)(std::ptr::null()); - display_handle.display = display as _; - display_handle.screen = (xlib.XDefaultScreen)(display) as _; - } - } + let display = display.downcast::().unwrap(); - rwh_05::RawDisplayHandle::Xlib(display_handle) + let mut display_handle = rwh_05::XlibDisplayHandle::empty(); + display_handle.display = + unsafe { gdk_x11::ffi::gdk_x11_display_get_xdisplay(display.as_ptr() as *mut _) }; + display_handle.screen = display.screen().screen_number(); + display_handle.into() } } #[cfg(feature = "rwh_06")] + #[inline] pub fn raw_display_handle_rwh_06(&self) -> Result { + let display = self.display.clone(); if self.is_wayland() { - let display = unsafe { - gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.display.as_ptr() as *mut _) - }; - let display = unsafe { std::ptr::NonNull::new_unchecked(display) }; - let display_handle = rwh_06::WaylandDisplayHandle::new(display); - Ok(rwh_06::RawDisplayHandle::Wayland(display_handle)) + use gdk_wayland::wayland_client::Proxy; + + Ok( + rwh_06::WaylandDisplayHandle::new({ + let ptr = display + .downcast::() + .unwrap() + .wl_display() + .unwrap() + .id() + .as_ptr(); + std::ptr::NonNull::new(ptr as *mut _).expect("wl_display will never be null") + }) + .into(), + ) } else { - unsafe { - if let Ok(xlib) = x11_dl::xlib::Xlib::open() { - let display = (xlib.XOpenDisplay)(std::ptr::null()); - let screen = (xlib.XDefaultScreen)(display) as _; - let display = std::ptr::NonNull::new_unchecked(display as _); - let display_handle = rwh_06::XlibDisplayHandle::new(Some(display), screen); - Ok(rwh_06::RawDisplayHandle::Xlib(display_handle)) - } else { - Err(rwh_06::HandleError::Unavailable) - } - } + let display = display.downcast::().unwrap(); + let xdisplay = std::ptr::NonNull::new(unsafe { + gdk_x11::ffi::gdk_x11_display_get_xdisplay(display.as_ptr() as *mut _) + }); + let xscreen = display.screen().screen_number(); + Ok(rwh_06::XlibDisplayHandle::new(xdisplay, xscreen).into()) } } @@ -147,14 +146,14 @@ impl EventLoopWindowTarget { #[inline] pub fn cursor_position(&self) -> Result, ExternalError> { - util::cursor_position(self.is_wayland()) + Ok(PhysicalPosition::new(0., 0.)) } #[inline] pub fn set_progress_bar(&self, progress: ProgressBarState) { if let Err(e) = self .window_requests_tx - .send((WindowId::dummy(), WindowRequest::ProgressBarState(progress))) + .send_blocking((WindowId::dummy(), WindowRequest::ProgressBarState(progress))) { log::warn!("Fail to send update progress bar request: {e}"); } @@ -162,7 +161,7 @@ impl EventLoopWindowTarget { #[inline] pub fn set_badge_count(&self, count: Option, desktop_filename: Option) { - if let Err(e) = self.window_requests_tx.send(( + if let Err(e) = self.window_requests_tx.send_blocking(( WindowId::dummy(), WindowRequest::BadgeCount(count, desktop_filename), )) { @@ -174,7 +173,7 @@ impl EventLoopWindowTarget { pub fn set_theme(&self, theme: Option) { if let Err(e) = self .window_requests_tx - .send((WindowId::dummy(), WindowRequest::SetTheme(theme))) + .send_blocking((WindowId::dummy(), WindowRequest::SetTheme(theme))) { log::warn!("Fail to send update theme request: {e}"); } @@ -185,11 +184,11 @@ pub struct EventLoop { /// Window target. window_target: RootELW, /// User event sender for EventLoopProxy - pub(crate) user_event_tx: crossbeam_channel::Sender>, + pub(crate) user_event_tx: async_channel::Sender>, /// Event queue of EventLoop - events: crossbeam_channel::Receiver>, + events: async_channel::Receiver>, /// Draw queue of EventLoop - draws: crossbeam_channel::Receiver, + draws: async_channel::Receiver, /// Boolean to control device event thread run_device_thread: Option>, } @@ -215,20 +214,17 @@ impl EventLoop { } fn new_gtk(app_id: Option<&str>) -> Result, Box> { - // This should be done by gtk::Application::new, but does not work properly - gtk::init()?; let context = MainContext::default(); - let app = gtk::Application::new(app_id, gio::ApplicationFlags::empty()); + let app = Application::new(app_id, gtk::gio::ApplicationFlags::empty()); let app_ = app.clone(); - let cancellable: Option<&Cancellable> = None; - app.register(cancellable)?; + app.register(gtk::gio::Cancellable::NONE)?; // Send StartCause::Init event - let (event_tx, event_rx) = crossbeam_channel::unbounded(); - let (draw_tx, draw_rx) = crossbeam_channel::unbounded(); + let (event_tx, event_rx) = async_channel::unbounded(); + let (draw_tx, draw_rx) = async_channel::unbounded(); let event_tx_ = event_tx.clone(); app.connect_activate(move |_| { - if let Err(e) = event_tx_.send(Event::NewEvents(StartCause::Init)) { + if let Err(e) = event_tx_.send_blocking(Event::NewEvents(StartCause::Init)) { log::warn!("Failed to send init event to event channel: {}", e); } }); @@ -236,7 +232,7 @@ impl EventLoop { let user_event_tx = event_tx.clone(); // Create event loop window target. - let (window_requests_tx, window_requests_rx) = glib::MainContext::channel(Priority::default()); + let (window_requests_tx, window_requests_rx) = async_channel::unbounded(); let display = gdk::Display::default() .expect("GdkDisplay not found. This usually means `gkt_init` hasn't called yet."); let window_target = EventLoopWindowTarget { @@ -250,23 +246,25 @@ impl EventLoop { // Spawn x11 thread to receive Device events. let run_device_thread = if window_target.is_x11() { - let (device_tx, device_rx) = glib::MainContext::channel(glib::Priority::default()); + let (device_tx, device_rx) = async_channel::unbounded(); let user_event_tx = user_event_tx.clone(); let run_device_thread = Rc::new(AtomicBool::new(true)); let run = run_device_thread.clone(); device::spawn(device_tx); - device_rx.attach(Some(&context), move |event| { - if let Err(e) = user_event_tx.send(Event::DeviceEvent { - device_id: DEVICE_ID, - event, - }) { - log::warn!("Fail to send device event to event channel: {}", e); - } - if run.load(Ordering::Relaxed) { - glib::ControlFlow::Continue - } else { - glib::ControlFlow::Break + + context.spawn_local(async move { + while let Ok(event) = device_rx.recv().await { + if let Err(e) = user_event_tx.send_blocking(Event::DeviceEvent { + device_id: DEVICE_ID, + event, + }) { + log::warn!("Fail to send device event to event channel: {}", e); + } + if !run.load(Ordering::Relaxed) { + break; + } } + glib::ControlFlow::Break }); Some(run_device_thread) } else { @@ -276,218 +274,184 @@ impl EventLoop { let mut taskbar = TaskbarIndicator::new(); let is_wayland = window_target.is_wayland(); - // Window Request - window_requests_rx.attach(Some(&context), move |(id, request)| { - if let Some(window) = app_.window_by_id(id.0) { - match request { - WindowRequest::Title(title) => window.set_title(&title), - WindowRequest::Position((x, y)) => window.move_(x, y), - WindowRequest::Size((w, h)) => window.resize(w, h), - WindowRequest::SizeConstraints(constraints) => { - util::set_size_constraints(&window, constraints); - } - WindowRequest::Visible(visible) => { - if visible { - window.show_all(); - } else { - window.hide(); + context.spawn_local(async move { + // Window Request + while let Ok((id, request)) = window_requests_rx.recv().await { + if let Some(window) = app_.window_by_id(id.0) { + match request { + WindowRequest::Title(title) => window.set_title(Some(&title)), + WindowRequest::Size((w, h)) => window.set_default_size(w, h), + WindowRequest::SizeConstraints(constraints) => { + util::set_size_constraints(&window, constraints); } - } - WindowRequest::Focus => { - window.present_with_time(gdk::ffi::GDK_CURRENT_TIME as _); - } - WindowRequest::Resizable(resizable) => window.set_resizable(resizable), - WindowRequest::Closable(closable) => window.set_deletable(closable), - WindowRequest::Minimized(minimized) => { - if minimized { - window.iconify(); - } else { - window.deiconify(); + WindowRequest::Visible(visible) => { + window.set_visible(visible); } - } - WindowRequest::Maximized(maximized, resizable) => { - if maximized { - let maximize_process = util::WindowMaximizeProcess::new(window.clone(), resizable); - glib::idle_add_local_full(glib::Priority::DEFAULT_IDLE, move || { - let mut maximize_process = maximize_process.borrow_mut(); - maximize_process.next_step() - }); - } else { - window.unmaximize(); + WindowRequest::Focus => { + window.present(); } - } - WindowRequest::DragWindow => { - if let Some(cursor) = window - .display() - .default_seat() - .and_then(|seat| seat.pointer()) - { - let (_, x, y) = cursor.position(); - window.begin_move_drag(1, x, y, 0); + WindowRequest::Resizable(resizable) => window.set_resizable(resizable), + WindowRequest::Closable(closable) => window.set_deletable(closable), + WindowRequest::Minimized(minimized) => { + if minimized { + window.minimize(); + } else { + window.unminimize(); + } } - } - WindowRequest::DragResizeWindow(direction) => { - if let Some(cursor) = window - .display() - .default_seat() - .and_then(|seat| seat.pointer()) - { - let (_, x, y) = cursor.position(); - window.begin_resize_drag( - direction.to_gtk_edge(), - 1, - x, - y, - gtk::gdk::ffi::GDK_CURRENT_TIME as _, - ); + WindowRequest::Maximized(maximized, resizable) => { + if maximized { + let maximize_process = util::WindowMaximizeProcess::new(window.clone(), resizable); + glib::idle_add_local_full(glib::Priority::DEFAULT_IDLE, move || { + let mut maximize_process = maximize_process.borrow_mut(); + maximize_process.next_step() + }); + } else { + window.unmaximize(); + } } - } - WindowRequest::Fullscreen(fullscreen) => match fullscreen { - Some(f) => { - if let Fullscreen::Borderless(m) = f { - if let Some(monitor) = m { - let display = window.display(); - let monitor = monitor.inner; - let monitors = display.n_monitors(); - for i in 0..monitors { - let m = display.monitor(i).unwrap(); - if m == monitor.monitor { - let screen = display.default_screen(); - window.fullscreen_on_monitor(&screen, i); - } - } - } else { - window.fullscreen(); + WindowRequest::DragWindow => { + let cursor = util::default_pointer(&RootExt::display(&window)); + let surface = window.surface(); + if let (Some(cursor), Some(surface)) = (cursor, surface) { + let pos = surface.device_position(&cursor); + let toplevel = util::surface_as_toplevel(surface); + + if let (Ok(toplevel), Some((x, y, _))) = (toplevel, pos) { + toplevel.begin_move( + &cursor, + gdk::BUTTON_PRIMARY as _, + x, + y, + gdk::CURRENT_TIME as _, + ); } } } - None => window.unfullscreen(), - }, - WindowRequest::Decorations(decorations) => window.set_decorated(decorations), - WindowRequest::AlwaysOnBottom(always_on_bottom) => { - window.set_keep_below(always_on_bottom) - } - WindowRequest::AlwaysOnTop(always_on_top) => window.set_keep_above(always_on_top), - WindowRequest::WindowIcon(window_icon) => { - if let Some(icon) = window_icon { - window.set_icon(Some(&icon.inner.into())); - } - } - WindowRequest::UserAttention(request_type) => { - window.set_urgency_hint(request_type.is_some()) - } - WindowRequest::SetSkipTaskbar(skip) => { - window.set_skip_taskbar_hint(skip); - window.set_skip_pager_hint(skip) - } - WindowRequest::BackgroundColor(css_provider, color) => { - unsafe { window.set_data("background_color", color) }; - - let style_context = window.style_context(); - style_context.remove_provider(&css_provider); - - if let Some(color) = color { - let theme = format!( - r#" - window {{ - background-color: rgba({},{},{},{}); - }} - "#, - color.0, - color.1, - color.2, - color.3 as f64 / 255.0 - ); - let _ = css_provider.load_from_data(theme.as_bytes()); - style_context.add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); - }; - } - WindowRequest::SetVisibleOnAllWorkspaces(visible) => { - if visible { - window.stick(); - } else { - window.unstick(); + WindowRequest::DragResizeWindow(direction) => { + let cursor = util::default_pointer(&RootExt::display(&window)); + let surface = window.surface(); + if let (Some(cursor), Some(surface)) = (cursor, surface) { + let pos = surface.device_position(&cursor); + let toplevel = util::surface_as_toplevel(surface); + + if let (Ok(toplevel), Some((x, y, _))) = (toplevel, pos) { + toplevel.begin_resize( + direction.to_gtk_edge(), + Some(&cursor), + gdk::BUTTON_PRIMARY as _, + x, + y, + gdk::CURRENT_TIME as _, + ); + } + } } - } - WindowRequest::CursorIcon(cursor) => { - if let Some(gdk_window) = window.window() { - let display = window.display(); - match cursor { - Some(cr) => { - gdk_window.set_cursor(Cursor::from_name(&display, cr.to_str()).as_ref()) + WindowRequest::Fullscreen(fullscreen) => match fullscreen { + Some(f) => { + if let Fullscreen::Borderless(m) = f { + if let Some(monitor) = m { + window.fullscreen_on_monitor(&monitor.inner.monitor); + } else { + window.fullscreen(); + } } - None => gdk_window - .set_cursor(Cursor::for_display(&display, CursorType::BlankCursor).as_ref()), } - }; - } - WindowRequest::CursorPosition((x, y)) => { - if let Some(cursor) = window - .display() - .default_seat() - .and_then(|seat| seat.pointer()) - { - if let Some(screen) = GtkWindowExt::screen(&window) { - cursor.warp(&screen, x, y); + None => window.unfullscreen(), + }, + WindowRequest::Decorations(decorations) => window.set_decorated(decorations), + WindowRequest::UserAttention(request_type) => { + if is_wayland && request_type.is_some() { + window.present(); + } else if let Some(surface) = window.surface() { + if let Ok(x11_surface) = surface.downcast::() { + x11_surface.set_urgency_hint(request_type.is_some()); + } } } - } - WindowRequest::CursorIgnoreEvents(ignore) => { - if ignore { - let empty_region = Region::create_rectangle(&RectangleInt::new(0, 0, 1, 1)); - window - .window() - .unwrap() - .input_shape_combine_region(&empty_region, 0, 0); - } else { - window.input_shape_combine_region(None) - }; - } - WindowRequest::ProgressBarState(_) => unreachable!(), - WindowRequest::BadgeCount(_, _) => unreachable!(), - WindowRequest::SetTheme(_) => unreachable!(), - WindowRequest::WireUpEvents { - transparent, - fullscreen, - cursor_moved, - } => { - window.add_events( - EventMask::POINTER_MOTION_MASK - | EventMask::BUTTON1_MOTION_MASK - | EventMask::BUTTON_PRESS_MASK - | EventMask::TOUCH_MASK - | EventMask::STRUCTURE_MASK - | EventMask::FOCUS_CHANGE_MASK - | EventMask::SCROLL_MASK, - ); - - let fullscreen = Rc::new(AtomicBool::new(fullscreen)); - let fullscreen_ = fullscreen.clone(); - window.connect_window_state_event(move |_window, event| { - let state = event.changed_mask(); - if state.contains(WindowState::FULLSCREEN) { - fullscreen_.store( - event.new_window_state().contains(WindowState::FULLSCREEN), - Ordering::Relaxed, + WindowRequest::BackgroundColor(css_provider, color) => { + let display = RootExt::display(&window); + gtk::style_context_remove_provider_for_display(&display, &css_provider); + + if let Some(color) = color { + let theme = format!( + r#" + window.tao-window-{} {{ + background-color: rgba({},{},{},{}); + }} + "#, + id.0, + color.0, + color.1, + color.2, + color.3 as f64 / 255.0 + ); + css_provider.load_from_data(&theme); + + gtk::style_context_add_provider_for_display( + &display, + &css_provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, ); } - glib::Propagation::Proceed - }); - - // Allow resizing unmaximized non-fullscreen undecorated window - let fullscreen_ = fullscreen.clone(); - window.connect_motion_notify_event(move |window, event| { - if !window.is_decorated() && window.is_resizable() && !window.is_maximized() { - if let Some(window) = window.window() { - let (cx, cy) = event.root(); - let (left, top) = window.position(); - let (w, h) = (window.width(), window.height()); - let (right, bottom) = (left + w, top + h); - let border = window.scale_factor() * 5; - let edge = crate::window::hit_test( - (left, top, right, bottom), - cx as _, - cy as _, + } + WindowRequest::CursorIcon(cursor) => match cursor { + Some(cr) => window.set_cursor(Cursor::from_name(cr.to_str(), None).as_ref()), + None => window.set_cursor(Cursor::from_name("none", None).as_ref()), + }, + WindowRequest::CursorIgnoreEvents(ignore) => { + let region = if ignore { + Region::create_rectangle(&RectangleInt::new(0, 0, 1, 1)) + } else { + Region::create() + }; + window.surface().unwrap().set_input_region(®ion); + } + WindowRequest::ProgressBarState(_) => unreachable!(), + WindowRequest::BadgeCount(_, _) => unreachable!(), + WindowRequest::SetTheme(_) => unreachable!(), + WindowRequest::WireUpEvents { + transparent, + fullscreen, + cursor_moved, + } => { + let motion_event_controller = EventControllerMotion::new(); + let key_event_controller = EventControllerKey::new(); + let focus_event_controller = EventControllerFocus::new(); + let scroll_event_controller = + EventControllerScroll::new(EventControllerScrollFlags::BOTH_AXES); + let primary_click_controller = GestureClick::new(); + let all_click_controller = GestureClick::new(); + + // Respond to primary mouse click or touch + primary_click_controller.set_button(gdk::BUTTON_PRIMARY); + + // Respond to any mouse click or touch + all_click_controller.set_button(0); + + let fullscreen = Rc::new(AtomicBool::new(fullscreen)); + let fullscreen_ = fullscreen.clone(); + + window.connect_fullscreened_notify(move |window| { + fullscreen_.store(window.is_fullscreen(), Ordering::Relaxed); + }); + + // Add window id as css class so we can style individual windows + window.add_css_class(&format!("tao-window-{}", id.0)); + + // Allow resizing unmaximized non-fullscreen undecorated window + let fullscreen_ = fullscreen.clone(); + let window_clone = window.clone(); + motion_event_controller.connect_motion(move |_, cx, cy| { + if !window_clone.is_decorated() + && window_clone.is_resizable() + && !window_clone.is_maximized() + { + let border = window_clone.scale_factor() * 5; + let edge: Option = crate::window::hit_test( + (0, 0, window_clone.width(), window_clone.height()), + cx.round() as _, + cy.round() as _, border, border, ); @@ -496,194 +460,147 @@ impl EventLoop { Some(e) if !fullscreen_.load(Ordering::Relaxed) => e.to_cursor_str(), _ => "default", }; - window.set_cursor(Cursor::from_name(&window.display(), edge).as_ref()); + window_clone.set_cursor(Cursor::from_name(edge, None).as_ref()); } - } - glib::Propagation::Proceed - }); - window.connect_button_press_event(move |window, event| { - const LMB: u32 = 1; - if (is_wayland || !window.is_decorated()) - && window.is_resizable() - && !window.is_maximized() - && event.button() == LMB - { - let (cx, cy) = event.root(); - let (left, top) = window.position(); - let (w, h) = window.size(); - let (right, bottom) = (left + w, top + h); - let border = window.scale_factor() * 5; - let edge = crate::window::hit_test( - (left, top, right, bottom), - cx as _, - cy as _, - border, - border, - ) - .map(|d| d.to_gtk_edge()) - // we return `WindowEdge::__Unknown` to be ignored later. - // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `WindowEdge` enum. - .unwrap_or(WindowEdge::__Unknown(8)); - // Ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges. - match edge { - WindowEdge::__Unknown(_) => (), - _ => { - // FIXME: calling `window.begin_resize_drag` uses the default cursor, it should show a resizing cursor instead - window.begin_resize_drag(edge, LMB as i32, cx as i32, cy as i32, event.time()) + }); + + // FIXME: This does nothing if the window has a visible child since the child consumes the click event. + // Is this even needed? + let window_clone = window.clone(); + primary_click_controller.connect_pressed(move |event, _, cx, cy| { + if !window_clone.is_decorated() + && window_clone.is_resizable() + && !window_clone.is_maximized() + { + let border = window_clone.scale_factor() * 5; + let edge = crate::window::hit_test( + (0, 0, window_clone.width(), window_clone.height()), + cx.round() as _, + cy.round() as _, + border, + border, + ) + .map(|d| d.to_gtk_edge()) + // we return `SurfaceEdge::__Unknown` to be ignored later. + // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `SurfaceEdge` enum. + .unwrap_or(SurfaceEdge::__Unknown(8)); + // Ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges. + match edge { + SurfaceEdge::__Unknown(_) => (), + _ => { + if let Some(surface) = window_clone.surface() { + if let Ok(toplevel) = util::surface_as_toplevel(surface) { + // FIXME: calling `window.begin_resize_drag` uses the default cursor, it should show a resizing cursor instead + toplevel.begin_resize( + edge, + event.device().as_ref(), + event.button() as i32, + cx, + cy, + event.current_event_time(), + ) + } + } + } } } - } + }); - glib::Propagation::Proceed - }); - window.connect_touch_event(move |window, event| { - if !window.is_decorated() && window.is_resizable() && !window.is_maximized() { - if let Some(window) = window.window() { - if let Some((cx, cy)) = event.root_coords() { - if let Some(device) = event.device() { - let (left, top) = window.position(); - let (w, h) = (window.width(), window.height()); - let (right, bottom) = (left + w, top + h); - let border = window.scale_factor() * 5; - let edge = crate::window::hit_test( - (left, top, right, bottom), - cx as _, - cy as _, - border, - border, - ) - .map(|d| d.to_gtk_edge()) - // we return `WindowEdge::__Unknown` to be ignored later. - // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `WindowEdge` enum. - .unwrap_or(WindowEdge::__Unknown(8)); - - // Ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges. - match edge { - WindowEdge::__Unknown(_) => (), - _ => window.begin_resize_drag_for_device( - edge, - &device, - 0, - cx as i32, - cy as i32, - event.time(), + let tx_clone = event_tx.clone(); + window.connect_close_request(move |_| { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CloseRequested, + }) { + log::warn!("Failed to send window close event to event channel: {}", e); + } + glib::Propagation::Stop + }); + + let tx_clone = event_tx.clone(); + + let _ = window + .clone() + .downcast::() + .map(|window| { + window.connect_resized(closure_local!( + move |window: ApplicationWindow, w: i32, h: i32| { + let scale_factor = window.scale_factor(); + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Resized( + LogicalSize::new(w, h).to_physical(scale_factor as f64), ), + }) { + log::warn!( + "Failed to send window resized event to event channel: {}", + e + ); } } - } + )); + }); + + let tx_clone = event_tx.clone(); + focus_event_controller.connect_enter(move |_| { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Focused(true), + }) { + log::warn!( + "Failed to send window focus-in event to event channel: {}", + e + ); } - } + }); - glib::Propagation::Proceed - }); + let tx_clone = event_tx.clone(); + focus_event_controller.connect_leave(move |_| { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Focused(false), + }) { + log::warn!( + "Failed to send window focus-out event to event channel: {}", + e + ); + } + }); - let tx_clone = event_tx.clone(); - window.connect_delete_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CloseRequested, - }) { - log::warn!("Failed to send window close event to event channel: {}", e); - } - glib::Propagation::Stop - }); - - let tx_clone = event_tx.clone(); - window.connect_configure_event(move |window, event| { - let scale_factor = window.scale_factor(); - - let (x, y) = window - .window() - .map(|w| w.root_origin()) - .unwrap_or_else(|| event.position()); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Moved( - LogicalPosition::new(x, y).to_physical(scale_factor as f64), - ), - }) { - log::warn!("Failed to send window moved event to event channel: {}", e); - } + let tx_clone = event_tx.clone(); + window.connect_destroy(move |_| { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Destroyed, + }) { + log::warn!( + "Failed to send window destroyed event to event channel: {}", + e + ); + } + }); - let (w, h) = event.size(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Resized( - LogicalSize::new(w, h).to_physical(scale_factor as f64), - ), - }) { - log::warn!( - "Failed to send window resized event to event channel: {}", - e - ); - } - false - }); - - let tx_clone = event_tx.clone(); - window.connect_focus_in_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Focused(true), - }) { - log::warn!( - "Failed to send window focus-in event to event channel: {}", - e - ); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_focus_out_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Focused(false), - }) { - log::warn!( - "Failed to send window focus-out event to event channel: {}", - e - ); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_destroy(move |_| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Destroyed, - }) { - log::warn!( - "Failed to send window destroyed event to event channel: {}", - e - ); - } - }); - - let tx_clone = event_tx.clone(); - window.connect_enter_notify_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorEntered { - device_id: DEVICE_ID, - }, - }) { - log::warn!( - "Failed to send cursor entered event to event channel: {}", - e - ); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_motion_notify_event(move |window, motion| { - if cursor_moved { - if let Some(cursor) = motion.device() { - let scale_factor = window.scale_factor(); - let (_, x, y) = cursor.window_at_position(); - if let Err(e) = tx_clone.send(Event::WindowEvent { + let tx_clone = event_tx.clone(); + motion_event_controller.connect_enter(move |_, _, _| { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorEntered { + device_id: DEVICE_ID, + }, + }) { + log::warn!( + "Failed to send cursor entered event to event channel: {}", + e + ); + } + }); + + let tx_clone = event_tx.clone(); + let window_clone = window.clone(); + motion_event_controller.connect_motion(move |_, x, y| { + if cursor_moved { + let scale_factor = window_clone.scale_factor(); + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { position: LogicalPosition::new(x, y).to_physical(scale_factor as f64), @@ -695,260 +612,257 @@ impl EventLoop { log::warn!("Failed to send cursor moved event to event channel: {}", e); } } - } - glib::Propagation::Stop - }); - - let tx_clone = event_tx.clone(); - window.connect_leave_notify_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorLeft { - device_id: DEVICE_ID, - }, - }) { - log::warn!("Failed to send cursor left event to event channel: {}", e); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_button_press_event(move |_, event| { - let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - _ => MouseButton::Other(button as u16), - }, - state: ElementState::Pressed, - device_id: DEVICE_ID, - // this field is depracted so it is fine to pass empty state - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!( - "Failed to send mouse input pressed event to event channel: {}", - e - ); - } - glib::Propagation::Stop - }); - - let tx_clone = event_tx.clone(); - window.connect_button_release_event(move |_, event| { - let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - _ => MouseButton::Other(button as u16), - }, - state: ElementState::Released, - device_id: DEVICE_ID, - // this field is depracted so it is fine to pass empty state - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!( - "Failed to send mouse input released event to event channel: {}", - e - ); - } - glib::Propagation::Stop - }); - - let tx_clone = event_tx.clone(); - window.connect_scroll_event(move |_, event| { - let (x, y) = event.delta(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, - delta: MouseScrollDelta::LineDelta(-x as f32, -y as f32), - phase: match event.direction() { - ScrollDirection::Smooth => TouchPhase::Moved, - _ => TouchPhase::Ended, + }); + + let tx_clone = event_tx.clone(); + motion_event_controller.connect_leave(move |_| { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorLeft { + device_id: DEVICE_ID, }, - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!("Failed to send scroll event to event channel: {}", e); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - let keyboard_handler = Rc::new(move |event_key: EventKey, element_state| { - // if we have a modifier lets send it - let mut mods = keyboard::get_modifiers(event_key.clone()); - if !mods.is_empty() { - // if we release the modifier tell the world - if ElementState::Released == element_state { - mods = ModifiersState::empty(); + }) { + log::warn!("Failed to send cursor left event to event channel: {}", e); } + }); - if let Err(e) = tx_clone.send(Event::WindowEvent { + let tx_clone = event_tx.clone(); + all_click_controller.connect_pressed(move |event, _, _, _| { + let button = event.button(); + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::ModifiersChanged(mods), + event: WindowEvent::MouseInput { + button: match button { + gdk::BUTTON_PRIMARY => MouseButton::Left, + gdk::BUTTON_MIDDLE => MouseButton::Middle, + gdk::BUTTON_SECONDARY => MouseButton::Right, + _ => MouseButton::Other(button as u16), + }, + state: ElementState::Pressed, + device_id: DEVICE_ID, + // this field is depracted so it is fine to pass empty state + modifiers: ModifiersState::empty(), + }, }) { log::warn!( - "Failed to send modifiers changed event to event channel: {}", + "Failed to send mouse input pressed event to event channel: {}", e ); - } else { - // stop here we don't want to send the key event - // as we emit the `ModifiersChanged` - return glib::ControlFlow::Continue; } - } - - // todo: implement repeat? - let event = keyboard::make_key_event(&event_key, false, None, element_state); + }); - if let Some(event) = event { - if let Err(e) = tx_clone.send(Event::WindowEvent { + let tx_clone = event_tx.clone(); + all_click_controller.connect_released(move |event, _, _, _| { + let button = event.button(); + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::KeyboardInput { + event: WindowEvent::MouseInput { + button: match button { + gdk::BUTTON_PRIMARY => MouseButton::Left, + gdk::BUTTON_MIDDLE => MouseButton::Middle, + gdk::BUTTON_SECONDARY => MouseButton::Right, + _ => MouseButton::Other(button as u16), + }, + state: ElementState::Released, device_id: DEVICE_ID, - event, - is_synthetic: false, + // this field is depracted so it is fine to pass empty state + modifiers: ModifiersState::empty(), }, }) { - log::warn!("Failed to send keyboard event to event channel: {}", e); + log::warn!( + "Failed to send mouse input released event to event channel: {}", + e + ); } - } - glib::ControlFlow::Continue - }); - - let tx_clone = event_tx.clone(); - // TODO Add actual IME from system - let ime = gtk::IMContextSimple::default(); - ime.set_client_window(window.window().as_ref()); - ime.focus_in(); - ime.connect_commit(move |_, s| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ReceivedImeText(s.to_string()), - }) { - log::warn!( - "Failed to send received IME text event to event channel: {}", - e - ); - } - }); - - let handler = keyboard_handler.clone(); - window.connect_key_press_event(move |_, event_key| { - handler(event_key.to_owned(), ElementState::Pressed); - ime.filter_keypress(event_key); - - glib::Propagation::Proceed - }); - - let handler = keyboard_handler.clone(); - window.connect_key_release_event(move |_, event_key| { - handler(event_key.to_owned(), ElementState::Released); - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_window_state_event(move |window, event| { - let state = event.changed_mask(); - if state.contains(WindowState::ICONIFIED) || state.contains(WindowState::MAXIMIZED) { - let scale_factor = window.scale_factor(); - - let (x, y) = window.position(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Moved( - LogicalPosition::new(x, y).to_physical(scale_factor as f64), - ), - }) { - log::warn!("Failed to send window moved event to event channel: {}", e); + }); + + let tx_clone = event_tx.clone(); + scroll_event_controller.connect_scroll(move |event, dx, dy| { + if let Some(gdk_event) = event + .current_event() + .and_then(|e| e.downcast::().ok()) + { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta: MouseScrollDelta::LineDelta(-dx as f32, -dy as f32), + phase: match gdk_event.direction() { + ScrollDirection::Smooth => TouchPhase::Moved, + _ => TouchPhase::Ended, + }, + modifiers: ModifiersState::empty(), + }, + }) { + log::warn!("Failed to send scroll event to event channel: {}", e); + } + } + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + let keyboard_handler = Rc::new(move |key: gdk::Key, keycode, element_state| { + // if we have a modifier lets send it + let mut mods = keyboard::get_modifiers(key, keycode); + if !mods.is_empty() { + // if we release the modifier tell the world + if ElementState::Released == element_state { + mods = ModifiersState::empty(); + } + + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(mods), + }) { + log::warn!( + "Failed to send modifiers changed event to event channel: {}", + e + ); + } else { + // stop here we don't want to send the key event + // as we emit the `ModifiersChanged` + return glib::ControlFlow::Continue; + } } - let (w, h) = window.size(); - if let Err(e) = tx_clone.send(Event::WindowEvent { + // todo: implement repeat? + let event = keyboard::make_key_event(&key, keycode, false, None, element_state); + + if let Some(event) = event { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }, + }) { + log::warn!("Failed to send keyboard event to event channel: {}", e); + } + } + glib::ControlFlow::Continue + }); + + let tx_clone = event_tx.clone(); + // TODO Add actual IME from system + let ime = gtk::IMContextSimple::default(); + ime.set_client_widget(Some(&window)); + ime.focus_in(); + ime.connect_commit(move |_, s| { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { window_id: RootWindowId(id), - event: WindowEvent::Resized( - LogicalSize::new(w, h).to_physical(scale_factor as f64), - ), + event: WindowEvent::ReceivedImeText(s.to_string()), }) { log::warn!( - "Failed to send window resized event to event channel: {}", + "Failed to send received IME text event to event channel: {}", e ); } - } - glib::Propagation::Proceed - }); - - // Receive draw events of the window. - let draw_clone = draw_tx.clone(); - window.connect_draw(move |window, cr| { - if let Err(e) = draw_clone.send(id) { - log::warn!("Failed to send redraw event to event channel: {}", e); + }); + + let handler = keyboard_handler.clone(); + key_event_controller.connect_key_pressed(move |event, key, keycode, _| { + handler( + key.to_owned(), + keycode.try_into().unwrap(), + ElementState::Pressed, + ); + ime.filter_keypress(event.current_event().unwrap()); + + glib::Propagation::Proceed + }); + + let handler = keyboard_handler.clone(); + key_event_controller.connect_key_released(move |_, key, keycode, _| { + handler( + key.to_owned(), + keycode.try_into().unwrap(), + ElementState::Released, + ); + }); + + let draw_clone = draw_tx.clone(); + let redraw_handler = Rc::new(move |window: >k::Window| { + let draw_clone = draw_clone.clone(); + window.frame_clock().unwrap().connect_paint(move |_| { + if let Err(e) = draw_clone.send_blocking(id) { + log::warn!("Failed to send redraw event to event channel: {}", e); + } + }); + }); + + if window.is_realized() { + let window_ = window.clone(); + redraw_handler(&window_); + } else { + // If the window isn't realized, it won't have a frame clock + // In this case we need to wait on the realize signal before we can add the redraw event handler. + let signal_id = Rc::new(RefCell::new(None)); + let signal_id_ = signal_id.clone(); + let handler = redraw_handler.clone(); + let id = window.connect_realize(move |window| { + if let Some(id) = signal_id_.take() { + handler(window); + window.disconnect(id); + } + }); + signal_id.borrow_mut().replace(id); } + // Make window transparent if requested. if transparent { - let background_color = unsafe { - window - .data::>("background_color") - .and_then(|c| c.as_ref().clone()) - }; - - let rgba = background_color - .map(|(r, g, b, a)| (r as f64, g as f64, b as f64, a as f64 / 255.0)) - .unwrap_or((0., 0., 0., 0.)); - - let rect = window - .child() - .map(|c| c.allocation()) - .unwrap_or_else(|| window.allocation()); - - cr.rectangle( - rect.x() as _, - rect.y() as _, - rect.width() as _, - rect.height() as _, + let display = RootExt::display(&window); + + let provider = gtk::CssProvider::new(); + let theme = format!( + r#" + window.tao-window-{} {{ + background-color: rgba(0,0,0,0.0); + }} + "#, + id.0 + ); + + provider.load_from_data(&theme); + + gtk::style_context_add_provider_for_display( + &display, + &provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, ); - cr.set_source_rgba(rgba.0, rgba.1, rgba.2, rgba.3); - cr.set_operator(cairo::Operator::Source); - let _ = cr.fill(); - cr.set_operator(cairo::Operator::Over); } - glib::Propagation::Proceed - }); - } - } - } else if id == WindowId::dummy() { - match request { - WindowRequest::ProgressBarState(state) => { - taskbar.update(state); - } - WindowRequest::BadgeCount(count, desktop_filename) => { - taskbar.update_count(count, desktop_filename); + window.add_controller(motion_event_controller); + window.add_controller(key_event_controller); + window.add_controller(focus_event_controller); + window.add_controller(scroll_event_controller); + window.add_controller(primary_click_controller); + window.add_controller(all_click_controller); + } } - WindowRequest::SetTheme(theme) => { - if let Some(settings) = Settings::default() { - match theme { - Some(Theme::Dark) => settings.set_gtk_application_prefer_dark_theme(true), - Some(Theme::Light) | None => settings.set_gtk_application_prefer_dark_theme(false), + } else if id == WindowId::dummy() { + match request { + WindowRequest::ProgressBarState(state) => { + taskbar.update(state); + } + WindowRequest::BadgeCount(count, desktop_filename) => { + taskbar.update_count(count, desktop_filename); + } + WindowRequest::SetTheme(theme) => { + if let Some(settings) = Settings::default() { + match theme { + Some(Theme::Dark) => settings.set_gtk_application_prefer_dark_theme(true), + Some(Theme::Light) | None => { + settings.set_gtk_application_prefer_dark_theme(false) + } + } } } + _ => unreachable!(), } - _ => unreachable!(), } } - glib::ControlFlow::Continue + glib::ControlFlow::Break }); // Create event loop itself. @@ -1088,7 +1002,7 @@ impl EventLoop { EventState::EventQueue => match control_flow { ControlFlow::ExitWithCode(code) => { callback(Event::LoopDestroyed, window_target, &mut control_flow); - break (code); + break code; } _ => match events.try_recv() { Ok(event) => match event { @@ -1119,7 +1033,8 @@ impl EventLoop { } }, } - gtk::main_iteration_do(blocking); + let context = MainContext::default(); + context.iteration(blocking); }; if let Some(run_device_thread) = run_device_thread { run_device_thread.store(false, Ordering::Relaxed); @@ -1145,7 +1060,7 @@ impl EventLoop { /// Used to send custom events to `EventLoop`. #[derive(Debug)] pub struct EventLoopProxy { - user_event_tx: crossbeam_channel::Sender>, + user_event_tx: async_channel::Sender>, } impl Clone for EventLoopProxy { @@ -1165,8 +1080,8 @@ impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self .user_event_tx - .send(Event::UserEvent(event)) - .map_err(|SendError(event)| { + .send_blocking(Event::UserEvent(event)) + .map_err(|async_channel::SendError(event)| { if let Event::UserEvent(error) = event { EventLoopClosed(error) } else { @@ -1265,16 +1180,16 @@ impl ResizeDirection { } } - fn to_gtk_edge(&self) -> WindowEdge { + fn to_gtk_edge(&self) -> SurfaceEdge { match self { - ResizeDirection::East => WindowEdge::East, - ResizeDirection::North => WindowEdge::North, - ResizeDirection::NorthEast => WindowEdge::NorthEast, - ResizeDirection::NorthWest => WindowEdge::NorthWest, - ResizeDirection::South => WindowEdge::South, - ResizeDirection::SouthEast => WindowEdge::SouthEast, - ResizeDirection::SouthWest => WindowEdge::SouthWest, - ResizeDirection::West => WindowEdge::West, + ResizeDirection::East => SurfaceEdge::East, + ResizeDirection::North => SurfaceEdge::North, + ResizeDirection::NorthEast => SurfaceEdge::NorthEast, + ResizeDirection::NorthWest => SurfaceEdge::NorthWest, + ResizeDirection::South => SurfaceEdge::South, + ResizeDirection::SouthEast => SurfaceEdge::SouthEast, + ResizeDirection::SouthWest => SurfaceEdge::SouthWest, + ResizeDirection::West => SurfaceEdge::West, } } } diff --git a/src/platform_impl/linux/gtk_window/imp.rs b/src/platform_impl/linux/gtk_window/imp.rs new file mode 100644 index 000000000..9315ebd46 --- /dev/null +++ b/src/platform_impl/linux/gtk_window/imp.rs @@ -0,0 +1,59 @@ +use std::{ + rc::Rc, + sync::{ + atomic::{AtomicI32, Ordering}, + OnceLock, + }, +}; + +use gtk::{ + glib::{self, subclass::Signal}, + prelude::*, + subclass::prelude::*, +}; + +#[derive(Debug, Default)] +// By implementing Default we don't have to provide a `new` fn in our +// ObjectSubclass impl. +pub struct ApplicationWindow { + pub outer_size: Rc<(AtomicI32, AtomicI32)>, + pub inner_size: Rc<(AtomicI32, AtomicI32)>, +} + +#[glib::object_subclass] +impl ObjectSubclass for ApplicationWindow { + const NAME: &'static str = "ExTaoWindow"; + type Type = super::ApplicationWindow; + type ParentType = gtk::ApplicationWindow; +} + +impl ObjectImpl for ApplicationWindow { + fn signals() -> &'static [Signal] { + static SIGNALS: OnceLock> = OnceLock::new(); + SIGNALS.get_or_init(|| { + vec![Signal::builder("resized") + .param_types([i32::static_type(), i32::static_type()]) + .build()] + }) + } +} + +impl WidgetImpl for ApplicationWindow { + fn size_allocate(&self, width: i32, height: i32, baseline: i32) { + self.parent_size_allocate(width, height, baseline); + let obj = self.obj(); + + self.inner_size.0.store(width, Ordering::Release); + self.inner_size.1.store(height, Ordering::Release); + + obj.emit_by_name::<()>("resized", &[&width, &height]); + + if obj.is_realized() { + let surface = obj.surface().unwrap(); + self.outer_size.0.store(surface.width(), Ordering::Release); + self.outer_size.1.store(surface.height(), Ordering::Release); + } + } +} +impl WindowImpl for ApplicationWindow {} +impl ApplicationWindowImpl for ApplicationWindow {} diff --git a/src/platform_impl/linux/gtk_window/mod.rs b/src/platform_impl/linux/gtk_window/mod.rs new file mode 100644 index 000000000..c45a6fcd0 --- /dev/null +++ b/src/platform_impl/linux/gtk_window/mod.rs @@ -0,0 +1,53 @@ +mod imp; + +use std::{rc::Rc, sync::atomic::AtomicI32}; + +use gtk::{ + gio, + glib::{ + self, object::ObjectExt, subclass::types::ObjectSubclassIsExt, Object, RustClosure, + SignalHandlerId, + }, +}; + +use crate::window::WindowAttributes; + +use super::{Parent, PlatformSpecificWindowBuilderAttributes}; + +glib::wrapper! { + pub struct ApplicationWindow(ObjectSubclass) + @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, + @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager; +} + +impl ApplicationWindow { + pub fn new( + app: >k::Application, + attributes: &WindowAttributes, + pl_attribs: &PlatformSpecificWindowBuilderAttributes, + ) -> Self { + // Create new window + let mut window_builder = Object::builder() + .property("application", app) + .property("title", &attributes.title) + .property("deletable", attributes.closable) + .property("decorated", attributes.decorations); + + if let Parent::ChildOf(parent) = &pl_attribs.parent { + window_builder = window_builder.property("transient-for", parent); + } + + window_builder.build() + } + + pub fn inner_size(&self) -> &Rc<(AtomicI32, AtomicI32)> { + &self.imp().inner_size + } + pub fn outer_size(&self) -> &Rc<(AtomicI32, AtomicI32)> { + &self.imp().outer_size + } + + pub fn connect_resized(&self, f: RustClosure) -> SignalHandlerId { + self.connect_closure("resized", false, f) + } +} diff --git a/src/platform_impl/linux/keyboard.rs b/src/platform_impl/linux/keyboard.rs index ea86312ad..4ec577591 100644 --- a/src/platform_impl/linux/keyboard.rs +++ b/src/platform_impl/linux/keyboard.rs @@ -7,19 +7,10 @@ use crate::{ event::{ElementState, KeyEvent}, keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKeyCode}, }; -use gtk::{ - gdk::{self, keys::constants::*, EventKey}, - glib, -}; -use std::{ - collections::HashSet, - ffi::c_void, - os::raw::{c_int, c_uint}, - ptr, slice, - sync::Mutex, -}; +use gtk::{gdk, prelude::DisplayExtManual}; +use std::{collections::HashSet, sync::Mutex}; -pub type RawKey = gdk::keys::Key; +pub type RawKey = gdk::Key; lazy_static! { static ref KEY_STRINGS: Mutex> = Mutex::new(HashSet::new()); @@ -38,84 +29,84 @@ fn insert_or_get_key_str(string: String) -> &'static str { #[allow(clippy::just_underscores_and_digits, non_upper_case_globals)] pub(crate) fn raw_key_to_key(gdk_key: RawKey) -> Option> { match gdk_key { - Escape => Some(Key::Escape), - BackSpace => Some(Key::Backspace), - Tab | ISO_Left_Tab => Some(Key::Tab), - Return => Some(Key::Enter), - Control_L | Control_R => Some(Key::Control), - Alt_L | Alt_R => Some(Key::Alt), - Shift_L | Shift_R => Some(Key::Shift), + RawKey::Escape => Some(Key::Escape), + RawKey::BackSpace => Some(Key::Backspace), + RawKey::Tab | RawKey::ISO_Left_Tab => Some(Key::Tab), + RawKey::Return => Some(Key::Enter), + RawKey::Control_L | RawKey::Control_R => Some(Key::Control), + RawKey::Alt_L | RawKey::Alt_R => Some(Key::Alt), + RawKey::Shift_L | RawKey::Shift_R => Some(Key::Shift), // TODO: investigate mapping. Map Meta_[LR]? - Super_L | Super_R => Some(Key::Super), - Caps_Lock => Some(Key::CapsLock), - F1 => Some(Key::F1), - F2 => Some(Key::F2), - F3 => Some(Key::F3), - F4 => Some(Key::F4), - F5 => Some(Key::F5), - F6 => Some(Key::F6), - F7 => Some(Key::F7), - F8 => Some(Key::F8), - F9 => Some(Key::F9), - F10 => Some(Key::F10), - F11 => Some(Key::F11), - F12 => Some(Key::F12), - F13 => Some(Key::F13), - F14 => Some(Key::F14), - F15 => Some(Key::F15), - F16 => Some(Key::F16), - F17 => Some(Key::F17), - F18 => Some(Key::F18), - F19 => Some(Key::F19), - F20 => Some(Key::F20), - F21 => Some(Key::F21), - F22 => Some(Key::F22), - F23 => Some(Key::F23), - F24 => Some(Key::F24), + RawKey::Super_L | RawKey::Super_R => Some(Key::Super), + RawKey::Caps_Lock => Some(Key::CapsLock), + RawKey::F1 => Some(Key::F1), + RawKey::F2 => Some(Key::F2), + RawKey::F3 => Some(Key::F3), + RawKey::F4 => Some(Key::F4), + RawKey::F5 => Some(Key::F5), + RawKey::F6 => Some(Key::F6), + RawKey::F7 => Some(Key::F7), + RawKey::F8 => Some(Key::F8), + RawKey::F9 => Some(Key::F9), + RawKey::F10 => Some(Key::F10), + RawKey::F11 => Some(Key::F11), + RawKey::F12 => Some(Key::F12), + RawKey::F13 => Some(Key::F13), + RawKey::F14 => Some(Key::F14), + RawKey::F15 => Some(Key::F15), + RawKey::F16 => Some(Key::F16), + RawKey::F17 => Some(Key::F17), + RawKey::F18 => Some(Key::F18), + RawKey::F19 => Some(Key::F19), + RawKey::F20 => Some(Key::F20), + RawKey::F21 => Some(Key::F21), + RawKey::F22 => Some(Key::F22), + RawKey::F23 => Some(Key::F23), + RawKey::F24 => Some(Key::F24), - Print => Some(Key::PrintScreen), - Scroll_Lock => Some(Key::ScrollLock), + RawKey::Print => Some(Key::PrintScreen), + RawKey::Scroll_Lock => Some(Key::ScrollLock), // Pause/Break not audio. - Pause => Some(Key::Pause), + RawKey::Pause => Some(Key::Pause), - Insert => Some(Key::Insert), - Delete => Some(Key::Delete), - Home => Some(Key::Home), - End => Some(Key::End), - Page_Up => Some(Key::PageUp), - Page_Down => Some(Key::PageDown), - Num_Lock => Some(Key::NumLock), + RawKey::Insert => Some(Key::Insert), + RawKey::Delete => Some(Key::Delete), + RawKey::Home => Some(Key::Home), + RawKey::End => Some(Key::End), + RawKey::Page_Up => Some(Key::PageUp), + RawKey::Page_Down => Some(Key::PageDown), + RawKey::Num_Lock => Some(Key::NumLock), - Up => Some(Key::ArrowUp), - Down => Some(Key::ArrowDown), - Left => Some(Key::ArrowLeft), - Right => Some(Key::ArrowRight), - Clear => Some(Key::Clear), + RawKey::Up => Some(Key::ArrowUp), + RawKey::Down => Some(Key::ArrowDown), + RawKey::Left => Some(Key::ArrowLeft), + RawKey::Right => Some(Key::ArrowRight), + RawKey::Clear => Some(Key::Clear), - Menu => Some(Key::ContextMenu), - WakeUp => Some(Key::WakeUp), - Launch0 => Some(Key::LaunchApplication1), - Launch1 => Some(Key::LaunchApplication2), - ISO_Level3_Shift => Some(Key::AltGraph), + RawKey::Menu => Some(Key::ContextMenu), + RawKey::WakeUp => Some(Key::WakeUp), + RawKey::Launch0 => Some(Key::LaunchApplication1), + RawKey::Launch1 => Some(Key::LaunchApplication2), + RawKey::ISO_Level3_Shift => Some(Key::AltGraph), - KP_Begin => Some(Key::Clear), - KP_Delete => Some(Key::Delete), - KP_Down => Some(Key::ArrowDown), - KP_End => Some(Key::End), - KP_Enter => Some(Key::Enter), - KP_F1 => Some(Key::F1), - KP_F2 => Some(Key::F2), - KP_F3 => Some(Key::F3), - KP_F4 => Some(Key::F4), - KP_Home => Some(Key::Home), - KP_Insert => Some(Key::Insert), - KP_Left => Some(Key::ArrowLeft), - KP_Page_Down => Some(Key::PageDown), - KP_Page_Up => Some(Key::PageUp), - KP_Right => Some(Key::ArrowRight), + RawKey::KP_Begin => Some(Key::Clear), + RawKey::KP_Delete => Some(Key::Delete), + RawKey::KP_Down => Some(Key::ArrowDown), + RawKey::KP_End => Some(Key::End), + RawKey::KP_Enter => Some(Key::Enter), + RawKey::KP_F1 => Some(Key::F1), + RawKey::KP_F2 => Some(Key::F2), + RawKey::KP_F3 => Some(Key::F3), + RawKey::KP_F4 => Some(Key::F4), + RawKey::KP_Home => Some(Key::Home), + RawKey::KP_Insert => Some(Key::Insert), + RawKey::KP_Left => Some(Key::ArrowLeft), + RawKey::KP_Page_Down => Some(Key::PageDown), + RawKey::KP_Page_Up => Some(Key::PageUp), + RawKey::KP_Right => Some(Key::ArrowRight), // KP_Separator? What does it map to? - KP_Tab => Some(Key::Tab), - KP_Up => Some(Key::ArrowUp), + RawKey::KP_Tab => Some(Key::Tab), + RawKey::KP_Up => Some(Key::ArrowUp), // TODO: more mappings (media etc) _ => None, } @@ -124,14 +115,47 @@ pub(crate) fn raw_key_to_key(gdk_key: RawKey) -> Option> { #[allow(clippy::just_underscores_and_digits, non_upper_case_globals)] pub(crate) fn raw_key_to_location(raw: RawKey) -> KeyLocation { match raw { - Control_L | Shift_L | Alt_L | Super_L | Meta_L => KeyLocation::Left, - Control_R | Shift_R | Alt_R | Super_R | Meta_R => KeyLocation::Right, - KP_0 | KP_1 | KP_2 | KP_3 | KP_4 | KP_5 | KP_6 | KP_7 | KP_8 | KP_9 | KP_Add | KP_Begin - | KP_Decimal | KP_Delete | KP_Divide | KP_Down | KP_End | KP_Enter | KP_Equal | KP_F1 - | KP_F2 | KP_F3 | KP_F4 | KP_Home | KP_Insert | KP_Left | KP_Multiply | KP_Page_Down - | KP_Page_Up | KP_Right | KP_Separator | KP_Space | KP_Subtract | KP_Tab | KP_Up => { - KeyLocation::Numpad + RawKey::Control_L | RawKey::Shift_L | RawKey::Alt_L | RawKey::Super_L | RawKey::Meta_L => { + KeyLocation::Left + } + RawKey::Control_R | RawKey::Shift_R | RawKey::Alt_R | RawKey::Super_R | RawKey::Meta_R => { + KeyLocation::Right } + RawKey::KP_0 + | RawKey::KP_1 + | RawKey::KP_2 + | RawKey::KP_3 + | RawKey::KP_4 + | RawKey::KP_5 + | RawKey::KP_6 + | RawKey::KP_7 + | RawKey::KP_8 + | RawKey::KP_9 + | RawKey::KP_Add + | RawKey::KP_Begin + | RawKey::KP_Decimal + | RawKey::KP_Delete + | RawKey::KP_Divide + | RawKey::KP_Down + | RawKey::KP_End + | RawKey::KP_Enter + | RawKey::KP_Equal + | RawKey::KP_F1 + | RawKey::KP_F2 + | RawKey::KP_F3 + | RawKey::KP_F4 + | RawKey::KP_Home + | RawKey::KP_Insert + | RawKey::KP_Left + | RawKey::KP_Multiply + | RawKey::KP_Page_Down + | RawKey::KP_Page_Up + | RawKey::KP_Right + | RawKey::KP_Separator + | RawKey::KP_Space + | RawKey::KP_Subtract + | RawKey::KP_Tab + | RawKey::KP_Up => KeyLocation::Numpad, _ => KeyLocation::Standard, } } @@ -147,15 +171,12 @@ const MODIFIER_MAP: &[(Key<'static>, ModifiersState)] = &[ // we need to have the modifier before the second key is entered to follow // other os' logic -- this way we can emit the new `ModifiersState` before // we receive the next key, if needed the developer can update his local state. -pub(crate) fn get_modifiers(key: EventKey) -> ModifiersState { +pub(crate) fn get_modifiers(key: RawKey, scancode: u16) -> ModifiersState { // a keycode (scancode in Windows) is a code that refers to a physical keyboard key. - let scancode = key.hardware_keycode(); - // a keyval (keysym in X) is a "logical" key name, such as GDK_Enter, GDK_a, GDK_space, etc. - let keyval = key.keyval(); // unicode value - let unicode = keyval.to_unicode(); + let unicode = key.to_unicode(); // translate to tao::keyboard::Key - let key_from_code = raw_key_to_key(keyval).unwrap_or_else(|| { + let key_from_code = raw_key_to_key(key).unwrap_or_else(|| { if let Some(key) = unicode { if key >= ' ' && key != '\x7f' { Key::Character(insert_or_get_key_str(key.to_string())) @@ -178,25 +199,22 @@ pub(crate) fn get_modifiers(key: EventKey) -> ModifiersState { } pub(crate) fn make_key_event( - key: &EventKey, + key: &RawKey, + scancode: u16, is_repeat: bool, key_override: Option, state: ElementState, ) -> Option { // a keycode (scancode in Windows) is a code that refers to a physical keyboard key. - let scancode = key.hardware_keycode(); - // a keyval (keysym in X) is a "logical" key name, such as GDK_Enter, GDK_a, GDK_space, etc. - let keyval_without_modifiers = key.keyval(); - let keyval_with_modifiers = - hardware_keycode_to_keyval(scancode).unwrap_or_else(|| keyval_without_modifiers.clone()); + let keyval_with_modifiers = hardware_keycode_to_keyval(scancode).unwrap_or(*key); // get unicode value, with and without modifiers let text_without_modifiers = keyval_with_modifiers.to_unicode(); - let text_with_modifiers = keyval_without_modifiers.to_unicode(); + let text_with_modifiers = key.to_unicode(); // get physical key from the scancode (keycode) let physical_key = key_override.unwrap_or_else(|| KeyCode::from_scancode(scancode as u32)); // extract key without modifier - let key_without_modifiers = raw_key_to_key(keyval_with_modifiers.clone()).unwrap_or_else(|| { + let key_without_modifiers = raw_key_to_key(keyval_with_modifiers).unwrap_or_else(|| { if let Some(key) = text_without_modifiers { if key >= ' ' && key != '\x7f' { Key::Character(insert_or_get_key_str(key.to_string())) @@ -209,7 +227,7 @@ pub(crate) fn make_key_event( }); // extract the logical key - let logical_key = raw_key_to_key(keyval_without_modifiers).unwrap_or_else(|| { + let logical_key = raw_key_to_key(*key).unwrap_or_else(|| { if let Some(key) = text_with_modifiers { if key >= ' ' && key != '\x7f' { Key::Character(insert_or_get_key_str(key.to_string())) @@ -248,41 +266,14 @@ pub(crate) fn make_key_event( /// Map a hardware keycode to a keyval by performing a lookup in the keymap and finding the /// keyval with the lowest group and level fn hardware_keycode_to_keyval(keycode: u16) -> Option { - use glib::translate::FromGlib; - unsafe { - let keymap = gdk::ffi::gdk_keymap_get_default(); - - let mut nkeys = 0; - let mut keys: *mut gdk::ffi::GdkKeymapKey = ptr::null_mut(); - let mut keyvals: *mut c_uint = ptr::null_mut(); - - // call into gdk to retrieve the keyvals and keymap keys - gdk::ffi::gdk_keymap_get_entries_for_keycode( - keymap, - c_uint::from(keycode), - &mut keys as *mut *mut gdk::ffi::GdkKeymapKey, - &mut keyvals as *mut *mut c_uint, - &mut nkeys as *mut c_int, - ); - - if nkeys > 0 { - let keyvals_slice = slice::from_raw_parts(keyvals, nkeys as usize); - let keys_slice = slice::from_raw_parts(keys, nkeys as usize); + let display = gdk::Display::default()?; + let keymap = display.map_keycode(keycode.into())?; - let resolved_keyval = keys_slice.iter().enumerate().find_map(|(id, gdk_keymap)| { - if gdk_keymap.group == 0 && gdk_keymap.level == 0 { - Some(RawKey::from_glib(keyvals_slice[id])) - } else { - None - } - }); - - // notify glib to free the allocated arrays - glib::ffi::g_free(keyvals as *mut c_void); - glib::ffi::g_free(keys as *mut c_void); - - return resolved_keyval; + keymap.iter().find_map(|(keymap_key, key)| { + if keymap_key.group() == 0 && keymap_key.level() == 0 { + Some(*key) + } else { + None } - } - None + }) } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index e6b0d8f62..3cd66ffc9 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -11,8 +11,8 @@ mod monitor; mod util; mod window; +pub mod gtk_window; pub mod taskbar; -pub mod wayland; pub mod x11; pub use self::keycode::{keycode_from_scancode, keycode_to_scancode}; @@ -31,18 +31,13 @@ pub struct KeyEventExtra { } #[non_exhaustive] -#[derive(Clone)] +#[derive(Clone, Default)] pub enum Parent { + #[default] None, ChildOf(gtk::Window), } -impl Default for Parent { - fn default() -> Self { - Parent::None - } -} - #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub parent: Parent, diff --git a/src/platform_impl/linux/monitor.rs b/src/platform_impl/linux/monitor.rs index ceeea1265..79be2380f 100644 --- a/src/platform_impl/linux/monitor.rs +++ b/src/platform_impl/linux/monitor.rs @@ -2,7 +2,18 @@ // Copyright 2021-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 -use gtk::gdk::{self, prelude::MonitorExt, Display}; +use std::collections::VecDeque; + +use gtk::{ + gdk::{ + self, + prelude::{DisplayExt, MonitorExt}, + Monitor, + }, + gio::prelude::{ListModelExt, ListModelExtManual}, + glib::object::Cast, + prelude::{NativeExt, WidgetExt}, +}; use crate::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, @@ -15,8 +26,7 @@ pub struct MonitorHandle { } impl MonitorHandle { - pub fn new(display: &gdk::Display, number: i32) -> Self { - let monitor = display.monitor(number).unwrap(); + pub fn new(monitor: gdk::Monitor) -> Self { Self { monitor } } @@ -84,13 +94,40 @@ impl VideoMode { } } -pub fn from_point(display: &Display, x: f64, y: f64) -> Option { - if let Some(monitor) = display.monitor_at_point(x as i32, y as i32) { - (0..display.n_monitors()) - .map(|i| (i, display.monitor(i).unwrap())) - .find(|cur| cur.1.geometry() == monitor.geometry()) - .map(|x| MonitorHandle::new(display, x.0)) - } else { - None - } +#[inline] +pub fn current_monitor(window: &W) -> Option { + // `.surface()` returns `None` if the window is invisible; + // we fallback to the primary monitor + window + .surface() + .and_then(|surface| window.display().monitor_at_surface(&surface)) + .or_else(|| first_monitor(&window.display())) + .map(|monitor| { + let handle = MonitorHandle { monitor }; + RootMonitorHandle { inner: handle } + }) +} + +#[inline] +fn first_monitor(display: &W) -> Option { + display + .monitors() + .item(0) + .and_then(|m| m.dynamic_cast::().ok()) +} + +#[inline] +pub fn primary_monitor(display: &W) -> Option { + first_monitor(display).map(|monitor| RootMonitorHandle { + inner: MonitorHandle::new(monitor), + }) +} + +#[inline] +pub fn available_monitors(display: &W) -> VecDeque { + display + .monitors() + .iter::() + .map(|monitor| MonitorHandle::new(monitor.unwrap())) + .collect() } diff --git a/src/platform_impl/linux/util.rs b/src/platform_impl/linux/util.rs index 30cd8523b..f634a1b60 100644 --- a/src/platform_impl/linux/util.rs +++ b/src/platform_impl/linux/util.rs @@ -4,74 +4,75 @@ use crate::{ window::WindowSizeConstraints, }; use gtk::{ - gdk::{ + gdk, + glib::{ self, - prelude::{DeviceExt, SeatExt}, - Display, + object::{Cast, IsA}, + SignalHandlerId, }, - glib::{self}, - traits::{GtkWindowExt, WidgetExt}, + prelude::{DisplayExt, GtkWindowExt, NativeExt, SeatExt, SurfaceExt, WidgetExt}, }; use std::{cell::RefCell, rc::Rc}; #[inline] -pub fn cursor_position(is_wayland: bool) -> Result, ExternalError> { - if is_wayland { - Ok((0, 0).into()) - } else { - Display::default() - .map(|d| { - ( - d.default_seat().and_then(|s| s.pointer()), - d.default_group(), - ) - }) - .map(|(p, g)| { - p.map(|p| { - let (_, x, y) = p.position_double(); - LogicalPosition::new(x, y).to_physical(g.scale_factor() as _) - }) - }) - .map(|p| p.ok_or(ExternalError::Os(os_error!(super::OsError)))) - .ok_or(ExternalError::Os(os_error!(super::OsError)))? +pub fn on_window_realized(window: &W, f: F) -> SignalHandlerId +where + W: IsA, + F: Fn(&W) + 'static, +{ + // If the window is already realized, we won't get the signal initially. + if window.is_realized() { + f(window) } + window.connect_realize(f) +} + +#[inline] +pub fn surface_as_toplevel(surface: gdk::Surface) -> Result { + surface.clone().downcast::() +} + +#[inline] +pub fn default_pointer(display: &gdk::Display) -> Option { + display + .default_seat() + .and_then(|seat: gtk::gdk::Seat| seat.pointer()) +} + +#[inline] +pub fn cursor_position( + window: &W, +) -> Result, ExternalError> { + default_pointer(&window.display()) + .and_then(|pointer| { + window + .surface() + .and_then(|surface| surface.device_position(&pointer)) + .map(|(x, y, _)| LogicalPosition::new(x, y).to_physical::(window.scale_factor() as _)) + }) + .ok_or(ExternalError::Os(os_error!(super::OsError))) } pub fn set_size_constraints( window: &W, constraints: WindowSizeConstraints, ) { - let mut geom_mask = gdk::WindowHints::empty(); - if constraints.has_min() { - geom_mask |= gdk::WindowHints::MIN_SIZE; - } - if constraints.has_max() { - geom_mask |= gdk::WindowHints::MAX_SIZE; - } - let scale_factor = window.scale_factor() as f64; let min_size: LogicalSize = constraints.min_size_logical(scale_factor); - let max_size: LogicalSize = constraints.max_size_logical(scale_factor); - let picky_none: Option<>k::Window> = None; - window.set_geometry_hints( - picky_none, - Some(&gdk::Geometry::new( - min_size.width, - min_size.height, - max_size.width, - max_size.height, - 0, - 0, - 0, - 0, - 0f64, - 0f64, - gdk::Gravity::Center, - )), - geom_mask, - ) + let w = if min_size.width > -1 { + min_size.width + } else { + -1 + }; + let h = if min_size.height > -1 { + min_size.height + } else { + -1 + }; + + window.set_size_request(w, h); } pub struct WindowMaximizeProcess { diff --git a/src/platform_impl/linux/wayland/header.rs b/src/platform_impl/linux/wayland/header.rs deleted file mode 100644 index 480eb4c85..000000000 --- a/src/platform_impl/linux/wayland/header.rs +++ /dev/null @@ -1,36 +0,0 @@ -use gtk::{prelude::*, ApplicationWindow, EventBox, HeaderBar}; - -pub struct WlHeader; - -impl WlHeader { - pub fn setup(window: &ApplicationWindow, title: &str) { - let header = HeaderBar::builder() - .show_close_button(true) - .decoration_layout("menu:minimize,maximize,close") - .title(title) - .build(); - - let event_box = EventBox::new(); - event_box.set_above_child(true); - event_box.set_visible(true); - event_box.set_can_focus(false); - event_box.add(&header); - - window.set_titlebar(Some(&event_box)); - Self::connect_resize_window(&header, window); - } - - fn connect_resize_window(header: &HeaderBar, window: &ApplicationWindow) { - let header_weak = header.downgrade(); - window.connect_resizable_notify(move |window| { - if let Some(header) = header_weak.upgrade() { - let is_resizable = window.is_resizable(); - header.set_decoration_layout(if !is_resizable { - Some("menu:minimize,close") - } else { - Some("menu:minimize,maximize,close") - }); - } - }); - } -} diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs deleted file mode 100644 index a200d2603..000000000 --- a/src/platform_impl/linux/wayland/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -pub mod header; diff --git a/src/platform_impl/linux/window.rs b/src/platform_impl/linux/window.rs index 08c53a12e..07003e1f2 100644 --- a/src/platform_impl/linux/window.rs +++ b/src/platform_impl/linux/window.rs @@ -6,25 +6,16 @@ use std::{ cell::RefCell, collections::VecDeque, rc::Rc, - sync::{ - atomic::{AtomicBool, AtomicI32, Ordering}, - Arc, - }, + sync::atomic::{AtomicBool, AtomicI32, Ordering}, }; -use gtk::{ - gdk::WindowState, - glib::{self, translate::ToGlibPtr}, - prelude::*, - CssProvider, Settings, -}; +use gtk::{gdk, glib, prelude::*, CssProvider, Settings}; use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, + dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::MonitorHandle as RootMonitorHandle, - platform_impl::wayland::header::WlHeader, window::{ CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowSizeConstraints, RGBA, @@ -33,8 +24,9 @@ use crate::{ use super::{ event_loop::EventLoopWindowTarget, + gtk_window::ApplicationWindow, monitor::{self, MonitorHandle}, - util, Parent, PlatformSpecificWindowBuilderAttributes, + util, PlatformSpecificWindowBuilderAttributes, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -46,30 +38,21 @@ impl WindowId { } } -// Currently GTK doesn't provide feature for detect theme, so we need to check theme manually. -// ref: https://github.com/WebKit/WebKit/blob/e44ffaa0d999a9807f76f1805943eea204cfdfbc/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp#L587 -const GTK_THEME_SUFFIX_LIST: [&'static str; 3] = ["-dark", "-Dark", "-Darker"]; - pub struct Window { /// Window id. pub(crate) window_id: WindowId, /// Gtk application window. - pub(crate) window: gtk::ApplicationWindow, + pub(crate) window: ApplicationWindow, pub(crate) default_vbox: Option, /// Window requests sender - pub(crate) window_requests_tx: glib::Sender<(WindowId, WindowRequest)>, + pub(crate) window_requests_tx: async_channel::Sender<(WindowId, WindowRequest)>, scale_factor: Rc, - inner_position: Rc<(AtomicI32, AtomicI32)>, - outer_position: Rc<(AtomicI32, AtomicI32)>, - outer_size: Rc<(AtomicI32, AtomicI32)>, - inner_size: Rc<(AtomicI32, AtomicI32)>, maximized: Rc, - is_always_on_top: Rc, minimized: Rc, fullscreen: RefCell>, inner_size_constraints: RefCell, /// Draw event Sender - draw_tx: crossbeam_channel::Sender, + draw_tx: async_channel::Sender, preferred_theme: RefCell>, css_provider: CssProvider, } @@ -83,20 +66,17 @@ impl Window { let app = &event_loop_window_target.app; let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); let draw_tx = event_loop_window_target.draw_tx.clone(); - let is_wayland = event_loop_window_target.is_wayland(); - - let mut window_builder = gtk::ApplicationWindow::builder() - .application(app) - .accept_focus(attributes.focused); - if let Parent::ChildOf(parent) = pl_attribs.parent { - window_builder = window_builder.transient_for(&parent); - } + //let is_wayland = event_loop_window_target.is_wayland(); - let window = window_builder.build(); + let window = ApplicationWindow::new(app, &attributes, &pl_attribs); - if is_wayland { - WlHeader::setup(&window, &attributes.title); - } + let default_vbox = if pl_attribs.default_vbox { + let box_ = gtk::Box::new(gtk::Orientation::Vertical, 0); + window.set_child(Some(&box_)); + Some(box_) + } else { + None + }; let window_id = WindowId(window.id()); event_loop_window_target @@ -110,8 +90,7 @@ impl Window { .inner_size .map(|size| size.to_logical::(win_scale_factor as f64).into()) .unwrap_or((800, 600)); - window.set_default_size(1, 1); - window.resize(width, height); + window.set_default_size(width, height); if attributes.maximized { let maximize_process = util::WindowMaximizeProcess::new(window.clone(), attributes.resizable); @@ -123,126 +102,31 @@ impl Window { window.set_resizable(attributes.resizable); } - window.set_deletable(attributes.closable); - // Set Min/Max Size util::set_size_constraints(&window, attributes.inner_size_constraints); - // Set Position - if let Some(position) = attributes.position { - let (x, y): (i32, i32) = position.to_logical::(win_scale_factor as f64).into(); - window.move_(x, y); - } - - // Set GDK Visual - if pl_attribs.rgba_visual || attributes.transparent { - if let Some(screen) = GtkWindowExt::screen(&window) { - if let Some(visual) = screen.rgba_visual() { - window.set_visual(Some(&visual)); - } - } - } - - if pl_attribs.app_paintable || attributes.transparent { - // Set a few attributes to make the window can be painted. - // See Gtk drawing model for more info: - // https://docs.gtk.org/gtk3/drawing-model.html - window.set_app_paintable(true); - } - - if !pl_attribs.double_buffered { - let widget = window.upcast_ref::(); - if !event_loop_window_target.is_wayland() { - unsafe { - gtk::ffi::gtk_widget_set_double_buffered(widget.to_glib_none().0, 0); - } - } - } - - let default_vbox = if pl_attribs.default_vbox { - let box_ = gtk::Box::new(gtk::Orientation::Vertical, 0); - window.add(&box_); - Some(box_) - } else { - None - }; - // Rest attributes - window.set_title(&attributes.title); if let Some(Fullscreen::Borderless(m)) = &attributes.fullscreen { if let Some(monitor) = m { - let display = window.display(); - let monitor = &monitor.inner; - let monitors = display.n_monitors(); - for i in 0..monitors { - let m = display.monitor(i).unwrap(); - if m == monitor.monitor { - let screen = display.default_screen(); - window.fullscreen_on_monitor(&screen, i); - } - } + window.fullscreen_on_monitor(&monitor.inner.monitor); } else { window.fullscreen(); } } - window.set_visible(attributes.visible); - window.set_decorated(attributes.decorations); - - if attributes.always_on_bottom { - window.set_keep_below(attributes.always_on_bottom); - } - - if attributes.always_on_top { - window.set_keep_above(attributes.always_on_top); - } - - if attributes.visible_on_all_workspaces { - window.stick(); - } let preferred_theme = if let Some(settings) = Settings::default() { if let Some(preferred_theme) = attributes.preferred_theme { - match preferred_theme { - Theme::Dark => settings.set_gtk_application_prefer_dark_theme(true), - Theme::Light => { - if let Some(theme) = settings.gtk_theme_name() { - let theme = theme.as_str(); - // Remove dark variant. - if let Some(theme) = GTK_THEME_SUFFIX_LIST - .iter() - .find(|t| theme.ends_with(*t)) - .map(|v| theme.strip_suffix(v)) - { - settings.set_gtk_theme_name(theme); - } - } - } - } + settings.set_gtk_application_prefer_dark_theme(preferred_theme == Theme::Dark); } attributes.preferred_theme } else { None }; - if attributes.visible { - window.show_all(); - } else { - window.hide(); - } + window.present(); - // restore accept-focus after the window has been drawn - // if the window was initially created without focus - if !attributes.focused { - let signal_id = Arc::new(RefCell::new(None)); - let signal_id_ = signal_id.clone(); - let id = window.connect_draw(move |window, _| { - if let Some(id) = signal_id_.take() { - window.set_accept_focus(true); - window.disconnect(id); - } - glib::Propagation::Proceed - }); - signal_id.borrow_mut().replace(id); + if !attributes.visible { + window.set_visible(false); } // Check if we should paint the transparent background ourselves. @@ -251,7 +135,7 @@ impl Window { transparent = true; } let cursor_moved = pl_attribs.cursor_moved; - if let Err(e) = window_requests_tx.send(( + if let Err(e) = window_requests_tx.send_blocking(( window_id, WindowRequest::WireUpEvents { transparent, @@ -262,20 +146,7 @@ impl Window { log::warn!("Fail to send wire up events request: {}", e); } - let ( - scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, - maximized, - minimized, - is_always_on_top, - ) = Self::setup_signals(&window, Some(&attributes)); - - if let Some(icon) = attributes.window_icon { - window.set_icon(Some(&icon.inner.into())); - } + let (scale_factor, maximized, minimized) = Self::setup_signals(&window); let win = Self { window_id, @@ -284,98 +155,48 @@ impl Window { window_requests_tx, draw_tx, scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, maximized, minimized, - is_always_on_top, fullscreen: RefCell::new(attributes.fullscreen), inner_size_constraints: RefCell::new(attributes.inner_size_constraints), preferred_theme: RefCell::new(preferred_theme), css_provider: CssProvider::new(), }; - let _ = win.set_skip_taskbar(pl_attribs.skip_taskbar); win.set_background_color(attributes.background_color); Ok(win) } - fn setup_signals( - window: >k::ApplicationWindow, - attributes: Option<&WindowAttributes>, - ) -> ( - Rc, - Rc<(AtomicI32, AtomicI32)>, - Rc<(AtomicI32, AtomicI32)>, - Rc<(AtomicI32, AtomicI32)>, - Rc<(AtomicI32, AtomicI32)>, - Rc, - Rc, - Rc, - ) { + fn setup_signals(window: &ApplicationWindow) -> (Rc, Rc, Rc) { let win_scale_factor = window.scale_factor(); - let w_pos = window.position(); - let inner_position: Rc<(AtomicI32, AtomicI32)> = Rc::new((w_pos.0.into(), w_pos.1.into())); - let inner_position_clone = inner_position.clone(); - - let o_pos = window.window().map(|w| w.root_origin()).unwrap_or(w_pos); - let outer_position: Rc<(AtomicI32, AtomicI32)> = Rc::new((o_pos.0.into(), o_pos.1.into())); - let outer_position_clone = outer_position.clone(); - - let w_size = window.size(); - let inner_size: Rc<(AtomicI32, AtomicI32)> = Rc::new((w_size.0.into(), w_size.1.into())); - let inner_size_clone = inner_size.clone(); - - let o_size = window.window().map(|w| w.root_origin()).unwrap_or(w_pos); - let outer_size: Rc<(AtomicI32, AtomicI32)> = Rc::new((o_size.0.into(), o_size.1.into())); - let outer_size_clone = outer_size.clone(); - - window.connect_configure_event(move |window, event| { - let (x, y) = event.position(); - inner_position_clone.0.store(x, Ordering::Release); - inner_position_clone.1.store(y, Ordering::Release); - - let (w, h) = event.size(); - inner_size_clone.0.store(w as i32, Ordering::Release); - inner_size_clone.1.store(h as i32, Ordering::Release); - - let (x, y, w, h) = window - .window() - .map(|w| { - let rect = w.frame_extents(); - (rect.x(), rect.y(), rect.width(), rect.height()) - }) - .unwrap_or((x, y, w as i32, h as i32)); - - outer_position_clone.0.store(x, Ordering::Release); - outer_position_clone.1.store(y, Ordering::Release); - - outer_size_clone.0.store(w, Ordering::Release); - outer_size_clone.1.store(h, Ordering::Release); - - false - }); - let w_max = window.is_maximized(); let maximized: Rc = Rc::new(w_max.into()); - let max_clone = maximized.clone(); let minimized = Rc::new(AtomicBool::new(false)); + let max_clone = maximized.clone(); let minimized_clone = minimized.clone(); - let is_always_on_top = Rc::new(AtomicBool::new( - attributes.map(|a| a.always_on_top).unwrap_or(false), - )); - let is_always_on_top_clone = is_always_on_top.clone(); - - window.connect_window_state_event(move |_, event| { - let state = event.new_window_state(); - max_clone.store(state.contains(WindowState::MAXIMIZED), Ordering::Release); - minimized_clone.store(state.contains(WindowState::ICONIFIED), Ordering::Release); - is_always_on_top_clone.store(state.contains(WindowState::ABOVE), Ordering::Release); - glib::Propagation::Proceed + + // When a window is realized a new surface is created. + // All signal handlers on the surface must be re-added when this happens. + util::on_window_realized(window, move |window| { + let surface = window.surface().unwrap(); + + let toplevel = util::surface_as_toplevel(surface).unwrap(); + let max_clone = max_clone.clone(); + let minimized_clone = minimized_clone.clone(); + toplevel.connect_state_notify(move |t| { + let state = t.state(); + // Not available on wayland + minimized_clone.store( + state.contains(gdk::ToplevelState::MINIMIZED), + Ordering::Release, + ); + max_clone.store( + state.contains(gdk::ToplevelState::MAXIMIZED), + Ordering::Release, + ); + }); }); let scale_factor: Rc = Rc::new(win_scale_factor.into()); @@ -384,21 +205,12 @@ impl Window { scale_factor_clone.store(window.scale_factor(), Ordering::Release); }); - ( - scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, - maximized, - minimized, - is_always_on_top, - ) + (scale_factor, maximized, minimized) } pub(crate) fn new_from_gtk_window( event_loop_window_target: &EventLoopWindowTarget, - window: gtk::ApplicationWindow, + window: ApplicationWindow, ) -> Result { let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); let draw_tx = event_loop_window_target.draw_tx.clone(); @@ -409,16 +221,7 @@ impl Window { .borrow_mut() .insert(window_id); - let ( - scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, - maximized, - minimized, - is_always_on_top, - ) = Self::setup_signals(&window, None); + let (scale_factor, maximized, minimized) = Self::setup_signals(&window); let win = Self { window_id, @@ -427,13 +230,8 @@ impl Window { window_requests_tx, draw_tx, scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, maximized, minimized, - is_always_on_top, fullscreen: RefCell::new(None), inner_size_constraints: RefCell::new(WindowSizeConstraints::default()), preferred_theme: RefCell::new(None), @@ -452,43 +250,23 @@ impl Window { } pub fn request_redraw(&self) { - if let Err(e) = self.draw_tx.send(self.window_id) { + if let Err(e) = self.draw_tx.send_blocking(self.window_id) { log::warn!("Failed to send redraw event to event channel: {}", e); } } pub fn inner_position(&self) -> Result, NotSupportedError> { - let (x, y) = &*self.inner_position; - Ok( - LogicalPosition::new(x.load(Ordering::Acquire), y.load(Ordering::Acquire)) - .to_physical(self.scale_factor.load(Ordering::Acquire) as f64), - ) + Ok(PhysicalPosition::new(0, 0)) } pub fn outer_position(&self) -> Result, NotSupportedError> { - let (x, y) = &*self.outer_position; - Ok( - LogicalPosition::new(x.load(Ordering::Acquire), y.load(Ordering::Acquire)) - .to_physical(self.scale_factor.load(Ordering::Acquire) as f64), - ) + Ok(PhysicalPosition::new(0, 0)) } - pub fn set_outer_position>(&self, position: P) { - let (x, y): (i32, i32) = position - .into() - .to_logical::(self.scale_factor()) - .into(); - - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Position((x, y)))) - { - log::warn!("Fail to send position request: {}", e); - } - } + pub fn set_outer_position>(&self, _: P) {} pub fn set_background_color(&self, color: Option) { - if let Err(e) = self.window_requests_tx.send(( + if let Err(e) = self.window_requests_tx.send_blocking(( self.window_id, WindowRequest::BackgroundColor(self.css_provider.clone(), color), )) { @@ -497,7 +275,7 @@ impl Window { } pub fn inner_size(&self) -> PhysicalSize { - let (width, height) = &*self.inner_size; + let (width, height) = &**self.window.inner_size(); LogicalSize::new( width.load(Ordering::Acquire) as u32, @@ -511,14 +289,14 @@ impl Window { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::Size((width, height)))) + .send_blocking((self.window_id, WindowRequest::Size((width, height)))) { log::warn!("Fail to send size request: {}", e); } } pub fn outer_size(&self) -> PhysicalSize { - let (width, height) = &*self.outer_size; + let (width, height) = &**self.window.outer_size(); LogicalSize::new( width.load(Ordering::Acquire) as u32, @@ -530,7 +308,7 @@ impl Window { fn set_size_constraints(&self, constraints: WindowSizeConstraints) { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::SizeConstraints(constraints))) + .send_blocking((self.window_id, WindowRequest::SizeConstraints(constraints))) { log::warn!("Fail to send size constraint request: {}", e); } @@ -560,7 +338,7 @@ impl Window { pub fn set_title(&self, title: &str) { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::Title(title.to_string()))) + .send_blocking((self.window_id, WindowRequest::Title(title.to_string()))) { log::warn!("Fail to send title request: {}", e); } @@ -577,7 +355,7 @@ impl Window { pub fn set_visible(&self, visible: bool) { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::Visible(visible))) + .send_blocking((self.window_id, WindowRequest::Visible(visible))) { log::warn!("Fail to send visible request: {}", e); } @@ -587,7 +365,7 @@ impl Window { if !self.minimized.load(Ordering::Acquire) && self.window.get_visible() { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::Focus)) + .send_blocking((self.window_id, WindowRequest::Focus)) { log::warn!("Fail to send visible request: {}", e); } @@ -601,7 +379,7 @@ impl Window { pub fn set_resizable(&self, resizable: bool) { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::Resizable(resizable))) + .send_blocking((self.window_id, WindowRequest::Resizable(resizable))) { log::warn!("Fail to send resizable request: {}", e); } @@ -614,7 +392,7 @@ impl Window { pub fn set_closable(&self, closable: bool) { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::Closable(closable))) + .send_blocking((self.window_id, WindowRequest::Closable(closable))) { log::warn!("Fail to send closable request: {}", e); } @@ -623,7 +401,7 @@ impl Window { pub fn set_minimized(&self, minimized: bool) { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::Minimized(minimized))) + .send_blocking((self.window_id, WindowRequest::Minimized(minimized))) { log::warn!("Fail to send minimized request: {}", e); } @@ -632,7 +410,7 @@ impl Window { pub fn set_maximized(&self, maximized: bool) { let resizable = self.is_resizable(); - if let Err(e) = self.window_requests_tx.send(( + if let Err(e) = self.window_requests_tx.send_blocking(( self.window_id, WindowRequest::Maximized(maximized, resizable), )) { @@ -641,7 +419,7 @@ impl Window { } pub fn is_always_on_top(&self) -> bool { - self.is_always_on_top.load(Ordering::Acquire) + false } pub fn is_maximized(&self) -> bool { @@ -679,7 +457,7 @@ impl Window { pub fn drag_window(&self) -> Result<(), ExternalError> { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::DragWindow)) + .send_blocking((self.window_id, WindowRequest::DragWindow)) { log::warn!("Fail to send drag window request: {}", e); } @@ -689,7 +467,7 @@ impl Window { pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::DragResizeWindow(direction))) + .send_blocking((self.window_id, WindowRequest::DragResizeWindow(direction))) { log::warn!("Fail to send drag window request: {}", e); } @@ -700,7 +478,7 @@ impl Window { self.fullscreen.replace(fullscreen.clone()); if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::Fullscreen(fullscreen))) + .send_blocking((self.window_id, WindowRequest::Fullscreen(fullscreen))) { log::warn!("Fail to send fullscreen request: {}", e); } @@ -713,38 +491,17 @@ impl Window { pub fn set_decorations(&self, decorations: bool) { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::Decorations(decorations))) + .send_blocking((self.window_id, WindowRequest::Decorations(decorations))) { log::warn!("Fail to send decorations request: {}", e); } } - pub fn set_always_on_bottom(&self, always_on_bottom: bool) { - if let Err(e) = self.window_requests_tx.send(( - self.window_id, - WindowRequest::AlwaysOnBottom(always_on_bottom), - )) { - log::warn!("Fail to send always on bottom request: {}", e); - } - } + pub fn set_always_on_bottom(&self, _always_on_bottom: bool) {} - pub fn set_always_on_top(&self, always_on_top: bool) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::AlwaysOnTop(always_on_top))) - { - log::warn!("Fail to send always on top request: {}", e); - } - } + pub fn set_always_on_top(&self, _always_on_top: bool) {} - pub fn set_window_icon(&self, window_icon: Option) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::WindowIcon(window_icon))) - { - log::warn!("Fail to send window icon request: {}", e); - } - } + pub fn set_window_icon(&self, _window_icon: Option) {} pub fn set_ime_position>(&self, _position: P) { //TODO @@ -753,44 +510,23 @@ impl Window { pub fn request_user_attention(&self, request_type: Option) { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::UserAttention(request_type))) + .send_blocking((self.window_id, WindowRequest::UserAttention(request_type))) { log::warn!("Fail to send user attention request: {}", e); } } - pub fn set_visible_on_all_workspaces(&self, visible: bool) { - if let Err(e) = self.window_requests_tx.send(( - self.window_id, - WindowRequest::SetVisibleOnAllWorkspaces(visible), - )) { - log::warn!("Fail to send visible on all workspaces request: {}", e); - } - } pub fn set_cursor_icon(&self, cursor: CursorIcon) { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::CursorIcon(Some(cursor)))) + .send_blocking((self.window_id, WindowRequest::CursorIcon(Some(cursor)))) { log::warn!("Fail to send cursor icon request: {}", e); } } - pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { - let inner_pos = self.inner_position().unwrap_or_default(); - let (x, y): (i32, i32) = position - .into() - .to_logical::(self.scale_factor()) - .into(); - - if let Err(e) = self.window_requests_tx.send(( - self.window_id, - WindowRequest::CursorPosition((x + inner_pos.x, y + inner_pos.y)), - )) { - log::warn!("Fail to send cursor position request: {}", e); - } - - Ok(()) + pub fn set_cursor_position>(&self, _position: P) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) } pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { @@ -800,7 +536,7 @@ impl Window { pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::CursorIgnoreEvents(ignore))) + .send_blocking((self.window_id, WindowRequest::CursorIgnoreEvents(ignore))) { log::warn!("Fail to send cursor position request: {}", e); } @@ -816,7 +552,7 @@ impl Window { }; if let Err(e) = self .window_requests_tx - .send((self.window_id, WindowRequest::CursorIcon(cursor))) + .send_blocking((self.window_id, WindowRequest::CursorIcon(cursor))) { log::warn!("Fail to send cursor visibility request: {}", e); } @@ -824,74 +560,64 @@ impl Window { #[inline] pub fn cursor_position(&self) -> Result, ExternalError> { - util::cursor_position(self.is_wayland()) + util::cursor_position(&self.window) } + #[inline] pub fn current_monitor(&self) -> Option { - let display = self.window.display(); - // `.window()` returns `None` if the window is invisible; - // we fallback to the primary monitor - let monitor = self - .window - .window() - .and_then(|window| display.monitor_at_window(&window)) - .or_else(|| display.primary_monitor()); - - monitor.map(|monitor| RootMonitorHandle { - inner: MonitorHandle { monitor }, - }) + monitor::current_monitor(&self.window) } #[inline] pub fn available_monitors(&self) -> VecDeque { - let mut handles = VecDeque::new(); - let display = self.window.display(); - let numbers = display.n_monitors(); - - for i in 0..numbers { - let monitor = MonitorHandle::new(&display, i); - handles.push_back(monitor); - } - - handles + monitor::available_monitors(&self.display()) } + #[inline] pub fn primary_monitor(&self) -> Option { - let display = self.window.display(); - display.primary_monitor().map(|monitor| { - let handle = MonitorHandle { monitor }; - RootMonitorHandle { inner: handle } - }) + monitor::primary_monitor(&self.display()) + } + + #[inline] + pub fn monitor_from_point(&self, _: f64, _: f64) -> Option { + None } #[inline] - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - let display = &self.window.display(); - monitor::from_point(display, x, y).map(|inner| RootMonitorHandle { inner }) + fn display(&self) -> gdk::Display { + RootExt::display(&self.window) } fn is_wayland(&self) -> bool { - self.window.display().backend().is_wayland() + self.display().backend().is_wayland() } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { if self.is_wayland() { + use gdk_wayland::prelude::WaylandSurfaceExtManual; + use gdk_wayland::wayland_client::Proxy; + let mut window_handle = rwh_04::WaylandHandle::empty(); - if let Some(window) = self.window.window() { - window_handle.surface = - unsafe { gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _) }; + if let Some(surface) = self.window.surface() { + let ptr = surface + .downcast::() + .unwrap() + .wl_surface() + .unwrap() + .id() + .as_ptr(); + window_handle.surface = ptr as *mut _; } rwh_04::RawWindowHandle::Wayland(window_handle) } else { let mut window_handle = rwh_04::XlibHandle::empty(); - unsafe { - if let Some(window) = self.window.window() { - window_handle.window = gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _); - } + if let Some(surface) = self.window.surface() { + window_handle.window = surface.downcast::().unwrap().xid(); } + rwh_04::RawWindowHandle::Xlib(window_handle) } } @@ -900,61 +626,85 @@ impl Window { #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { if self.is_wayland() { + use gdk_wayland::prelude::WaylandSurfaceExtManual; + use gdk_wayland::wayland_client::Proxy; + let mut window_handle = rwh_05::WaylandWindowHandle::empty(); - if let Some(window) = self.window.window() { - window_handle.surface = - unsafe { gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _) }; + if let Some(surface) = self.window.surface() { + let ptr = surface + .downcast::() + .unwrap() + .wl_surface() + .unwrap() + .id() + .as_ptr(); + window_handle.surface = ptr as *mut _; } - rwh_05::RawWindowHandle::Wayland(window_handle) + window_handle.into() } else { let mut window_handle = rwh_05::XlibWindowHandle::empty(); - unsafe { - if let Some(window) = self.window.window() { - window_handle.window = gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _); - } + if let Some(surface) = self.window.surface() { + window_handle.window = surface.downcast::().unwrap().xid(); } - rwh_05::RawWindowHandle::Xlib(window_handle) + window_handle.into() } } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + let display = self.display(); if self.is_wayland() { + use gdk_wayland::wayland_client::Proxy; + let display = display + .downcast::() + .unwrap() + .wl_display() + .unwrap() + .id() + .as_ptr(); + let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); - display_handle.display = unsafe { - gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.window.display().as_ptr() as *mut _) - }; - rwh_05::RawDisplayHandle::Wayland(display_handle) + display_handle.display = display as *mut _; + display_handle.into() } else { - let mut display_handle = rwh_05::XlibDisplayHandle::empty(); - unsafe { - if let Ok(xlib) = x11_dl::xlib::Xlib::open() { - let display = (xlib.XOpenDisplay)(std::ptr::null()); - display_handle.display = display as _; - display_handle.screen = (xlib.XDefaultScreen)(display) as _; - } - } + let display = display.downcast::().unwrap(); - rwh_05::RawDisplayHandle::Xlib(display_handle) + let mut display_handle = rwh_05::XlibDisplayHandle::empty(); + display_handle.display = + unsafe { gdk_x11::ffi::gdk_x11_display_get_xdisplay(display.as_ptr() as *mut _) }; + display_handle.screen = display.screen().screen_number(); + display_handle.into() } } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { - if let Some(window) = self.window.window() { + if let Some(surface) = self.window.surface() { if self.is_wayland() { - let surface = - unsafe { gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _) }; - let surface = unsafe { std::ptr::NonNull::new_unchecked(surface) }; - let window_handle = rwh_06::WaylandWindowHandle::new(surface); - Ok(rwh_06::RawWindowHandle::Wayland(window_handle)) + use gdk_wayland::prelude::WaylandSurfaceExtManual; + use gdk_wayland::wayland_client::Proxy; + + Ok( + rwh_06::WaylandWindowHandle::new({ + let ptr = surface + .downcast::() + .unwrap() + .wl_surface() + .unwrap() + .id() + .as_ptr(); + std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null") + }) + .into(), + ) } else { - let xid = unsafe { gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _) }; - let window_handle = rwh_06::XlibWindowHandle::new(xid); - Ok(rwh_06::RawWindowHandle::Xlib(window_handle)) + Ok( + rwh_06::XlibWindowHandle::new(surface.downcast::().unwrap().xid()) + .into(), + ) } } else { Err(rwh_06::HandleError::Unavailable) @@ -964,50 +714,56 @@ impl Window { #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06(&self) -> Result { + let display = self.display(); if self.is_wayland() { - let display = unsafe { - gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.window.display().as_ptr() as *mut _) - }; - let display = unsafe { std::ptr::NonNull::new_unchecked(display) }; - let display_handle = rwh_06::WaylandDisplayHandle::new(display); - Ok(rwh_06::RawDisplayHandle::Wayland(display_handle)) + use gdk_wayland::wayland_client::Proxy; + + Ok( + rwh_06::WaylandDisplayHandle::new({ + let ptr = display + .downcast::() + .unwrap() + .wl_display() + .unwrap() + .id() + .as_ptr(); + std::ptr::NonNull::new(ptr as *mut _).expect("wl_display will never be null") + }) + .into(), + ) } else { - if let Ok(xlib) = x11_dl::xlib::Xlib::open() { - unsafe { - let display = (xlib.XOpenDisplay)(std::ptr::null()); - let screen = (xlib.XDefaultScreen)(display) as _; - let display = std::ptr::NonNull::new_unchecked(display as _); - let display_handle = rwh_06::XlibDisplayHandle::new(Some(display), screen); - Ok(rwh_06::RawDisplayHandle::Xlib(display_handle)) - } - } else { - Err(rwh_06::HandleError::Unavailable) - } - } - } + let display = display.downcast::().unwrap(); - pub fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError> { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::SetSkipTaskbar(skip))) - { - log::warn!("Fail to send skip taskbar request: {}", e); + Ok( + rwh_06::XlibDisplayHandle::new( + Some( + std::ptr::NonNull::new(unsafe { + gdk_x11::ffi::gdk_x11_display_get_xdisplay(display.as_ptr() as *mut _) + }) + .expect("X11 display should never be null"), + ), + display.screen().screen_number(), + ) + .into(), + ) } + } - Ok(()) + pub fn set_skip_taskbar(&self, _: bool) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) } pub fn set_progress_bar(&self, progress: ProgressBarState) { if let Err(e) = self .window_requests_tx - .send((WindowId::dummy(), WindowRequest::ProgressBarState(progress))) + .send_blocking((WindowId::dummy(), WindowRequest::ProgressBarState(progress))) { log::warn!("Fail to send update progress bar request: {}", e); } } pub fn set_badge_count(&self, count: Option, desktop_filename: Option) { - if let Err(e) = self.window_requests_tx.send(( + if let Err(e) = self.window_requests_tx.send_blocking(( WindowId::dummy(), WindowRequest::BadgeCount(count, desktop_filename), )) { @@ -1020,9 +776,10 @@ impl Window { return theme; } - if let Some(theme) = Settings::default().and_then(|s| s.gtk_theme_name()) { - let theme = theme.as_str(); - if GTK_THEME_SUFFIX_LIST.iter().any(|t| theme.ends_with(t)) { + if let Some(prefers_dark) = + Settings::default().map(|s| s.is_gtk_application_prefer_dark_theme()) + { + if prefers_dark { return Theme::Dark; } } @@ -1034,7 +791,7 @@ impl Window { *self.preferred_theme.borrow_mut() = theme; if let Err(e) = self .window_requests_tx - .send((WindowId::dummy(), WindowRequest::SetTheme(theme))) + .send_blocking((WindowId::dummy(), WindowRequest::SetTheme(theme))) { log::warn!("Fail to send set theme request: {e}"); } @@ -1049,7 +806,6 @@ unsafe impl Sync for Window {} #[non_exhaustive] pub enum WindowRequest { Title(String), - Position((i32, i32)), Size((i32, i32)), SizeConstraints(WindowSizeConstraints), Visible(bool), @@ -1062,20 +818,14 @@ pub enum WindowRequest { DragResizeWindow(ResizeDirection), Fullscreen(Option), Decorations(bool), - AlwaysOnBottom(bool), - AlwaysOnTop(bool), - WindowIcon(Option), UserAttention(Option), - SetSkipTaskbar(bool), CursorIcon(Option), - CursorPosition((i32, i32)), CursorIgnoreEvents(bool), WireUpEvents { transparent: bool, fullscreen: bool, cursor_moved: bool, }, - SetVisibleOnAllWorkspaces(bool), ProgressBarState(ProgressBarState), BadgeCount(Option, Option), SetTheme(Option), @@ -1084,8 +834,6 @@ pub enum WindowRequest { impl Drop for Window { fn drop(&mut self) { - unsafe { - self.window.destroy(); - } + self.window.destroy(); } } diff --git a/src/window.rs b/src/window.rs index 92c4246fd..21cfd5283 100644 --- a/src/window.rs +++ b/src/window.rs @@ -159,8 +159,7 @@ pub struct WindowAttributes { /// position. /// There may be a small gap between this position and the window due to the specifics of the /// Window Manager. - /// - **Linux**: The top left corner of the window, the window's "outer" position. - /// - **Linux(Wayland)**: Unsupported. + /// - **Linux**: Unsupported. /// - **Others**: Ignored. /// /// See [`Window::set_outer_position`]. @@ -687,7 +686,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window in the screen space coordinate system. /// - **Android:** Always returns [`NotSupportedError`]. - /// - **Linux(Wayland)**: Has no effect, since Wayland doesn't support a global cordinate system + /// - **Linux**: Has no effect, since Wayland doesn't support a global cordinate system #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { self.window.outer_position() @@ -702,7 +701,7 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the /// window in the screen space coordinate system. - /// - **Android / Linux(Wayland):** Unsupported. + /// - **Android / Linux:** Unsupported. #[inline] pub fn set_outer_position>(&self, position: P) { self.window.set_outer_position(position.into()) @@ -1055,7 +1054,7 @@ impl Window { /// ## Platform-specific /// /// - **Windows**: There is no guarantee that the window will be the bottom most but it will try to be. - /// - **iOS / Android:** Unsupported. + /// - **iOS / Android / Linux:** Unsupported. #[inline] pub fn set_always_on_bottom(&self, always_on_bottom: bool) { self.window.set_always_on_bottom(always_on_bottom) @@ -1065,7 +1064,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android:** Unsupported. + /// - **iOS / Android / Linux:** Unsupported. #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { self.window.set_always_on_top(always_on_top) @@ -1126,7 +1125,6 @@ impl Window { /// /// - **iOS / Android:** Unsupported. /// - **macOS:** `None` has no effect. - /// - **Linux:** Urgency levels have the same effect. #[inline] pub fn request_user_attention(&self, request_type: Option) { self.window.request_user_attention(request_type) @@ -1176,9 +1174,9 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Windows:** Unsupported. + /// - **iOS / Android / Windows / Linux:** Unsupported. pub fn set_visible_on_all_workspaces(&self, #[allow(unused)] visible: bool) { - #[cfg(any(target_os = "macos", target_os = "linux"))] + #[cfg(target_os = "macos")] self.window.set_visible_on_all_workspaces(visible) } @@ -1289,7 +1287,8 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Linux(Wayland)**: Unsupported, returns `0,0`. + /// - **iOS / Android**: Unsupported, returns `0,0`. + /// - **Linux**: Coordinates are relative to the window. #[inline] pub fn cursor_position(&self) -> Result, ExternalError> { self.window.cursor_position()