diff --git a/.github/workflows/cargo-deny.yml b/.github/workflows/cargo-deny.yml index 8047ae1..2ff7b5b 100644 --- a/.github/workflows/cargo-deny.yml +++ b/.github/workflows/cargo-deny.yml @@ -17,7 +17,7 @@ jobs: include: - name: Ubuntu GTK target: x86_64-unknown-linux-gnu - flags: "--no-default-features --features gtk3" + flags: "--no-default-features --features gtk4" - name: Ubuntu XDG target: x86_64-unknown-linux-gnu flags: "--no-default-features --features xdg-portal --exclude syn" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8bf3cc4..b309f89 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,7 +15,7 @@ jobs: - name: Ubuntu GTK os: ubuntu-latest target: x86_64-unknown-linux-gnu - flags: '--no-default-features --features gtk3' + flags: '--no-default-features --features gtk4' - name: Ubuntu XDG os: ubuntu-latest target: x86_64-unknown-linux-gnu diff --git a/CHANGELOG.md b/CHANGELOG.md index 014da09..fee444e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Replace GTK3 with GTK4 as GTK3 is unmaintained by upstream and has security issue: https://github.com/advisories/GHSA-wrw7-89jp-8q8g - Add `FileDialog::set_show_hidden_files` and `AsyncFileDialog::set_show_hidden_files` to control hidden file visibility. Supported on macOS, Windows, and Linux (GTK3). - Honor `FileDialog::set_directory` and `AsyncFileDialog::set_directory` when using the zenity fallback backend on Linux. - Fix `liblary` typo in docs diff --git a/Cargo.lock b/Cargo.lock index 473fecc..3083ec1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,69 +2,71 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ "objc2", ] [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "cairo-rs" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", +] [[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", ] [[package]] name = "cc" -version = "1.2.29" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-expr" -version = "0.15.8" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" dependencies = [ "smallvec", "target-lexicon", @@ -72,15 +74,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags", "block2", @@ -90,9 +92,9 @@ dependencies = [ [[package]] name = "dlib" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ "libloading", ] @@ -111,19 +113,35 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys", ] +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -136,9 +154,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -146,15 +164,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -163,15 +181,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -180,21 +198,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -204,15 +222,26 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] +[[package]] +name = "gdk-pixbuf" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f420376dbee041b2db374ce4573892a36222bb3f6c0c43e24f0d67eae9b646" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + [[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", @@ -222,10 +251,25 @@ dependencies = [ ] [[package]] -name = "gdk-sys" -version = "0.18.2" +name = "gdk4" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk4-sys" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -238,24 +282,74 @@ dependencies = [ "system-deps", ] +[[package]] +name = "gio" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "pin-project-lite", + "smallvec", +] + [[package]] name = "gio-sys" -version = "0.18.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", - "winapi", + "windows-sys", +] + +[[package]] +name = "glib" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "glib-sys" -version = "0.18.1" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445" dependencies = [ "libc", "system-deps", @@ -263,28 +357,116 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.18.0" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "graphene-rs" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1" +dependencies = [ + "glib", + "graphene-sys", + "libc", +] + +[[package]] +name = "graphene-sys" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gsk4" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62" +dependencies = [ + "cairo-rs", + "gdk4", + "glib", + "graphene-rs", + "gsk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gsk4-sys" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a" dependencies = [ + "cairo-sys-rs", + "gdk4-sys", "glib-sys", + "gobject-sys", + "graphene-sys", "libc", + "pango-sys", "system-deps", ] [[package]] -name = "gtk-sys" -version = "0.18.2" +name = "gtk4" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2" +dependencies = [ + "cairo-rs", + "field-offset", + "futures-channel", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "graphene-rs", + "gsk4", + "gtk4-macros", + "gtk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gtk4-macros" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gtk4-sys" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b" dependencies = [ - "atk-sys", "cairo-sys-rs", "gdk-pixbuf-sys", - "gdk-sys", + "gdk4-sys", "gio-sys", "glib-sys", "gobject-sys", + "graphene-sys", + "gsk4-sys", "libc", "pango-sys", "system-deps", @@ -292,9 +474,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -304,9 +486,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" -version = "2.9.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -314,62 +496,73 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.172" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link", ] [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] [[package]] name = "objc2" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] [[package]] name = "objc2-app-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags", "block2", @@ -379,9 +572,9 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags", "dispatch2", @@ -396,9 +589,9 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags", "objc2", @@ -407,15 +600,27 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pango" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438" +dependencies = [ + "gio", + "glib", + "libc", + "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", @@ -431,21 +636,15 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "pollster" @@ -453,29 +652,38 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.37.5" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -493,9 +701,7 @@ dependencies = [ "block2", "dispatch2", "futures", - "glib-sys", - "gobject-sys", - "gtk-sys", + "gtk4", "js-sys", "libc", "log", @@ -512,27 +718,36 @@ dependencies = [ "wayland-client", "wayland-protocols", "web-sys", - "windows-sys 0.61.2", + "windows-sys", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", ] [[package]] name = "rustix" -version = "0.38.44" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "scoped-tls" @@ -541,19 +756,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] -name = "serde" -version = "1.0.219" +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -562,11 +783,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -577,24 +798,21 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -603,9 +821,9 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.2.2" +version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ "cfg-expr", "heck", @@ -616,100 +834,101 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "toml" -version = "0.8.22" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ - "serde", + "indexmap", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.9" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", + "toml_parser", "winnow", ] +[[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", +] + +[[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 = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "version-compare" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -717,31 +936,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] [[package]] name = "wayland-backend" -version = "0.3.10" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", "downcast-rs", @@ -753,9 +972,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.10" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ "bitflags", "rustix", @@ -765,9 +984,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.8" +version = "0.32.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" dependencies = [ "bitflags", "wayland-backend", @@ -777,9 +996,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.6" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" dependencies = [ "proc-macro2", "quick-xml", @@ -788,9 +1007,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.6" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ "dlib", "log", @@ -799,200 +1018,34 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" -dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" -version = "0.7.10" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 6089f06..95c8f7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ documentation = "https://docs.rs/rfd" [features] default = ["xdg-portal", "wayland"] file-handle-inner = [] -gtk3 = ["gtk-sys", "glib-sys", "gobject-sys"] +gtk4 = [] xdg-portal = ["pollster"] # Enable wayland support for xdg-portal wayland = ["wayland-backend", "wayland-client", "wayland-protocols"] @@ -85,9 +85,7 @@ wayland-protocols = { version = "0.32", features = [ "client", ], optional = true } # GTK -gtk-sys = { version = "0.18.0", features = ["v3_24"], optional = true } -glib-sys = { version = "0.18.0", optional = true } -gobject-sys = { version = "0.18.0", optional = true } +gtk4 = { version = "0.11.1", features = ["v4_10"] } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2.69" diff --git a/README.md b/README.md index 6584cb5..9cc1737 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ It provides both asynchronous and synchronous APIs. Supported platforms: * Windows * macOS - * Linux & BSDs (GTK3 or XDG Desktop Portal) + * Linux & BSDs (GTK4 or XDG Desktop Portal) * WASM32 (async only) Refer to the [documentation](https://docs.rs/rfd) for more details. diff --git a/build.rs b/build.rs index 31f8732..9596be2 100644 --- a/build.rs +++ b/build.rs @@ -7,13 +7,15 @@ fn main() { (_, "windows") => {} ("wasm32", _) => {} _ => { - let gtk = std::env::var_os("CARGO_FEATURE_GTK3").is_some(); + let gtk = std::env::var_os("CARGO_FEATURE_GTK4").is_some(); let xdg = std::env::var_os("CARGO_FEATURE_XDG_PORTAL").is_some(); if gtk && xdg { - panic!("You can't enable both `gtk3` and `xdg-portal` features at once"); + panic!("You can't enable both `gtk` and `xdg-portal` features at once"); } else if !gtk && !xdg { - panic!("You need to choose at least one backend: `gtk3` or `xdg-portal` features for {target_arch}-{target_os}"); + panic!( + "You need to choose at least one backend: `gtk` or `xdg-portal` features for {target_arch}-{target_os}" + ); } } } diff --git a/examples/async.rs b/examples/async.rs index c14a56d..4e6d970 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -1,10 +1,22 @@ fn main() { // Spawn dialog on main thread - let task = rfd::AsyncFileDialog::new().pick_file(); + let task1 = rfd::AsyncFileDialog::new().pick_file(); + let task2 = rfd::AsyncFileDialog::new().pick_file(); // Await somewhere else execute(async { - let file = task.await; + let file = task1.await; + + if let Some(file) = file { + // If you are on native platform you can just get the path + #[cfg(not(target_arch = "wasm32"))] + println!("{:?}", file.path()); + + // If you care about wasm support you just read() the file + file.read().await; + } + + let file = task2.await; if let Some(file) = file { // If you are on native platform you can just get the path diff --git a/src/backend.rs b/src/backend.rs index a4edf24..c96a31c 100755 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,5 +1,5 @@ -use crate::message_dialog::MessageDialogResult; use crate::FileHandle; +use crate::message_dialog::MessageDialogResult; use std::future::Future; #[cfg(not(target_arch = "wasm32"))] use std::path::PathBuf; @@ -13,7 +13,7 @@ use std::pin::Pin; target_os = "netbsd", target_os = "openbsd" ), - not(feature = "gtk3") + not(feature = "gtk4") ))] mod linux; @@ -25,9 +25,10 @@ mod linux; target_os = "netbsd", target_os = "openbsd" ), - feature = "gtk3" + feature = "gtk4" ))] -mod gtk3; +mod gtk4; + #[cfg(target_os = "macos")] mod macos; #[cfg(target_arch = "wasm32")] @@ -42,7 +43,7 @@ mod win_cid; target_os = "netbsd", target_os = "openbsd" ), - not(feature = "gtk3") + not(feature = "gtk4"), ))] mod xdg_desktop_portal; diff --git a/src/backend/gtk3.rs b/src/backend/gtk3.rs deleted file mode 100644 index 8da5ec8..0000000 --- a/src/backend/gtk3.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod file_dialog; -mod message_dialog; - -mod gtk_future; - -mod utils; - -pub(self) trait AsGtkDialog { - fn gtk_dialog_ptr(&self) -> *mut gtk_sys::GtkDialog; - unsafe fn show(&self); -} diff --git a/src/backend/gtk3/file_dialog.rs b/src/backend/gtk3/file_dialog.rs deleted file mode 100644 index bd454d5..0000000 --- a/src/backend/gtk3/file_dialog.rs +++ /dev/null @@ -1,183 +0,0 @@ -pub mod dialog_ffi; - -use dialog_ffi::GtkFileDialog; - -use std::path::PathBuf; - -use super::utils::GtkGlobalThread; -use crate::backend::DialogFutureType; -use crate::{FileDialog, FileHandle}; - -use super::gtk_future::GtkDialogFuture; - -// -// File Picker -// - -use crate::backend::FilePickerDialogImpl; -impl FilePickerDialogImpl for FileDialog { - fn pick_file(self) -> Option { - GtkGlobalThread::instance().run_blocking(move || { - let dialog = GtkFileDialog::build_pick_file(&self); - - if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { - dialog.get_result() - } else { - None - } - }) - } - - fn pick_files(self) -> Option> { - GtkGlobalThread::instance().run_blocking(move || { - let dialog = GtkFileDialog::build_pick_files(&self); - - if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { - Some(dialog.get_results()) - } else { - None - } - }) - } -} - -use crate::backend::AsyncFilePickerDialogImpl; -impl AsyncFilePickerDialogImpl for FileDialog { - fn pick_file_async(self) -> DialogFutureType> { - let builder = move || GtkFileDialog::build_pick_file(&self); - - let future = GtkDialogFuture::new(builder, |dialog, res_id| { - if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { - dialog.get_result().map(FileHandle::wrap) - } else { - None - } - }); - - Box::pin(future) - } - - fn pick_files_async(self) -> DialogFutureType>> { - let builder = move || GtkFileDialog::build_pick_files(&self); - - let future = GtkDialogFuture::new(builder, |dialog, res_id| { - if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { - Some( - dialog - .get_results() - .into_iter() - .map(FileHandle::wrap) - .collect(), - ) - } else { - None - } - }); - - Box::pin(future) - } -} - -// -// Folder Picker -// - -use crate::backend::FolderPickerDialogImpl; -impl FolderPickerDialogImpl for FileDialog { - fn pick_folder(self) -> Option { - GtkGlobalThread::instance().run_blocking(move || { - let dialog = GtkFileDialog::build_pick_folder(&self); - - if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { - dialog.get_result() - } else { - None - } - }) - } - - fn pick_folders(self) -> Option> { - GtkGlobalThread::instance().run_blocking(move || { - let dialog = GtkFileDialog::build_pick_folders(&self); - - if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { - Some(dialog.get_results()) - } else { - None - } - }) - } -} - -use crate::backend::AsyncFolderPickerDialogImpl; -impl AsyncFolderPickerDialogImpl for FileDialog { - fn pick_folder_async(self) -> DialogFutureType> { - let builder = move || GtkFileDialog::build_pick_folder(&self); - - let future = GtkDialogFuture::new(builder, |dialog, res_id| { - if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { - dialog.get_result().map(FileHandle::wrap) - } else { - None - } - }); - - Box::pin(future) - } - - fn pick_folders_async(self) -> DialogFutureType>> { - let builder = move || GtkFileDialog::build_pick_folders(&self); - - let future = GtkDialogFuture::new(builder, |dialog, res_id| { - if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { - Some( - dialog - .get_results() - .into_iter() - .map(FileHandle::wrap) - .collect(), - ) - } else { - None - } - }); - - Box::pin(future) - } -} - -// -// File Save -// - -use crate::backend::FileSaveDialogImpl; -impl FileSaveDialogImpl for FileDialog { - fn save_file(self) -> Option { - GtkGlobalThread::instance().run_blocking(move || { - let dialog = GtkFileDialog::build_save_file(&self); - - if dialog.run() == gtk_sys::GTK_RESPONSE_ACCEPT { - dialog.get_result() - } else { - None - } - }) - } -} - -use crate::backend::AsyncFileSaveDialogImpl; -impl AsyncFileSaveDialogImpl for FileDialog { - fn save_file_async(self) -> DialogFutureType> { - let builder = move || GtkFileDialog::build_save_file(&self); - - let future = GtkDialogFuture::new(builder, |dialog, res_id| { - if res_id == gtk_sys::GTK_RESPONSE_ACCEPT { - dialog.get_result().map(FileHandle::wrap) - } else { - None - } - }); - - Box::pin(future) - } -} diff --git a/src/backend/gtk3/file_dialog/dialog_ffi.rs b/src/backend/gtk3/file_dialog/dialog_ffi.rs deleted file mode 100644 index bdcc4c1..0000000 --- a/src/backend/gtk3/file_dialog/dialog_ffi.rs +++ /dev/null @@ -1,299 +0,0 @@ -use super::super::AsGtkDialog; -use crate::FileDialog; -use gtk_sys::GtkFileChooserNative; - -use std::{ - ffi::{CStr, CString}, - ops::Deref, - path::{Path, PathBuf}, - ptr, -}; - -#[repr(i32)] -pub enum GtkFileChooserAction { - Open = 0, - Save = 1, - SelectFolder = 2, - // CreateFolder = 3, -} - -pub struct GtkFileDialog { - pub ptr: *mut GtkFileChooserNative, -} - -impl GtkFileDialog { - fn new(title: &str, action: GtkFileChooserAction) -> Self { - let title = CString::new(title).unwrap(); - - let ptr = unsafe { - let dialog = gtk_sys::gtk_file_chooser_native_new( - title.as_ptr(), - ptr::null_mut(), - action as i32, - // passing null for the texts will use the default text, which has full support for i18n - std::ptr::null(), - std::ptr::null(), - ); - dialog as _ - }; - - Self { ptr } - } - - fn add_filters(&mut self, filters: &[crate::file_dialog::Filter]) { - for f in filters.iter() { - if let Ok(name) = CString::new(f.name.as_str()) { - unsafe { - let filter = gtk_sys::gtk_file_filter_new(); - - let paterns: Vec<_> = f - .extensions - .iter() - .filter_map(|e| CString::new(format!("*.{}", e)).ok()) - .collect(); - - gtk_sys::gtk_file_filter_set_name(filter, name.as_ptr()); - - for p in paterns.iter() { - gtk_sys::gtk_file_filter_add_pattern(filter, p.as_ptr()); - } - - gtk_sys::gtk_file_chooser_add_filter(self.ptr as _, filter); - } - } - } - } - - fn set_file_name(&self, name: Option<&str>) { - if let Some(name) = name { - if let Ok(name) = CString::new(name) { - unsafe { - gtk_sys::gtk_file_chooser_set_filename(self.ptr as _, name.as_ptr()); - } - } - } - } - - fn set_current_name(&self, name: Option<&str>) { - if let Some(name) = name { - if let Ok(name) = CString::new(name) { - unsafe { - gtk_sys::gtk_file_chooser_set_current_name(self.ptr as _, name.as_ptr()); - } - } - } - } - - fn set_path(&self, path: Option<&Path>) { - if let Some(path) = path { - if let Some(path) = path.to_str() { - if let Ok(path) = CString::new(path) { - unsafe { - gtk_sys::gtk_file_chooser_set_current_folder(self.ptr as _, path.as_ptr()); - } - } - } - } - } - - fn set_show_hidden(&self, show: Option) { - if let Some(show) = show { - unsafe { - gtk_sys::gtk_file_chooser_set_show_hidden(self.ptr as _, show as i32); - } - } - } - - pub fn get_result(&self) -> Option { - let cstr = unsafe { - let chosen_filename = gtk_sys::gtk_file_chooser_get_filename(self.ptr as _); - CStr::from_ptr(chosen_filename).to_str() - }; - - if let Ok(cstr) = cstr { - Some(PathBuf::from(cstr.to_owned())) - } else { - None - } - } - - pub fn get_results(&self) -> Vec { - #[derive(Debug)] - struct FileList(*mut glib_sys::GSList); - - impl Iterator for FileList { - type Item = glib_sys::GSList; - fn next(&mut self) -> Option { - let curr_ptr = self.0; - - if !curr_ptr.is_null() { - let curr = unsafe { *curr_ptr }; - - self.0 = curr.next; - - Some(curr) - } else { - None - } - } - } - - let chosen_filenames = - unsafe { gtk_sys::gtk_file_chooser_get_filenames(self.ptr as *mut _) }; - - let paths: Vec = FileList(chosen_filenames) - .filter_map(|item| { - let cstr = unsafe { CStr::from_ptr(item.data as _).to_str() }; - - if let Ok(cstr) = cstr { - Some(PathBuf::from(cstr.to_owned())) - } else { - None - } - }) - .collect(); - - paths - } - - pub fn run(&self) -> i32 { - unsafe { gtk_sys::gtk_native_dialog_run(self.ptr as *mut _) } - } -} - -impl GtkFileDialog { - pub fn build_pick_file(opt: &FileDialog) -> Self { - let mut dialog = GtkFileDialog::new( - opt.title.as_deref().unwrap_or("Open File"), - GtkFileChooserAction::Open, - ); - - dialog.add_filters(&opt.filters); - dialog.set_path(opt.starting_directory.as_deref()); - dialog.set_show_hidden(opt.show_hidden_files); - - if let (Some(mut path), Some(file_name)) = - (opt.starting_directory.to_owned(), opt.file_name.as_deref()) - { - path.push(file_name); - dialog.set_file_name(path.deref().to_str()); - } else { - dialog.set_file_name(opt.file_name.as_deref()); - } - - dialog - } - - pub fn build_save_file(opt: &FileDialog) -> Self { - let mut dialog = GtkFileDialog::new( - opt.title.as_deref().unwrap_or("Save File"), - GtkFileChooserAction::Save, - ); - - unsafe { gtk_sys::gtk_file_chooser_set_do_overwrite_confirmation(dialog.ptr as _, 1) }; - - dialog.add_filters(&opt.filters); - dialog.set_path(opt.starting_directory.as_deref()); - dialog.set_show_hidden(opt.show_hidden_files); - - if let (Some(mut path), Some(file_name)) = - (opt.starting_directory.to_owned(), opt.file_name.as_deref()) - { - path.push(file_name); - if path.exists() { - // the user edited an existing document - dialog.set_file_name(path.deref().to_str()); - } else { - // the user just created a new document - dialog.set_current_name(opt.file_name.as_deref()); - } - } else { - // the user just created a new document - dialog.set_current_name(opt.file_name.as_deref()); - } - - dialog - } - - pub fn build_pick_folder(opt: &FileDialog) -> Self { - let dialog = GtkFileDialog::new( - opt.title.as_deref().unwrap_or("Select Folder"), - GtkFileChooserAction::SelectFolder, - ); - dialog.set_path(opt.starting_directory.as_deref()); - dialog.set_show_hidden(opt.show_hidden_files); - - if let (Some(mut path), Some(file_name)) = - (opt.starting_directory.to_owned(), opt.file_name.as_deref()) - { - path.push(file_name); - dialog.set_file_name(path.deref().to_str()); - } else { - dialog.set_file_name(opt.file_name.as_deref()); - } - - dialog - } - - pub fn build_pick_folders(opt: &FileDialog) -> Self { - let dialog = GtkFileDialog::new( - opt.title.as_deref().unwrap_or("Select Folder"), - GtkFileChooserAction::SelectFolder, - ); - unsafe { gtk_sys::gtk_file_chooser_set_select_multiple(dialog.ptr as _, 1) }; - dialog.set_path(opt.starting_directory.as_deref()); - dialog.set_show_hidden(opt.show_hidden_files); - - if let (Some(mut path), Some(file_name)) = - (opt.starting_directory.to_owned(), opt.file_name.as_deref()) - { - path.push(file_name); - dialog.set_file_name(path.deref().to_str()); - } else { - dialog.set_file_name(opt.file_name.as_deref()); - } - - dialog - } - - pub fn build_pick_files(opt: &FileDialog) -> Self { - let mut dialog = GtkFileDialog::new( - opt.title.as_deref().unwrap_or("Open File"), - GtkFileChooserAction::Open, - ); - - unsafe { gtk_sys::gtk_file_chooser_set_select_multiple(dialog.ptr as _, 1) }; - dialog.add_filters(&opt.filters); - dialog.set_path(opt.starting_directory.as_deref()); - dialog.set_show_hidden(opt.show_hidden_files); - - if let (Some(mut path), Some(file_name)) = - (opt.starting_directory.to_owned(), opt.file_name.as_deref()) - { - path.push(file_name); - dialog.set_file_name(path.deref().to_str()); - } else { - dialog.set_file_name(opt.file_name.as_deref()); - } - - dialog - } -} - -impl AsGtkDialog for GtkFileDialog { - fn gtk_dialog_ptr(&self) -> *mut gtk_sys::GtkDialog { - self.ptr as *mut _ - } - - unsafe fn show(&self) { - gtk_sys::gtk_native_dialog_show(self.ptr as *mut _); - } -} - -impl Drop for GtkFileDialog { - fn drop(&mut self) { - unsafe { - gtk_sys::gtk_native_dialog_destroy(self.ptr as _); - } - } -} diff --git a/src/backend/gtk3/gtk_future.rs b/src/backend/gtk3/gtk_future.rs deleted file mode 100644 index b0fb721..0000000 --- a/src/backend/gtk3/gtk_future.rs +++ /dev/null @@ -1,140 +0,0 @@ -use super::utils::GtkGlobalThread; - -use std::pin::Pin; -use std::sync::{Arc, Mutex}; - -use std::task::{Context, Poll, Waker}; - -use super::AsGtkDialog; - -struct FutureState { - waker: Option, - data: Option, - dialog: Option, -} - -unsafe impl Send for FutureState {} - -pub(super) struct GtkDialogFuture { - state: Arc>>, -} - -unsafe impl Send for GtkDialogFuture {} - -impl GtkDialogFuture { - pub fn new(build: B, cb: F) -> Self - where - B: FnOnce() -> D + Send + 'static, - F: Fn(&mut D, i32) -> R + Send + 'static, - { - let state = Arc::new(Mutex::new(FutureState { - waker: None, - data: None, - dialog: None, - })); - - { - let state = state.clone(); - let callback = { - let state = state.clone(); - - move |res_id| { - let mut state = state.lock().unwrap(); - - if let Some(mut dialog) = state.dialog.take() { - state.data = Some(cb(&mut dialog, res_id)); - } - - if let Some(waker) = state.waker.take() { - waker.wake(); - } - } - }; - - GtkGlobalThread::instance().run(move || { - let mut state = state.lock().unwrap(); - state.dialog = Some(build()); - - unsafe { - let dialog = state.dialog.as_ref().unwrap(); - dialog.show(); - - let ptr = dialog.gtk_dialog_ptr(); - connect_response(ptr as *mut _, callback); - } - }); - } - - Self { state } - } -} - -impl std::future::Future for GtkDialogFuture { - type Output = R; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut state = self.state.lock().unwrap(); - - if state.data.is_some() { - Poll::Ready(state.data.take().unwrap()) - } else { - state.waker = Some(cx.waker().clone()); - Poll::Pending - } - } -} - -use gobject_sys::GCallback; -use gtk_sys::{GtkDialog, GtkResponseType}; -use std::ffi::c_void; -use std::os::raw::c_char; - -unsafe fn connect_raw( - receiver: *mut gobject_sys::GObject, - signal_name: *const c_char, - trampoline: GCallback, - closure: *mut F, -) { - use std::mem; - - use glib_sys::gpointer; - - unsafe extern "C" fn destroy_closure(ptr: *mut c_void, _: *mut gobject_sys::GClosure) { - // destroy - let _ = Box::::from_raw(ptr as *mut _); - } - assert_eq!(mem::size_of::<*mut F>(), mem::size_of::()); - assert!(trampoline.is_some()); - let handle = gobject_sys::g_signal_connect_data( - receiver, - signal_name, - trampoline, - closure as *mut _, - Some(destroy_closure::), - 0, - ); - assert!(handle > 0); -} - -unsafe fn connect_response(dialog: *mut GtkDialog, f: F) { - use std::mem::transmute; - - unsafe extern "C" fn response_trampoline( - _this: *mut gtk_sys::GtkDialog, - res: GtkResponseType, - f: glib_sys::gpointer, - ) { - let f: &F = &*(f as *const F); - - f(res); - } - let f: Box = Box::new(f); - connect_raw( - dialog as *mut _, - b"response\0".as_ptr() as *const _, - Some(transmute::<_, unsafe extern "C" fn()>( - response_trampoline:: as *const (), - )), - Box::into_raw(f), - ); -} diff --git a/src/backend/gtk3/message_dialog.rs b/src/backend/gtk3/message_dialog.rs deleted file mode 100644 index 234969c..0000000 --- a/src/backend/gtk3/message_dialog.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::ffi::CString; -use std::ptr; - -use super::gtk_future::GtkDialogFuture; -use super::utils::GtkGlobalThread; -use super::AsGtkDialog; - -use crate::message_dialog::{MessageButtons, MessageDialog, MessageLevel}; -use crate::MessageDialogResult; - -pub struct GtkMessageDialog { - buttons: MessageButtons, - ptr: *mut gtk_sys::GtkDialog, -} - -impl GtkMessageDialog { - pub fn new(opt: MessageDialog) -> Self { - let level = match opt.level { - MessageLevel::Info => gtk_sys::GTK_MESSAGE_INFO, - MessageLevel::Warning => gtk_sys::GTK_MESSAGE_WARNING, - MessageLevel::Error => gtk_sys::GTK_MESSAGE_ERROR, - }; - - let buttons = match opt.buttons { - MessageButtons::Ok => gtk_sys::GTK_BUTTONS_OK, - MessageButtons::OkCancel => gtk_sys::GTK_BUTTONS_OK_CANCEL, - MessageButtons::YesNo => gtk_sys::GTK_BUTTONS_YES_NO, - MessageButtons::YesNoCancel => gtk_sys::GTK_BUTTONS_NONE, - MessageButtons::OkCustom(_) => gtk_sys::GTK_BUTTONS_NONE, - MessageButtons::OkCancelCustom(_, _) => gtk_sys::GTK_BUTTONS_NONE, - MessageButtons::YesNoCancelCustom(_, _, _) => gtk_sys::GTK_BUTTONS_NONE, - }; - - let custom_buttons = match &opt.buttons { - MessageButtons::YesNoCancel => vec![ - Some((CString::new("Yes").unwrap(), gtk_sys::GTK_RESPONSE_YES)), - Some((CString::new("No").unwrap(), gtk_sys::GTK_RESPONSE_NO)), - Some(( - CString::new("Cancel").unwrap(), - gtk_sys::GTK_RESPONSE_CANCEL, - )), - None, - ], - MessageButtons::OkCustom(ok_text) => vec![ - Some(( - CString::new(ok_text.as_bytes()).unwrap(), - gtk_sys::GTK_RESPONSE_OK, - )), - None, - ], - MessageButtons::OkCancelCustom(ok_text, cancel_text) => vec![ - Some(( - CString::new(ok_text.as_bytes()).unwrap(), - gtk_sys::GTK_RESPONSE_OK, - )), - Some(( - CString::new(cancel_text.as_bytes()).unwrap(), - gtk_sys::GTK_RESPONSE_CANCEL, - )), - ], - MessageButtons::YesNoCancelCustom(yes_text, no_text, cancel_text) => vec![ - Some(( - CString::new(yes_text.as_bytes()).unwrap(), - gtk_sys::GTK_RESPONSE_YES, - )), - Some(( - CString::new(no_text.as_bytes()).unwrap(), - gtk_sys::GTK_RESPONSE_NO, - )), - Some(( - CString::new(cancel_text.as_bytes()).unwrap(), - gtk_sys::GTK_RESPONSE_CANCEL, - )), - None, - ], - _ => vec![], - }; - - let s: &str = &opt.title; - let title = CString::new(s).unwrap(); - let s: &str = &opt.description; - let description = CString::new(s).unwrap(); - - let ptr = unsafe { - let dialog = gtk_sys::gtk_message_dialog_new( - ptr::null_mut(), - gtk_sys::GTK_DIALOG_MODAL, - level, - buttons, - b"%s\0".as_ptr() as *mut _, - title.as_ptr(), - ) as *mut gtk_sys::GtkDialog; - - set_child_labels_selectable(dialog); - // Also set the window title, otherwise it would be empty - gtk_sys::gtk_window_set_title(dialog as _, title.as_ptr()); - - for custom_button in custom_buttons { - if let Some((custom_button_cstr, response_id)) = custom_button { - gtk_sys::gtk_dialog_add_button( - dialog, - custom_button_cstr.as_ptr(), - response_id, - ); - } - } - - dialog - }; - - unsafe { - gtk_sys::gtk_message_dialog_format_secondary_text(ptr as *mut _, description.as_ptr()); - } - - Self { - ptr, - buttons: opt.buttons, - } - } - - pub fn run(self) -> MessageDialogResult { - let res = unsafe { gtk_sys::gtk_dialog_run(self.ptr) }; - - use MessageButtons::*; - match (&self.buttons, res) { - (Ok | OkCancel, gtk_sys::GTK_RESPONSE_OK) => MessageDialogResult::Ok, - (Ok | OkCancel | YesNoCancel, gtk_sys::GTK_RESPONSE_CANCEL) => { - MessageDialogResult::Cancel - } - (YesNo | YesNoCancel, gtk_sys::GTK_RESPONSE_YES) => MessageDialogResult::Yes, - (YesNo | YesNoCancel, gtk_sys::GTK_RESPONSE_NO) => MessageDialogResult::No, - (OkCustom(custom), gtk_sys::GTK_RESPONSE_OK) => { - MessageDialogResult::Custom(custom.to_owned()) - } - (OkCancelCustom(custom, _), gtk_sys::GTK_RESPONSE_OK) => { - MessageDialogResult::Custom(custom.to_owned()) - } - (OkCancelCustom(_, custom), gtk_sys::GTK_RESPONSE_CANCEL) => { - MessageDialogResult::Custom(custom.to_owned()) - } - (YesNoCancelCustom(custom, _, _), gtk_sys::GTK_RESPONSE_YES) => { - MessageDialogResult::Custom(custom.to_owned()) - } - (YesNoCancelCustom(_, custom, _), gtk_sys::GTK_RESPONSE_NO) => { - MessageDialogResult::Custom(custom.to_owned()) - } - (YesNoCancelCustom(_, _, custom), gtk_sys::GTK_RESPONSE_CANCEL) => { - MessageDialogResult::Custom(custom.to_owned()) - } - _ => MessageDialogResult::Cancel, - } - } -} - -unsafe fn is_label(type_instance: *const gobject_sys::GTypeInstance) -> bool { - (*(*type_instance).g_class).g_type == gtk_sys::gtk_label_get_type() -} - -/// Sets the child labels of a widget selectable -unsafe fn set_child_labels_selectable(dialog: *mut gtk_sys::GtkDialog) { - let area = gtk_sys::gtk_message_dialog_get_message_area(dialog as _); - let mut children = gtk_sys::gtk_container_get_children(area as _); - while !children.is_null() { - let child = (*children).data; - if is_label(child as _) { - gtk_sys::gtk_label_set_selectable(child as _, 1); - } - children = (*children).next; - } -} - -impl Drop for GtkMessageDialog { - fn drop(&mut self) { - unsafe { - gtk_sys::gtk_widget_destroy(self.ptr as *mut _); - } - } -} - -impl AsGtkDialog for GtkMessageDialog { - fn gtk_dialog_ptr(&self) -> *mut gtk_sys::GtkDialog { - self.ptr as *mut _ - } - unsafe fn show(&self) { - gtk_sys::gtk_widget_show_all(self.ptr as *mut _); - } -} - -use crate::backend::MessageDialogImpl; - -impl MessageDialogImpl for MessageDialog { - fn show(self) -> MessageDialogResult { - GtkGlobalThread::instance().run_blocking(move || { - let dialog = GtkMessageDialog::new(self); - dialog.run() - }) - } -} - -use crate::backend::AsyncMessageDialogImpl; -use crate::backend::DialogFutureType; - -impl AsyncMessageDialogImpl for MessageDialog { - fn show_async(self) -> DialogFutureType { - let builder = move || GtkMessageDialog::new(self); - - let future = GtkDialogFuture::new(builder, |_, res| match res { - gtk_sys::GTK_RESPONSE_OK => MessageDialogResult::Ok, - gtk_sys::GTK_RESPONSE_CANCEL => MessageDialogResult::Cancel, - gtk_sys::GTK_RESPONSE_YES => MessageDialogResult::Yes, - gtk_sys::GTK_RESPONSE_NO => MessageDialogResult::No, - gtk_sys::GTK_RESPONSE_DELETE_EVENT => MessageDialogResult::Cancel, - _ => unreachable!(), - }); - Box::pin(future) - } -} diff --git a/src/backend/gtk3/utils.rs b/src/backend/gtk3/utils.rs deleted file mode 100644 index 08655a4..0000000 --- a/src/backend/gtk3/utils.rs +++ /dev/null @@ -1,125 +0,0 @@ -use std::ptr; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::OnceLock; -use std::sync::{Arc, Condvar, Mutex}; -use std::thread::spawn; - -static GTK_THREAD: OnceLock = OnceLock::new(); - -/// GTK functions are not thread-safe, and must all be called from the thread that initialized GTK. To ensure this, we -/// spawn one thread the first time a GTK dialog is opened and keep it open for the entire lifetime of the application, -/// as GTK cannot be de-initialized or re-initialized on another thread. You're stuck on the thread on which you first -/// initialize GTK. -pub struct GtkGlobalThread { - running: Arc, -} - -impl GtkGlobalThread { - /// Return the global, lazily-initialized instance of the global GTK thread. - pub(super) fn instance() -> &'static Self { - GTK_THREAD.get_or_init(|| Self::new()) - } - - fn new() -> Self { - // When the GtkGlobalThread is eventually dropped, we will set `running` to false and wake up the loop so - // gtk_main_iteration unblocks and we exit the thread on the next iteration. - let running = Arc::new(AtomicBool::new(true)); - let thread_running = Arc::clone(&running); - - spawn(move || { - let initialized = - unsafe { gtk_sys::gtk_init_check(ptr::null_mut(), ptr::null_mut()) == 1 }; - if !initialized { - return; - } - - loop { - if !thread_running.load(Ordering::Acquire) { - break; - } - - unsafe { - gtk_sys::gtk_main_iteration(); - } - } - }); - - Self { - running: Arc::new(AtomicBool::new(true)), - } - } - - /// Run a function on the GTK thread, blocking on the result which is then passed back. - pub(super) fn run_blocking< - T: Send + Clone + std::fmt::Debug + 'static, - F: FnOnce() -> T + Send + 'static, - >( - &self, - cb: F, - ) -> T { - let data: Arc<(Mutex>, _)> = Arc::new((Mutex::new(None), Condvar::new())); - let thread_data = Arc::clone(&data); - let mut cb = Some(cb); - unsafe { - connect_idle(move || { - // connect_idle takes a FnMut; convert our FnOnce into that by ensuring we only call it once - let res = cb.take().expect("Callback should only be called once")(); - - // pass the result back to the main thread - let (lock, cvar) = &*thread_data; - *lock.lock().unwrap() = Some(res); - cvar.notify_all(); - - glib_sys::GFALSE - }); - }; - - // wait for GTK thread to execute the callback and place the result into `data` - let lock_res = data - .1 - .wait_while(data.0.lock().unwrap(), |res| res.is_none()) - .unwrap(); - lock_res.as_ref().unwrap().clone() - } - - /// Launch a function on the GTK thread without blocking. - pub(super) fn run(&self, cb: F) { - let mut cb = Some(cb); - unsafe { - connect_idle(move || { - cb.take().expect("Callback should only be called once")(); - glib_sys::GFALSE - }); - }; - } -} - -impl Drop for GtkGlobalThread { - fn drop(&mut self) { - self.running.store(false, Ordering::Release); - unsafe { glib_sys::g_main_context_wakeup(std::ptr::null_mut()) }; - } -} - -unsafe fn connect_idle glib_sys::gboolean + Send + 'static>(f: F) { - unsafe extern "C" fn response_trampoline glib_sys::gboolean + Send + 'static>( - f: glib_sys::gpointer, - ) -> glib_sys::gboolean { - let f: &mut F = &mut *(f as *mut F); - - f() - } - let f_box: Box = Box::new(f); - - unsafe extern "C" fn destroy_closure(ptr: *mut std::ffi::c_void) { - // destroy - let _ = Box::::from_raw(ptr as *mut _); - } - - glib_sys::g_idle_add_full( - glib_sys::G_PRIORITY_DEFAULT_IDLE, - Some(response_trampoline::), - Box::into_raw(f_box) as glib_sys::gpointer, - Some(destroy_closure::), - ); -} diff --git a/src/backend/gtk4.rs b/src/backend/gtk4.rs new file mode 100644 index 0000000..e2d0c74 --- /dev/null +++ b/src/backend/gtk4.rs @@ -0,0 +1,461 @@ +use crate::MessageButtons; +use crate::backend::DialogFutureType; +use crate::file_dialog::Filter; +use crate::message_dialog::MessageDialog; +use crate::{FileDialog, FileHandle, MessageDialogResult}; +use gtk4::{Window, gio, glib, prelude::*}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc; +use std::sync::{Arc, Condvar, Mutex, OnceLock}; + +static GTK_THREAD: OnceLock = OnceLock::new(); + +type GtkJob = Box; + +struct GtkGlobalThread { + context: glib::MainContext, + running: Arc, + sender: mpsc::Sender, +} + +impl GtkGlobalThread { + fn instance() -> &'static Self { + GTK_THREAD.get_or_init(Self::new) + } + + fn new() -> Self { + let context = glib::MainContext::default(); + let running = Arc::new(AtomicBool::new(true)); + let thread_context = context.clone(); + let thread_running = Arc::clone(&running); + let (sender, receiver) = mpsc::channel::(); + + std::thread::spawn(move || { + let _guard = thread_context + .acquire() + .expect("failed to acquire GTK main context"); + thread_context + .with_thread_default(|| { + let _ = gtk4::init(); + + while thread_running.load(Ordering::Acquire) { + let job = match receiver.recv() { + Ok(job) => job, + Err(_) => break, + }; + job(); + } + }) + .expect("failed to set GTK main context as thread default"); + }); + + Self { + context, + running, + sender, + } + } + + fn run_blocking(&self, cb: F) -> T + where + T: Send + Clone + 'static, + F: FnOnce() -> T + Send + 'static, + { + let result = Arc::new((Mutex::new(None), Condvar::new())); + let result_clone = Arc::clone(&result); + + self.sender + .send(Box::new(move || { + let value = cb(); + let (lock, cvar) = &*result_clone; + *lock.lock().unwrap() = Some(value); + cvar.notify_all(); + })) + .expect("failed to send GTK job to GTK thread"); + + let (lock, cvar) = &*result; + let result = cvar + .wait_while(lock.lock().unwrap(), |value| value.is_none()) + .unwrap(); + result.clone().unwrap() + } +} + +impl Drop for GtkGlobalThread { + fn drop(&mut self) { + self.running.store(false, Ordering::Release); + let _ = self.sender.send(Box::new(|| {})); + self.context.wakeup(); + } +} + +fn async_thread(f: F) -> DialogFutureType +where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + Box::pin(async move { + let (tx, rx) = crate::oneshot::channel(); + + std::thread::spawn(move || { + tx.send(f()).ok(); + }); + + rx.await + .expect("gtk4 worker thread dropped before returning a result") + }) +} + +fn path_to_file(path: &Path) -> gio::File { + gio::File::for_path(path) +} + +fn first_path(file: gio::File) -> Option { + file.path() +} + +fn all_paths(files: gio::ListModel) -> Vec { + let mut paths = Vec::with_capacity(files.n_items() as usize); + + for index in 0..files.n_items() { + let file = files + .item(index) + .and_downcast::() + .and_then(|file| file.path()); + + if let Some(path) = file { + paths.push(path); + } + } + + paths +} + +fn add_filters(dialog: >k4::FileDialog, filters: &[Filter]) { + if filters.is_empty() { + return; + } + + let list = gio::ListStore::new::(); + let mut first_filter = None; + + for filter in filters { + let gtk_filter = gtk4::FileFilter::new(); + gtk_filter.set_name(Some(&filter.name)); + + for extension in &filter.extensions { + if extension == "*" || extension.is_empty() { + gtk_filter.add_pattern("*"); + } else { + gtk_filter.add_pattern(&format!("*.{extension}")); + } + } + + if first_filter.is_none() { + first_filter = Some(gtk_filter.clone()); + } + + list.append(>k_filter); + } + + dialog.set_filters(Some(&list)); + if let Some(first_filter) = first_filter.as_ref() { + dialog.set_default_filter(Some(first_filter)); + } +} + +fn set_directory(dialog: >k4::FileDialog, directory: Option<&Path>) { + if let Some(directory) = directory { + dialog.set_initial_folder(Some(&path_to_file(directory))); + } +} + +fn set_file_name(dialog: >k4::FileDialog, directory: Option<&Path>, file_name: Option<&str>) { + match (directory, file_name) { + (Some(directory), Some(file_name)) => { + let path = directory.join(file_name); + dialog.set_initial_file(Some(&path_to_file(&path))); + } + (None, Some(file_name)) => dialog.set_initial_name(Some(file_name)), + _ => {} + } +} + +fn set_save_name(dialog: >k4::FileDialog, directory: Option<&Path>, file_name: Option<&str>) { + match (directory, file_name) { + (Some(directory), Some(file_name)) => { + let path = directory.join(file_name); + if path.exists() { + dialog.set_initial_file(Some(&path_to_file(&path))); + } else { + dialog.set_initial_name(Some(file_name)); + } + } + (_, Some(file_name)) => dialog.set_initial_name(Some(file_name)), + _ => {} + } +} + +fn configure_dialog(dialog: >k4::FileDialog, options: &FileDialog) { + add_filters(dialog, &options.filters); + set_directory(dialog, options.starting_directory.as_deref()); + dialog.set_modal(true); +} + +fn build_file_dialog(options: &FileDialog, title: &str) -> gtk4::FileDialog { + let dialog = gtk4::FileDialog::new(); + dialog.set_title(options.title.as_deref().unwrap_or(title)); + configure_dialog(&dialog, options); + dialog +} + +fn build_pick_file(options: &FileDialog) -> gtk4::FileDialog { + let dialog = build_file_dialog(options, "Open File"); + set_file_name( + &dialog, + options.starting_directory.as_deref(), + options.file_name.as_deref(), + ); + dialog +} + +fn build_pick_files(options: &FileDialog) -> gtk4::FileDialog { + let dialog = build_file_dialog(options, "Open File"); + set_file_name( + &dialog, + options.starting_directory.as_deref(), + options.file_name.as_deref(), + ); + dialog +} + +fn build_pick_folder(options: &FileDialog) -> gtk4::FileDialog { + let dialog = build_file_dialog(options, "Select Folder"); + set_file_name( + &dialog, + options.starting_directory.as_deref(), + options.file_name.as_deref(), + ); + dialog +} + +fn build_pick_folders(options: &FileDialog) -> gtk4::FileDialog { + let dialog = build_file_dialog(options, "Select Folder"); + set_file_name( + &dialog, + options.starting_directory.as_deref(), + options.file_name.as_deref(), + ); + dialog +} + +fn build_save_file(options: &FileDialog) -> gtk4::FileDialog { + let dialog = build_file_dialog(options, "Save File"); + set_save_name( + &dialog, + options.starting_directory.as_deref(), + options.file_name.as_deref(), + ); + dialog +} + +fn run_pick_file(dialog: gtk4::FileDialog) -> Option { + let context = glib::MainContext::ref_thread_default(); + context + .block_on(dialog.open_future(Some(&Window::builder().title("dummy").build()))) + .ok() + .and_then(first_path) +} + +fn run_pick_files(dialog: gtk4::FileDialog) -> Option> { + let context = glib::MainContext::ref_thread_default(); + context + .block_on(dialog.open_multiple_future(Some(&Window::builder().title("dummy").build()))) + .ok() + .map(all_paths) +} + +fn run_pick_folder(dialog: gtk4::FileDialog) -> Option { + let context = glib::MainContext::ref_thread_default(); + context + .block_on(dialog.select_folder_future(Some(&Window::builder().title("dummy").build()))) + .ok() + .and_then(first_path) +} + +fn run_pick_folders(dialog: gtk4::FileDialog) -> Option> { + let context = glib::MainContext::ref_thread_default(); + context + .block_on( + dialog.select_multiple_folders_future(Some(&Window::builder().title("dummy").build())), + ) + .ok() + .map(all_paths) +} + +fn run_save_file(dialog: gtk4::FileDialog) -> Option { + let context = glib::MainContext::ref_thread_default(); + context + .block_on(dialog.save_future(Some(&Window::builder().title("dummy").build()))) + .ok() + .and_then(first_path) +} + +fn message_result(buttons: &MessageButtons, response: i32) -> MessageDialogResult { + use MessageButtons::*; + + match (buttons, response) { + (Ok, 1) => MessageDialogResult::Cancel, + (Ok, 0) => MessageDialogResult::Ok, + (OkCancel, 0) => MessageDialogResult::Cancel, + (OkCancel, 1) => MessageDialogResult::Ok, + (YesNo, 0) => MessageDialogResult::No, + (YesNo, 1) => MessageDialogResult::Yes, + (YesNoCancel, 0) => MessageDialogResult::Cancel, + (YesNoCancel, 1) => MessageDialogResult::Yes, + (YesNoCancel, 2) => MessageDialogResult::No, + (OkCustom(_), 1) => MessageDialogResult::Cancel, + (OkCustom(custom), 0) => MessageDialogResult::Custom(custom.to_owned()), + (OkCancelCustom(_, custom), 0) => MessageDialogResult::Custom(custom.to_owned()), + (OkCancelCustom(custom, _), 1) => MessageDialogResult::Custom(custom.to_owned()), + (YesNoCancelCustom(_, _, custom), 0) => MessageDialogResult::Custom(custom.to_owned()), + (YesNoCancelCustom(custom, _, _), 1) => MessageDialogResult::Custom(custom.to_owned()), + (YesNoCancelCustom(_, custom, _), 2) => MessageDialogResult::Custom(custom.to_owned()), + _ => MessageDialogResult::Cancel, + } +} + +fn run_message_dialog(options: MessageDialog) -> MessageDialogResult { + let dialog = gtk4::AlertDialog::builder() + .modal(true) + .message(&options.title) + .detail(&options.description) + .build(); + + dialog.set_default_button(1); + dialog.set_cancel_button(0); + + match &options.buttons { + MessageButtons::Ok => { + dialog.set_buttons(&["OK"]); + dialog.set_default_button(0); + dialog.set_cancel_button(1); + } + MessageButtons::OkCancel => { + dialog.set_buttons(&["Cancel", "OK"]); + } + MessageButtons::YesNo => { + dialog.set_buttons(&["No", "Yes"]); + } + MessageButtons::YesNoCancel => { + dialog.set_buttons(&["Cancel", "Yes", "No"]); + } + MessageButtons::OkCustom(ok) => { + dialog.set_buttons(&[ok.as_str()]); + dialog.set_default_button(0); + dialog.set_cancel_button(1); + } + MessageButtons::OkCancelCustom(ok, cancel) => { + dialog.set_buttons(&[cancel.as_str(), ok.as_str()]); + } + MessageButtons::YesNoCancelCustom(yes, no, cancel) => { + dialog.set_buttons(&[cancel.as_str(), yes.as_str(), no.as_str()]); + } + } + + let context = glib::MainContext::ref_thread_default(); + let response = context + .block_on(dialog.choose_future(Some(&Window::builder().title("dummy").build()))) + .map(|result| match options.buttons { + MessageButtons::Ok | MessageButtons::OkCustom(_) => match result { + _ => 0, + }, + _ => result, + }) + .unwrap_or(match options.buttons { + // for one button dialog, 1 is cancel and 0 is ok + MessageButtons::Ok | MessageButtons::OkCustom(_) => 1, + _ => 0, + }); + + message_result(&options.buttons, response) +} + +use crate::backend::FilePickerDialogImpl; +impl FilePickerDialogImpl for FileDialog { + fn pick_file(self) -> Option { + GtkGlobalThread::instance().run_blocking(move || run_pick_file(build_pick_file(&self))) + } + + fn pick_files(self) -> Option> { + GtkGlobalThread::instance().run_blocking(move || run_pick_files(build_pick_files(&self))) + } +} + +use crate::backend::AsyncFilePickerDialogImpl; +impl AsyncFilePickerDialogImpl for FileDialog { + fn pick_file_async(self) -> DialogFutureType> { + async_thread(move || Self::pick_file(self).map(FileHandle::wrap)) + } + + fn pick_files_async(self) -> DialogFutureType>> { + async_thread(move || { + Self::pick_files(self).map(|files| files.into_iter().map(FileHandle::wrap).collect()) + }) + } +} + +use crate::backend::FolderPickerDialogImpl; +impl FolderPickerDialogImpl for FileDialog { + fn pick_folder(self) -> Option { + GtkGlobalThread::instance().run_blocking(move || run_pick_folder(build_pick_folder(&self))) + } + + fn pick_folders(self) -> Option> { + GtkGlobalThread::instance() + .run_blocking(move || run_pick_folders(build_pick_folders(&self))) + } +} + +use crate::backend::AsyncFolderPickerDialogImpl; +impl AsyncFolderPickerDialogImpl for FileDialog { + fn pick_folder_async(self) -> DialogFutureType> { + async_thread(move || Self::pick_folder(self).map(FileHandle::wrap)) + } + + fn pick_folders_async(self) -> DialogFutureType>> { + async_thread(move || { + Self::pick_folders(self) + .map(|folders| folders.into_iter().map(FileHandle::wrap).collect()) + }) + } +} + +use crate::backend::FileSaveDialogImpl; +impl FileSaveDialogImpl for FileDialog { + fn save_file(self) -> Option { + GtkGlobalThread::instance().run_blocking(move || run_save_file(build_save_file(&self))) + } +} + +use crate::backend::AsyncFileSaveDialogImpl; +impl AsyncFileSaveDialogImpl for FileDialog { + fn save_file_async(self) -> DialogFutureType> { + async_thread(move || Self::save_file(self).map(FileHandle::wrap)) + } +} + +use crate::backend::MessageDialogImpl; +impl MessageDialogImpl for MessageDialog { + fn show(self) -> MessageDialogResult { + GtkGlobalThread::instance().run_blocking(move || run_message_dialog(self)) + } +} + +use crate::backend::AsyncMessageDialogImpl; +impl AsyncMessageDialogImpl for MessageDialog { + fn show_async(self) -> DialogFutureType { + async_thread(move || Self::show(self)) + } +} diff --git a/src/file_dialog.rs b/src/file_dialog.rs index 291e98f..5d8a171 100755 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -114,7 +114,6 @@ impl FileDialog { /// Supported platforms: /// * Windows /// * Mac - /// * Linux (GTK3 only, not XDG Portal) pub fn set_show_hidden_files(mut self, show: bool) -> Self { self.show_hidden_files = Some(show); self @@ -268,7 +267,6 @@ impl AsyncFileDialog { /// Supported platforms: /// * Windows /// * Mac - /// * Linux (GTK3 only, not XDG Portal) pub fn set_show_hidden_files(mut self, show: bool) -> Self { self.file_dialog = self.file_dialog.set_show_hidden_files(show); self diff --git a/src/lib.rs b/src/lib.rs index 8cffa07..ff0bcfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! //! * Windows //! * macOS -//! * Linux & BSDs (GTK3 or XDG Desktop Portal) +//! * Linux & BSDs (GTK4 or XDG Desktop Portal) //! * WASM32 (async only) //! //! # Examples @@ -37,20 +37,20 @@ //! //! # Linux & BSD backends //! -//! On Linux & BSDs, two backends are available, one using the [GTK3 Rust bindings](https://gtk-rs.org/) +//! On Linux & BSDs, two backends are available, one using the [GTK4 Rust bindings](https://gtk-rs.org/) //! and the other using the [XDG Desktop Portal](https://github.com/flatpak/xdg-desktop-portal) //! D-Bus API through `libdbus` or [zenity](https://gitlab.gnome.org/GNOME/zenity). //! //! ## GTK backend -//! The GTK backend is used when the `xdg-portal` feature is disabled with the [`default-features = false`](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features), and `gtk3` is enabled instead. The GTK3 +//! The GTK backend is used when the `xdg-portal` feature is disabled with the [`default-features = false`](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features), and `gtk4` is enabled instead. The GTK4 //! backend requires the C library and development headers to be installed to build RFD. The package //! names on various distributions are: //! //! | Distribution | Installation Command | //! | --------------- | ------------ | -//! | Fedora | dnf install gtk3-devel | -//! | Arch | pacman -S gtk3 | -//! | Debian & Ubuntu | apt install libgtk-3-dev | +//! | Fedora | dnf install gtk4-devel | +//! | Arch | pacman -S gtk4 | +//! | Debian & Ubuntu | apt install libgtk-4-dev | //! //! ## XDG Desktop Portal backend //! The XDG Desktop Portal backend is used with the `xdg-portal` Cargo feature which is enabled by default. This backend will use either the GTK or KDE file dialog depending on the desktop environment @@ -98,7 +98,7 @@ //! Here is an [example](https://github.com/PolyMeilex/rfd/tree/master/examples/message-custom-buttons) using [embed-resource](https://docs.rs/embed-resource/latest/embed_resource/). //! //! # Cargo features -//! * `gtk3`: Uses GTK for dialogs on Linux & BSDs; has no effect on Windows and macOS +//! * `gtk4`: Uses GTK for dialogs on Linux & BSDs; has no effect on Windows and macOS //! * `xdg-portal`: Uses XDG Desktop Portal instead of GTK on Linux & BSDs //! * `common-controls-v6`: Use `TaskDialogIndirect` API from ComCtl32.dll v6 for showing message dialog. This is necessary if you need to customize dialog button texts. //!