diff --git a/.changes/libadwaita-feature.md b/.changes/libadwaita-feature.md new file mode 100644 index 000000000..4c36a3b61 --- /dev/null +++ b/.changes/libadwaita-feature.md @@ -0,0 +1,5 @@ +--- +"tao": minor +--- + +Add optional `libadwaita` feature for GTK4 Linux builds. When enabled, uses `adw::Application` instead of `gtk::Application` for proper GNOME HIG styling (rounded corners, themed menus, etc.). diff --git a/.changes/linux-gtk4.md b/.changes/linux-gtk4.md new file mode 100644 index 000000000..697a8c039 --- /dev/null +++ b/.changes/linux-gtk4.md @@ -0,0 +1,5 @@ +--- +"tao": minor +--- + +Port the Linux backend from GTK3 to GTK4 (`gtk4` 0.11, `glib` 0.22). Adds an optional `libadwaita` feature. Public gtk types exposed through the Unix extension traits are now their GTK4 equivalents, and the minimum supported Rust version rises to 1.92 and the minimum system GTK to 4.10 (both required by gtk4-rs 0.11 and the `Accessible` interface the window type implements). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 199c8030e..eb0edea57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: if: matrix.platform.os == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev + sudo apt-get install -y libgtk-4-dev libdbus-1-dev - name: Install GCC Multilib if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') @@ -63,8 +63,9 @@ jobs: if: contains(matrix.platform.target, 'android') run: cargo install cargo-apk - - uses: dtolnay/rust-toolchain@1.74 + - uses: dtolnay/rust-toolchain@master with: + toolchain: "1.92" targets: ${{ matrix.platform.target }} - name: Check documentation diff --git a/Cargo.lock b/Cargo.lock index c88ff1210..618c4e713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,7 +92,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -108,26 +108,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.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", ] [[package]] @@ -173,9 +162,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.7.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bitstream-io" @@ -230,7 +219,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -253,27 +242,25 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cairo-rs" -version = "0.18.5" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "cairo-sys-rs", "glib", "libc", - "once_cell", - "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.18.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 7.0.8", ] [[package]] @@ -301,7 +288,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", - "target-lexicon", + "target-lexicon 0.12.16", +] + +[[package]] +name = "cfg-expr" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" +dependencies = [ + "smallvec", + "target-lexicon 0.13.5", ] [[package]] @@ -338,6 +335,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" @@ -360,7 +366,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "core-foundation", "core-graphics-types", "foreign-types", @@ -373,7 +379,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "core-foundation", "core-graphics-types", "foreign-types", @@ -386,7 +392,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "core-foundation", "libc", ] @@ -469,7 +475,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "block2 0.6.2", "libc", "objc2 0.6.4", @@ -483,7 +489,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -515,7 +521,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -539,7 +545,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "bytemuck", "drm-ffi", "drm-fourcc", @@ -617,6 +623,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "exr" version = "1.73.0" @@ -691,7 +718,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -749,7 +776,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -772,52 +799,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.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +checksum = "25f420376dbee041b2db374ce4573892a36222bb3f6c0c43e24f0d67eae9b646" dependencies = [ "gdk-pixbuf-sys", "gio", "glib", "libc", - "once_cell", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.18.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +checksum = "48f31b37b1fc4b48b54f6b91b7ef04c18e00b4585d98359dd7b998774bbd91fb" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 7.0.8", ] [[package]] -name = "gdk-sys" -version = "0.18.2" +name = "gdk4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +checksum = "d81e2a6c6ecba2aab60633a98df1868b03fa0bfdce8105edc27c1bccf71f0e39" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk4-sys" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d8f608d8d7d229975c4d0d026f5d3071598c4ddab3c5262b0a31840fec78d13" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -827,34 +853,59 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps", + "system-deps 7.0.8", +] + +[[package]] +name = "gdk4-wayland" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d95828dadc0357dee236cc6a5712808bd894d3217159982592ce24f2b8aaf7" +dependencies = [ + "gdk4", + "gdk4-wayland-sys", + "gio", + "glib", + "libc", + "wayland-backend", + "wayland-client", + "wayland-protocols", ] [[package]] -name = "gdkwayland-sys" -version = "0.18.2" +name = "gdk4-wayland-sys" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +checksum = "653cafb8715f2ac1c56edaf8060d9360ae3acbe3c6fb61b676a2917d42b668cf" dependencies = [ - "gdk-sys", "glib-sys", - "gobject-sys", "libc", - "pkg-config", - "system-deps", + "system-deps 7.0.8", +] + +[[package]] +name = "gdk4-x11" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d892b4cf5a07e90e1390e93cd792975746c68b59f790a2de9e96ce77211ea9a" +dependencies = [ + "gdk4", + "gdk4-x11-sys", + "gio", + "glib", + "libc", ] [[package]] -name = "gdkx11-sys" -version = "0.18.2" +name = "gdk4-x11-sys" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +checksum = "24d41f2e28f62378b081288914325d73bddbb6ccbbadb41aa6392afc71d27002" dependencies = [ - "gdk-sys", + "gdk4-sys", "glib-sys", "libc", - "system-deps", - "x11", + "system-deps 7.0.8", ] [[package]] @@ -890,9 +941,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.18.4" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +checksum = "8b3e1f669909c326b9413bde5a742097b8c90a7d78f45326db13668984769ded" dependencies = [ "futures-channel", "futures-core", @@ -901,32 +952,30 @@ dependencies = [ "gio-sys", "glib", "libc", - "once_cell", "pin-project-lite", "smallvec", - "thiserror", ] [[package]] name = "gio-sys" -version = "0.18.1" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", - "winapi", + "system-deps 7.0.8", + "windows-sys 0.59.0", ] [[package]] name = "glib" -version = "0.18.5" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "futures-channel", "futures-core", "futures-executor", @@ -938,96 +987,144 @@ dependencies = [ "gobject-sys", "libc", "memchr", - "once_cell", "smallvec", - "thiserror", ] [[package]] name = "glib-macros" -version = "0.18.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19" dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.0", - "proc-macro-error", + "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] name = "glib-sys" -version = "0.18.1" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233" dependencies = [ "libc", - "system-deps", + "system-deps 7.0.8", ] [[package]] name = "gobject-sys" -version = "0.18.0" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 7.0.8", ] [[package]] -name = "gtk" -version = "0.18.2" +name = "graphene-rs" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +checksum = "eb856b9c558971c3f13ab692358926da710b046932a4e087aedcc35b040d7dff" +dependencies = [ + "glib", + "graphene-sys", +] + +[[package]] +name = "graphene-sys" +version = "0.22.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ffdfde88f3570d3705e0d8a2433e036d387a1f2930bbf47eafcb5f569fd04" +dependencies = [ + "glib-sys", + "libc", + "system-deps 7.0.8", +] + +[[package]] +name = "gsk4" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867be1c5f14dcb8f552c0eff6e9a9b1da5f8b43943e8efc3a63c889d84952ff" 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.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +checksum = "5b7c7eb2e681ee896646cfb8872b431f24d09f53ba9283289d9b10caa6707088" 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.8", +] + +[[package]] +name = "gtk4" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a0a0466484f64b07b5b8184d43fa46be78eb0b8e04ae4e179af31d770b76d9" +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.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +checksum = "5ac7179400a36a04de039c24206bb841c5596992b907b43b23ee8d5bdc40d00e" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn", +] + +[[package]] +name = "gtk4-sys" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f954786af0b1984425c4446b77f5ff6594346181316be3f850caab1c6f01" +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.8", ] [[package]] @@ -1042,15 +1139,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.4.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -1179,7 +1270,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1244,9 +1335,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.11.4" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -1260,7 +1351,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1331,6 +1422,37 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +[[package]] +name = "libadwaita" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0da4e27b20d3e71f830e5b0f0188d22c257986bf421c02cfde777fe07932a4" +dependencies = [ + "gdk4", + "gio", + "glib", + "gtk4", + "libadwaita-sys", + "libc", + "pango", +] + +[[package]] +name = "libadwaita-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaee067051c5d3c058d050d167688b80b67de1950cfca77730549aa761fc5d7d" +dependencies = [ + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps 7.0.8", +] + [[package]] name = "libc" version = "0.2.178" @@ -1427,9 +1549,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" @@ -1471,7 +1593,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "jni-sys", "log", "ndk-sys", @@ -1529,7 +1651,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1576,10 +1698,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1613,7 +1735,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -1625,7 +1747,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-foundation 0.3.2", ] @@ -1646,7 +1768,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "dispatch2", "objc2 0.6.4", ] @@ -1657,7 +1779,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "dispatch2", "objc2 0.6.4", "objc2-core-foundation", @@ -1690,7 +1812,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", @@ -1708,7 +1830,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -1720,7 +1842,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", ] @@ -1731,7 +1853,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", ] @@ -1742,7 +1864,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1754,7 +1876,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -1767,7 +1889,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -1779,7 +1901,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-cloud-kit", @@ -1812,29 +1934,33 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pango" -version = "0.18.3" +version = "0.22.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +checksum = "5d800d8d0de2ad5d0fb046f5344dbaba14a003cf3dd27cc21d85893d35ea316c" dependencies = [ "gio", "glib", - "libc", - "once_cell", "pango-sys", ] [[package]] name = "pango-sys" -version = "0.18.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6" dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 7.0.8", ] +[[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" @@ -1884,9 +2010,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "png" @@ -1912,54 +2038,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" -dependencies = [ - "toml_edit 0.20.7", -] - -[[package]] -name = "proc-macro-crate" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" -dependencies = [ - "toml_edit 0.22.24", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" 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.25.12+spec-1.1.0", ] [[package]] @@ -1987,7 +2070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2082,7 +2165,7 @@ dependencies = [ "rand", "rand_chacha", "simd_helpers", - "system-deps", + "system-deps 6.2.2", "thiserror", "v_frame", "wasm-bindgen", @@ -2150,7 +2233,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", ] [[package]] @@ -2203,7 +2286,7 @@ version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2216,7 +2299,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.9.4", @@ -2283,7 +2366,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2295,6 +2378,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2327,9 +2419,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "softbuffer" @@ -2369,16 +2461,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.117" @@ -2398,7 +2480,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2407,10 +2489,23 @@ 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 0.8.20", + "version-compare", +] + +[[package]] +name = "system-deps" +version = "7.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" +dependencies = [ + "cfg-expr 0.20.8", + "heck", "pkg-config", - "toml", + "toml 1.1.2+spec-1.1.0", "version-compare", ] @@ -2418,7 +2513,8 @@ dependencies = [ name = "tao" version = "0.35.3" dependencies = [ - "bitflags 2.7.0", + "async-channel", + "bitflags 2.13.0", "block2 0.6.2", "core-foundation", "core-graphics 0.25.0", @@ -2428,11 +2524,12 @@ dependencies = [ "dlopen2", "dpi", "env_logger", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", + "gdk4-wayland", + "gdk4-x11", + "gtk4", "image", "jni", + "libadwaita", "libc", "log", "ndk", @@ -2464,7 +2561,7 @@ version = "0.1.3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2473,6 +2570,12 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "thiserror" version = "1.0.69" @@ -2490,7 +2593,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2534,11 +2637,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.8", + "toml_datetime 0.6.8", "toml_edit 0.22.24", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + [[package]] name = "toml_datetime" version = "0.6.8" @@ -2549,40 +2667,54 @@ dependencies = [ ] [[package]] -name = "toml_edit" -version = "0.19.15" +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", - "toml_datetime", - "winnow 0.5.40", + "serde", + "serde_spanned 0.6.8", + "toml_datetime 0.6.8", + "winnow 0.7.14", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.7.14", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", ] +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "tracing" version = "0.1.41" @@ -2657,12 +2789,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" @@ -2711,7 +2837,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -2744,12 +2870,24 @@ version = "0.31.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.13.0", "rustix 1.0.7", "wayland-backend", "wayland-scanner", ] +[[package]] +name = "wayland-protocols" +version = "0.32.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + [[package]] name = "wayland-scanner" version = "0.31.9" @@ -2873,7 +3011,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2884,7 +3022,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3141,18 +3279,18 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.5.40" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -3169,16 +3307,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" @@ -3231,7 +3359,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -3253,7 +3381,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3273,7 +3401,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -3296,7 +3424,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 26240c2d3..045361c9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ authors = [ "The winit contributors", ] edition = "2021" -rust-version = "1.74" +rust-version = "1.92" keywords = ["windowing"] license = "Apache-2.0" readme = "README.md" @@ -33,8 +33,9 @@ serde = ["dep:serde", "dpi/serde"] rwh_04 = ["dep:rwh_04"] rwh_05 = ["dep:rwh_05"] rwh_06 = ["dep:rwh_06"] -x11 = ["dep:gdkx11-sys", "dep:x11-dl"] +x11 = ["dep:gdk4-x11", "dep:x11-dl"] dbus = ["dep:dbus"] +libadwaita = ["dep:libadwaita"] [workspace] members = ["tao-macros"] @@ -53,6 +54,7 @@ rwh_06 = { package = "raw-window-handle", version = "0.6", features = [ ], optional = true } bitflags = "2" crossbeam-channel = "0.5" +async-channel = "2.3" url = "2" dpi = "0.1" @@ -165,10 +167,11 @@ objc2-foundation = { version = "0.3", default-features = false, features = [ ] } [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] -gtk = "0.18" -gdkx11-sys = { version = "0.18", optional = true } -gdkwayland-sys = "0.18.0" +gtk4 = { version = "0.11", features = ["v4_10"] } +gdk4-x11 = { version = "0.11", optional = true } +gdk4-wayland = { version = "0.11", features = ["wayland_crate"] } x11-dl = { version = "2.21", optional = true } parking_lot = "0.12" dlopen2 = "0.8.0" dbus = { version = "0.9", optional = true } +libadwaita = { version = "0.9", optional = true } 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 c96fb9361..054210b9d 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -25,7 +25,7 @@ use crate::{ error::{ExternalError, OsError}, event_loop::{EventLoopBuilder, EventLoopWindowTarget}, monitor::MonitorHandle, - platform_impl::{Parent, Window as UnixWindow}, + platform_impl::{gtk_window::ApplicationWindow, Parent, Window as UnixWindow}, window::{Window, WindowBuilder}, }; @@ -72,15 +72,15 @@ 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; + /// Returns the `gtk4::ApplicatonWindow` from gtk crate that is used by this window. + 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`]. - fn default_vbox(&self) -> Option<>k::Box>; + /// Returns the vertical `gtk4::Box` that is added by default as the sole child of this window. + /// Returns `None` if the default vertical `gtk4::Box` creation was disabled by [`WindowBuilderExtUnix::with_default_vbox`]. + fn default_vbox(&self) -> Option<>k4::Box>; /// Whether to show the window icon in the taskbar or not. fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError>; @@ -89,11 +89,11 @@ pub trait WindowExtUnix { } impl WindowExtUnix for Window { - fn gtk_window(&self) -> >k::ApplicationWindow { + fn gtk_window(&self) -> &ApplicationWindow { &self.window.window } - fn default_vbox(&self) -> Option<>k::Box> { + fn default_vbox(&self) -> Option<>k4::Box> { self.window.default_vbox.as_ref() } @@ -103,7 +103,7 @@ 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 }) @@ -119,7 +119,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 gtk4::prelude::IsA) -> WindowBuilder; /// Whether to enable or disable the internal draw for transparent window. /// @@ -152,7 +152,7 @@ pub trait WindowBuilderExtUnix { /// Default is `true`. fn with_cursor_moved_event(self, cursor_moved: bool) -> WindowBuilder; - /// Whether to create a vertical `gtk::Box` and add it as the sole child of this window. + /// Whether to create a vertical `gtk4::Box` and add it as the sole child of this window. /// Created by default. fn with_default_vbox(self, add: bool) -> WindowBuilder; } @@ -163,8 +163,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 gtk4::prelude::IsA) -> WindowBuilder { + use gtk4::prelude::Cast; self.platform_specific.parent = Parent::ChildOf(parent.clone().upcast()); self } @@ -221,7 +221,7 @@ pub trait EventLoopWindowTargetExtUnix { // fn wayland_display(&self) -> Option<*mut raw::c_void>; /// Returns the gtk application for this event loop. - fn gtk_app(&self) -> >k::Application; + fn gtk_app(&self) -> >k4::Application; /// Sets the badge count on the taskbar fn set_badge_count(&self, count: Option, desktop_filename: Option); @@ -265,8 +265,9 @@ impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { // } #[inline] - fn gtk_app(&self) -> >k::Application { - &self.p.app + fn gtk_app(&self) -> >k4::Application { + use gtk4::prelude::Cast; + self.p.app.upcast_ref() } #[inline] @@ -297,12 +298,12 @@ unsafe extern "C" fn x_error_callback( /// Additional methods on `MonitorHandle` that are specific to Unix. pub trait MonitorHandleExtUnix { /// Returns the gdk handle of the monitor. - fn gdk_monitor(&self) -> >k::gdk::Monitor; + fn gdk_monitor(&self) -> >k4::gdk::Monitor; } impl MonitorHandleExtUnix for MonitorHandle { #[inline] - fn gdk_monitor(&self) -> >k::gdk::Monitor { + fn gdk_monitor(&self) -> >k4::gdk::Monitor { &self.inner.monitor } } diff --git a/src/platform_impl/linux/device.rs b/src/platform_impl/linux/device.rs index f31971d73..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()); @@ -63,7 +62,7 @@ pub fn spawn(device_tx: glib::Sender) { state, }; - if let Err(e) = device_tx.send(DeviceEvent::Key(event)) { + 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 0859023b6..d358db8e1 100644 --- a/src/platform_impl/linux/event_loop.rs +++ b/src/platform_impl/linux/event_loop.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::{ - cell::{Cell, RefCell}, + cell::RefCell, collections::{HashSet, VecDeque}, error::Error, process, @@ -12,18 +12,27 @@ 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}, +use gtk4::{ + cairo::{RectangleInt, Region}, + gdk::{self, Cursor, ScrollDirection, SurfaceEdge}, + glib::{self, closure_local, MainContext}, prelude::*, - Settings, + EventControllerFocus, EventControllerKey, EventControllerMotion, EventControllerScroll, + EventControllerScrollFlags, GestureClick, Settings, }; +// Libadwaita support - conditional Application type +#[cfg(feature = "libadwaita")] +use libadwaita as adw; + +#[cfg(not(feature = "libadwaita"))] +use gtk4::Application; + +#[cfg(feature = "libadwaita")] +type GtkApp = adw::Application; +#[cfg(not(feature = "libadwaita"))] +type GtkApp = Application; + #[cfg(feature = "x11")] use crate::platform_impl::platform::device; use crate::{ @@ -35,17 +44,19 @@ use crate::{ event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, keyboard::ModifiersState, monitor::MonitorHandle as RootMonitorHandle, - platform_impl::platform::DEVICE_ID, window::{ CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, WindowId as RootWindowId, }, }; use super::{ + gtk_window::ApplicationWindow, keyboard, monitor::{self, MonitorHandle}, - taskbar, util, + taskbar, + util::{self}, window::{WindowId, WindowRequest}, + DEVICE_ID, }; use taskbar::TaskbarIndicator; @@ -55,89 +66,91 @@ pub struct EventLoopWindowTarget { /// Gdk display pub(crate) display: gdk::Display, /// Gtk application - pub(crate) app: gtk::Application, + pub(crate) app: GtkApp, /// 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.map(|monitor| RootMonitorHandle { - inner: MonitorHandle { monitor }, - }) + 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 gdk4_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 { gdk4_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 gdk4_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 { #[cfg(feature = "x11")] - 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 { + gdk4_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()) } #[cfg(not(feature = "x11"))] - Err(rwh_06::HandleError::Unavailable) + Err(rwh_06::HandleError::NotSupported) } } @@ -152,14 +165,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}"); } @@ -167,7 +180,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), )) { @@ -179,7 +192,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}"); } @@ -190,11 +203,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>, } @@ -220,20 +233,28 @@ 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()?; + // Initialize GTK or libadwaita + #[cfg(feature = "libadwaita")] + adw::init().expect("Failed to initialize libadwaita"); + #[cfg(not(feature = "libadwaita"))] + gtk4::init()?; + let context = MainContext::default(); - let app = gtk::Application::new(app_id, gio::ApplicationFlags::empty()); + + // Create application with appropriate type + #[cfg(feature = "libadwaita")] + let app = adw::Application::new(app_id, gtk4::gio::ApplicationFlags::empty()); + #[cfg(not(feature = "libadwaita"))] + let app = Application::new(app_id, gtk4::gio::ApplicationFlags::empty()); let app_ = app.clone(); - let cancellable: Option<&Cancellable> = None; - app.register(cancellable)?; + app.register(gtk4::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); } }); @@ -241,7 +262,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 { @@ -256,23 +277,25 @@ impl EventLoop { // Spawn x11 thread to receive Device events. #[cfg(feature = "x11")] 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 { @@ -293,218 +316,187 @@ impl EventLoop { } } - // 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 { + #[cfg(feature = "x11")] + 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); + gtk4::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); + + gtk4::style_context_add_provider_for_display( + &display, + &css_provider, + gtk4::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(Some(®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, ); @@ -513,196 +505,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(); - let last_motion_time = Rc::new(Cell::new(0u32)); - window.connect_motion_notify_event(move |window, motion| { - let last = last_motion_time.replace(motion.time()); - if cursor_moved && motion.time() != last { - 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), @@ -714,36 +657,30 @@ impl EventLoop { log::warn!("Failed to send cursor moved event to event channel: {}", e); } } - } - glib::Propagation::Proceed - }); - - 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(); - let last_press_time = Rc::new(Cell::new(0u32)); - window.connect_button_press_event(move |_, event| { - let last = last_press_time.replace(event.time()); - if event.time() != last { + }); + + 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, + }, + }) { + log::warn!("Failed to send cursor left event to event channel: {}", e); + } + }); + + let tx_clone = event_tx.clone(); + all_click_controller.connect_pressed(move |event, _, _, _| { let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, + gdk::BUTTON_PRIMARY => MouseButton::Left, + gdk::BUTTON_MIDDLE => MouseButton::Middle, + gdk::BUTTON_SECONDARY => MouseButton::Right, _ => MouseButton::Other(button as u16), }, state: ElementState::Pressed, @@ -757,23 +694,18 @@ impl EventLoop { e ); } - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - let last_release_time = Rc::new(Cell::new(0u32)); - window.connect_button_release_event(move |_, event| { - let last = last_release_time.replace(event.time()); - if event.time() != last { + }); + + let tx_clone = event_tx.clone(); + all_click_controller.connect_released(move |event, _, _, _| { let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { + if let Err(e) = tx_clone.send_blocking(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, + gdk::BUTTON_PRIMARY => MouseButton::Left, + gdk::BUTTON_MIDDLE => MouseButton::Middle, + gdk::BUTTON_SECONDARY => MouseButton::Right, _ => MouseButton::Other(button as u16), }, state: ElementState::Released, @@ -787,201 +719,199 @@ impl EventLoop { e ); } - } - glib::Propagation::Proceed - }); - - 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, - }, - 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(); - } + }); - if let Err(e) = tx_clone.send(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 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 + }); - // todo: implement repeat? - let event = keyboard::make_key_event(&event_key, false, None, element_state); + 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 Some(event) = event { - if let Err(e) = tx_clone.send(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); + 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; + } } - } - 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); + + // 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 (w, h) = window.size(); - if let Err(e) = tx_clone.send(Event::WindowEvent { + let tx_clone = event_tx.clone(); + // TODO Add actual IME from system + let ime = gtk4::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: >k4::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()) - }; - - 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 = gtk4::CssProvider::new(); + let theme = format!( + r#" + window.tao-window-{} {{ + background-color: rgba(0,0,0,0.0); + }} + "#, + id.0 + ); + + provider.load_from_data(&theme); + + gtk4::style_context_add_provider_for_display( + &display, + &provider, + gtk4::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() { - settings.set_gtk_application_prefer_dark_theme(theme == Some(Theme::Dark)); - if let Err(e) = event_tx.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ThemeChanged(theme.unwrap_or_default()), - }) { - log::warn!( - "Failed to send window theme changed event to event channel: {}", - e - ); + } 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() { + settings.set_gtk_application_prefer_dark_theme(theme == Some(Theme::Dark)); + if let Err(e) = event_tx.send_blocking(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ThemeChanged(theme.unwrap_or_default()), + }) { + log::warn!( + "Failed to send window theme changed event to event channel: {}", + e + ); + } } } + _ => unreachable!(), } - _ => unreachable!(), } } - glib::ControlFlow::Continue + glib::ControlFlow::Break }); // Create event loop itself. @@ -1161,7 +1091,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); @@ -1187,7 +1118,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 { @@ -1207,8 +1138,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 { @@ -1307,16 +1238,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..d99361adc --- /dev/null +++ b/src/platform_impl/linux/gtk_window/imp.rs @@ -0,0 +1,70 @@ +use std::{ + rc::Rc, + sync::{ + atomic::{AtomicI32, Ordering}, + OnceLock, + }, +}; + +use gtk4::{ + glib::{self, subclass::Signal}, + prelude::*, + subclass::prelude::*, +}; + +// Libadwaita support - conditional imports +#[cfg(feature = "libadwaita")] +use libadwaita::subclass::application_window::AdwApplicationWindowImpl; + +#[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; + + #[cfg(feature = "libadwaita")] + type ParentType = libadwaita::ApplicationWindow; + #[cfg(not(feature = "libadwaita"))] + type ParentType = gtk4::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 {} + +#[cfg(feature = "libadwaita")] +impl AdwApplicationWindowImpl 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..afd05dfdf --- /dev/null +++ b/src/platform_impl/linux/gtk_window/mod.rs @@ -0,0 +1,72 @@ +mod imp; + +use std::{rc::Rc, sync::atomic::AtomicI32}; + +use gtk4::{ + gio, + glib::{ + self, object::ObjectExt, subclass::types::ObjectSubclassIsExt, Object, RustClosure, + SignalHandlerId, + }, +}; + +use crate::window::WindowAttributes; + +use super::{Parent, PlatformSpecificWindowBuilderAttributes}; + +// Libadwaita support - conditional Application type +#[cfg(feature = "libadwaita")] +use libadwaita as adw; + +#[cfg(feature = "libadwaita")] +type AppType = adw::Application; +#[cfg(not(feature = "libadwaita"))] +type AppType = gtk4::Application; + +#[cfg(feature = "libadwaita")] +glib::wrapper! { + pub struct ApplicationWindow(ObjectSubclass) + @extends gtk4::Widget, gtk4::Window, gtk4::ApplicationWindow, adw::ApplicationWindow, + @implements gio::ActionGroup, gio::ActionMap, gtk4::Accessible, gtk4::Buildable, + gtk4::ConstraintTarget, gtk4::Native, gtk4::Root, gtk4::ShortcutManager; +} + +#[cfg(not(feature = "libadwaita"))] +glib::wrapper! { + pub struct ApplicationWindow(ObjectSubclass) + @extends gtk4::Widget, gtk4::Window, gtk4::ApplicationWindow, + @implements gio::ActionGroup, gio::ActionMap, gtk4::Accessible, gtk4::Buildable, + gtk4::ConstraintTarget, gtk4::Native, gtk4::Root, gtk4::ShortcutManager; +} + +impl ApplicationWindow { + pub fn new( + app: &AppType, + 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/icon.rs b/src/platform_impl/linux/icon.rs index bc1c18df1..4d5b7ef05 100644 --- a/src/platform_impl/linux/icon.rs +++ b/src/platform_impl/linux/icon.rs @@ -2,7 +2,7 @@ // Copyright 2021-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 -use gtk::gdk_pixbuf::{Colorspace, Pixbuf}; +use gtk4::gdk_pixbuf::{Colorspace, Pixbuf}; use crate::window::BadIcon; diff --git a/src/platform_impl/linux/keyboard.rs b/src/platform_impl/linux/keyboard.rs index 34c92546a..b7440577e 100644 --- a/src/platform_impl/linux/keyboard.rs +++ b/src/platform_impl/linux/keyboard.rs @@ -7,20 +7,11 @@ use crate::{ event::{ElementState, KeyEvent}, keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKeyCode}, }; -use gtk::{ - gdk::{self, keys::constants::*, EventKey}, - glib, -}; +use gtk4::{gdk, prelude::DisplayExtManual}; use once_cell::sync::Lazy; -use std::{ - collections::HashSet, - ffi::c_void, - os::raw::{c_int, c_uint}, - ptr, slice, - sync::Mutex, -}; +use std::{collections::HashSet, sync::Mutex}; -pub type RawKey = gdk::keys::Key; +pub type RawKey = gdk::Key; static KEY_STRINGS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); @@ -37,90 +28,90 @@ 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), // JIS - Zenkaku_Hankaku => Some(Key::ZenkakuHankaku), - Hiragana_Katakana => Some(Key::HiraganaKatakana), - Henkan => Some(Key::Convert), - Muhenkan => Some(Key::NonConvert), + RawKey::Zenkaku_Hankaku => Some(Key::ZenkakuHankaku), + RawKey::Hiragana_Katakana => Some(Key::HiraganaKatakana), + RawKey::Henkan => Some(Key::Convert), + RawKey::Muhenkan => Some(Key::NonConvert), // TODO: more mappings (media etc) _ => None, } @@ -129,14 +120,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, } } @@ -152,15 +176,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())) @@ -183,20 +204,17 @@ 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(keyval_without_modifiers); + 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)); @@ -214,7 +232,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())) @@ -253,41 +271,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 aa317542e..428417d23 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -14,6 +14,7 @@ mod portal; mod util; mod window; +pub mod gtk_window; pub mod taskbar; #[cfg(feature = "x11")] pub mod x11; @@ -38,7 +39,7 @@ pub struct KeyEventExtra { pub enum Parent { #[default] None, - ChildOf(gtk::Window), + ChildOf(gtk4::Window), } #[derive(Clone)] diff --git a/src/platform_impl/linux/monitor.rs b/src/platform_impl/linux/monitor.rs index ceeea1265..c17d043be 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 gtk4::{ + 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/portal.rs b/src/platform_impl/linux/portal.rs index c0df6a43d..087905e99 100644 --- a/src/platform_impl/linux/portal.rs +++ b/src/platform_impl/linux/portal.rs @@ -1,11 +1,14 @@ -#[cfg(feature = "dbus")] +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + use dbus::{ arg::Variant, blocking::{Connection, SyncConnection}, message::MatchRule, Error, }; -use gtk::glib::{ControlFlow, MainContext, Priority, Sender}; +use gtk4::glib::MainContext; use log::warn; use std::{thread, time::Duration}; @@ -31,10 +34,12 @@ pub fn theme() -> Result { Ok(color_scheme_to_theme(result.0 .0 .0)) } -pub fn receive_theme_changed(window_tx: Sender<(WindowId, WindowRequest)>) -> Result<(), Error> { +pub fn receive_theme_changed( + window_tx: async_channel::Sender<(WindowId, WindowRequest)>, +) -> Result<(), Error> { let conn = SyncConnection::new_session()?; let match_rule = MatchRule::new_signal("org.freedesktop.portal.Settings", "SettingChanged"); - let (tx, rx) = MainContext::channel(Priority::DEFAULT); + let (tx, rx) = async_channel::unbounded(); conn.add_match(match_rule, move |_: (), _, msg| { let mut iter = msg.iter_init(); @@ -43,19 +48,21 @@ pub fn receive_theme_changed(window_tx: Sender<(WindowId, WindowRequest)>) -> Re iter.read::<&str>(), // Key iter.read::>(), // Value ) { - if let Err(e) = tx.send(color_scheme_to_theme(value.0)) { + if let Err(e) = tx.send_blocking(color_scheme_to_theme(value.0)) { warn!("Failed to send theme change via channel: {}", e); } } true })?; - rx.attach(None, move |theme| { - if let Err(e) = window_tx.send((WindowId::dummy(), WindowRequest::SetTheme(Some(theme)))) { - warn!("Failed to send theme change request: {}", e); - ControlFlow::Break - } else { - ControlFlow::Continue + MainContext::default().spawn_local(async move { + while let Ok(theme) = rx.recv().await { + if let Err(e) = + window_tx.send_blocking((WindowId::dummy(), WindowRequest::SetTheme(Some(theme)))) + { + warn!("Failed to send theme change request: {}", e); + break; + } } }); diff --git a/src/platform_impl/linux/util.rs b/src/platform_impl/linux/util.rs index 30cd8523b..0ededdc20 100644 --- a/src/platform_impl/linux/util.rs +++ b/src/platform_impl/linux/util.rs @@ -3,75 +3,76 @@ use crate::{ error::ExternalError, window::WindowSizeConstraints, }; -use gtk::{ - gdk::{ +use gtk4::{ + 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: gtk4::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/window.rs b/src/platform_impl/linux/window.rs index 0308f1bf2..c8c35b5d1 100644 --- a/src/platform_impl/linux/window.rs +++ b/src/platform_impl/linux/window.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 use std::{ - cell::RefCell, collections::VecDeque, rc::Rc, sync::{ @@ -12,15 +11,13 @@ use std::{ }, }; -use gtk::{ - gdk::WindowState, - glib::{self, translate::ToGlibPtr}, - prelude::*, - CssProvider, EventBox, Settings, -}; +use gtk4::{gdk, glib, prelude::*, CssProvider, Settings}; + +#[cfg(feature = "libadwaita")] +use libadwaita::prelude::AdwApplicationWindowExt; 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, @@ -32,8 +29,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)] @@ -49,17 +47,12 @@ pub struct Window { /// Window id. pub(crate) window_id: WindowId, /// Gtk application window. - pub(crate) window: gtk::ApplicationWindow, - pub(crate) default_vbox: Option, + 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, // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` // otherwise unsynchronized &RefCell from multiple threads @@ -68,7 +61,7 @@ pub struct Window { // otherwise unsynchronized &RefCell from multiple threads inner_size_constraints: RwLock, /// Draw event Sender - draw_tx: crossbeam_channel::Sender, + draw_tx: async_channel::Sender, // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` // otherwise unsynchronized &RefCell from multiple threads preferred_theme: RwLock>, @@ -85,14 +78,19 @@ impl Window { let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); let draw_tx = event_loop_window_target.draw_tx.clone(); - let mut window_builder = gtk::ApplicationWindow::builder() - .application(app) - .accept_focus(attributes.focusable && attributes.focused); - if let Parent::ChildOf(parent) = pl_attribs.parent { - window_builder = window_builder.transient_for(&parent); - } + let window = ApplicationWindow::new(app, &attributes, &pl_attribs); - let window = window_builder.build(); + let default_vbox = if pl_attribs.default_vbox { + let box_ = gtk4::Box::new(gtk4::Orientation::Vertical, 0); + // AdwApplicationWindow uses set_content(), gtk4::ApplicationWindow uses set_child() + #[cfg(feature = "libadwaita")] + window.set_content(Some(&box_)); + #[cfg(not(feature = "libadwaita"))] + window.set_child(Some(&box_)); + Some(box_) + } else { + None + }; let window_id = WindowId(window.id()); event_loop_window_target @@ -109,104 +107,27 @@ impl Window { window.set_default_size(width, height); - // Trying to prevent Wayland Protocol Error about mismatched xdg_surface_buffer sizes - let maximized = attributes.maximized; - let resizable = attributes.resizable; - let configure_handler_id = Rc::new(RefCell::new(None)); - let configure_handler_id_ = configure_handler_id.clone(); - let id = window.connect_configure_event(move |window, _| { - if let Some(id) = configure_handler_id_.take() { - window.disconnect(id); - if maximized { - window.maximize(); - } - window.set_resizable(resizable); - } - false - }); - configure_handler_id.borrow_mut().replace(id); - - window.set_deletable(attributes.closable); + if attributes.maximized { + let maximize_process = util::WindowMaximizeProcess::new(window.clone(), attributes.resizable); + glib::idle_add_local_full(glib::Priority::HIGH_IDLE, move || { + let mut maximize_process = maximize_process.borrow_mut(); + maximize_process.next_step() + }); + } else { + window.set_resizable(attributes.resizable); + } // 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 window.display().backend().is_wayland() && !attributes.decorations { - // Enable CSD to prevent compositor from rendering server-side decorations - let event_box = EventBox::new(); - window.set_titlebar(Some(&event_box)); - } - - 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(); - } // Set initial `preferred_theme` value to current portal color-scheme #[cfg(feature = "dbus")] @@ -220,25 +141,10 @@ impl Window { } } - 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 and is supposed to be focusable - if attributes.focusable && !attributes.focused { - let signal_id = Rc::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. @@ -247,7 +153,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, @@ -258,20 +164,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, @@ -280,98 +173,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: RwLock::new(attributes.fullscreen), inner_size_constraints: RwLock::new(attributes.inner_size_constraints), preferred_theme: RwLock::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()); @@ -380,21 +223,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(); @@ -405,16 +239,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, @@ -423,13 +248,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: RwLock::new(None), inner_size_constraints: RwLock::new(WindowSizeConstraints::default()), preferred_theme: RwLock::new(None), @@ -448,43 +268,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), )) { @@ -493,7 +293,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, @@ -507,14 +307,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, @@ -526,7 +326,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); } @@ -556,7 +356,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); } @@ -573,7 +373,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); } @@ -583,7 +383,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); } @@ -591,7 +391,7 @@ impl Window { } pub fn set_focusable(&self, focusable: bool) { - self.window.set_accept_focus(focusable); + self.window.set_can_focus(focusable); } pub fn is_focused(&self) -> bool { @@ -601,7 +401,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 +414,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 +423,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 +432,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 +441,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 +479,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 +489,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 +500,7 @@ impl Window { *self.fullscreen.write().unwrap() = 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 +513,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 +532,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 +558,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 +574,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 +582,63 @@ 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 gdk4_wayland::{prelude::WaylandSurfaceExtManual, 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,66 +647,90 @@ impl Window { #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { if self.is_wayland() { + use gdk4_wayland::{prelude::WaylandSurfaceExtManual, 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 gdk4_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 { gdk4_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 gdk4_wayland::{prelude::WaylandSurfaceExtManual, 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 { #[cfg(feature = "x11")] { - 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(), + ) } #[cfg(not(feature = "x11"))] - Err(rwh_06::HandleError::Unavailable) + Err(rwh_06::HandleError::NotSupported) } } else { Err(rwh_06::HandleError::Unavailable) @@ -969,53 +740,61 @@ 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 gdk4_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 { #[cfg(feature = "x11")] - 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(); + + Ok( + rwh_06::XlibDisplayHandle::new( + Some( + std::ptr::NonNull::new(unsafe { + gdk4_x11::ffi::gdk_x11_display_get_xdisplay(display.as_ptr() as *mut _) + }) + .expect("X11 display should never be null"), + ), + display.screen().screen_number(), + ) + .into(), + ) } #[cfg(not(feature = "x11"))] - Err(rwh_06::HandleError::Unavailable) + Err(rwh_06::HandleError::NotSupported) } } - 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(()) + 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), )) { @@ -1033,6 +812,14 @@ impl Window { return portal_theme; } + if let Some(prefers_dark) = + Settings::default().map(|s| s.is_gtk_application_prefer_dark_theme()) + { + if prefers_dark { + return Theme::Dark; + } + } + Theme::Light } @@ -1040,7 +827,7 @@ impl Window { *self.preferred_theme.write().unwrap() = 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}"); } @@ -1055,7 +842,6 @@ unsafe impl Sync for Window {} #[non_exhaustive] pub enum WindowRequest { Title(String), - Position((i32, i32)), Size((i32, i32)), SizeConstraints(WindowSizeConstraints), Visible(bool), @@ -1068,20 +854,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), @@ -1090,8 +870,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 9ec5ccc49..7b79a3056 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`]. @@ -718,7 +717,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() @@ -733,7 +732,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()) @@ -1098,9 +1097,7 @@ impl Window { /// ## Platform-specific /// /// - **Windows**: There is no guarantee that the window will be the bottom most but it will try to be. - /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. - /// - **Linux(Wayland) / iOS / Android:** Unsupported. - // TODO: Unsupported in gtk4 + /// - **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) @@ -1110,9 +1107,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. - /// - **Linux(Wayland) / iOS / Android:** Unsupported. - // TODO: Unsupported in gtk4 + /// - **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) @@ -1173,7 +1168,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) @@ -1223,9 +1217,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) } @@ -1336,7 +1330,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()