widget gallery and compositor test bench
Update and align interface features
Cargo.lock | 721 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Cargo.toml | 4 +-
README.md | 2 +-
src/main.rs | 310 ++++++++++++++++++++++----
4 files changed, 988 insertions(+), 49 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 81d6851..84a29b5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
[[package]]
name = "arrayref"
version = "0.3.9"
@@ -38,6 +44,143 @@ dependencies = [
"libloading",
]
+[[package]]
+name = "async-broadcast"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-io"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix 1.1.4",
+ "slab",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
+dependencies = [
+ "async-channel",
+ "async-io",
+ "async-lock",
+ "async-signal",
+ "async-task",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix 1.1.4",
+]
+
+[[package]]
+name = "async-recursion"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 1.1.4",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
+name = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
[[package]]
name = "autocfg"
version = "1.5.1"
@@ -86,6 +229,19 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+[[package]]
+name = "blocking"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite",
+ "piper",
+]
+
[[package]]
name = "bumpalo"
version = "3.20.3"
@@ -167,7 +323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
-name = "clear-test-suite"
+name = "clear-test-interface"
version = "0.1.0"
dependencies = [
"bytemuck",
@@ -183,6 +339,7 @@ dependencies = [
"wayland-client",
"wgpu",
"xkeysym",
+ "zbus",
]
[[package]]
@@ -198,6 +355,7 @@ dependencies = [
"raw-window-handle",
"resvg",
"serde",
+ "serde_json",
"smithay-client-toolkit",
"tokio",
"wayland-backend",
@@ -358,6 +516,33 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
+[[package]]
+name = "endi"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
+
+[[package]]
+name = "enumflags2"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -393,6 +578,33 @@ dependencies = [
"num-traits",
]
+[[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 = "fastrand"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
+
[[package]]
name = "fdeflate"
version = "0.3.7"
@@ -495,6 +707,25 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+[[package]]
+name = "futures-io"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
+
+[[package]]
+name = "futures-lite"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
[[package]]
name = "futures-task"
version = "0.3.32"
@@ -513,6 +744,19 @@ dependencies = [
"slab",
]
+[[package]]
+name = "getrandom"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+ "wasip3",
+]
+
[[package]]
name = "gif"
version = "0.13.3"
@@ -646,12 +890,24 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
[[package]]
name = "hexf-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
+[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
[[package]]
name = "imagesize"
version = "0.12.0"
@@ -666,8 +922,16 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
+ "serde",
+ "serde_core",
]
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
[[package]]
name = "jni-sys"
version = "0.3.1"
@@ -742,6 +1006,12 @@ dependencies = [
"smallvec",
]
+[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
[[package]]
name = "libc"
version = "0.2.186"
@@ -836,6 +1106,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "metal"
version = "0.31.0"
@@ -936,6 +1215,22 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "ordered-stream"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[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.5"
@@ -977,6 +1272,17 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+[[package]]
+name = "piper"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
[[package]]
name = "pkg-config"
version = "0.3.33"
@@ -1022,6 +1328,25 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[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.106"
@@ -1055,6 +1380,12 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
[[package]]
name = "range-alloc"
version = "0.1.5"
@@ -1251,6 +1582,12 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
[[package]]
name = "serde"
version = "1.0.228"
@@ -1281,6 +1618,30 @@ dependencies = [
"syn",
]
+[[package]]
+name = "serde_json"
+version = "1.0.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "shlex"
version = "2.0.1"
@@ -1480,6 +1841,19 @@ dependencies = [
"libc",
]
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom",
+ "once_cell",
+ "rustix 1.1.4",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "termcolor"
version = "1.4.1"
@@ -1598,6 +1972,67 @@ dependencies = [
"syn",
]
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.25.12+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
+dependencies = [
+ "indexmap",
+ "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 = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
[[package]]
name = "ttf-parser"
version = "0.20.0"
@@ -1610,6 +2045,17 @@ version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
+[[package]]
+name = "uds_windows"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
+dependencies = [
+ "memoffset",
+ "tempfile",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "unicode-bidi"
version = "0.3.18"
@@ -1703,6 +2149,17 @@ dependencies = [
"xmlwriter",
]
+[[package]]
+name = "uuid"
+version = "1.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
+dependencies = [
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
[[package]]
name = "version_check"
version = "0.9.5"
@@ -1715,6 +2172,24 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+[[package]]
+name = "wasip2"
+version = "1.0.3+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
+dependencies = [
+ "wit-bindgen 0.57.1",
+]
+
+[[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen 0.51.0",
+]
+
[[package]]
name = "wasm-bindgen"
version = "0.2.122"
@@ -1770,6 +2245,40 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags 2.12.0",
+ "hashbrown 0.15.5",
+ "indexmap",
+ "semver",
+]
+
[[package]]
name = "wayland-backend"
version = "0.3.15"
@@ -2151,6 +2660,109 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+[[package]]
+name = "winnow"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck",
+ "indexmap",
+ "prettyplease",
+ "syn",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.12.0",
+ "indexmap",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
+
[[package]]
name = "xcursor"
version = "0.3.10"
@@ -2195,8 +2807,115 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1"
+[[package]]
+name = "zbus"
+version = "5.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "enumflags2",
+ "event-listener",
+ "futures-core",
+ "futures-lite",
+ "hex",
+ "libc",
+ "ordered-stream",
+ "rustix 1.1.4",
+ "serde",
+ "serde_repr",
+ "tracing",
+ "uds_windows",
+ "uuid",
+ "windows-sys 0.61.2",
+ "winnow",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "5.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "zbus_names",
+ "zvariant",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "4.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
+dependencies = [
+ "serde",
+ "winnow",
+ "zvariant",
+]
+
[[package]]
name = "zeno"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+
+[[package]]
+name = "zvariant"
+version = "5.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "winnow",
+ "zvariant_derive",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "5.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn",
+ "winnow",
+]
diff --git a/Cargo.toml b/Cargo.toml
index ccb35e4..1c32bb3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "clear-test-suite"
+name = "clear-test-interface"
version = "0.1.0"
edition = "2021"
@@ -17,3 +17,5 @@ calloop-wayland-source = "0.3.0"
wayland-client = { version = "0.31", features = ["system"] }
xkeysym = "0.2"
resvg = "0.41.0"
+zbus = "5"
+
diff --git a/README.md b/README.md
index 40a3d03..640e520 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Clear Test Suite
+# Clear Test Interface
This repository contains integration and verification tests for the `clear` desktop environment programs, specifically:
- `clear-computing-environment-server` (Wayland compositor with background blur support)
diff --git a/src/main.rs b/src/main.rs
index c6166ba..c564687 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -41,12 +41,14 @@ use calloop_wayland_source::WaylandSource;
struct Vertex {
position: [f32; 2],
color: [f32; 4],
+ clip_circle: [f32; 3],
}
impl Vertex {
- const ATTRIBS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![
+ const ATTRIBS: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
0 => Float32x2,
1 => Float32x4,
+ 2 => Float32x3,
];
fn desc() -> wgpu::VertexBufferLayout<'static> {
@@ -69,12 +71,12 @@ fn quad_vertices(
let y1 = 1.0 - ((y + h) / surface_h) * 2.0;
[
- Vertex { position: [x0, y0], color },
- Vertex { position: [x1, y0], color },
- Vertex { position: [x0, y1], color },
- Vertex { position: [x1, y0], color },
- Vertex { position: [x1, y1], color },
- Vertex { position: [x0, y1], color },
+ Vertex { position: [x0, y0], color, clip_circle: [0.0, 0.0, 0.0] },
+ Vertex { position: [x1, y0], color, clip_circle: [0.0, 0.0, 0.0] },
+ Vertex { position: [x0, y1], color, clip_circle: [0.0, 0.0, 0.0] },
+ Vertex { position: [x1, y0], color, clip_circle: [0.0, 0.0, 0.0] },
+ Vertex { position: [x1, y1], color, clip_circle: [0.0, 0.0, 0.0] },
+ Vertex { position: [x0, y1], color, clip_circle: [0.0, 0.0, 0.0] },
]
}
@@ -95,6 +97,7 @@ fn make_text_buffer(font_system: &mut FontSystem, text: &str, size: f32) -> Buff
enum Page {
Widgets,
Windows,
+ Xdg,
}
struct State {
@@ -275,7 +278,7 @@ impl State {
};
make_text_buffer(&mut font_system, &title, 16.0)
} else {
- make_text_buffer(&mut font_system, "Clear Test Suite - Widgets", 16.0)
+ make_text_buffer(&mut font_system, "Clear Test Interface - Widgets", 16.0)
};
let status_buffer = make_text_buffer(&mut font_system, "Select a test case to begin verification.", 12.0);
@@ -298,7 +301,7 @@ impl State {
} else {
vec![
Box::new(Header::new()), // 0
- Box::new(Paginator::new(150.0, vec!["Widgets".to_string(), "Windows".to_string()])), // 1
+ Box::new(Paginator::new(56.0, vec!["Widgets".to_string(), "Windows".to_string(), "XDG".to_string()]).with_tabs_rotated(true)), // 1
Box::new(StatusBar::new()), // 2
// "Widgets" page (indices 3..13)
@@ -354,6 +357,10 @@ impl State {
).with_font_size(11.0).with_color([0x83, 0x83, 0x8a])), // 29 (Info panel description)
Box::new(RangeSlider::new().with_label("RangeSlider")), // 30 (RangeSlider widget)
Box::new(Trackpad::new().with_label("Trackpad")), // 31 (Trackpad widget)
+ Box::new(Panel::new(0.0, 0.0, 450.0, 200.0).with_label("XDG Desktop Portal FileChooser")), // 32
+ Box::new(Label::new("This page verifies the integration of the XDG Desktop Portal\nFile Chooser in the Clear environment.").with_font_size(12.0).with_color([0xcc, 0xcc, 0xd4])), // 33
+ Box::new(Button::new(0.0, 0.0, 180.0, 40.0).with_label("Open File Dialog")), // 34
+ Box::new(Button::new(0.0, 0.0, 180.0, 40.0).with_label("Save File Dialog")), // 35
]
};
@@ -416,14 +423,16 @@ impl State {
0..=2 => true,
3..=13 | 30 | 31 => self.current_page == Page::Widgets,
14..=29 => self.current_page == Page::Windows,
+ 32..=35 => self.current_page == Page::Xdg,
_ => false,
}
}
fn update_page_title(&mut self) {
let title = match self.current_page {
- Page::Widgets => "Clear Test Suite - Widgets",
- Page::Windows => "Clear Test Suite - Windows",
+ Page::Widgets => "Clear Test Interface - Widgets",
+ Page::Windows => "Clear Test Interface - Windows",
+ Page::Xdg => "Clear Test Interface - XDG Portal",
};
self.label_buffer = make_text_buffer(&mut self.font_system, title, 16.0);
}
@@ -439,6 +448,7 @@ impl State {
0..=2 => true,
3..=13 | 30 | 31 => current_page == Page::Widgets,
14..=29 => current_page == Page::Windows,
+ 32..=35 => current_page == Page::Xdg,
_ => false,
}
};
@@ -533,6 +543,7 @@ impl State {
0..=2 => true,
3..=13 | 30 | 31 => current_page == Page::Widgets,
14..=29 => current_page == Page::Windows,
+ 32..=35 => current_page == Page::Xdg,
_ => false,
}
};
@@ -557,7 +568,7 @@ impl State {
text_viewport.update(queue, viewport);
let scale_f32 = *scale as f32;
- let left_margin = if is_child { 20.0 } else { 170.0 };
+ let left_margin = if is_child { 20.0 } else { 76.0 };
let mut areas: Vec<TextArea> = vec![
TextArea {
@@ -772,6 +783,7 @@ impl State {
0..=2 => true,
3..=13 | 30 | 31 => current_page == Page::Widgets,
14..=29 => current_page == Page::Windows,
+ 32..=35 => current_page == Page::Xdg,
_ => false,
}
};
@@ -797,37 +809,42 @@ fn demo_positions(sw: f32, sh: f32) -> Vec<(f32, f32, f32, f32)> {
(0.0, sh - 28.0, sw, 28.0), // 2 status_bar
// "Widgets" page only
- (170.0, 50.0, 140.0, 40.0), // 3 verify_opacity
- (320.0, 50.0, 140.0, 40.0), // 4 verify_blur
- (470.0, 50.0, 140.0, 40.0), // 5 verify_layout
- (170.0, 120.0, 400.0, 200.0), // 6 panel
- (170.0, 340.0, 140.0, 40.0), // 7 run_diagnostics
- (320.0, 340.0, 140.0, 40.0), // 8 reset
- (170.0, 410.0, 24.0, 24.0), // 9 checkbox
- (280.0, 410.0, 48.0, 24.0), // 10 toggle
- (400.0, 410.0, 160.0, 24.0), // 11 progress_bar
- (170.0, 470.0, 300.0, 24.0), // 12 slider
- (500.0, 470.0, 120.0, 24.0), // 13 spinbox
+ (76.0, 50.0, 140.0, 40.0), // 3 verify_opacity
+ (226.0, 50.0, 140.0, 40.0), // 4 verify_blur
+ (376.0, 50.0, 140.0, 40.0), // 5 verify_layout
+ (76.0, 120.0, 400.0, 200.0), // 6 panel
+ (76.0, 340.0, 140.0, 40.0), // 7 run_diagnostics
+ (226.0, 340.0, 140.0, 40.0), // 8 reset
+ (76.0, 410.0, 24.0, 24.0), // 9 checkbox
+ (186.0, 410.0, 48.0, 24.0), // 10 toggle
+ (306.0, 410.0, 160.0, 24.0), // 11 progress_bar
+ (76.0, 470.0, 300.0, 24.0), // 12 slider
+ (406.0, 470.0, 120.0, 24.0), // 13 spinbox
// "Windows" page only
- (170.0, 100.0, 400.0, 250.0), // 14 Panel (Window area)
- (170.0, 360.0, 140.0, 40.0), // 15 Button: Create Window
- (320.0, 360.0, 140.0, 40.0), // 16 Button: Tile Windows
- (170.0, 410.0, 24.0, 24.0), // 17 Checkbox: Enable Opacity
- (205.0, 415.0, 150.0, 16.0), // 18 Label: "Enable Opacity"
- (170.0, 444.0, 300.0, 32.0), // 19 Slider: Transparency level
- (480.0, 452.0, 150.0, 16.0), // 20 Label: "Transparency level"
- (470.0, 360.0, 175.0, 40.0), // 21 Dropdown: Window type
- (470.0, 410.0, 175.0, 40.0), // 22 Dropdown: Window shape
- (170.0, 490.0, 24.0, 24.0), // 23 Checkbox: Enable Border
- (205.0, 495.0, 150.0, 16.0), // 24 Label: "Enable Border"
- (470.0, 490.0, 175.0, 40.0), // 25 Spinbox: Width
- (470.0, 540.0, 175.0, 40.0), // 26 Spinbox: Height
- (590.0, 100.0, 190.0, 250.0), // 27 Panel: Info background
- (600.0, 110.0, 170.0, 20.0), // 28 Label: Info header
- (600.0, 140.0, 170.0, 200.0), // 29 Label: Info description
- (170.0, 530.0, 300.0, 24.0), // 30 RangeSlider
- (590.0, 120.0, 190.0, 200.0), // 31 Trackpad
+ (76.0, 100.0, 400.0, 250.0), // 14 Panel (Window area)
+ (76.0, 360.0, 140.0, 40.0), // 15 Button: Create Window
+ (226.0, 360.0, 140.0, 40.0), // 16 Button: Tile Windows
+ (76.0, 410.0, 24.0, 24.0), // 17 Checkbox: Enable Opacity
+ (111.0, 415.0, 150.0, 16.0), // 18 Label: "Enable Opacity"
+ (76.0, 444.0, 300.0, 32.0), // 19 Slider: Transparency level
+ (386.0, 452.0, 150.0, 16.0), // 20 Label: "Transparency level"
+ (376.0, 360.0, 175.0, 40.0), // 21 Dropdown: Window type
+ (376.0, 410.0, 175.0, 40.0), // 22 Dropdown: Window shape
+ (76.0, 490.0, 24.0, 24.0), // 23 Checkbox: Enable Border
+ (111.0, 495.0, 150.0, 16.0), // 24 Label: "Enable Border"
+ (376.0, 490.0, 175.0, 40.0), // 25 Spinbox: Width
+ (376.0, 540.0, 175.0, 40.0), // 26 Spinbox: Height
+ (496.0, 100.0, 190.0, 250.0), // 27 Panel: Info background
+ (506.0, 110.0, 170.0, 20.0), // 28 Label: Info header
+ (506.0, 140.0, 170.0, 200.0), // 29 Label: Info description
+ (76.0, 530.0, 300.0, 24.0), // 30 RangeSlider
+ (496.0, 120.0, 190.0, 200.0), // 31 Trackpad
+ // "XDG" page only
+ (76.0, 100.0, 450.0, 200.0), // 32 Panel: XDG Portal background
+ (96.0, 140.0, 410.0, 40.0), // 33 Label: XDG explanation
+ (96.0, 220.0, 180.0, 40.0), // 34 Button: Open File Dialog
+ (296.0, 220.0, 180.0, 40.0), // 35 Button: Save File Dialog
]
}
@@ -868,6 +885,7 @@ struct AppState {
initial_width: f32,
initial_height: f32,
child_shape: Option<String>,
+ sender: calloop::channel::Sender<String>,
}
impl CompositorHandler for AppState {
@@ -1039,6 +1057,7 @@ impl PointerHandler for AppState {
0..=2 => true,
3..=13 | 30 | 31 => current_page == Page::Widgets,
14..=29 => current_page == Page::Windows,
+ 32..=35 => current_page == Page::Xdg,
_ => false,
}
};
@@ -1076,6 +1095,7 @@ impl PointerHandler for AppState {
0..=2 => true,
3..=13 | 30 | 31 => current_page == Page::Widgets,
14..=29 => current_page == Page::Windows,
+ 32..=35 => current_page == Page::Xdg,
_ => false,
}
};
@@ -1148,6 +1168,7 @@ impl PointerHandler for AppState {
0..=2 => true,
3..=13 | 30 | 31 => current_page == Page::Widgets,
14..=29 => current_page == Page::Windows,
+ 32..=35 => current_page == Page::Xdg,
_ => false,
}
};
@@ -1179,10 +1200,14 @@ impl PointerHandler for AppState {
st.current_page = Page::Widgets;
st.update_page_title();
st.update_status_text("Viewing Widgets Page");
- } else {
+ } else if selected == 1 {
st.current_page = Page::Windows;
st.update_page_title();
st.update_status_text("Viewing Windows Page");
+ } else {
+ st.current_page = Page::Xdg;
+ st.update_page_title();
+ st.update_status_text("Viewing XDG Page");
}
st.apply_layout();
st.upload_vertices();
@@ -1358,6 +1383,30 @@ impl PointerHandler for AppState {
st.update_status_text("Window Action: Tile active client windows");
changed = true;
}
+ } else if st.current_page == Page::Xdg {
+ let mut open_file = false;
+ let mut save_file = false;
+ if st.widgets[34].take_click() {
+ open_file = true;
+ } else if st.widgets[35].take_click() {
+ save_file = true;
+ }
+
+ if open_file {
+ st.update_status_text("Opening Open File Dialog...");
+ let sender_clone = self.sender.clone();
+ std::thread::spawn(move || {
+ open_file_dialog_portal(sender_clone);
+ });
+ changed = true;
+ } else if save_file {
+ st.update_status_text("Opening Save File Dialog...");
+ let sender_clone = self.sender.clone();
+ std::thread::spawn(move || {
+ save_file_dialog_portal(sender_clone);
+ });
+ changed = true;
+ }
}
}
}
@@ -1367,7 +1416,23 @@ impl PointerHandler for AppState {
}
}
}
- PointerEventKind::Axis { .. } => {}
+ PointerEventKind::Axis { horizontal, vertical, .. } => {
+ if let Some(st) = &mut self.state {
+ let h_scroll = horizontal.absolute as f32;
+ let v_scroll = vertical.absolute as f32;
+ let delta = clear_ui::widget::MouseScrollDelta::LineDelta(-h_scroll / 10.0, -v_scroll / 10.0);
+ let mut changed = false;
+ for w in &mut st.widgets {
+ if w.mouse_wheel(&delta, st.cursor_x, st.cursor_y) {
+ changed = true;
+ }
+ }
+ if changed {
+ st.upload_vertices();
+ self.redraw = true;
+ }
+ }
+ }
}
}
}
@@ -1591,6 +1656,8 @@ fn main() {
let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
let qh = event_queue.handle();
+ let (sender, channel) = calloop::channel::channel::<String>();
+
let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
let xdg_shell_state = XdgShell::bind(&globals, &qh).unwrap();
let shm_state = Shm::bind(&globals, &qh).unwrap();
@@ -1622,6 +1689,7 @@ fn main() {
initial_width: c_w,
initial_height: c_h,
child_shape: child_shape.clone(),
+ sender,
};
event_queue.roundtrip(&mut app).unwrap();
@@ -1662,8 +1730,8 @@ fn main() {
}
window.set_app_id(&app_id);
} else {
- window.set_title("Clear Test Suite - Diagnostics Dashboard");
- window.set_app_id("clear-test-suite");
+ window.set_title("Clear Test Interface - Diagnostics Dashboard");
+ window.set_app_id("clear-test-interface");
}
if is_child {
window.set_min_size(Some((c_w as u32, c_h as u32)));
@@ -1694,7 +1762,17 @@ fn main() {
let mut event_loop = EventLoop::try_new().unwrap();
let loop_handle = event_loop.handle();
- WaylandSource::new(conn, event_queue).insert(loop_handle).unwrap();
+ WaylandSource::new(conn, event_queue).insert(loop_handle.clone()).unwrap();
+
+ loop_handle.insert_source(channel, |event, _metadata, app_state: &mut AppState| {
+ if let calloop::channel::Event::Msg(msg) = event {
+ if let Some(state) = &mut app_state.state {
+ state.update_status_text(&msg);
+ state.upload_vertices();
+ }
+ app_state.redraw = true;
+ }
+ }).unwrap();
let mut last_tick = std::time::Instant::now();
loop {
@@ -1726,3 +1804,143 @@ fn main() {
}
}
}
+
+fn percent_decode(s: &str) -> String {
+ let mut bytes = Vec::new();
+ let mut chars = s.as_bytes().iter();
+ while let Some(&b) = chars.next() {
+ if b == b'%' {
+ if let (Some(&h), Some(&l)) = (chars.next(), chars.next()) {
+ if let Ok(hex) = String::from_utf8(vec![h, l]) {
+ if let Ok(decoded) = u8::from_str_radix(&hex, 16) {
+ bytes.push(decoded);
+ continue;
+ }
+ }
+ }
+ }
+ bytes.push(b);
+ }
+ String::from_utf8_lossy(&bytes).into_owned()
+}
+
+fn open_file_dialog_portal(sender: calloop::channel::Sender<String>) {
+ let result = (|| -> Result<(), Box<dyn std::error::Error>> {
+ let conn = zbus::blocking::Connection::session()?;
+ let mut options = std::collections::HashMap::new();
+ options.insert("handle_token".to_string(), zbus::zvariant::Value::from("clear_test_open_token"));
+
+ let msg: zbus::Message = conn.call_method(
+ Some("org.freedesktop.portal.Desktop"),
+ "/org/freedesktop/portal/desktop",
+ Some("org.freedesktop.portal.FileChooser"),
+ "OpenFile",
+ &("", "Open File Dialog", &options),
+ )?;
+ let reply: zbus::zvariant::OwnedObjectPath = msg.body().deserialize()?;
+
+ let request_proxy = zbus::blocking::Proxy::new(
+ &conn,
+ "org.freedesktop.portal.Desktop",
+ reply,
+ "org.freedesktop.portal.Request",
+ )?;
+
+ let mut signal_stream = request_proxy.receive_signal("Response")?;
+ if let Some(msg) = signal_stream.next() {
+ let (response_code, results): (u32, std::collections::HashMap<String, zbus::zvariant::OwnedValue>) = msg.body().deserialize()?;
+ if response_code == 0 {
+ if let Some(val) = results.get("uris") {
+ if let Ok(uris) = Vec::<String>::try_from(val.clone()) {
+ let mut file_paths = Vec::new();
+ for uri in uris {
+ let path = uri.trim_start_matches("file://");
+ file_paths.push(percent_decode(path));
+ }
+ if !file_paths.is_empty() {
+ let _ = sender.send(format!("Selected: {}", file_paths.join(", ")));
+ } else {
+ let _ = sender.send("Selected no files".to_string());
+ }
+ } else {
+ let _ = sender.send("Selected success, but failed to parse URIs".to_string());
+ }
+ } else {
+ let _ = sender.send("Selected success, but no uris".to_string());
+ }
+ } else if response_code == 1 {
+ let _ = sender.send("File dialog cancelled by user".to_string());
+ } else {
+ let _ = sender.send(format!("File dialog closed (code {})", response_code));
+ }
+ } else {
+ let _ = sender.send("Request closed without response".to_string());
+ }
+ Ok(())
+ })();
+
+ if let Err(e) = result {
+ let _ = sender.send(format!("Error: {}", e));
+ }
+}
+
+fn save_file_dialog_portal(sender: calloop::channel::Sender<String>) {
+ let result = (|| -> Result<(), Box<dyn std::error::Error>> {
+ let conn = zbus::blocking::Connection::session()?;
+ let mut options = std::collections::HashMap::new();
+ options.insert("handle_token".to_string(), zbus::zvariant::Value::from("clear_test_save_token"));
+
+ let msg: zbus::Message = conn.call_method(
+ Some("org.freedesktop.portal.Desktop"),
+ "/org/freedesktop/portal/desktop",
+ Some("org.freedesktop.portal.FileChooser"),
+ "SaveFile",
+ &("", "Save File Dialog", &options),
+ )?;
+ let reply: zbus::zvariant::OwnedObjectPath = msg.body().deserialize()?;
+
+ let request_proxy = zbus::blocking::Proxy::new(
+ &conn,
+ "org.freedesktop.portal.Desktop",
+ reply,
+ "org.freedesktop.portal.Request",
+ )?;
+
+ let mut signal_stream = request_proxy.receive_signal("Response")?;
+ if let Some(msg) = signal_stream.next() {
+ let (response_code, results): (u32, std::collections::HashMap<String, zbus::zvariant::OwnedValue>) = msg.body().deserialize()?;
+ if response_code == 0 {
+ if let Some(val) = results.get("uris") {
+ if let Ok(uris) = Vec::<String>::try_from(val.clone()) {
+ let mut file_paths = Vec::new();
+ for uri in uris {
+ let path = uri.trim_start_matches("file://");
+ file_paths.push(percent_decode(path));
+ }
+ if !file_paths.is_empty() {
+ let _ = sender.send(format!("Saved to: {}", file_paths.join(", ")));
+ } else {
+ let _ = sender.send("Saved to no file".to_string());
+ }
+ } else {
+ let _ = sender.send("Save success, but failed to parse URIs".to_string());
+ }
+ } else {
+ let _ = sender.send("Save success, but no uris".to_string());
+ }
+ } else if response_code == 1 {
+ let _ = sender.send("Save dialog cancelled by user".to_string());
+ } else {
+ let _ = sender.send(format!("Save dialog closed (code {})", response_code));
+ }
+ } else {
+ let _ = sender.send("Request closed without response".to_string());
+ }
+ Ok(())
+ })();
+
+ if let Err(e) = result {
+ let _ = sender.send(format!("Error: {}", e));
+ }
+}
+