diff --git a/Cargo.lock b/Cargo.lock
index c615b8cb..cb09fabb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -230,6 +230,28 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dbc3a507a82b17ba0d98f6ce8fd6954ea0c8152e98009d36a40d8dcc8ce078a"
+[[package]]
+name = "ashpd"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39"
+dependencies = [
+ "async-fs",
+ "async-net",
+ "enumflags2",
+ "futures-channel",
+ "futures-util",
+ "rand 0.9.2",
+ "raw-window-handle",
+ "serde",
+ "serde_repr",
+ "url",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "zbus",
+]
+
[[package]]
name = "askar-crypto"
version = "0.3.7"
@@ -325,6 +347,18 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002"
+[[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"
@@ -350,6 +384,49 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "async-executor"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5"
+dependencies = [
+ "async-lock",
+ "blocking",
+ "futures-lite",
+]
+
+[[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",
+ "slab",
+ "windows-sys 0.61.1",
+]
+
[[package]]
name = "async-lock"
version = "3.4.1"
@@ -361,12 +438,52 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "async-net"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
+dependencies = [
+ "async-io",
+ "blocking",
+ "futures-lite",
+]
+
[[package]]
name = "async-once-cell"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a"
+[[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",
+]
+
+[[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 2.0.106",
+]
+
[[package]]
name = "async-rx"
version = "0.1.3"
@@ -377,6 +494,24 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "async-signal"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.61.1",
+]
+
[[package]]
name = "async-stream"
version = "0.3.6"
@@ -399,6 +534,12 @@ dependencies = [
"syn 2.0.106",
]
+[[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"
@@ -668,6 +809,19 @@ dependencies = [
"objc2",
]
+[[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 = "bls12_381"
version = "0.8.0"
@@ -714,6 +868,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
[[package]]
name = "bytes"
version = "1.11.1"
@@ -1429,7 +1589,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.1",
]
[[package]]
@@ -1439,6 +1599,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [
"bitflags 2.10.0",
+ "block2",
+ "libc",
"objc2",
]
@@ -1571,6 +1733,33 @@ dependencies = [
"cfg-if",
]
+[[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 2.0.106",
+]
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -1584,7 +1773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.1",
]
[[package]]
@@ -2529,6 +2718,21 @@ dependencies = [
"icu_properties",
]
+[[package]]
+name = "image"
+version = "0.25.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "moxcms",
+ "num-traits",
+ "png 0.18.0",
+ "zune-core 0.5.1",
+ "zune-jpeg",
+]
+
[[package]]
name = "imagesize"
version = "0.12.0"
@@ -2814,7 +3018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
- "windows-targets 0.48.5",
+ "windows-targets 0.53.4",
]
[[package]]
@@ -3010,7 +3214,7 @@ dependencies = [
"makepad-platform",
"makepad-rustybuzz",
"makepad-vector",
- "png",
+ "png 0.17.16",
"sdfer",
"serde",
"ttf-parser",
@@ -3306,7 +3510,7 @@ name = "makepad-zune-png"
version = "0.4.10"
source = "git+https://github.com/makepad/makepad?branch=dev#2898fb1367a95385df79141cd765bf8cf0719b8d"
dependencies = [
- "zune-core",
+ "zune-core 0.4.12",
"zune-inflate",
]
@@ -3697,6 +3901,15 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "mime"
version = "0.3.17"
@@ -3709,6 +3922,16 @@ version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc"
+[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -3736,6 +3959,16 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "moxcms"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
+dependencies = [
+ "num-traits",
+ "pxfm",
+]
+
[[package]]
name = "multihash"
version = "0.19.3"
@@ -3941,6 +4174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
dependencies = [
"bitflags 2.10.0",
+ "block2",
"objc2",
"objc2-foundation",
]
@@ -4069,6 +4303,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+[[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 = "p256"
version = "0.13.2"
@@ -4244,6 +4488,17 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+[[package]]
+name = "piper"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
[[package]]
name = "pkcs1"
version = "0.7.5"
@@ -4284,6 +4539,39 @@ dependencies = [
"miniz_oxide",
]
+[[package]]
+name = "png"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
+dependencies = [
+ "bitflags 2.10.0",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix",
+ "windows-sys 0.61.1",
+]
+
+[[package]]
+name = "pollster"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
+
[[package]]
name = "poly1305"
version = "0.8.0"
@@ -4448,6 +4736,15 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
+[[package]]
+name = "pxfm"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "quick-xml"
version = "0.37.5"
@@ -4602,6 +4899,12 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223"
+[[package]]
+name = "raw-window-handle"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
+
[[package]]
name = "readlock"
version = "0.1.9"
@@ -4758,6 +5061,30 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "rfd"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
+dependencies = [
+ "ashpd",
+ "block2",
+ "dispatch2",
+ "js-sys",
+ "log",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+ "pollster",
+ "raw-window-handle",
+ "urlencoding",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "rgb"
version = "0.8.52"
@@ -4883,6 +5210,7 @@ dependencies = [
"futures-util",
"hashbrown 0.16.1",
"htmlize",
+ "image",
"imbl",
"imghdr",
"indexmap 2.13.0",
@@ -4892,11 +5220,13 @@ dependencies = [
"matrix-sdk",
"matrix-sdk-base",
"matrix-sdk-ui",
+ "mime_guess",
"percent-encoding",
"quinn",
"rand 0.8.5",
"rangemap",
"reqwest",
+ "rfd",
"robius-directories",
"robius-location",
"robius-open",
@@ -5146,7 +5476,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.1",
]
[[package]]
@@ -5477,6 +5807,17 @@ dependencies = [
"serde_core",
]
+[[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 2.0.106",
+]
+
[[package]]
name = "serde_spanned"
version = "1.0.3"
@@ -5993,7 +6334,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.1",
]
[[package]]
@@ -6098,7 +6439,7 @@ dependencies = [
"bytemuck",
"cfg-if",
"log",
- "png",
+ "png 0.17.16",
"tiny-skia-path",
]
@@ -6476,6 +6817,17 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6"
+[[package]]
+name = "uds_windows"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
+dependencies = [
+ "memoffset",
+ "tempfile",
+ "winapi",
+]
+
[[package]]
name = "ulid"
version = "1.2.1"
@@ -6968,15 +7320,37 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39b7d07a236abaef6607536ccfaf19b396dbe3f5110ddb73d39f4562902ed382"
+[[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-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.61.1",
]
+[[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"
version = "0.56.0"
@@ -7524,6 +7898,67 @@ dependencies = [
"synstructure",
]
+[[package]]
+name = "zbus"
+version = "5.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1"
+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",
+ "serde",
+ "serde_repr",
+ "tracing",
+ "uds_windows",
+ "uuid",
+ "windows-sys 0.61.1",
+ "winnow",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "5.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+ "zbus_names",
+ "zvariant",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "4.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f"
+dependencies = [
+ "serde",
+ "winnow",
+ "zvariant",
+]
+
[[package]]
name = "zerocopy"
version = "0.8.27"
@@ -7630,6 +8065,12 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+[[package]]
+name = "zune-core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
+
[[package]]
name = "zune-inflate"
version = "0.2.54"
@@ -7638,3 +8079,53 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.5.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe"
+dependencies = [
+ "zune-core 0.5.1",
+]
+
+[[package]]
+name = "zvariant"
+version = "5.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "url",
+ "winnow",
+ "zvariant_derive",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "5.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn 2.0.106",
+ "winnow",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 5c041614..48886ff8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,10 +44,12 @@ hashbrown = { version = "0.16", features = ["raw-entry"] }
htmlize = "1.0.5"
indexmap = "2.6.0"
imghdr = "0.7.0"
+image = { version = "0.25", default-features = false, features = ["jpeg", "png"] }
linkify = "0.10.0"
matrix-sdk-base = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "main" }
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "main", default-features = false, features = [ "e2e-encryption", "automatic-room-key-forwarding", "markdown", "sqlite", "rustls-tls", "bundled-sqlite", "sso-login" ] }
matrix-sdk-ui = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "main", default-features = false, features = [ "rustls-tls" ] }
+mime_guess = "2.0.5"
## Use the same ruma version as what's specified in matrix-sdk's Cargo.toml.
## Enable a few extra features:
## * "compat-optional" feature to allow missing body field in m.room.tombstone event.
@@ -64,7 +66,7 @@ tokio = { version = "1.43.1", features = ["macros", "rt-multi-thread"] }
tracing-subscriber = "0.3.17"
unicode-segmentation = "1.11.0"
url = "2.5.0"
-
+rfd = { version = "0.15.3", features = ["ashpd", "urlencoding", "xdg-portal"] }
## Dependencies for TSP support.
## Commit "f0bc4625dcd729e07e4a36257df2f1d94c81cef4" is the most recent one without the invalid change to pin serde to 1.0.219.
diff --git a/resources/icons/add_attachment.svg b/resources/icons/add_attachment.svg
new file mode 100644
index 00000000..e378316d
--- /dev/null
+++ b/resources/icons/add_attachment.svg
@@ -0,0 +1,17 @@
+
+
+
\ No newline at end of file
diff --git a/resources/icons/file.svg b/resources/icons/file.svg
new file mode 100644
index 00000000..3a249469
--- /dev/null
+++ b/resources/icons/file.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/app.rs b/src/app.rs
index d3ea88bd..046bc8a1 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -14,7 +14,7 @@ use crate::{
}, login::login_screen::LoginAction, logout::logout_confirm_modal::{LogoutAction, LogoutConfirmModalAction, LogoutConfirmModalWidgetRefExt}, persistence, profile::user_profile_cache::clear_user_profile_cache, room::BasicRoomDetails, shared::{callout_tooltip::{
CalloutTooltipWidgetRefExt,
TooltipAction,
- }, confirmation_modal::{ConfirmationModalContent, ConfirmationModalWidgetRefExt}, image_viewer::{ImageViewerAction, LoadState}, popup_list::{PopupKind, enqueue_popup_notification}}, sliding_sync::{DirectMessageRoomAction, MatrixRequest, current_user_id, submit_async_request}, utils::RoomNameId, verification::VerificationAction, verification_modal::{
+ }, confirmation_modal::{ConfirmationModalContent, ConfirmationModalWidgetRefExt}, file_upload_modal::FilePreviewerAction, image_viewer::{ImageViewerAction, LoadState}, popup_list::{PopupKind, enqueue_popup_notification}}, sliding_sync::{DirectMessageRoomAction, MatrixRequest, current_user_id, submit_async_request}, utils::RoomNameId, verification::VerificationAction, verification_modal::{
VerificationModalAction,
VerificationModalWidgetRefExt,
}
@@ -39,6 +39,7 @@ live_design! {
use crate::home::event_source_modal::EventSourceModal;
use crate::shared::callout_tooltip::CalloutTooltip;
use crate::shared::image_viewer::ImageViewer;
+ use crate::shared::file_upload_modal::FileUploadModal;
use link::tsp_link::TspVerificationModal;
@@ -101,6 +102,13 @@ live_design! {
image_viewer_modal_inner = {}
}
}
+ file_upload_modal = {
+ content: {
+ height: Fill, width: Fill,
+ align: {x: 0.5, y: 0.5},
+ file_upload_modal_inner = {}
+ }
+ }
// Context menus should be shown in front of other UI elements,
// but behind verification modals.
@@ -508,6 +516,17 @@ impl MatchEvent for App {
}
_ => {}
}
+ match action.downcast_ref() {
+ Some(FilePreviewerAction::Show(_)) => {
+ self.ui.modal(ids!(file_upload_modal)).open(cx);
+ continue;
+ }
+ Some(FilePreviewerAction::Hide) => {
+ self.ui.modal(ids!(file_upload_modal)).close(cx);
+ continue;
+ }
+ _ => {}
+ }
// Handle actions to open/close the TSP verification modal.
#[cfg(feature = "tsp")] {
use std::ops::Deref;
diff --git a/src/home/mod.rs b/src/home/mod.rs
index 8f8ea8e9..4e6eabad 100644
--- a/src/home/mod.rs
+++ b/src/home/mod.rs
@@ -29,6 +29,8 @@ pub mod new_message_context_menu;
pub mod room_context_menu;
pub mod link_preview;
pub mod room_image_viewer;
+pub mod thumbnail_loading;
+pub mod upload_progress;
pub fn live_design(cx: &mut Cx) {
search_messages::live_design(cx);
@@ -59,4 +61,6 @@ pub fn live_design(cx: &mut Cx) {
light_themed_dock::live_design(cx);
event_reaction_list::live_design(cx);
link_preview::live_design(cx);
+ thumbnail_loading::live_design(cx);
+ upload_progress::live_design(cx);
}
diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs
index 35205143..3cd64840 100644
--- a/src/home/room_screen.rs
+++ b/src/home/room_screen.rs
@@ -957,6 +957,7 @@ impl Widget for RoomScreen {
timeline_kind: tl.kind.clone(),
room_members,
room_avatar_url,
+ timeline_update_sender: Some(tl.update_sender.clone()),
}
} else if let Some(room_name) = &self.room_name_id {
// Fallback case: we have a room_name but no tl_state yet
@@ -967,6 +968,7 @@ impl Widget for RoomScreen {
.expect("BUG: room_name_id was set but timeline_kind was missing"),
room_members: None,
room_avatar_url: None,
+ timeline_update_sender: None,
}
} else {
// No room selected yet, skip event handling that requires room context
@@ -982,6 +984,7 @@ impl Widget for RoomScreen {
timeline_kind: TimelineKind::MainRoom { room_id },
room_members: None,
room_avatar_url: None,
+ timeline_update_sender: None,
}
};
let mut room_scope = Scope::with_props(&room_props);
@@ -1617,6 +1620,32 @@ impl RoomScreen {
tl.tombstone_info = Some(successor_room_details);
}
TimelineUpdate::LinkPreviewFetched => {}
+ TimelineUpdate::FileUploadConfirmed(file_data) => {
+ // Handle file upload confirmation from the file upload modal.
+ // This ensures the upload is associated with the correct timeline.
+ let room_input_bar = self.view.room_input_bar(ids!(room_input_bar));
+ if let Some(replied_to) = room_input_bar.handle_file_upload_confirmed(cx) {
+ submit_async_request(MatrixRequest::SendAttachment {
+ timeline_kind: tl.kind.clone(),
+ file_data,
+ replied_to,
+ #[cfg(feature = "tsp")]
+ sign_with_tsp: room_input_bar.is_tsp_signing_enabled(cx),
+ });
+ }
+ }
+ TimelineUpdate::FileUploadUpdate { current, total } => {
+ let room_input_bar = self.view.room_input_bar(ids!(room_input_bar));
+ room_input_bar.set_progress_value(cx, current, total);
+ }
+ TimelineUpdate::FileUploadAbortHandle(handle) => {
+ let room_input_bar = self.view.room_input_bar(ids!(room_input_bar));
+ room_input_bar.set_abort_handle(handle);
+ }
+ TimelineUpdate::FileUploadError { error, file_data } => {
+ let room_input_bar = self.view.room_input_bar(ids!(room_input_bar));
+ room_input_bar.handle_upload_error(cx, error, file_data);
+ }
}
}
@@ -2274,6 +2303,7 @@ impl RoomScreen {
content_drawn_since_last_update: RangeSet::new(),
profile_drawn_since_last_update: RangeSet::new(),
update_receiver,
+ update_sender: update_sender.clone(),
request_sender,
media_cache: MediaCache::new(Some(update_sender.clone())),
link_preview_cache: LinkPreviewCache::new(Some(update_sender)),
@@ -2631,6 +2661,10 @@ pub struct RoomScreenProps {
pub timeline_kind: TimelineKind,
pub room_members: Option>>,
pub room_avatar_url: Option,
+ /// The sender for timeline updates, used for operations like file uploads
+ /// that need to notify this specific timeline.
+ /// This is `None` in fallback cases where the timeline state isn't fully loaded yet.
+ pub timeline_update_sender: Option>,
}
@@ -2759,6 +2793,25 @@ pub enum TimelineUpdate {
Tombstoned(SuccessorRoomDetails),
/// A notice that link preview data for a URL has been fetched and is now available.
LinkPreviewFetched,
+ /// A file upload was confirmed from the file upload modal.
+ FileUploadConfirmed(crate::shared::file_upload_modal::FileData),
+ /// An update on the progress of a file upload that is currently in-flight.
+ FileUploadUpdate {
+ /// Current progress value (e.g., bytes uploaded).
+ current: u64,
+ /// Total value to reach (e.g., total file size in bytes).
+ total: u64,
+ },
+ /// A file upload failed with an error.
+ /// Includes the file data so the user can retry the upload.
+ FileUploadError {
+ /// The error message describing why the upload failed.
+ error: String,
+ /// The file data that can be used to retry the upload.
+ file_data: crate::shared::file_upload_modal::FileData,
+ },
+ /// A notice that a file upload in this timeline was aborted.
+ FileUploadAbortHandle(tokio::task::AbortHandle),
}
thread_local! {
@@ -2820,6 +2873,10 @@ struct TimelineUiState {
/// which is okay because a sender on an unbounded channel never needs to block.
update_receiver: crossbeam_channel::Receiver,
+ /// The channel sender for timeline updates for this room.
+ /// This is passed to child widgets that need to send updates to this timeline.
+ update_sender: crossbeam_channel::Sender,
+
/// The sender for timeline requests from a RoomScreen showing this room
/// to the background async task that handles this room's timeline updates.
request_sender: TimelineRequestSender,
diff --git a/src/home/thumbnail_loading.rs b/src/home/thumbnail_loading.rs
new file mode 100644
index 00000000..7caaef80
--- /dev/null
+++ b/src/home/thumbnail_loading.rs
@@ -0,0 +1,40 @@
+//! A simple loading view displayed while generating thumbnails for file uploads.
+
+use makepad_widgets::*;
+
+live_design! {
+ use link::theme::*;
+ use link::shaders::*;
+ use link::widgets::*;
+ use crate::shared::styles::*;
+
+ // A view that displays a loading spinner and text while generating thumbnails.
+ pub ThumbnailLoadingView = {
+ visible: false,
+ width: Fill,
+ height: Fit,
+ padding: {top: 8, bottom: 8, left: 10, right: 10}
+ flow: Right,
+ spacing: 10,
+ align: {y: 0.5}
+
+ loading_spinner = {
+ width: 25,
+ height: 25,
+ draw_bg: {
+ color: (COLOR_ACTIVE_PRIMARY)
+ border_size: 3.0,
+ }
+ }
+
+ loading_text =