diff --git a/Cargo.lock b/Cargo.lock index aa0a277..b6d93ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,6 +213,16 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002" +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + [[package]] name = "async-channel" version = "2.2.0" @@ -221,11 +231,142 @@ checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", "event-listener 5.2.0", - "event-listener-strategy", + "event-listener-strategy 0.5.0", "futures-core", "pin-project-lite", ] +[[package]] +name = "async-executor" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +dependencies = [ + "async-lock 3.3.0", + "async-task", + "concurrent-queue", + "fastrand 2.0.1", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.24", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock 3.3.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.5.0", + "rustix 0.38.32", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.32", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "async-signal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +dependencies = [ + "async-io 2.3.2", + "async-lock 2.8.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.32", + "signal-hook-registry", + "slab", + "windows-sys 0.48.0", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -248,6 +389,12 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "async-task" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" + [[package]] name = "async-trait" version = "0.1.73" @@ -259,6 +406,12 @@ dependencies = [ "syn 2.0.52", ] +[[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.1.0" @@ -391,6 +544,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +dependencies = [ + "async-channel", + "async-lock 3.3.0", + "async-task", + "fastrand 2.0.1", + "futures-io", + "futures-lite 2.3.0", + "piper", + "tracing", +] + [[package]] name = "bs58" version = "0.5.0" @@ -911,6 +1080,17 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -962,6 +1142,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.3.7" @@ -973,6 +1163,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "displaydoc" version = "0.2.4" @@ -1062,6 +1263,27 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +[[package]] +name = "enumflags2" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1070,23 +1292,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -1099,6 +1310,23 @@ dependencies = [ "str-buf", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "event-listener" version = "4.0.3" @@ -1121,6 +1349,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + [[package]] name = "event-listener-strategy" version = "0.5.0" @@ -1205,6 +1443,15 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -1349,6 +1596,34 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.0.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.28" @@ -1557,6 +1832,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.4" @@ -1709,6 +1990,7 @@ dependencies = [ "mime_guess", "modalkit", "modalkit-ratatui", + "notify-rust", "open", "pretty_assertions", "rand", @@ -1742,7 +2024,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows 0.48.0", ] [[package]] @@ -1930,7 +2212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.17", + "rustix 0.38.32", "windows-sys 0.48.0", ] @@ -2092,9 +2374,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -2118,6 +2400,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mac-notification-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51fca4d74ff9dbaac16a01b924bc3693fa2bba0862c2c633abc73f9a8ea21f64" +dependencies = [ + "cc", + "dirs-next", + "objc-foundation", + "objc_id", + "time", +] + [[package]] name = "macroific" version = "1.3.1" @@ -2611,6 +2906,19 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify-rust" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "827c5edfa80235ded4ab3fe8e9dc619b4f866ef16fe9b1c6b8a7f8692c0f2226" +dependencies = [ + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2796,6 +3104,16 @@ dependencies = [ "vcpkg", ] +[[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 = "overload" version = "0.1.1" @@ -2952,6 +3270,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + [[package]] name = "pkcs7" version = "0.4.1" @@ -2994,7 +3323,7 @@ dependencies = [ "base64", "indexmap 1.9.3", "line-wrap", - "quick-xml", + "quick-xml 0.29.0", "serde", "time", ] @@ -3012,6 +3341,36 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +dependencies = [ + "cfg-if", + "concurrent-queue", + "pin-project-lite", + "rustix 0.38.32", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -3151,6 +3510,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.35" @@ -3238,7 +3606,7 @@ dependencies = [ "image", "rand", "ratatui", - "rustix 0.38.17", + "rustix 0.38.32", "serde", ] @@ -3611,15 +3979,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.17" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys 0.4.8", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.13", + "windows-sys 0.52.0", ] [[package]] @@ -3798,6 +4166,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "serde_spanned" version = "0.6.5" @@ -3819,6 +4198,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -3987,6 +4377,12 @@ dependencies = [ "der", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "str-buf" version = "1.0.6" @@ -4124,6 +4520,16 @@ dependencies = [ "libc", ] +[[package]] +name = "tauri-winrt-notification" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006851c9ccefa3c38a7646b8cec804bb429def3da10497bfa977179869c3e8e2" +dependencies = [ + "quick-xml 0.30.0", + "windows 0.51.1", +] + [[package]] name = "temp-dir" version = "0.1.12" @@ -4137,9 +4543,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.0.1", "redox_syscall 0.3.5", - "rustix 0.38.17", + "rustix 0.38.32", "windows-sys 0.48.0", ] @@ -4508,6 +4914,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 0.9.0", + "tempfile", + "winapi", +] + [[package]] name = "ulid" version = "1.1.2" @@ -4687,6 +5104,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "walkdir" version = "2.4.0" @@ -4832,7 +5255,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.17", + "rustix 0.38.32", ] [[package]] @@ -4890,6 +5313,25 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -5149,6 +5591,16 @@ version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" +[[package]] +name = "xdg-home" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e5a325c3cb8398ad6cf859c1135b25dd29e186679cf2da7581d9679f63b38e" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "xml5ever" version = "0.17.0" @@ -5175,6 +5627,72 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.7.32" @@ -5223,3 +5741,41 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/Cargo.toml b/Cargo.toml index 545884e..da68a57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ libc = "0.2" markup5ever_rcdom = "0.2.0" mime = "^0.3.16" mime_guess = "^2.0.4" +notify-rust = { version = "4.10.0", default-features = false, features = ["zbus", "serde"] } open = "3.2.0" rand = "0.8.5" ratatui = "0.23" diff --git a/docs/iamb.5.md b/docs/iamb.5.md index 7c52a3c..e2c6e73 100644 --- a/docs/iamb.5.md +++ b/docs/iamb.5.md @@ -85,6 +85,16 @@ overridden as described in *PROFILES*. **message_user_color** (type: boolean) > Defines whether or not the message body is colored like the username. +**notifications** (type: notifications object) +> Configures push-notifications, which are delivered as desktop +> notifications if available. +> *enabled* `true` to enable the feature, defaults to `false`. +> *show_message* to show the message in the desktop notification. Defaults +> to `true`. Messages are truncated beyond a small length. +> The notification _rules_ are stored server side, loaded once at startup, +> and are currently not configurable in iamb. In other words, you can +> simply change the rules with another client. + **image_preview** (type: image_preview object) > Enable image previews and configure it. An empty object will enable the > feature with default settings, omitting it will disable the feature. diff --git a/src/base.rs b/src/base.rs index 370a42c..3636a54 100644 --- a/src/base.rs +++ b/src/base.rs @@ -761,6 +761,9 @@ pub struct RoomInfo { /// The display names for users in this room. pub display_names: HashMap, + + /// The last time the room was rendered, used to detect if it is currently open. + pub draw_last: Option, } impl RoomInfo { @@ -1192,6 +1195,9 @@ pub struct ChatStore { /// Image preview "protocol" picker. pub picker: Option, + + /// Last draw time, used to match with RoomInfo's draw_last. + pub draw_curr: Option, } impl ChatStore { @@ -1212,6 +1218,7 @@ impl ChatStore { verifications: Default::default(), need_load: Default::default(), sync_info: Default::default(), + draw_curr: None, } } diff --git a/src/config.rs b/src/config.rs index ca3da65..ec634a5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -391,6 +391,12 @@ pub enum UserDisplayStyle { DisplayName, } +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] +pub struct Notifications { + pub enabled: bool, + pub show_message: Option, +} + #[derive(Clone)] pub struct ImagePreviewValues { pub size: ImagePreviewSize, @@ -476,6 +482,7 @@ pub struct TunableValues { pub message_user_color: bool, pub default_room: Option, pub open_command: Option>, + pub notifications: Notifications, pub image_preview: Option, } @@ -496,6 +503,7 @@ pub struct Tunables { pub message_user_color: Option, pub default_room: Option, pub open_command: Option>, + pub notifications: Option, pub image_preview: Option, } @@ -518,6 +526,7 @@ impl Tunables { message_user_color: self.message_user_color.or(other.message_user_color), default_room: self.default_room.or(other.default_room), open_command: self.open_command.or(other.open_command), + notifications: self.notifications.or(other.notifications), image_preview: self.image_preview.or(other.image_preview), } } @@ -538,6 +547,7 @@ impl Tunables { message_user_color: self.message_user_color.unwrap_or(false), default_room: self.default_room, open_command: self.open_command, + notifications: self.notifications.unwrap_or_default(), image_preview: self.image_preview.map(ImagePreview::values), } } diff --git a/src/main.rs b/src/main.rs index d80ef44..34fd319 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,7 @@ use std::ops::DerefMut; use std::process; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, Instant}; use clap::Parser; use matrix_sdk::crypto::encrypt_room_key_export; @@ -66,6 +66,7 @@ mod commands; mod config; mod keybindings; mod message; +mod notifications; mod preview; mod sled_export; mod util; @@ -305,6 +306,7 @@ impl Application { // Don't show terminal cursor when we show a dialog. let hide_cursor = !dialogstr.is_empty(); + store.application.draw_curr = Some(Instant::now()); let screen = Screen::new(store) .show_dialog(dialogstr) .show_mode(modestr) diff --git a/src/notifications.rs b/src/notifications.rs new file mode 100644 index 0000000..ea63c3b --- /dev/null +++ b/src/notifications.rs @@ -0,0 +1,221 @@ +use std::time::SystemTime; + +use matrix_sdk::{ + notification_settings::{IsEncrypted, IsOneToOne, NotificationSettings, RoomNotificationMode}, + room::Room as MatrixRoom, + ruma::{ + api::client::push::get_notifications::v3::Notification, + events::{room::message::MessageType, AnyMessageLikeEventContent, AnySyncTimelineEvent}, + MilliSecondsSinceUnixEpoch, + RoomId, + }, + Client, +}; +use unicode_segmentation::UnicodeSegmentation; + +use crate::{ + base::{AsyncProgramStore, IambError, IambResult}, + config::ApplicationSettings, +}; + +pub async fn register_notifications( + client: &Client, + settings: &ApplicationSettings, + store: &AsyncProgramStore, +) { + if !settings.tunables.notifications.enabled { + return; + } + let show_message = settings.tunables.notifications.show_message; + let server_settings = client.notification_settings().await; + let Some(startup_ts) = MilliSecondsSinceUnixEpoch::from_system_time(SystemTime::now()) else { + return; + }; + + let store = store.clone(); + client + .register_notification_handler(move |notification, room: MatrixRoom, client: Client| { + let store = store.clone(); + let server_settings = server_settings.clone(); + async move { + let mode = global_or_room_mode(&server_settings, &room).await; + if mode == RoomNotificationMode::Mute { + return; + } + + if is_open(&store, room.room_id()).await { + return; + } + + match parse_notification(notification, room).await { + Ok((summary, body, server_ts)) => { + if server_ts < startup_ts { + return; + } + + let mut desktop_notification = notify_rust::Notification::new(); + desktop_notification + .summary(&summary) + .appname("iamb") + .timeout(notify_rust::Timeout::Milliseconds(3000)) + .action("default", "default"); + + if is_missing_mention(&body, mode, &client) { + return; + } + if show_message != Some(false) { + if let Some(body) = body { + desktop_notification.body(&body); + } + } + if let Err(err) = desktop_notification.show() { + tracing::error!("Failed to send notification: {err}") + } + }, + Err(err) => { + tracing::error!("Failed to extract notification data: {err}") + }, + } + } + }) + .await; +} + +async fn global_or_room_mode( + settings: &NotificationSettings, + room: &MatrixRoom, +) -> RoomNotificationMode { + let room_mode = settings.get_user_defined_room_notification_mode(room.room_id()).await; + if let Some(mode) = room_mode { + return mode; + } + let is_one_to_one = match room.is_direct().await { + Ok(true) => IsOneToOne::Yes, + _ => IsOneToOne::No, + }; + let is_encrypted = match room.is_encrypted().await { + Ok(true) => IsEncrypted::Yes, + _ => IsEncrypted::No, + }; + settings + .get_default_room_notification_mode(is_encrypted, is_one_to_one) + .await +} + +fn is_missing_mention(body: &Option, mode: RoomNotificationMode, client: &Client) -> bool { + if let Some(body) = body { + if mode == RoomNotificationMode::MentionsAndKeywordsOnly { + let mentioned = match client.user_id() { + Some(user_id) => body.contains(user_id.localpart()), + _ => false, + }; + return !mentioned; + } + } + false +} + +async fn is_open(store: &AsyncProgramStore, room_id: &RoomId) -> bool { + let mut locked = store.lock().await; + if let Some(draw_curr) = locked.application.draw_curr { + let info = locked.application.get_room_info(room_id.to_owned()); + if let Some(draw_last) = info.draw_last { + return draw_last == draw_curr; + } + } + false +} + +pub async fn parse_notification( + notification: Notification, + room: MatrixRoom, +) -> IambResult<(String, Option, MilliSecondsSinceUnixEpoch)> { + let event = notification.event.deserialize().map_err(IambError::from)?; + + let server_ts = event.origin_server_ts(); + + let sender_id = event.sender(); + let sender = room.get_member_no_sync(sender_id).await.map_err(IambError::from)?; + + let sender_name = sender + .as_ref() + .and_then(|m| m.display_name()) + .unwrap_or_else(|| sender_id.localpart()); + + let body = event_notification_body( + &event, + sender_name, + room.is_direct().await.map_err(IambError::from)?, + ) + .map(truncate); + return Ok((sender_name.to_string(), body, server_ts)); +} + +pub fn event_notification_body( + event: &AnySyncTimelineEvent, + sender_name: &str, + is_direct: bool, +) -> Option { + let AnySyncTimelineEvent::MessageLike(event) = event else { + return None; + }; + + match event.original_content()? { + AnyMessageLikeEventContent::RoomMessage(message) => { + let body = match message.msgtype { + MessageType::Audio(_) => { + format!("{sender_name} sent an audio file.") + }, + MessageType::Emote(content) => { + let message = &content.body; + format!("{sender_name}: {message}") + }, + MessageType::File(_) => { + format!("{sender_name} sent a file.") + }, + MessageType::Image(_) => { + format!("{sender_name} sent an image.") + }, + MessageType::Location(_) => { + format!("{sender_name} sent their location.") + }, + MessageType::Notice(content) => { + let message = &content.body; + format!("{sender_name}: {message}") + }, + MessageType::ServerNotice(content) => { + let message = &content.body; + format!("{sender_name}: {message}") + }, + MessageType::Text(content) => { + if is_direct { + content.body + } else { + let message = &content.body; + format!("{sender_name}: {message}") + } + }, + MessageType::Video(_) => { + format!("{sender_name} sent a video.") + }, + MessageType::VerificationRequest(_) => { + format!("{sender_name} sent a verification request.") + }, + _ => unimplemented!(), + }; + Some(body) + }, + AnyMessageLikeEventContent::Sticker(_) => Some(format!("{sender_name} sent a sticker.")), + _ => None, + } +} + +fn truncate(s: String) -> String { + static MAX_LENGTH: usize = 100; + if s.graphemes(true).count() > MAX_LENGTH { + let truncated: String = s.graphemes(true).take(MAX_LENGTH).collect(); + truncated + "..." + } else { + s + } +} diff --git a/src/tests.rs b/src/tests.rs index f4ff074..2a14ad3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -27,6 +27,7 @@ use crate::{ user_style_from_color, ApplicationSettings, DirectoryValues, + Notifications, ProfileConfig, SortOverrides, TunableValues, @@ -164,6 +165,7 @@ pub fn mock_room() -> RoomInfo { fetch_last: None, users_typing: None, display_names: HashMap::new(), + draw_last: None, } } @@ -198,6 +200,7 @@ pub fn mock_tunables() -> TunableValues { open_command: None, username_display: UserDisplayStyle::Username, message_user_color: false, + notifications: Notifications { enabled: false, show_message: None }, image_preview: None, } } diff --git a/src/windows/room/scrollback.rs b/src/windows/room/scrollback.rs index b423e49..9ca1978 100644 --- a/src/windows/room/scrollback.rs +++ b/src/windows/room/scrollback.rs @@ -1437,6 +1437,8 @@ impl<'a> StatefulWidget for Scrollback<'a> { .need_load .insert(state.room_id.to_owned(), Need::MESSAGES); } + + info.draw_last = self.store.application.draw_curr; } } diff --git a/src/worker.rs b/src/worker.rs index 7ffa13f..274f7a4 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -86,6 +86,7 @@ use modalkit::errors::UIError; use modalkit::prelude::{EditInfo, InfoMessage}; use crate::base::Need; +use crate::notifications::register_notifications; use crate::{ base::{ AsyncProgramStore, @@ -1242,12 +1243,14 @@ impl ClientWorker { self.load_handle = tokio::spawn({ let client = self.client.clone(); + let settings = self.settings.clone(); async move { let load = load_older_forever(&client, &store); let rcpt = send_receipts_forever(&client, &store); let room = refresh_rooms_forever(&client, &store); - let ((), (), ()) = tokio::join!(load, rcpt, room); + let notifications = register_notifications(&client, &settings, &store); + let ((), (), (), ()) = tokio::join!(load, rcpt, room, notifications); } }) .into();