ashpd-0.13.11/.cargo_vcs_info.json0000644000000001441046102023000123170ustar { "git": { "sha1": "3ee41bb02b79eeccddc5d86ae52998d8225d1f6b" }, "path_in_vcs": "client" }ashpd-0.13.11/Cargo.lock0000644000001475551046102023000103140ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "annotate-snippets" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ "anstyle", "unicode-width", ] [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "ashpd" version = "0.13.11" dependencies = [ "async-trait", "enumflags2", "futures-channel", "futures-util", "gdk4-wayland", "gdk4-x11", "getrandom", "glib", "gstreamer", "gtk4", "pipewire", "quick-xml", "raw-window-handle", "reis", "serde", "serde_json", "serde_repr", "tokio", "tracing", "wayland-backend", "wayland-client", "wayland-protocols", "zbus", ] [[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", "slab", "windows-sys", ] [[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", ] [[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.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", ] [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bindgen" version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "annotate-snippets", "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", ] [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cairo-rs" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" dependencies = [ "bitflags", "cairo-sys-rs", "glib", "libc", ] [[package]] name = "cairo-sys-rs" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54" dependencies = [ "glib-sys", "libc", "system-deps", ] [[package]] name = "cc" version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom 7.1.3", ] [[package]] name = "cfg-expr" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clang-sys" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "convert_case" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ "unicode-segmentation", ] [[package]] name = "cookie-factory" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "dlib" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ "libloading", ] [[package]] name = "downcast-rs" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys", ] [[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.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "field-offset" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", "rustc_version", ] [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "futures" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[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-macro" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "slab", ] [[package]] name = "gdk-pixbuf" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25f420376dbee041b2db374ce4573892a36222bb3f6c0c43e24f0d67eae9b646" dependencies = [ "gdk-pixbuf-sys", "gio", "glib", "libc", ] [[package]] name = "gdk-pixbuf-sys" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f31b37b1fc4b48b54f6b91b7ef04c18e00b4585d98359dd7b998774bbd91fb" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", "system-deps", ] [[package]] name = "gdk4" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa528049fd8726974a7aa1a6e1421f891e7579bea6cc6d54056ab4d1a1b937e7" dependencies = [ "cairo-rs", "gdk-pixbuf", "gdk4-sys", "gio", "glib", "libc", "pango", ] [[package]] name = "gdk4-sys" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dd48b1b03dce78ab52805ac35cfb69c48af71a03af5723231d8583718738377" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gio-sys", "glib-sys", "gobject-sys", "libc", "pango-sys", "pkg-config", "system-deps", ] [[package]] name = "gdk4-wayland" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7027433b8f19075ab22f01d0e0ba4373e303ffdb4ce4f846a2763cb020ea9bdc" dependencies = [ "gdk4", "gdk4-wayland-sys", "gio", "glib", "libc", ] [[package]] name = "gdk4-wayland-sys" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "653cafb8715f2ac1c56edaf8060d9360ae3acbe3c6fb61b676a2917d42b668cf" dependencies = [ "glib-sys", "libc", "system-deps", ] [[package]] name = "gdk4-x11" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13a00e4c617d6570633e44ba3ff221350669edf6cf3447a40f882dfb6f1dfdc3" dependencies = [ "gdk4", "gdk4-x11-sys", "gio", "glib", "libc", "x11-dl", ] [[package]] name = "gdk4-x11-sys" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d41f2e28f62378b081288914325d73bddbb6ccbbadb41aa6392afc71d27002" dependencies = [ "gdk4-sys", "glib-sys", "libc", "system-deps", ] [[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 = "gio" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "816b6743c46b217aa8fba679095ac6f2162fd53259dc8f186fcdbff9c555db03" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-util", "gio-sys", "glib", "libc", "pin-project-lite", "smallvec", ] [[package]] name = "gio-sys" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", "windows-sys", ] [[package]] name = "glib" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "039f93465ac17e6cb02d16f16572cd3e43a77e736d5ecc461e71b9c9c5c0569c" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", "futures-util", "gio-sys", "glib-macros", "glib-sys", "gobject-sys", "libc", "memchr", "smallvec", ] [[package]] name = "glib-macros" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda575994e3689b1bc12f89c3df621ead46ff292623b76b4710a3a5b79be54bb" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "glib-sys" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1eb23a616a3dbc7fc15bbd26f58756ff0b04c8a894df3f0680cd21011db6a642" dependencies = [ "libc", "system-deps", ] [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gobject-sys" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18eda93f09d3778f38255b231b17ef67195013a592c91624a4daf8bead875565" dependencies = [ "glib-sys", "libc", "system-deps", ] [[package]] name = "graphene-rs" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1" dependencies = [ "glib", "graphene-sys", "libc", ] [[package]] name = "graphene-sys" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c" dependencies = [ "glib-sys", "libc", "pkg-config", "system-deps", ] [[package]] name = "gsk4" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62" dependencies = [ "cairo-rs", "gdk4", "glib", "graphene-rs", "gsk4-sys", "libc", "pango", ] [[package]] name = "gsk4-sys" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a" dependencies = [ "cairo-sys-rs", "gdk4-sys", "glib-sys", "gobject-sys", "graphene-sys", "libc", "pango-sys", "system-deps", ] [[package]] name = "gstreamer" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad8d6d77fae07708536f86a34ac15fc36c7e958ef7930b75119dba0242936f08" dependencies = [ "cfg-if", "futures-channel", "futures-core", "futures-util", "glib", "gstreamer-sys", "itertools 0.14.0", "kstring", "libc", "muldiv", "num-integer", "num-rational", "option-operations", "pastey", "pin-project-lite", "smallvec", "thiserror", ] [[package]] name = "gstreamer-sys" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85d09343b4c23d64b3ef35f1f644598860cc9a4617e7ccded141de97cd528608" dependencies = [ "cfg-if", "glib-sys", "gobject-sys", "libc", "system-deps", ] [[package]] name = "gtk4" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f671029e3f5288fd35e03a6e6b19e1ce643b10a3d261d33d183e453f6c52fe" dependencies = [ "cairo-rs", "field-offset", "futures-channel", "gdk-pixbuf", "gdk4", "gio", "glib", "graphene-rs", "gsk4", "gtk4-macros", "gtk4-sys", "libc", "pango", ] [[package]] name = "gtk4-macros" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "gtk4-sys" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0786e7e8e0550d0ab2df4d0d90032f22033e07d5ed78b6a1b2e51b05340339e" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gdk4-sys", "gio-sys", "glib-sys", "gobject-sys", "graphene-sys", "gsk4-sys", "libc", "pango-sys", "system-deps", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" 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 = "id-arena" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "indexmap" version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", "serde", "serde_core", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "kstring" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" dependencies = [ "static_assertions", ] [[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.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", "windows-link", ] [[package]] name = "libspa" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4" dependencies = [ "bitflags", "cc", "convert_case", "cookie-factory", "libc", "libspa-sys", "nix", "nom 8.0.0", "system-deps", ] [[package]] name = "libspa-sys" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "901049455d2eb6decf9058235d745237952f4804bc584c5fcb41412e6adcc6e0" dependencies = [ "bindgen", "cc", "system-deps", ] [[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", "windows-sys", ] [[package]] name = "muldiv" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" [[package]] name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "nom" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ "memchr", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "option-operations" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aca39cf52b03268400c16eeb9b56382ea3c3353409309b63f5c8f0b1faf42754" dependencies = [ "pastey", ] [[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 = "pango" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25d8f224eddef627b896d2f7b05725b3faedbd140e0e8343446f0d34f34238ee" dependencies = [ "gio", "glib", "libc", "pango-sys", ] [[package]] name = "pango-sys" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", ] [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "pastey" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" [[package]] name = "pin-project-lite" 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 = "pipewire" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44" dependencies = [ "anyhow", "bitflags", "libc", "libspa", "libspa-sys", "nix", "once_cell", "pipewire-sys", "thiserror", ] [[package]] name = "pipewire-sys" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb028afee0d6ca17020b090e3b8fa2d7de23305aef975c7e5192a5050246ea36" dependencies = [ "bindgen", "libspa-sys", "system-deps", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[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", ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", ] [[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" 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 = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reis" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aee09758db35e73eb1398c6ef321973ce7f4aad86573ba08c1c8ef98d350b46" dependencies = [ "enumflags2", "futures", "log", "rustix", "tokio", ] [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 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 = "serde_spanned" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ "errno", "libc", ] [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", "windows-sys", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "7.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[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", "windows-sys", ] [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio" version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "tracing", "windows-sys", ] [[package]] name = "tokio-macros" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "toml" version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow 0.7.15", ] [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_datetime" version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" version = "0.25.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ "indexmap", "toml_datetime 1.0.1+spec-1.1.0", "toml_parser", "winnow 1.0.0", ] [[package]] name = "toml_parser" version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ "winnow 1.0.0", ] [[package]] name = "toml_writer" version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" [[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 = "uds_windows" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", "windows-sys", ] [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uuid" version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "js-sys", "serde_core", "wasm-bindgen", ] [[package]] name = "version-compare" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" [[package]] name = "wasi" 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.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[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", ] [[package]] name = "wasm-bindgen" version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" 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", "hashbrown 0.15.5", "indexmap", "semver", ] [[package]] name = "wayland-backend" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406" dependencies = [ "cc", "downcast-rs", "rustix", "scoped-tls", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" version = "0.31.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3" dependencies = [ "bitflags", "rustix", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" version = "0.32.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7" dependencies = [ "bitflags", "wayland-backend", "wayland-client", "wayland-scanner", ] [[package]] name = "wayland-scanner" version = "0.31.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3" dependencies = [ "proc-macro2", "quick-xml", "quote", ] [[package]] name = "wayland-sys" version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17" dependencies = [ "dlib", "log", "pkg-config", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] [[package]] name = "winnow" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" 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-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", "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 = "x11-dl" version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ "libc", "once_cell", "pkg-config", ] [[package]] name = "zbus" version = "5.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" 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", "tokio", "tracing", "uds_windows", "uuid", "windows-sys", "winnow 0.7.15", "zbus_macros", "zbus_names", "zvariant", ] [[package]] name = "zbus_macros" version = "5.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", "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 0.7.15", "zvariant", ] [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zvariant" version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" dependencies = [ "endi", "enumflags2", "serde", "winnow 0.7.15", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", "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", "winnow 0.7.15", ] ashpd-0.13.11/Cargo.toml0000644000000126751046102023000103310ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" rust-version = "1.87" name = "ashpd" version = "0.13.11" authors = ["Bilal Elmoussaoui "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "XDG portals wrapper in Rust using zbus" readme = "README.md" keywords = [ "portal", "flatpak", "xdg", "desktop", "dbus", ] categories = [ "gui", "os::linux-apis", "api-bindings", ] license = "MIT" repository = "https://github.com/bilelmoussaoui/ashpd" [package.metadata.docs.rs] features = [ "backend", "gtk4", "raw_handle", "account", "background", "camera", "clipboard", "dynamic_launcher", "email", "file_chooser", "game_mode", "global_shortcuts", "inhibit", "input_capture", "location", "memory_monitor", "network_monitor", "notification", "open_uri", "power_profile_monitor", "print", "proxy_resolver", "realtime", "remote_desktop", "screencast", "screenshot", "secret", "settings", "trash", "usb", "wallpaper", "flatpak", "documents", ] rustdoc-args = ["--generate-link-to-definition"] [features] account = [] async-io = [ "zbus/async-io", "futures-util/io", ] backend = ["dep:async-trait"] background = [] camera = [] clipboard = [] default = ["tokio"] documents = ["dep:serde_repr"] dynamic_launcher = ["dep:serde_repr"] email = [] file_chooser = ["dep:serde_repr"] flatpak = ["dep:serde_repr"] frontend = [ "account", "background", "camera", "clipboard", "dynamic_launcher", "email", "file_chooser", "game_mode", "global_shortcuts", "inhibit", "input_capture", "location", "memory_monitor", "network_monitor", "notification", "open_uri", "power_profile_monitor", "print", "proxy_resolver", "realtime", "remote_desktop", "screencast", "screenshot", "secret", "settings", "trash", "usb", "wallpaper", "flatpak", "documents", ] game_mode = ["dep:serde_repr"] glib = ["dep:glib"] global_shortcuts = [] gtk4 = [ "gtk4_x11", "gtk4_wayland", ] gtk4_wayland = [ "gdk4wayland", "glib", "dep:gtk4", "dep:futures-channel", ] gtk4_x11 = [ "gdk4x11", "glib", "dep:gtk4", "dep:futures-channel", ] inhibit = ["dep:serde_repr"] input_capture = ["dep:serde_repr"] location = ["dep:serde_repr"] memory_monitor = [] network_monitor = ["dep:serde_repr"] notification = [] open_uri = [] pipewire = [ "dep:pipewire", "dep:futures-channel", ] power_profile_monitor = [] print = [] proxy_resolver = [] raw_handle = ["raw-window-handle"] realtime = [] remote_desktop = ["dep:serde_repr"] screencast = ["dep:serde_repr"] screenshot = [] secret = [] settings = [] tokio = [ "zbus/tokio", "dep:tokio", ] trash = ["dep:serde_repr"] usb = [] wallpaper = [] wayland = [ "wayland-client", "wayland-protocols", "wayland-backend", "dep:futures-channel", ] [lib] name = "ashpd" path = "src/lib.rs" [[example]] name = "input_capture" path = "examples/input_capture.rs" required-features = ["input_capture"] [[example]] name = "screen_cast_gstreamer" path = "examples/screen_cast_gstreamer.rs" required-features = ["screencast"] [[example]] name = "screen_cast_pw" path = "examples/screen_cast_pw.rs" required-features = [ "screencast", "pipewire", ] [dependencies.async-trait] version = "0.1" optional = true [dependencies.enumflags2] version = "0.7" [dependencies.futures-channel] version = "0.3" optional = true [dependencies.futures-util] version = "0.3" [dependencies.gdk4wayland] version = "0.11" optional = true package = "gdk4-wayland" [dependencies.gdk4x11] version = "0.11" features = ["xlib"] optional = true package = "gdk4-x11" [dependencies.getrandom] version = "0.4" [dependencies.glib] version = "0.22" optional = true [dependencies.gtk4] version = "0.11" optional = true [dependencies.pipewire] version = "0.9" optional = true [dependencies.raw-window-handle] version = "0.6" optional = true [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.serde_repr] version = "0.1" optional = true [dependencies.tokio] version = "1.43" optional = true default-features = false [dependencies.tracing] version = "0.1" optional = true [dependencies.wayland-backend] version = "0.3" features = ["client_system"] optional = true [dependencies.wayland-client] version = "0.31" optional = true [dependencies.wayland-protocols] version = "0.32" features = [ "unstable", "client", "staging", ] optional = true [dependencies.zbus] version = "5.13" default-features = false [dev-dependencies.gst] version = "0.25" package = "gstreamer" [dev-dependencies.pipewire] version = "0.9.0" [dev-dependencies.quick-xml] version = "0.39" [dev-dependencies.reis] version = "0.6" features = ["tokio"] [dev-dependencies.serde_json] version = "1.0" [dev-dependencies.tokio] version = "1.41" features = [ "rt-multi-thread", "macros", ] ashpd-0.13.11/Cargo.toml.orig000064400000000000000000000077141046102023000137660ustar 00000000000000[package] authors.workspace = true categories.workspace = true description = "XDG portals wrapper in Rust using zbus" edition.workspace = true keywords.workspace = true license.workspace = true name = "ashpd" repository.workspace = true version.workspace = true rust-version = "1.87" [features] async-io = ["zbus/async-io", "futures-util/io"] default = ["tokio"] frontend = [ "account", "background", "camera", "clipboard", "dynamic_launcher", "email", "file_chooser", "game_mode", "global_shortcuts", "inhibit", "input_capture", "location", "memory_monitor", "network_monitor", "notification", "open_uri", "power_profile_monitor", "print", "proxy_resolver", "realtime", "remote_desktop", "screencast", "screenshot", "secret", "settings", "trash", "usb", "wallpaper", "flatpak", "documents", ] backend = ["dep:async-trait"] account = [] background = [] camera = [] clipboard = [] dynamic_launcher = ["dep:serde_repr"] email = [] file_chooser = ["dep:serde_repr"] game_mode = ["dep:serde_repr"] global_shortcuts = [] inhibit = ["dep:serde_repr"] input_capture = ["dep:serde_repr"] location = ["dep:serde_repr"] memory_monitor = [] network_monitor = ["dep:serde_repr"] notification = [] open_uri = [] power_profile_monitor = [] print = [] proxy_resolver = [] realtime = [] remote_desktop = ["dep:serde_repr"] screencast = ["dep:serde_repr"] screenshot = [] secret = [] settings = [] trash = ["dep:serde_repr"] usb = [] wallpaper = [] flatpak = ["dep:serde_repr"] documents = ["dep:serde_repr"] gtk4 = ["gtk4_x11", "gtk4_wayland"] gtk4_wayland = ["gdk4wayland", "glib", "dep:gtk4", "dep:futures-channel"] gtk4_x11 = ["gdk4x11", "glib", "dep:gtk4", "dep:futures-channel"] raw_handle = ["raw-window-handle"] tokio = ["zbus/tokio", "dep:tokio"] glib = ["dep:glib"] wayland = ["wayland-client", "wayland-protocols", "wayland-backend", "dep:futures-channel"] pipewire = ["dep:pipewire", "dep:futures-channel"] [dependencies] async-trait = { version = "0.1", optional = true } enumflags2 = "0.7" getrandom = "0.4" futures-channel = { version = "0.3", optional = true } futures-util = "0.3" gdk4wayland = { package = "gdk4-wayland", version = "0.11", optional = true } gdk4x11 = { package = "gdk4-x11", version = "0.11", optional = true, features = [ "xlib" ] } glib = { version = "0.22", optional = true } gtk4 = { version = "0.11", optional = true } pipewire = { version = "0.9", optional = true } raw-window-handle = { version = "0.6", optional = true } serde = { version = "1.0", features = ["derive"] } serde_repr = { version = "0.1", optional = true } tokio = { version = "1.43", optional = true, default-features = false } tracing = { version = "0.1", optional = true } wayland-backend = { version = "0.3", optional = true, features = [ "client_system", ] } wayland-client = { version = "0.31", optional = true } wayland-protocols = { version = "0.32", optional = true, features = [ "unstable", "client", "staging", ] } zbus = { version = "5.13", default-features = false } [dev-dependencies] serde_json = "1.0" reis = { version = "0.6", features = ["tokio"] } pipewire = "0.9.0" tokio = { version = "1.41", features = [ "rt-multi-thread", "macros" ] } gst = { package = "gstreamer", version = "0.25" } quick-xml = "0.39" [package.metadata.docs.rs] features = ["backend", "gtk4", "raw_handle", "account","background","camera","clipboard","dynamic_launcher","email","file_chooser", "game_mode","global_shortcuts","inhibit","input_capture","location","memory_monitor", "network_monitor","notification","open_uri","power_profile_monitor","print", "proxy_resolver","realtime","remote_desktop","screencast","screenshot","secret", "settings","trash","usb","wallpaper","flatpak","documents" ] rustdoc-args = ["--generate-link-to-definition"] [[example]] name = "screen_cast_gstreamer" required-features = ["screencast"] [[example]] name = "screen_cast_pw" required-features = ["screencast", "pipewire"] [[example]] name = "input_capture" required-features = ["input_capture"] ashpd-0.13.11/LICENSE000064400000000000000000000020621046102023000120730ustar 00000000000000MIT License Copyright (c) 2020 Bilal Elmoussaoui Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ashpd-0.13.11/README.md000064400000000000000000000073671046102023000123620ustar 00000000000000# ASHPD [![docs](https://docs.rs/ashpd/badge.svg)](https://docs.rs/ashpd/) [![crates.io](https://img.shields.io/crates/v/ashpd)](https://crates.io/crates/ashpd) ![CI](https://github.com/bilelmoussaoui/ashpd/workflows/CI/badge.svg) ASHPD, acronym of Aperture Science Handheld Portal Device is a Rust & [zbus](https://github.com/z-galaxy/zbus) wrapper of the XDG portals DBus interfaces. The library aims to provide an easy way to interact with the various portals defined per the [specifications](https://flatpak.github.io/xdg-desktop-portal/docs/). It provides an alternative to the C library [https://github.com/flatpak/libportal](https://github.com/flatpak/libportal) ## Examples Ask the compositor to pick a color ```rust,no_run use ashpd::desktop::Color; async fn run() -> ashpd::Result<()> { let color = Color::pick().send().await?.response()?; println!("({}, {}, {})", color.red(), color.green(), color.blue()); Ok(()) } ``` Start a PipeWire stream from the user's camera ```rust,no_run use ashpd::desktop::camera::Camera; pub async fn run() -> ashpd::Result<()> { let camera = Camera::new().await?; if camera.is_present().await? { camera.request_access(Default::default()).await?; let remote_fd = camera.open_pipe_wire_remote(Default::default()).await?; // pass the remote fd to GStreamer for example } Ok(()) } ``` ## Optional features | Feature | Description | Default | | --- | ----------- | ------- | | tracing | Record various debug information using the `tracing` library | No | | tokio | Enable tokio runtime on zbus dependency | Yes | | async-io | Enable the use of the async-io crates, compatible with runtimes such smol or glib | No | | backend | *unstable* Enables APIs useful for writing portals implementations | No | | glib | Make all the enums derive `glib::Enum`. Flags are not supported yet | No | | gtk4 | Implement `From` for [`gdk4::RGBA`](https://gtk-rs.org/gtk4-rs/stable/latest/docs/gdk4/struct.RGBA.html) Provides `WindowIdentifier::from_native` that takes a [`IsA`](https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.Native.html) | No | | gtk4_wayland |Provides `WindowIdentifier::from_native` that takes a [`IsA`](https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.Native.html) with Wayland backend support only | No | | gtk4_x11 |Provides `WindowIdentifier::from_native` that takes a [`IsA`](https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/struct.Native.html) with X11 backend support only | No | | pipewire | Provides `ashpd::desktop::camera::pipewire_streams` that helps you retrieve the various camera streams associated with the retrieved file descriptor| No | | raw_handle | Provides `WindowIdentifier::from_raw_handle` and `WindowIdentifier::as_raw_handle` for [raw-window-handle](https://lib.rs/crates/raw-window-handle) crate | No | | wayland | Provides `WindowIdentifier::from_wayland` for [wayland-client](https://lib.rs/crates/wayland-client) crate | No | | backend | Enables portal backend implementation supoport | No | ## Demo The library comes with a [demo](./demo/client) built using the [GTK 4 Rust bindings](https://gtk-rs.org/gtk4-rs) and previews most of the portals. It is meant as a test case for the portals (from a distributor perspective) and as a way for the developers to see which portals exists and how to integrate them into their application using ASHPD. ## Backend demo The library also comes with a [backend demo](./demo/backend/README.md) that exemplifies how to implement [a portal backend](https://flatpak.github.io/xdg-desktop-portal/docs/impl-dbus-interfaces.html). ashpd-0.13.11/examples/input_capture.rs000064400000000000000000000135031046102023000161360ustar 00000000000000use std::time::Duration; use ashpd::desktop::input_capture::{ Barrier, BarrierID, Capabilities, CreateSessionOptions, InputCapture, ReleaseOptions, StartOptions, }; use futures_util::StreamExt; #[tokio::main] async fn main() -> ashpd::Result<()> { let input_capture = InputCapture::new().await?; let capabilities = Capabilities::Keyboard | Capabilities::Pointer; // Try create_session2 + start first, fall back to legacy let session = match input_capture.create_session2(Default::default()).await { Ok(session) => { let start_request = input_capture .start( &session, None, StartOptions::default().set_capabilities(capabilities), ) .await?; let _start_response = start_request.response()?; session } Err(ashpd::Error::RequiresVersion(_want, _have)) => { let (session, _caps) = input_capture .create_session( None, CreateSessionOptions::default().set_capabilities(capabilities), ) .await?; session } Err(err) => { return Err(err); } }; let zones_response = input_capture.zones(&session, Default::default()).await?; let zones = zones_response.response()?; println!("Available Zones: {:?}", zones); // Set up barriers on all edges of each zone, even the overlapping ones (if we // have multiple zones). Those will fail and that's fine. The alternative is // a more sophisticated algorithm to only set up barriers on the outside // edges. let barriers: Vec = zones .regions() .iter() .enumerate() .flat_map(|(n, region)| { let x = region.x_offset(); let y = region.y_offset(); let width = region.width() as i32; let height = region.height() as i32; // Create barriers for all four edges let edges = [ ("left", (x, y, x, y + height - 1)), ("right", (x + width - 1, y, x + width - 1, y + height - 1)), ("top", (x, y, x + width - 1, y)), ("bottom", (x, y + height - 1, x + width - 1, y + height - 1)), ]; edges.into_iter().enumerate().map(move |(edge_idx, (edge_name, position))| { // Create unique barrier ID: zone_n * 4 + edge_idx + 1 let barrier_id = (n * 4 + edge_idx + 1) as u32; let id = BarrierID::new(barrier_id).expect("barrier-id must be non-zero"); println!( "Creating barrier {} ({} edge) at position {:?} for zone ({}, {}, {}x{})", id, edge_name, position, region.x_offset(), region.y_offset(), region.width(), region.height() ); Barrier::new(id, position) }) }) .collect(); let barriers_response = input_capture .set_pointer_barriers(&session, &barriers, zones.zone_set(), Default::default()) .await?; let result = barriers_response.response()?; let failed = result.failed_barriers(); if !failed.is_empty() { println!("Failed barriers: {:?}", failed); } else { println!("All barriers set successfully"); } // Actual input events will be coming from the EIS file descriptor, once // any barrier activates. let eifd = input_capture .connect_to_eis(&session, Default::default()) .await?; println!("Connected to EIS, fd: {:?}", eifd); // Enable input capture so the barriers can trigger input_capture.enable(&session, Default::default()).await?; println!("Input capture enabled - move cursor to any edge to trigger"); // Listen for activation events, once we get the event we'll see EI events // flow too. let mut activated_stream = input_capture.receive_activated().await?; let mut deactivated_stream = input_capture.receive_deactivated().await?; println!("\nWaiting for input capture to be triggered..."); println!("Press Ctrl+C to exit\n"); loop { tokio::select! { Some(activated) = activated_stream.next() => { println!("Input capture ACTIVATED!"); println!(" Activation ID: {:?}", activated.activation_id()); println!(" Cursor position: {:?}", activated.cursor_position()); println!(" Barrier ID: {:?}", activated.barrier_id()); println!(" (Input events would now be captured via libei)"); // Once InputCapture activates, we can't interact with the machine anymore so // force a InputCapture.Release after 5s. println!(" Waiting 5 seconds before releasing..."); tokio::time::sleep(Duration::from_secs(5)).await; // Release the input capture input_capture.release(&session, ReleaseOptions::default().set_activation_id(activated.activation_id())).await?; println!(" Released input capture"); // And re-enable so we can play this game again. But wait a second first // to give the user a chance to move away from the barrier. tokio::time::sleep(Duration::from_secs(1)).await; input_capture.enable(&session, Default::default()).await?; } Some(deactivated) = deactivated_stream.next() => { println!("Input capture DEACTIVATED"); println!(" Activation ID: {:?}", deactivated.activation_id()); } } } } ashpd-0.13.11/examples/screen_cast_gstreamer.rs000064400000000000000000000054011046102023000176140ustar 00000000000000use std::os::fd::{AsRawFd, OwnedFd}; use ashpd::desktop::screencast::{ CursorMode, Screencast, SelectSourcesOptions, SourceType, Stream, }; use gst::prelude::*; async fn open_portal() -> ashpd::Result<(Stream, OwnedFd)> { let proxy = Screencast::new().await?; let session = proxy.create_session(Default::default()).await?; proxy .select_sources( &session, SelectSourcesOptions::default() .set_cursor_mode(CursorMode::Embedded) .set_sources(SourceType::Monitor | SourceType::Window | SourceType::Virtual) .set_multiple(false) .set_restore_token(None) .set_persist_mode(ashpd::desktop::PersistMode::ExplicitlyRevoked), ) .await?; let response = proxy .start(&session, None, Default::default()) .await? .response()?; let stream = response .streams() .first() .expect("No stream found or selected") .to_owned(); let fd = proxy .open_pipe_wire_remote(&session, Default::default()) .await?; Ok((stream, fd)) } #[tokio::main] async fn main() -> ashpd::Result<()> { gst::init().unwrap(); let (stream, stream_fd) = open_portal().await?; let pipewire_node_id = &stream.pipe_wire_node_id(); let stream_raw_fd = &stream_fd.as_raw_fd(); let pipewire_element = gst::ElementFactory::make("pipewiresrc") .property("fd", stream_raw_fd) .property("path", pipewire_node_id.to_string()) .build() .unwrap(); let convert = gst::ElementFactory::make("videoconvert").build().unwrap(); let wayland_sink = gst::ElementFactory::make("waylandsink").build().unwrap(); let pipeline = gst::Pipeline::default(); pipeline .add_many([&pipewire_element, &convert, &wayland_sink]) .unwrap(); gst::Element::link_many([&pipewire_element, &convert, &wayland_sink]).unwrap(); pipeline.set_state(gst::State::Playing).unwrap(); let bus = pipeline.bus().unwrap(); for msg in bus.iter_timed(gst::ClockTime::NONE) { use gst::MessageView; match msg.view() { MessageView::Eos(..) => { println!("EOS"); break; } MessageView::Error(err) => { pipeline.set_state(gst::State::Null).unwrap(); eprintln!( "Got error from {}: {} ({})", msg.src() .map(|s| String::from(s.path_string())) .unwrap_or_else(|| "None".into()), err.error(), err.debug().unwrap_or_else(|| "".into()), ); break; } _ => (), } } Ok(()) } ashpd-0.13.11/examples/screen_cast_pw.rs000064400000000000000000000154711046102023000162610ustar 00000000000000use std::os::fd::{IntoRawFd, OwnedFd}; use ashpd::desktop::{ PersistMode, screencast::{ CursorMode, Screencast, SelectSourcesOptions, SourceType, Stream as ScreencastStream, }, }; use pipewire as pw; use pw::{properties::properties, spa}; struct UserData { format: spa::param::video::VideoInfoRaw, } async fn open_portal() -> ashpd::Result<(ScreencastStream, OwnedFd)> { let proxy = Screencast::new().await?; let session = proxy.create_session(Default::default()).await?; proxy .select_sources( &session, SelectSourcesOptions::default() .set_cursor_mode(CursorMode::Hidden) .set_sources(SourceType::Monitor | SourceType::Window) .set_multiple(false) .set_restore_token(None) .set_persist_mode(PersistMode::DoNot), ) .await?; let response = proxy .start(&session, None, Default::default()) .await? .response()?; let stream = response .streams() .first() .expect("no stream found / selected") .to_owned(); let fd = proxy .open_pipe_wire_remote(&session, Default::default()) .await?; Ok((stream, fd)) } async fn start_streaming(node_id: u32, fd: OwnedFd) -> Result<(), pw::Error> { pw::init(); let mainloop = pw::main_loop::MainLoopBox::new(None)?; let context = pw::context::ContextBox::new(mainloop.loop_(), None)?; let core = context.connect_fd(fd, None)?; let data = UserData { format: Default::default(), }; let stream = pw::stream::StreamBox::new( &core, "video-test", properties! { *pw::keys::MEDIA_TYPE => "Video", *pw::keys::MEDIA_CATEGORY => "Capture", *pw::keys::MEDIA_ROLE => "Screen", }, )?; let _listener = stream .add_local_listener_with_user_data(data) .state_changed(|_, _, old, new| { println!("State changed: {:?} -> {:?}", old, new); }) .param_changed(|_, user_data, id, param| { let Some(param) = param else { return; }; if id != pw::spa::param::ParamType::Format.as_raw() { return; } let (media_type, media_subtype) = match pw::spa::param::format_utils::parse_format(param) { Ok(v) => v, Err(_) => return, }; if media_type != pw::spa::param::format::MediaType::Video || media_subtype != pw::spa::param::format::MediaSubtype::Raw { return; } user_data .format .parse(param) .expect("Failed to parse param changed to VideoInfoRaw"); println!("got video format:"); println!( "\tformat: {} ({:?})", user_data.format.format().as_raw(), user_data.format.format() ); println!( "\tsize: {}x{}", user_data.format.size().width, user_data.format.size().height ); println!( "\tframerate: {}/{}", user_data.format.framerate().num, user_data.format.framerate().denom ); // prepare to render video of this size }) .process(|stream, _| { match stream.dequeue_buffer() { None => println!("out of buffers"), Some(mut buffer) => { let datas = buffer.datas_mut(); if datas.is_empty() { return; } // copy frame data to screen let data = &mut datas[0]; println!("got a frame of size {}", data.chunk().size()); } } }) .register()?; println!("Created stream {:#?}", stream); let obj = pw::spa::pod::object!( pw::spa::utils::SpaTypes::ObjectParamFormat, pw::spa::param::ParamType::EnumFormat, pw::spa::pod::property!( pw::spa::param::format::FormatProperties::MediaType, Id, pw::spa::param::format::MediaType::Video ), pw::spa::pod::property!( pw::spa::param::format::FormatProperties::MediaSubtype, Id, pw::spa::param::format::MediaSubtype::Raw ), pw::spa::pod::property!( pw::spa::param::format::FormatProperties::VideoFormat, Choice, Enum, Id, pw::spa::param::video::VideoFormat::RGB, pw::spa::param::video::VideoFormat::RGB, pw::spa::param::video::VideoFormat::RGBA, pw::spa::param::video::VideoFormat::RGBx, pw::spa::param::video::VideoFormat::BGRx, pw::spa::param::video::VideoFormat::YUY2, pw::spa::param::video::VideoFormat::I420, ), pw::spa::pod::property!( pw::spa::param::format::FormatProperties::VideoSize, Choice, Range, Rectangle, pw::spa::utils::Rectangle { width: 320, height: 240 }, pw::spa::utils::Rectangle { width: 1, height: 1 }, pw::spa::utils::Rectangle { width: 4096, height: 4096 } ), pw::spa::pod::property!( pw::spa::param::format::FormatProperties::VideoFramerate, Choice, Range, Fraction, pw::spa::utils::Fraction { num: 25, denom: 1 }, pw::spa::utils::Fraction { num: 0, denom: 1 }, pw::spa::utils::Fraction { num: 1000, denom: 1 } ), ); let values: Vec = pw::spa::pod::serialize::PodSerializer::serialize( std::io::Cursor::new(Vec::new()), &pw::spa::pod::Value::Object(obj), ) .unwrap() .0 .into_inner(); let mut params = [spa::pod::Pod::from_bytes(&values).unwrap()]; stream.connect( spa::utils::Direction::Input, Some(node_id), pw::stream::StreamFlags::AUTOCONNECT | pw::stream::StreamFlags::MAP_BUFFERS, &mut params, )?; println!("Connected stream"); mainloop.run(); Ok(()) } #[tokio::main] async fn main() { let (stream, fd) = open_portal().await.expect("failed to open portal"); let pipewire_node_id = stream.pipe_wire_node_id(); println!( "node id {}, fd {}", pipewire_node_id, &fd.try_clone().unwrap().into_raw_fd() ); if let Err(e) = start_streaming(pipewire_node_id, fd).await { eprintln!("Error: {}", e); }; } ashpd-0.13.11/src/activation_token/gtk4.rs000064400000000000000000000031531046102023000164370ustar 00000000000000use glib::translate::from_glib_full; use gtk4::{gio, prelude::*}; use crate::ActivationToken; impl ActivationToken { /// Gets an activation token from a window. /// /// Support for the XDG Activation Protocol was added in GLib 2.76, this /// method will return `None` on older versions. #[cfg_attr(docsrs, doc(cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))))] pub fn from_window(widget: &impl IsA<::gtk4::Widget>) -> Option { if glib::check_version(2, 76, 0).is_some() { #[cfg(feature = "tracing")] tracing::info!("Need glib 2.76 for XDG Activation protocol support"); return None; } let display = widget.as_ref().display(); let context = display.app_launch_context(); // g_app_launch_context_get_startup_notify_id only accepts nullable // parameters since 2.82. On older versions we use the vfunc. if glib::check_version(2, 82, 0).is_some() { unsafe { let klass: *mut gtk4::gio::ffi::GAppLaunchContextClass = &context.class().parent()? as *const _ as *mut _; let get_startup_notify_id = (*klass).get_startup_notify_id.as_ref()?; from_glib_full::<_, Option>(get_startup_notify_id( context.as_ptr().cast(), std::ptr::null_mut(), std::ptr::null_mut(), )) } } else { context .startup_notify_id(gio::AppInfo::NONE, &[]) .map(String::from) } .map(Self::from) } } ashpd-0.13.11/src/activation_token/mod.rs000064400000000000000000000021741046102023000163470ustar 00000000000000use std::ops::Deref; use serde::{Deserialize, Serialize}; use zbus::zvariant::Type; #[cfg(any(feature = "gtk4_wayland", feature = "gtk4_x11"))] mod gtk4; #[cfg(feature = "wayland")] mod wayland; /// A token that can be used to activate an application. /// /// No guarantees are made for the token structure. #[derive(Debug, Deserialize, Serialize, Type, PartialEq, Eq, Hash, Clone)] pub struct ActivationToken(String); impl From for ActivationToken { fn from(value: String) -> Self { Self(value) } } impl From<&str> for ActivationToken { fn from(value: &str) -> Self { Self(value.to_owned()) } } impl From for String { fn from(value: ActivationToken) -> String { value.0 } } impl Deref for ActivationToken { type Target = str; fn deref(&self) -> &Self::Target { self.0.as_str() } } impl AsRef for ActivationToken { fn as_ref(&self) -> &str { self.0.as_str() } } impl std::fmt::Display for ActivationToken { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_ref()) } } ashpd-0.13.11/src/activation_token/wayland.rs000064400000000000000000000215161046102023000172300ustar 00000000000000use wayland_backend::sys::client::Backend; use wayland_client::{ Proxy, QueueHandle, protocol::{wl_registry, wl_seat::WlSeat, wl_surface::WlSurface}, }; use wayland_protocols::xdg::activation::v1::client::{ xdg_activation_token_v1::{Event, XdgActivationTokenV1}, xdg_activation_v1::XdgActivationV1, }; use crate::{ActivationToken, AppID}; // Supported versions. const XDG_ACTIVATION_V1_VERSION: u32 = 1; #[derive(Default)] struct WaylandHandle { wl_token: Option, raw_token: Option, } impl Drop for WaylandHandle { fn drop(&mut self) { if let Some(wl_token) = self.wl_token.take() { wl_token.destroy(); } } } impl ActivationToken { #[cfg(feature = "wayland")] #[cfg_attr(docsrs, doc(cfg(feature = "wayland")))] /// Create an instance of [`ActivationToken`] from a Wayland surface. pub async fn from_surface(app_id: Option, surface: &WlSurface) -> Option { let backend = surface.backend().upgrade()?; let conn = wayland_client::Connection::from_backend(backend); Self::new_wayland_inner(app_id, conn, surface, None, None).await } #[cfg(feature = "wayland")] #[cfg_attr(docsrs, doc(cfg(feature = "wayland")))] /// Create an instance of [`ActivationToken`] from a Wayland surface with /// serial and seat. /// /// The serial and seat information prove that the activation is in response /// to a recent user action. Some compositors might refuse to activate /// windows when the token doesn't have a valid and recent event serial. /// /// The serial typically comes from input events like `wl_pointer.button` or /// `wl_keyboard.key`. pub async fn from_surface_with_serial( app_id: Option, surface: &WlSurface, serial: u32, seat: &WlSeat, ) -> Option { let backend = surface.backend().upgrade()?; let conn = wayland_client::Connection::from_backend(backend); Self::new_wayland_inner(app_id, conn, surface, Some(serial), Some(seat)).await } #[cfg(feature = "wayland")] #[cfg_attr(docsrs, doc(cfg(feature = "wayland")))] /// Create an instance of [`ActivationToken`] from a Wayland surface. /// /// # Safety /// /// Both surface and display pointers have to be valid. pub async unsafe fn from_wayland_raw( app_id: Option, surface_ptr: *mut std::ffi::c_void, display_ptr: *mut std::ffi::c_void, ) -> Option { if surface_ptr.is_null() || display_ptr.is_null() { return None; } let backend = unsafe { Backend::from_foreign_display(display_ptr as *mut _) }; let conn = wayland_client::Connection::from_backend(backend); let obj_id = unsafe { wayland_backend::sys::client::ObjectId::from_ptr( WlSurface::interface(), surface_ptr as *mut _, ) } .ok()?; let surface = WlSurface::from_id(&conn, obj_id).ok()?; Self::new_wayland_inner(app_id, conn, &surface, None, None).await } #[cfg(feature = "wayland")] #[cfg_attr(docsrs, doc(cfg(feature = "wayland")))] /// Create an instance of [`ActivationToken`] from a Wayland surface with /// serial and seat. /// /// The serial and seat information prove that the activation is in response /// to a recent user action. Some compositors might refuse to activate /// windows when the token doesn't have a valid and recent event serial. /// /// The serial typically comes from input events like `wl_pointer.button` or /// `wl_keyboard.key`. /// /// # Safety /// /// Both surface, display, and seat pointers have to be valid. pub async unsafe fn from_wayland_raw_with_serial( app_id: Option, surface_ptr: *mut std::ffi::c_void, display_ptr: *mut std::ffi::c_void, serial: u32, seat_ptr: *mut std::ffi::c_void, ) -> Option { if surface_ptr.is_null() || display_ptr.is_null() { return None; } let backend = unsafe { Backend::from_foreign_display(display_ptr as *mut _) }; let conn = wayland_client::Connection::from_backend(backend); let surface_obj_id = unsafe { wayland_backend::sys::client::ObjectId::from_ptr( WlSurface::interface(), surface_ptr as *mut _, ) } .ok()?; let surface = WlSurface::from_id(&conn, surface_obj_id).ok()?; let seat = if !seat_ptr.is_null() { let seat_obj_id = unsafe { wayland_backend::sys::client::ObjectId::from_ptr( WlSeat::interface(), seat_ptr as *mut _, ) } .ok()?; Some(WlSeat::from_id(&conn, seat_obj_id).ok()?) } else { None }; Self::new_wayland_inner(app_id, conn, &surface, Some(serial), seat.as_ref()).await } async fn new_wayland_inner( app_id: Option, conn: wayland_client::Connection, surface: &WlSurface, serial: Option, seat: Option<&WlSeat>, ) -> Option { let (sender, receiver) = futures_channel::oneshot::channel::>(); // Cheap clone, protocol objects are essentially smart pointers let surface = surface.clone(); let seat = seat.cloned(); std::thread::spawn(move || { match wayland_export_token(app_id, conn, &surface, serial, seat.as_ref()) { Ok(window_handle) => sender.send(Some(window_handle)).unwrap(), Err(_err) => { #[cfg(feature = "tracing")] tracing::info!("Could not get wayland window identifier: {_err}"); sender.send(None).unwrap(); } } }); receiver.await.unwrap() } } impl wayland_client::Dispatch for WaylandHandle { fn event( state: &mut Self, _proxy: &XdgActivationTokenV1, event: ::Event, _data: &(), _connhandle: &wayland_client::Connection, _qhandle: &QueueHandle, ) { if let Event::Done { token } = event { state.raw_token = Some(token); } } } impl wayland_client::Dispatch for WaylandHandle { fn event( state: &mut Self, registry: &wl_registry::WlRegistry, event: wl_registry::Event, _data: &(), _connhandle: &wayland_client::Connection, qhandle: &QueueHandle, ) { if let wl_registry::Event::Global { name, interface, version, } = event && &interface == "xdg_activation_v1" { #[cfg(feature = "tracing")] tracing::info!("Found wayland interface {interface} v{version}"); let wl_activation = registry.bind::( name, version.min(XDG_ACTIVATION_V1_VERSION), qhandle, (), ); let wl_token = wl_activation.get_activation_token(qhandle, ()); state.wl_token = Some(wl_token); wl_activation.destroy(); } } } impl wayland_client::Dispatch for WaylandHandle { fn event( _state: &mut Self, _activation: &XdgActivationV1, _event: wayland_protocols::xdg::activation::v1::client::xdg_activation_v1::Event, _data: &(), _connhandle: &wayland_client::Connection, _qhandle: &QueueHandle, ) { } } fn wayland_export_token( app_id: Option, conn: wayland_client::Connection, surface: &WlSurface, serial: Option, seat: Option<&WlSeat>, ) -> Result> { let display = conn.display(); let mut event_queue = conn.new_event_queue(); let mut state = WaylandHandle::default(); let qhandle = event_queue.handle(); display.get_registry(&qhandle, ()); event_queue.roundtrip(&mut state)?; if let Some(wl_token) = state.wl_token.take() { if let Some(app_id) = app_id { wl_token.set_app_id(app_id.to_string()); } wl_token.set_surface(surface); if let Some(serial) = serial && let Some(seat) = seat { wl_token.set_serial(serial, seat); } wl_token.commit(); event_queue.roundtrip(&mut state)?; }; if let Some(raw_token) = state.raw_token.take() { Ok(ActivationToken::from(raw_token)) } else { #[cfg(feature = "tracing")] tracing::error!("Failed to get a response from the wayland server"); Err(Box::new(crate::Error::NoResponse)) } } ashpd-0.13.11/src/app_id.rs000064400000000000000000000211471046102023000134640ustar 00000000000000use std::{ops::Deref, str::FromStr}; use serde::{Deserialize, Deserializer, Serialize}; use zbus::zvariant::{self, Basic, Type}; /// The application ID. /// /// See . #[derive(Debug, Serialize, PartialEq, Type, Eq, Hash, Clone)] pub struct AppID(String); impl Basic for AppID { const SIGNATURE_CHAR: char = String::SIGNATURE_CHAR; const SIGNATURE_STR: &'static str = String::SIGNATURE_STR; } impl zvariant::NoneValue for AppID { type NoneType = String; fn null_value() -> String { String::default() } } impl FromStr for AppID { type Err = crate::Error; fn from_str(value: &str) -> Result { if is_valid_app_id(value) { Ok(Self(value.to_owned())) } else { Err(Self::Err::InvalidAppID) } } } impl TryFrom for AppID { type Error = crate::Error; fn try_from(value: String) -> Result { value.parse::() } } impl TryFrom<&str> for AppID { type Error = crate::Error; fn try_from(value: &str) -> Result { value.parse::() } } impl From for String { fn from(value: AppID) -> String { value.0 } } impl AsRef for AppID { fn as_ref(&self) -> &str { self.0.as_ref() } } impl Deref for AppID { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } impl std::fmt::Display for AppID { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_ref()) } } impl<'de> Deserialize<'de> for AppID { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let app_id = String::deserialize(deserializer)?; app_id .parse::() .map_err(|err| serde::de::Error::custom(err.to_string())) } } /// The application ID or the desktop file name without .desktop suffix. /// /// As unsandboxed applications might not be defining a strict application ID, /// this type allows the backend implementation to fallback to the inner string /// without causing an error and manually handle those applications. #[derive(Debug, Eq, Hash, PartialEq, Type)] #[zvariant(signature = "s")] pub struct MaybeAppID(Result); impl MaybeAppID { /// Returns the inner value. If the ID is valid, then `Ok` contains the /// `AppID` else `Err` contains the invalid app ID as string. pub fn inner(&self) -> &Result { &self.0 } } impl zvariant::NoneValue for MaybeAppID { type NoneType = String; fn null_value() -> String { String::default() } } impl From for MaybeAppID { fn from(value: AppID) -> Self { Self(Ok(value)) } } impl From for MaybeAppID { fn from(value: String) -> Self { Self(value.parse::().or(Err(value))) } } impl From<&str> for MaybeAppID { fn from(value: &str) -> Self { Self(value.parse::().or(Err(String::from(value)))) } } impl Serialize for MaybeAppID { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(self.to_string().as_str()) } } impl<'de> Deserialize<'de> for MaybeAppID { fn deserialize(deserializer: D) -> std::result::Result where D: Deserializer<'de>, { let inner = String::deserialize(deserializer)?; Ok(Self(inner.parse::().or(Err(inner)))) } } impl std::fmt::Display for MaybeAppID { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.0 { Ok(app_id) => f.write_str(app_id), Err(app_id) => f.write_str(app_id), } } } /// The ID of a file in the document store. #[derive(Debug, Serialize, Deserialize, Type, PartialEq, Eq, Hash, Clone)] pub struct DocumentID(String); impl Basic for DocumentID { const SIGNATURE_CHAR: char = String::SIGNATURE_CHAR; const SIGNATURE_STR: &'static str = String::SIGNATURE_STR; } impl From<&str> for DocumentID { fn from(value: &str) -> Self { Self(value.to_owned()) } } impl From for DocumentID { fn from(value: String) -> Self { Self(value) } } impl From for String { fn from(value: DocumentID) -> String { value.0 } } impl AsRef for DocumentID { fn as_ref(&self) -> &str { self.0.as_ref() } } impl Deref for DocumentID { type Target = str; fn deref(&self) -> &Self::Target { &self.0 } } impl std::fmt::Display for DocumentID { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_ref()) } } // Helpers fn is_valid_app_id(string: &str) -> bool { let len = string.len(); // The app id has to be between 0 < len <= 255 if len == 0 || 255 < len { return false; } let elements: Vec<&str> = string.split('.').collect(); let segments = elements.len(); if segments < 2 { return false; } for (idx_segment, element) in elements.iter().enumerate() { // No empty segments. if element.is_empty() { return false; } for (idx_char, c) in element.chars().enumerate() { // First char cannot be a digit. if idx_char == 0 && c.is_ascii_digit() { return false; } if !is_valid_app_id_char(c) { return false; } // Only the last segment can contain `-`. if idx_segment < segments - 1 && c == '-' { return false; } } } true } /// Only valid chars are a-z A-Z 0-9 - _ fn is_valid_app_id_char(c: char) -> bool { c.is_ascii_alphanumeric() || matches!(c, '-' | '_') } #[cfg(test)] mod tests { use super::*; #[test] fn test_is_valid_app_id() { assert!(is_valid_app_id("a.b")); assert!(is_valid_app_id("a_c.b_c.h_c")); assert!(is_valid_app_id("a.c-b")); assert!(is_valid_app_id("a.c2.d")); assert!(!is_valid_app_id("a")); assert!(!is_valid_app_id("")); assert!(!is_valid_app_id("a-z.b.c.d")); assert!(!is_valid_app_id("a.b-z.c.d")); assert!(!is_valid_app_id("a.b.c-z.d")); assert!(!is_valid_app_id("a.0b.c")); assert!(!is_valid_app_id("a..c")); assert!(!is_valid_app_id("a.é")); assert!(!is_valid_app_id("a.京")); // Tests from // https://github.com/bilelmoussaoui/flatpak-vscode/blob/master/src/test/suite/extension.test.ts assert!(is_valid_app_id("_org.SomeApp")); assert!(is_valid_app_id("com.org.SomeApp")); assert!(is_valid_app_id("com.org_._SomeApp")); assert!(is_valid_app_id("com.org._1SomeApp")); assert!(is_valid_app_id("com.org._1_SomeApp")); assert!(is_valid_app_id( "VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.a111111111111" )); assert!(!is_valid_app_id("com.org-._SomeApp")); assert!(!is_valid_app_id("package")); assert!(!is_valid_app_id("NoDot")); assert!(!is_valid_app_id("No-dot")); assert!(!is_valid_app_id("No_dot")); assert!(!is_valid_app_id("Has.Two..Consecutive.Dots")); assert!(!is_valid_app_id("HasThree...Consecutive.Dots")); assert!(!is_valid_app_id(".StartsWith.A.Period")); assert!(!is_valid_app_id(".")); assert!(!is_valid_app_id("Ends.With.A.Period.")); assert!(!is_valid_app_id("0P.Starts.With.A.Digit")); assert!(!is_valid_app_id("com.org.1SomeApp")); assert!(!is_valid_app_id("Element.Starts.With.A.1Digit")); assert!(!is_valid_app_id( "VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.VeryLongApplicationId.a1111111111112" )); assert!(!is_valid_app_id("")); assert!(!is_valid_app_id("contains.;nvalid.characters")); assert!(!is_valid_app_id("con\nins.invalid.characters")); assert!(!is_valid_app_id("con/ains.invalid.characters")); assert!(!is_valid_app_id("conta|ns.invalid.characters")); assert!(!is_valid_app_id("contæins.inva_å_lid.characters")); } } ashpd-0.13.11/src/backend/access.rs000064400000000000000000000104751046102023000150620ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use crate::{ MaybeAppID, WindowIdentifierType, backend::{ Result, request::{Request, RequestImpl}, }, desktop::{HandleToken, Icon, request::Response}, zvariant::{ self, Optional, OwnedObjectPath, as_value::{self, optional}, }, }; #[derive(Deserialize, zvariant::Type)] #[zvariant(signature = "dict")] pub struct AccessOptions { #[serde(default, with = "optional")] modal: Option, #[serde(default, with = "optional")] deny_label: Option, #[serde(default, with = "optional")] grant_label: Option, #[serde(default, with = "optional")] icon: Option, #[serde(default, with = "as_value")] choices: Vec, } #[derive(Clone, Deserialize, zvariant::Type, Debug)] pub struct Choice(String, String, Vec<(String, String)>, String); impl Choice { /// The choice's unique id pub fn id(&self) -> &str { &self.0 } /// The user visible label of the choice. pub fn label(&self) -> &str { &self.1 } /// Pairs of choices. pub fn pairs(&self) -> Vec<(&str, &str)> { self.2 .iter() .map(|(x, y)| (x.as_str(), y.as_str())) .collect::>() } /// The initially selected value. pub fn initial_selection(&self) -> &str { &self.3 } } impl AccessOptions { pub fn is_modal(&self) -> Option { self.modal } pub fn deny_label(&self) -> Option<&str> { self.deny_label.as_deref() } pub fn grant_label(&self) -> Option<&str> { self.grant_label.as_deref() } pub fn icon(&self) -> Option { self.icon.as_ref().map(|i| Icon::with_names([i])) } pub fn choices(&self) -> &[Choice] { &self.choices } } #[derive(Serialize, Debug, zvariant::Type, Default)] #[zvariant(signature = "dict")] pub struct AccessResponse { #[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")] choices: Vec<(String, String)>, } impl AccessResponse { /// Adds a selected choice (key, value). #[must_use] pub fn choice(mut self, key: &str, value: &str) -> Self { self.choices.push((key.to_owned(), value.to_owned())); self } } #[async_trait] pub trait AccessImpl: RequestImpl { #[allow(clippy::too_many_arguments)] #[doc(alias = "AccessDialog")] async fn access_dialog( &self, token: HandleToken, app_id: Option, window_identifier: Option, title: String, subtitle: String, body: String, options: AccessOptions, ) -> Result; } pub(crate) struct AccessInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl AccessInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Access")] impl AccessInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 1 } #[allow(clippy::too_many_arguments)] #[zbus(out_args("response", "results"))] async fn access_dialog( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, title: String, subtitle: String, body: String, options: AccessOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "Access::AccessDialog", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.access_dialog( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), title, subtitle, body, options, ) .await }, ) .await } } ashpd-0.13.11/src/backend/account.rs000064400000000000000000000042411046102023000152470ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; use crate::{ MaybeAppID, WindowIdentifierType, backend::{ Result, request::{Request, RequestImpl}, }, desktop::{ HandleToken, account::{UserInformation, UserInformationOptions}, request::Response, }, zvariant::{Optional, OwnedObjectPath}, }; #[async_trait] pub trait AccountImpl: RequestImpl { #[doc(alias = "GetUserInformation")] async fn get_user_information( &self, token: HandleToken, app_id: Option, window_identifier: Option, options: UserInformationOptions, ) -> Result; } pub(crate) struct AccountInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl AccountInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Account")] impl AccountInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 1 } #[zbus(name = "GetUserInformation")] #[zbus(out_args("response", "results"))] async fn get_user_information( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, options: UserInformationOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "Account::GetUserInformation", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.get_user_information( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), options, ) .await }, ) .await } } ashpd-0.13.11/src/backend/app_chooser.rs000064400000000000000000000111001046102023000161050ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use crate::{ ActivationToken, MaybeAppID, PortalError, Uri, WindowIdentifierType, backend::request::{Request, RequestImpl}, desktop::{HandleToken, Response}, zvariant::{ Optional, OwnedObjectPath, Type, as_value::{self, optional}, }, }; #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] pub struct ChooserOptions { #[serde(default, with = "optional")] last_choice: Option, #[serde(default, with = "optional")] modal: Option, #[serde(default, with = "optional")] content_type: Option, #[serde(default, with = "optional")] uri: Option, #[serde(default, with = "optional")] filename: Option, #[serde(default, with = "optional")] activation_token: Option, } impl ChooserOptions { pub fn last_choice(&self) -> Option<&MaybeAppID> { self.last_choice.as_ref() } pub fn modal(&self) -> Option { self.modal } pub fn content_type(&self) -> Option<&str> { self.content_type.as_deref() } pub fn uri(&self) -> Option<&Uri> { self.uri.as_ref() } pub fn filename(&self) -> Option<&str> { self.filename.as_deref() } pub fn activation_token(&self) -> Option<&ActivationToken> { self.activation_token.as_ref() } } #[derive(Debug, Serialize, Type)] #[zvariant(signature = "dict")] pub struct Choice { #[serde(with = "as_value")] choice: MaybeAppID, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] activation_token: Option, } impl Choice { pub fn new(choice: MaybeAppID) -> Self { Self { choice, activation_token: None, } } #[must_use] pub fn activation_token( mut self, activation_token: impl Into>, ) -> Self { self.activation_token = activation_token.into(); self } } #[async_trait] pub trait AppChooserImpl: RequestImpl { #[doc(alias = "ChooseApplication")] async fn choose_application( &self, token: HandleToken, app_id: Option, parent_window: Option, choices: Vec, options: ChooserOptions, ) -> Result; #[doc(alias = "UpdateChoices")] async fn update_choices( &self, token: HandleToken, choices: Vec, ) -> Result<(), PortalError>; } pub(crate) struct AppChooserInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl AppChooserInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.AppChooser")] impl AppChooserInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 2 } #[zbus(out_args("response", "results"))] async fn choose_application( &self, handle: OwnedObjectPath, app_id: Optional, parent_window: Optional, choices: Vec, options: ChooserOptions, ) -> Result, PortalError> { let imp = Arc::clone(&self.imp); Request::spawn( "AppChooser::ChooseApplication", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.choose_application( HandleToken::try_from(&handle).unwrap(), app_id.into(), parent_window.into(), choices, options, ) .await }, ) .await } async fn update_choices( &self, handle: OwnedObjectPath, choices: Vec, ) -> Result<(), PortalError> { #[cfg(feature = "tracing")] tracing::debug!("AppChooser::UpdateChoices"); let token = HandleToken::try_from(&handle).unwrap(); let response = self.imp.update_choices(token, choices).await; #[cfg(feature = "tracing")] tracing::debug!("AppChooser::UpdateChoices returned {:#?}", response); response } } ashpd-0.13.11/src/backend/background.rs000064400000000000000000000115421046102023000157340ustar 00000000000000use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use enumflags2::{BitFlags, bitflags}; use serde::Serialize; use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::{ MaybeAppID, PortalError, backend::request::{Request, RequestImpl}, desktop::{HandleToken, Response}, zbus::object_server::SignalEmitter, zvariant::{OwnedObjectPath, Type, as_value}, }; #[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Type)] #[repr(u32)] pub enum Activity { Forbid = 0, Allow = 1, AllowInstance = 2, } #[derive(Debug, Serialize, Type)] #[zvariant(signature = "dict")] pub struct Background { #[serde(with = "as_value")] result: Activity, } impl Background { pub fn new(activity: Activity) -> Self { Self { result: activity } } } #[derive(Serialize_repr, Copy, Clone, PartialEq, Eq, Debug, Type)] #[repr(u32)] pub enum AppState { Background = 0, Running = 1, Active = 2, } #[bitflags] #[derive(Deserialize_repr, PartialEq, Eq, Copy, Clone, Debug, Type)] #[repr(u32)] pub enum AutoStartFlags { DBusActivation = 1, } #[async_trait] pub trait BackgroundSignalEmitter: Send + Sync { #[doc(alias = "RunningApplicationsChanged")] async fn emit_changed(&self) -> zbus::Result<()>; } #[async_trait] pub trait BackgroundImpl: RequestImpl { #[doc(alias = "GetAppState")] async fn get_app_state(&self) -> Result, PortalError>; #[doc(alias = "NotifyBackground")] async fn notify_background( &self, token: HandleToken, app_id: MaybeAppID, name: &str, ) -> Result; #[doc(alias = "EnableAutostart")] async fn enable_autostart( &self, app_id: MaybeAppID, enable: bool, commandline: Vec, flags: BitFlags, ) -> Result; // Set the signal emitter, allowing to notify of changes. fn set_signal_emitter(&mut self, signal_emitter: Arc); } pub(crate) struct BackgroundInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl BackgroundInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } pub async fn changed(&self) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; Self::running_applications_changed(iface_ref.signal_emitter()).await } } #[async_trait] impl BackgroundSignalEmitter for BackgroundInterface { async fn emit_changed(&self) -> zbus::Result<()> { self.changed().await } } #[zbus::interface(name = "org.freedesktop.impl.portal.Background")] impl BackgroundInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 2 } #[zbus(out_args("apps"))] async fn get_app_state(&self) -> Result, PortalError> { #[cfg(feature = "tracing")] tracing::debug!("Background::GetAppState"); let response = self.imp.get_app_state().await; #[cfg(feature = "tracing")] tracing::debug!("Background::GetAppState returned {:#?}", response); response } #[zbus(out_args("response", "results"))] async fn notify_background( &self, handle: OwnedObjectPath, app_id: MaybeAppID, name: String, ) -> Result, PortalError> { let imp = Arc::clone(&self.imp); Request::spawn( "Background::NotifyBackground", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.notify_background(HandleToken::try_from(&handle).unwrap(), app_id, &name) .await }, ) .await } #[zbus(out_args("result"))] async fn enable_autostart( &self, app_id: MaybeAppID, enable: bool, commandline: Vec, flags: BitFlags, ) -> Result { #[cfg(feature = "tracing")] tracing::debug!("Background::EnableAutostart"); let response = self .imp .enable_autostart(app_id, enable, commandline, flags) .await; #[cfg(feature = "tracing")] tracing::debug!("Background::EnableAutostart returned {:#?}", response); response } #[zbus(signal)] async fn running_applications_changed(signal_ctxt: &SignalEmitter<'_>) -> zbus::Result<()>; } ashpd-0.13.11/src/backend/builder.rs000064400000000000000000000376471046102023000152610ustar 00000000000000use std::sync::{Arc, Mutex}; use enumflags2::BitFlags; use futures_util::{StreamExt, task::SpawnExt}; use zbus::names::{OwnedWellKnownName, WellKnownName}; use crate::backend::{Result, session::SessionManager}; pub struct Builder { name: OwnedWellKnownName, flags: BitFlags, #[cfg(feature = "account")] account_impl: Option>, access_impl: Option>, app_chooser_impl: Option>, #[cfg(feature = "background")] background_impl: Option>, #[cfg(feature = "email")] email_impl: Option>, #[cfg(feature = "file_chooser")] file_chooser_impl: Option>, lockdown_impl: Option>, #[cfg(feature = "documents")] permission_store_impl: Option>, #[cfg(feature = "print")] print_impl: Option>, #[cfg(feature = "screencast")] screencast_impl: Option>, #[cfg(feature = "screenshot")] screenshot_impl: Option>, #[cfg(feature = "secret")] secret_impl: Option>, #[cfg(feature = "settings")] settings_impl: Option>, #[cfg(feature = "wallpaper")] wallpaper_impl: Option>, #[cfg(feature = "usb")] usb_impl: Option>, spawn: Option>, name_lost: Option>, sessions: Arc>, } impl Builder { pub fn new<'a, W>(well_known_name: W) -> zbus::Result where W: TryInto>, >>::Error: Into, { let well_known_name = well_known_name.try_into().map_err(Into::into)?; Ok(Self { name: well_known_name.into(), // same flags as zbus::Connection::request_name flags: zbus::fdo::RequestNameFlags::ReplaceExisting | zbus::fdo::RequestNameFlags::DoNotQueue, #[cfg(feature = "account")] account_impl: None, access_impl: None, app_chooser_impl: None, #[cfg(feature = "background")] background_impl: None, #[cfg(feature = "email")] email_impl: None, #[cfg(feature = "file_chooser")] file_chooser_impl: None, lockdown_impl: None, #[cfg(feature = "documents")] permission_store_impl: None, #[cfg(feature = "print")] print_impl: None, #[cfg(feature = "screencast")] screencast_impl: None, #[cfg(feature = "screenshot")] screenshot_impl: None, #[cfg(feature = "secret")] secret_impl: None, #[cfg(feature = "settings")] settings_impl: None, #[cfg(feature = "wallpaper")] wallpaper_impl: None, #[cfg(feature = "usb")] usb_impl: None, spawn: None, name_lost: None, sessions: Arc::new(Mutex::new(SessionManager::default())), }) } pub fn with_flags(mut self, flags: BitFlags) -> Self { self.flags = flags; self } #[cfg(not(any(feature = "tokio")))] pub fn with_spawn( mut self, spawn: impl futures_util::task::Spawn + Send + Sync + 'static, ) -> Self { self.spawn = Some(Arc::new(spawn)); self } pub fn with_name_lost(mut self, name_lost: impl Fn() + Send + Sync + 'static) -> Self { self.name_lost = Some(Arc::new(name_lost)); self } #[cfg(feature = "account")] pub fn account(mut self, imp: impl crate::backend::account::AccountImpl + 'static) -> Self { self.account_impl = Some(Arc::new(imp)); self } pub fn access(mut self, imp: impl crate::backend::access::AccessImpl + 'static) -> Self { self.access_impl = Some(Arc::new(imp)); self } pub fn app_chooser( mut self, imp: impl crate::backend::app_chooser::AppChooserImpl + 'static, ) -> Self { self.app_chooser_impl = Some(Arc::new(imp)); self } #[cfg(feature = "background")] pub fn background( mut self, imp: impl crate::backend::background::BackgroundImpl + 'static, ) -> Self { self.background_impl = Some(Arc::new(imp)); self } #[cfg(feature = "email")] pub fn email(mut self, imp: impl crate::backend::email::EmailImpl + 'static) -> Self { self.email_impl = Some(Arc::new(imp)); self } #[cfg(feature = "file_chooser")] pub fn file_chooser( mut self, imp: impl crate::backend::file_chooser::FileChooserImpl + 'static, ) -> Self { self.file_chooser_impl = Some(Arc::new(imp)); self } pub fn lockdown(mut self, imp: impl crate::backend::lockdown::LockdownImpl + 'static) -> Self { self.lockdown_impl = Some(Arc::new(imp)); self } #[cfg(feature = "documents")] pub fn permission_store( mut self, imp: impl crate::backend::permission_store::PermissionStoreImpl + 'static, ) -> Self { self.permission_store_impl = Some(Arc::new(imp)); self } #[cfg(feature = "print")] pub fn print(mut self, imp: impl crate::backend::print::PrintImpl + 'static) -> Self { self.print_impl = Some(Arc::new(imp)); self } #[cfg(feature = "screencast")] pub fn screencast( mut self, imp: impl crate::backend::screencast::ScreencastImpl + 'static, ) -> Self { self.screencast_impl = Some(Arc::new(imp)); self } #[cfg(feature = "screenshot")] pub fn screenshot( mut self, imp: impl crate::backend::screenshot::ScreenshotImpl + 'static, ) -> Self { self.screenshot_impl = Some(Arc::new(imp)); self } #[cfg(feature = "secret")] pub fn secret(mut self, imp: impl crate::backend::secret::SecretImpl + 'static) -> Self { self.secret_impl = Some(Arc::new(imp)); self } #[cfg(feature = "settings")] pub fn settings(mut self, imp: impl crate::backend::settings::SettingsImpl + 'static) -> Self { self.settings_impl = Some(Arc::new(imp)); self } #[cfg(feature = "wallpaper")] pub fn wallpaper( mut self, imp: impl crate::backend::wallpaper::WallpaperImpl + 'static, ) -> Self { self.wallpaper_impl = Some(Arc::new(imp)); self } #[cfg(feature = "usb")] pub fn usb(mut self, imp: impl crate::backend::usb::UsbImpl + 'static) -> Self { self.usb_impl = Some(Arc::new(imp)); self } pub async fn build(self) -> Result<()> { let connection = crate::proxy::Proxy::connection().await?; self.build_with_connection(connection).await } pub async fn build_with_connection(self, connection: zbus::Connection) -> Result<()> { #[cfg(feature = "tokio")] let spawn = self.spawn.unwrap_or(Arc::new(super::spawn::TokioSpawner)); #[cfg(not(feature = "tokio"))] let spawn = self .spawn .expect("Must provide a spawner when not using tokio"); if let Some(name_lost) = self.name_lost { let proxy = zbus::fdo::DBusProxy::new(&connection).await?; let mut name_lost_stream = proxy.receive_name_lost().await?; if let Err(error) = spawn.spawn(async move { while (name_lost_stream.next().await).is_some() { name_lost(); } }) { return Err(crate::PortalError::Failed(error.to_string())); } } let object_server = connection.object_server(); #[cfg(feature = "account")] if let Some(imp) = self.account_impl { let portal = crate::backend::account::AccountInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Account`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } if let Some(imp) = self.access_impl { let portal = crate::backend::access::AccessInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Access`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } if let Some(imp) = self.app_chooser_impl { let portal = crate::backend::app_chooser::AppChooserInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.AppChooser`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "background")] if let Some(imp) = self.background_impl { let portal = crate::backend::background::BackgroundInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Background`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "email")] if let Some(imp) = self.email_impl { let portal = crate::backend::email::EmailInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Email`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "file_chooser")] if let Some(imp) = self.file_chooser_impl { let portal = crate::backend::file_chooser::FileChooserInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.FileChooser`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } if let Some(imp) = self.lockdown_impl { let portal = crate::backend::lockdown::LockdownInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Lockdown`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "documents")] if let Some(imp) = self.permission_store_impl { let portal = crate::backend::permission_store::PermissionStoreInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.PermissionStore`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "print")] if let Some(imp) = self.print_impl { let portal = crate::backend::print::PrintInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Print`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "screencast")] if let Some(imp) = self.screencast_impl { let portal = crate::backend::screencast::ScreencastInterface::new( imp, connection.clone(), Arc::clone(&spawn), Arc::clone(&self.sessions), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.ScreenCast`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "screenshot")] if let Some(imp) = self.screenshot_impl { let portal = crate::backend::screenshot::ScreenshotInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Screenshot`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "secret")] if let Some(imp) = self.secret_impl { let portal = crate::backend::secret::SecretInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Secret`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "settings")] if let Some(imp) = self.settings_impl { let portal = crate::backend::settings::SettingsInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Settings`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "wallpaper")] if let Some(imp) = self.wallpaper_impl { let portal = crate::backend::wallpaper::WallpaperInterface::new( imp, connection.clone(), Arc::clone(&spawn), ); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Wallpaper`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } #[cfg(feature = "usb")] if let Some(imp) = self.usb_impl { let portal = crate::backend::usb::UsbInterface::new(imp, connection.clone(), Arc::clone(&spawn)); #[cfg(feature = "tracing")] tracing::debug!("Serving interface `org.freedesktop.impl.portal.Usb`"); object_server .at("/org/freedesktop/portal/desktop", portal) .await?; } connection .request_name_with_flags(self.name, self.flags) .await?; Ok(()) } } ashpd-0.13.11/src/backend/email.rs000064400000000000000000000037171046102023000147110ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; use crate::{ MaybeAppID, WindowIdentifierType, backend::{ Result, request::{Request, RequestImpl}, }, desktop::{HandleToken, email::EmailOptions, request::Response}, zvariant::{Optional, OwnedObjectPath}, }; #[async_trait] pub trait EmailImpl: RequestImpl { #[doc(alias = "ComposeEmail")] async fn compose( &self, token: HandleToken, app_id: Option, window_identifier: Option, options: EmailOptions, ) -> Result<()>; } pub(crate) struct EmailInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl EmailInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Email")] impl EmailInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 4 } #[zbus(out_args("response", "results"))] async fn compose_email( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, options: EmailOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "Email::ComposeEmail", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.compose( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), options, ) .await }, ) .await } } ashpd-0.13.11/src/backend/file_chooser.rs000064400000000000000000000107231046102023000162560ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; use crate::{ MaybeAppID, WindowIdentifierType, backend::{ Result, request::{Request, RequestImpl}, }, desktop::{ HandleToken, file_chooser::{OpenFileOptions, SaveFileOptions, SaveFilesOptions, SelectedFiles}, request::Response, }, zvariant::{Optional, OwnedObjectPath}, }; #[async_trait] pub trait FileChooserImpl: RequestImpl { #[doc(alias = "OpenFile")] async fn open_file( &self, token: HandleToken, app_id: Option, window_identifier: Option, title: &str, options: OpenFileOptions, ) -> Result; #[doc(alias = "SaveFile")] async fn save_file( &self, token: HandleToken, app_id: Option, window_identifier: Option, title: &str, options: SaveFileOptions, ) -> Result; #[doc(alias = "SaveFiles")] async fn save_files( &self, token: HandleToken, app_id: Option, window_identifier: Option, title: &str, options: SaveFilesOptions, ) -> Result; } pub(crate) struct FileChooserInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl FileChooserInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.FileChooser")] impl FileChooserInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 4 } #[zbus(out_args("response", "results"))] async fn open_file( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, title: String, options: OpenFileOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "FileChooser::OpenFile", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.open_file( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), &title, options, ) .await }, ) .await } #[zbus(out_args("response", "results"))] async fn save_file( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, title: String, options: SaveFileOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "FileChooser::SaveFile", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.save_file( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), &title, options, ) .await }, ) .await } #[zbus(out_args("response", "results"))] async fn save_files( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, title: String, options: SaveFilesOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "FileChooser::SaveFiles", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.save_files( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), &title, options, ) .await }, ) .await } } ashpd-0.13.11/src/backend/lockdown.rs000064400000000000000000000157361046102023000154460ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; #[async_trait] pub trait LockdownImpl: Send + Sync { #[doc(alias = "disable-printing")] async fn disable_printing(&self) -> bool; #[doc(alias = "disable-printing")] async fn set_disable_printing(&self, disable_printing: bool) -> zbus::Result<()>; #[doc(alias = "disable-save-to-disk")] async fn disable_save_to_disk(&self) -> bool; #[doc(alias = "disable-save-to-disk")] async fn set_disable_save_to_disk(&self, disable_save_to_disk: bool) -> zbus::Result<()>; #[doc(alias = "disable-application-handlers")] async fn disable_application_handlers(&self) -> bool; #[doc(alias = "disable-application-handlers")] async fn set_disable_application_handlers( &self, disable_application_handlers: bool, ) -> zbus::Result<()>; #[doc(alias = "disable-location")] async fn disable_location(&self) -> bool; #[doc(alias = "disable-location")] async fn set_disable_location(&self, disable_location: bool) -> zbus::Result<()>; #[doc(alias = "disable-camera")] async fn disable_camera(&self) -> bool; #[doc(alias = "disable-camera")] async fn set_disable_camera(&self, disable_camera: bool) -> zbus::Result<()>; #[doc(alias = "disable-microphone")] async fn disable_microphone(&self) -> bool; #[doc(alias = "disable-microphone")] async fn set_disable_microphone(&self, disable_microphone: bool) -> zbus::Result<()>; #[doc(alias = "disable-sound-output")] async fn disable_sound_output(&self) -> bool; #[doc(alias = "disable-sound-output")] async fn set_disable_sound_output(&self, disable_sound_output: bool) -> zbus::Result<()>; } pub(crate) struct LockdownInterface { imp: Arc, #[allow(dead_code)] cnx: zbus::Connection, #[allow(dead_code)] spawn: Arc, } impl LockdownInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Lockdown")] impl LockdownInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 1 } #[zbus(property, name = "disable-printing")] async fn disable_printing(&self) -> bool { self.imp.disable_printing().await } #[zbus(property, name = "disable-printing")] async fn set_disable_printing(&self, disable_printing: bool) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; let ctxt = iface_ref.signal_emitter(); self.imp.set_disable_printing(disable_printing).await?; self.disable_printing_changed(ctxt).await?; Ok(()) } #[zbus(property, name = "disable-save-to-disk")] async fn disable_save_to_disk(&self) -> bool { self.imp.disable_save_to_disk().await } #[zbus(property, name = "disable-save-to-disk")] async fn set_disable_save_to_disk(&self, disable_save_to_disk: bool) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; let ctxt = iface_ref.signal_emitter(); self.imp .set_disable_save_to_disk(disable_save_to_disk) .await?; self.disable_save_to_disk_changed(ctxt).await?; Ok(()) } #[zbus(property, name = "disable-application-handlers")] async fn disable_application_handlers(&self) -> bool { self.imp.disable_application_handlers().await } #[zbus(property, name = "disable-application-handlers")] async fn set_disable_application_handlers( &self, disable_application_handlers: bool, ) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; let ctxt = iface_ref.signal_emitter(); self.imp .set_disable_application_handlers(disable_application_handlers) .await?; self.disable_application_handlers_changed(ctxt).await?; Ok(()) } #[zbus(property, name = "disable-location")] async fn disable_location(&self) -> bool { self.imp.disable_location().await } #[zbus(property, name = "disable-location")] async fn set_disable_location(&self, disable_location: bool) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; let ctxt = iface_ref.signal_emitter(); self.imp.set_disable_location(disable_location).await?; self.disable_location_changed(ctxt).await?; Ok(()) } #[zbus(property, name = "disable-camera")] async fn disable_camera(&self) -> bool { self.imp.disable_camera().await } #[zbus(property, name = "disable-camera")] async fn set_disable_camera(&self, disable_camera: bool) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; let ctxt = iface_ref.signal_emitter(); self.imp.set_disable_camera(disable_camera).await?; self.disable_camera_changed(ctxt).await?; Ok(()) } #[zbus(property, name = "disable-microphone")] async fn disable_microphone(&self) -> bool { self.imp.disable_microphone().await } #[zbus(property, name = "disable-microphone")] async fn set_disable_microphone(&self, disable_microphone: bool) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; let ctxt = iface_ref.signal_emitter(); self.imp.set_disable_microphone(disable_microphone).await?; self.disable_microphone_changed(ctxt).await?; Ok(()) } #[zbus(property, name = "disable-sound-output")] async fn disable_sound_output(&self) -> bool { self.imp.disable_sound_output().await } #[zbus(property, name = "disable-sound-output")] async fn set_disable_sound_output(&self, disable_sound_output: bool) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; let ctxt = iface_ref.signal_emitter(); self.imp .set_disable_sound_output(disable_sound_output) .await?; self.disable_sound_output_changed(ctxt).await?; Ok(()) } } ashpd-0.13.11/src/backend/mod.rs000064400000000000000000000025711046102023000143760ustar 00000000000000pub type Result = std::result::Result; pub mod access; #[cfg(feature = "account")] #[cfg_attr(docsrs, doc(cfg(feature = "account")))] pub mod account; pub mod app_chooser; #[cfg(feature = "background")] #[cfg_attr(docsrs, doc(cfg(feature = "background")))] pub mod background; mod builder; pub use builder::Builder; #[cfg(feature = "email")] #[cfg_attr(docsrs, doc(cfg(feature = "email")))] pub mod email; #[cfg(feature = "file_chooser")] #[cfg_attr(docsrs, doc(cfg(feature = "file_chooser")))] pub mod file_chooser; pub mod lockdown; #[cfg(feature = "documents")] #[cfg_attr(docsrs, doc(cfg(feature = "documents")))] pub mod permission_store; #[cfg(feature = "print")] #[cfg_attr(docsrs, doc(cfg(feature = "print")))] pub mod print; pub mod request; #[cfg(feature = "screencast")] #[cfg_attr(docsrs, doc(cfg(feature = "screencast")))] pub mod screencast; #[cfg(feature = "screenshot")] #[cfg_attr(docsrs, doc(cfg(feature = "screenshot")))] pub mod screenshot; #[cfg(feature = "secret")] #[cfg_attr(docsrs, doc(cfg(feature = "secret")))] pub mod secret; pub mod session; #[cfg(feature = "settings")] #[cfg_attr(docsrs, doc(cfg(feature = "settings")))] pub mod settings; mod spawn; #[cfg(feature = "usb")] #[cfg_attr(docsrs, doc(cfg(feature = "usb")))] pub mod usb; #[cfg(feature = "wallpaper")] #[cfg_attr(docsrs, doc(cfg(feature = "wallpaper")))] pub mod wallpaper; ashpd-0.13.11/src/backend/permission_store.rs000064400000000000000000000175651046102023000172340ustar 00000000000000use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use crate::{ MaybeAppID, PortalError, documents::{DocumentID, Permission}, zbus::object_server::SignalEmitter, zvariant::{OwnedValue, Value}, }; #[async_trait] pub trait PermissionStoreSignalEmitter: Send + Sync { async fn emit_document_changed( &self, table: &str, id: DocumentID, deleted: bool, data: Value<'_>, permissions: HashMap>, ) -> zbus::Result<()>; } #[async_trait] pub trait PermissionStoreImpl: Send + Sync { #[doc(alias = "Lookup")] async fn lookup( &self, table: &str, id: DocumentID, ) -> Result<(HashMap>, OwnedValue), PortalError>; #[doc(alias = "Set")] async fn set( &self, table: &str, create: bool, id: DocumentID, app_permissions: HashMap>, data: Value<'_>, ) -> Result<(), PortalError>; #[doc(alias = "Delete")] async fn delete(&self, table: &str, id: DocumentID) -> Result<(), PortalError>; #[doc(alias = "SetValue")] async fn set_value( &self, table: &str, create: bool, id: DocumentID, data: Value<'_>, ) -> Result<(), PortalError>; #[doc(alias = "List")] async fn list(&self, table: &str) -> Result, PortalError>; #[doc(alias = "GetPermission")] async fn get_permission( &self, table: &str, id: DocumentID, app: MaybeAppID, ) -> Result, PortalError>; #[doc(alias = "SetPermission")] async fn set_permission( &self, table: &str, create: bool, id: DocumentID, app: MaybeAppID, permissions: Vec, ) -> Result<(), PortalError>; #[doc(alias = "DeletePermission")] async fn delete_permission( &self, table: &str, id: DocumentID, app: MaybeAppID, ) -> Result<(), PortalError>; // Set the signal emitter, allowing to notify of changes. fn set_signal_emitter(&mut self, signal_emitter: Arc); } pub(crate) struct PermissionStoreInterface { imp: Arc, #[allow(dead_code)] cnx: zbus::Connection, #[allow(dead_code)] spawn: Arc, } impl PermissionStoreInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } pub async fn document_changed( &self, table: &str, id: DocumentID, deleted: bool, data: Value<'_>, permissions: HashMap>, ) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; Self::changed( iface_ref.signal_emitter(), table, id, deleted, data, permissions, ) .await } } #[async_trait] impl PermissionStoreSignalEmitter for PermissionStoreInterface { #[doc(alias = "Changed")] async fn emit_document_changed( &self, table: &str, id: DocumentID, deleted: bool, data: Value<'_>, permissions: HashMap>, ) -> zbus::Result<()> { self.document_changed(table, id, deleted, data, permissions) .await } } #[zbus::interface(name = "org.freedesktop.impl.portal.PermissionStore")] impl PermissionStoreInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 2 } #[zbus(out_args("permissions", "data"))] async fn lookup( &self, table: &str, id: DocumentID, ) -> Result<(HashMap>, OwnedValue), PortalError> { #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::Lookup"); let response = self.imp.lookup(table, id).await; #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::Lookup returned {:#?}", response); response } async fn set( &self, table: &str, create: bool, id: DocumentID, app_permissions: HashMap>, data: Value<'_>, ) -> Result<(), PortalError> { #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::Set"); let response = self.imp.set(table, create, id, app_permissions, data).await; #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::Set returned {:#?}", response); response } async fn set_value( &self, table: &str, create: bool, id: DocumentID, data: Value<'_>, ) -> Result<(), PortalError> { #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::SetValue"); let response = self.imp.set_value(table, create, id, data).await; #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::SetValue returned {:#?}", response); response } #[zbus(out_args("ids"))] async fn list(&self, table: &str) -> Result, PortalError> { #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::List"); let response = self.imp.list(table).await; #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::List returned {:#?}", response); response } #[zbus(out_args("permissions"))] async fn get_permission( &self, table: &str, id: DocumentID, app: MaybeAppID, ) -> Result, PortalError> { #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::GetPermission"); let response = self.imp.get_permission(table, id, app).await; #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::GetPermission returned {:#?}", response); response } async fn set_permission( &self, table: &str, create: bool, id: DocumentID, app: MaybeAppID, permissions: Vec, ) -> Result<(), PortalError> { #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::SetPermission"); let response = self .imp .set_permission(table, create, id, app, permissions) .await; #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::SetPermission returned {:#?}", response); response } async fn delete_permission( &self, table: &str, id: DocumentID, app: MaybeAppID, ) -> Result<(), PortalError> { #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::DeletePermission"); let response = self.imp.delete_permission(table, id, app).await; #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::DeletePermission returned {:#?}", response); response } async fn delete(&self, table: &str, id: DocumentID) -> Result<(), PortalError> { #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::Delete"); let response = self.imp.delete(table, id).await; #[cfg(feature = "tracing")] tracing::debug!("PermissionStore::Delete returned {:#?}", response); response } #[zbus(signal)] async fn changed( signal_ctxt: &SignalEmitter<'_>, table: &str, id: DocumentID, deleted: bool, data: Value<'_>, permissions: HashMap>, ) -> zbus::Result<()>; } ashpd-0.13.11/src/backend/print.rs000064400000000000000000000071711046102023000147540ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; use crate::{ MaybeAppID, WindowIdentifierType, backend::{ Result, request::{Request, RequestImpl}, }, desktop::{ HandleToken, print::{PageSetup, PreparePrint, PreparePrintOptions, PrintOptions, Settings}, request::Response, }, zvariant::{self, Optional, OwnedObjectPath}, }; #[async_trait] pub trait PrintImpl: RequestImpl { #[allow(clippy::too_many_arguments)] #[doc(alias = "PreparePrint")] async fn prepare_print( &self, token: HandleToken, app_id: Option, parent_window: Option, title: String, settings: Settings, page_setup: PageSetup, options: PreparePrintOptions, ) -> Result; #[doc(alias = "Print")] async fn print( &self, token: HandleToken, app_id: Option, parent_window: Option, title: String, fd: zvariant::OwnedFd, options: PrintOptions, ) -> Result<()>; } pub(crate) struct PrintInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl PrintInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Print")] impl PrintInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 3 } #[allow(clippy::too_many_arguments)] #[zbus(out_args("response", "results"))] async fn prepare_print( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, title: String, settings: Settings, page_setup: PageSetup, options: PreparePrintOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "Print::PreparePrint", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.prepare_print( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), title, settings, page_setup, options, ) .await }, ) .await } #[allow(clippy::too_many_arguments)] #[zbus(out_args("response", "results"))] async fn print( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, title: String, fd: zvariant::OwnedFd, options: PrintOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "Print::Print", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.print( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), title, fd, options, ) .await }, ) .await } } ashpd-0.13.11/src/backend/request.rs000064400000000000000000000073511046102023000153100ustar 00000000000000use std::{boxed::Box, future::Future, sync::Arc}; use async_trait::async_trait; use futures_util::{ future::{AbortHandle, abortable}, lock::Mutex, task::SpawnExt, }; use zbus::zvariant::{ObjectPath, OwnedObjectPath}; use crate::{ PortalError, desktop::{HandleToken, Response}, }; #[async_trait] pub trait RequestImpl: Send + Sync { #[doc(alias = "Close")] async fn close(&self, token: HandleToken); } pub struct Request { close_cb: Mutex>>, path: OwnedObjectPath, abort_handle: AbortHandle, #[allow(dead_code)] cnx: zbus::Connection, } impl Request { pub fn path(&self) -> ObjectPath<'_> { self.path.as_ref() } pub(crate) async fn spawn( _method: &'static str, cnx: &zbus::Connection, path: OwnedObjectPath, imp: Arc, spawn: Arc, callback: impl Future>, ) -> crate::backend::Result> where R: RequestImpl + 'static + ?Sized, T: std::fmt::Debug, { #[cfg(feature = "tracing")] tracing::debug!("{_method}"); let (fut, abort_handle) = abortable(callback); let token = HandleToken::try_from(&path).unwrap(); let close_cb = move || { if let Err(err) = spawn.spawn(async move { RequestImpl::close(&*imp, token).await; }) { #[cfg(feature = "tracing")] tracing::error!("Failed to spawn request closer: {}", err); let _ = err; } }; let request = Request::new(close_cb, path.clone(), abort_handle, cnx.clone()); let server = cnx.object_server(); #[cfg(feature = "tracing")] tracing::debug!( "Serving `org.freedesktop.impl.portal.Request` at {:?}", path.as_str() ); server.at(&path, request).await?; let result = fut.await; #[cfg(feature = "tracing")] tracing::debug!("{_method} returned {:#?}", result); #[cfg(feature = "tracing")] tracing::debug!("Releasing request {:?}", path.as_str()); server.remove::(&path).await?; let response = match result { Err(_) => Response::cancelled(), Ok(result) => match result { Ok(results) => Response::ok(results), Err(error) => match error { PortalError::Cancelled(_) => Response::cancelled(), other => { return Err(other); } }, }, }; Ok(response) } pub(crate) fn new( close_cb: impl FnOnce() + Send + Sync + 'static, path: OwnedObjectPath, abort_handle: AbortHandle, cnx: zbus::Connection, ) -> Self { Self { close_cb: Mutex::new(Some(Box::new(close_cb))), path, abort_handle, cnx, } } } impl PartialEq for Request { fn eq(&self, other: &Self) -> bool { self.path == other.path } } impl Eq for Request {} #[zbus::interface(name = "org.freedesktop.impl.portal.Request")] impl Request { async fn close( &self, #[zbus(object_server)] server: &zbus::ObjectServer, ) -> zbus::fdo::Result<()> { self.abort_handle.abort(); if let Some(close_cb) = self.close_cb.lock().await.take() { close_cb(); } // Drop the request as it served it purpose once closed #[cfg(feature = "tracing")] tracing::debug!("Releasing request {:?}", self.path.as_str()); server.remove::(&self.path).await?; Ok(()) } } ashpd-0.13.11/src/backend/screencast.rs000064400000000000000000000161131046102023000157460ustar 00000000000000use std::sync::{Arc, Mutex}; use async_trait::async_trait; use enumflags2::BitFlags; use serde::Serialize; use crate::{ MaybeAppID, PortalError, WindowIdentifierType, backend::{ Result, request::{Request, RequestImpl}, session::{CreateSessionResponse, Session, SessionImpl, SessionManager}, }, desktop::{ CreateSessionOptions, HandleToken, request::Response, screencast::{CursorMode, SelectSourcesOptions, SourceType, StartCastOptions, Streams}, }, zvariant::{Optional, OwnedObjectPath, Type}, }; #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] pub struct SelectSourcesResponse {} #[async_trait] pub trait ScreencastImpl: RequestImpl + SessionImpl { #[doc(alias = "AvailableSourceTypes")] fn available_source_types(&self) -> BitFlags; #[doc(alias = "AvailableCursorModes")] fn available_cursor_mode(&self) -> BitFlags; #[doc(alias = "CreateSession")] async fn create_session( &self, token: HandleToken, session_token: HandleToken, app_id: Option, options: CreateSessionOptions, ) -> Result; #[doc(alias = "SelectSources")] async fn select_sources( &self, session_token: HandleToken, app_id: Option, options: SelectSourcesOptions, ) -> Result; #[doc(alias = "Start")] async fn start_cast( &self, session_token: HandleToken, app_id: Option, window_identifier: Option, options: StartCastOptions, ) -> Result; } pub(crate) struct ScreencastInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, sessions: Arc>, } impl ScreencastInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, sessions: Arc>, ) -> Self { Self { imp, cnx, spawn, sessions, } } } #[zbus::interface(name = "org.freedesktop.impl.portal.ScreenCast")] impl ScreencastInterface { #[zbus( property(emits_changed_signal = "const"), name = "AvailableSourceTypes" )] fn available_source_types(&self) -> u32 { let imp = Arc::clone(&self.imp); imp.available_source_types().bits() } #[zbus( property(emits_changed_signal = "const"), name = "AvailableCursorModes" )] fn available_cursor_mode(&self) -> u32 { let imp = Arc::clone(&self.imp); imp.available_cursor_mode().bits() } #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 5 } #[zbus(name = "CreateSession")] #[zbus(out_args("response", "results"))] async fn create_session( &self, handle: OwnedObjectPath, session_handle: OwnedObjectPath, app_id: Optional, options: CreateSessionOptions, ) -> Result> { let session_token = HandleToken::try_from(&session_handle).unwrap(); { let sessions = self.sessions.lock().unwrap(); if sessions.contains(&session_token) { let errormsg = format!("A session with handle `{session_token}` already exists"); #[cfg(feature = "tracing")] tracing::error!("ScreencastInterface::create_session: {}", errormsg); return Err(PortalError::Exist(errormsg)); } } let imp = Arc::clone(&self.imp); let token = session_token.clone(); let result = Request::spawn( "ScreenCast::CreateSession", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.create_session( HandleToken::try_from(&handle).unwrap(), token, app_id.into(), options, ) .await }, ) .await; if result.is_ok() { #[cfg(feature = "tracing")] tracing::debug!( "ScreencastInterface::create_session: session with handle `{session_token}` created" ); let session = Session::new( session_handle, Arc::clone(&self.sessions), Some(Arc::clone(&self.imp) as Arc), ); session.serve(self.cnx.clone()).await?; { let mut sessions = self.sessions.lock().unwrap(); sessions.add(session); } } else { #[cfg(feature = "tracing")] tracing::error!( "ScreencastInterface::create_session: failed to create a session with handle `{session_token}`" ); } result } #[zbus(name = "SelectSources")] #[zbus(out_args("response", "results"))] async fn select_sources( &self, handle: OwnedObjectPath, session_handle: OwnedObjectPath, app_id: Optional, options: SelectSourcesOptions, ) -> Result> { let session_token = HandleToken::try_from(&session_handle).unwrap(); { let sessions = self.sessions.lock().unwrap(); sessions.try_contains(&session_token)?; } let imp = Arc::clone(&self.imp); Request::spawn( "ScreenCast::SelectSources", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.select_sources(session_token, app_id.into(), options) .await }, ) .await } #[zbus(name = "Start")] #[zbus(out_args("response", "results"))] async fn start( &self, handle: OwnedObjectPath, session_handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, options: StartCastOptions, ) -> Result> { let session_token = HandleToken::try_from(&session_handle).unwrap(); { let sessions = self.sessions.lock().unwrap(); sessions.try_contains(&session_token)?; } let imp = Arc::clone(&self.imp); Request::spawn( "ScreenCast::Start", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.start_cast( session_token, app_id.into(), window_identifier.into(), options, ) .await }, ) .await } } ashpd-0.13.11/src/backend/screenshot.rs000064400000000000000000000063631046102023000157770ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; use crate::{ MaybeAppID, WindowIdentifierType, backend::{ Result, request::{Request, RequestImpl}, }, desktop::{ Color, HandleToken, request::Response, screenshot::{ColorOptions, Screenshot as ScreenshotResponse, ScreenshotOptions}, }, zvariant::{Optional, OwnedObjectPath}, }; #[async_trait] pub trait ScreenshotImpl: RequestImpl { #[doc(alias = "Screenshot")] async fn screenshot( &self, token: HandleToken, app_id: Option, window_identifier: Option, options: ScreenshotOptions, ) -> Result; #[doc(alias = "PickColor")] async fn pick_color( &self, token: HandleToken, app_id: Option, window_identifier: Option, options: ColorOptions, ) -> Result; } pub(crate) struct ScreenshotInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl ScreenshotInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Screenshot")] impl ScreenshotInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 2 } #[zbus(name = "Screenshot")] #[zbus(out_args("response", "results"))] async fn screenshot( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, options: ScreenshotOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "Screenshot::Screenshot", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.screenshot( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), options, ) .await }, ) .await } #[zbus(name = "PickColor")] #[zbus(out_args("response", "results"))] async fn pick_color( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, options: ColorOptions, ) -> Result> { let imp = Arc::clone(&self.imp); Request::spawn( "Screenshot::PickColor", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.pick_color( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), options, ) .await }, ) .await } } ashpd-0.13.11/src/backend/secret.rs000064400000000000000000000036121046102023000151010ustar 00000000000000use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use zbus::zvariant::{self, OwnedValue}; use crate::{ MaybeAppID, backend::{ Result, request::{Request, RequestImpl}, }, desktop::{HandleToken, Response}, }; #[async_trait] pub trait SecretImpl: RequestImpl { #[doc(alias = "RetrieveSecret")] async fn retrieve( &self, token: HandleToken, app_id: MaybeAppID, fd: std::os::fd::OwnedFd, ) -> Result>; } pub(crate) struct SecretInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl SecretInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Secret")] impl SecretInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 1 } #[zbus(out_args("response", "results"))] async fn retrieve_secret( &self, handle: zvariant::OwnedObjectPath, app_id: MaybeAppID, fd: zvariant::OwnedFd, _options: HashMap, ) -> Result>> { let imp = Arc::clone(&self.imp); Request::spawn( "Secret::RetrieveSecret", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.retrieve( HandleToken::try_from(&handle).unwrap(), app_id, std::os::fd::OwnedFd::from(fd), ) .await }, ) .await } } ashpd-0.13.11/src/backend/session.rs000064400000000000000000000131451046102023000153010ustar 00000000000000use std::{ collections::HashMap, sync::{Arc, Mutex}, }; use async_trait::async_trait; use serde::Serialize; use zbus::{ object_server::SignalEmitter, zvariant::{OwnedObjectPath, Type, as_value}, }; use crate::{PortalError, desktop::HandleToken}; pub(crate) struct Session { path: OwnedObjectPath, manager: Arc>, monitor: Option>, } impl Session { pub(crate) fn new( path: OwnedObjectPath, manager: Arc>, monitor: Option>, ) -> Self { Self { path, manager, monitor, } } pub fn token(&self) -> HandleToken { HandleToken::try_from(&self.path).unwrap() } pub async fn serve(&self, cnx: zbus::Connection) -> zbus::Result { let interface = SessionInterface::new( self.path.clone(), Arc::clone(&self.manager), self.monitor.clone(), ); cnx.object_server().at(&self.path, interface).await } } impl PartialEq for Session { fn eq(&self, other: &Self) -> bool { self.path == other.path } } impl std::fmt::Debug for Session { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Session").field("path", &self.path).finish() } } struct SessionInterface { path: OwnedObjectPath, manager: Arc>, monitor: Option>, } impl SessionInterface { fn new( path: OwnedObjectPath, manager: Arc>, monitor: Option>, ) -> Self { Self { path, manager, monitor, } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Session")] impl SessionInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 1 } #[doc(alias = "Close")] async fn close( &self, #[zbus(object_server)] server: &zbus::ObjectServer, ) -> zbus::fdo::Result<()> { #[cfg(feature = "tracing")] tracing::debug!("SessionInterface::Close {}", self.path.as_str()); let token = HandleToken::try_from(&self.path).unwrap(); { // Let the session manager know so it can update // its internal map of tracked sessions. let mut manager = self.manager.lock().unwrap(); let _ = manager.remove(&token); } if let Some(monitor) = &self.monitor { // The backend implements the `SessionImpl` trait, // it wants to be notified that the session was closed. let _ = monitor.session_closed(token).await; } // This method intentionally does *not* emit the `Closed` signal. server.remove::(&self.path).await?; Ok(()) } #[zbus(signal)] async fn closed(signal_emitter: &SignalEmitter<'_>) -> zbus::Result<()>; } #[async_trait] /// A trait that backends that create long-lived sessions should implement /// to be notified when a session has been closed. pub trait SessionImpl: Send + Sync { #[doc(alias = "Closed")] async fn session_closed(&self, session_token: HandleToken) -> crate::backend::Result<()>; } #[derive(Serialize, Type, Debug)] #[zvariant(signature = "dict")] pub struct CreateSessionResponse { #[serde(with = "as_value")] session_id: HandleToken, } impl CreateSessionResponse { pub fn new(token: HandleToken) -> Self { Self { session_id: token } } } #[derive(Default)] // Not thread-safe! If it needs to be accessed from several threads, // consider wrapping it in a Mutex. pub(crate) struct SessionManager { sessions: HashMap, } impl SessionManager { /// Expects to find a tracked session with the given handle. /// Returns a suitable portal error if not. pub fn try_contains(&self, token: &HandleToken) -> crate::backend::Result<&Session> { self.sessions .get(token) .ok_or(PortalError::NotFound(format!("Unknown session: `{token}`"))) } /// Tests whether a session with the given handle token already exists. pub fn contains(&self, token: &HandleToken) -> bool { #[cfg(feature = "tracing")] tracing::debug!("SessionManager::contains: tracked sessions: {:?}", &self); self.try_contains(token).is_ok() } /// Adds a session to the list of tracked sessions. /// Assumes that no session with the same handle token is already tracked /// (if there was one, it is silently discarded and backends are not /// notified). To avoid such a situation, it is recommended to call /// `contains()` prior to instantiating and adding a session. pub fn add(&mut self, session: Session) { let token = session.token(); let _ = self.sessions.insert(token.clone(), session); } /// Removes a session from the list of tracked sessions. /// Returns an error if no session with the given handle token was tracked. pub fn remove(&mut self, token: &HandleToken) -> crate::backend::Result<()> { if self.sessions.remove(token).is_some() { Ok(()) } else { let message = format!("Unknown session: `{token}`"); #[cfg(feature = "tracing")] tracing::error!("{}", message.as_str()); Err(PortalError::NotFound(message)) } } } impl std::fmt::Debug for SessionManager { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.sessions.keys()).finish() } } ashpd-0.13.11/src/backend/settings.rs000064400000000000000000000112021046102023000154460ustar 00000000000000use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use crate::{ PortalError, desktop::{ Color, settings::{ ACCENT_COLOR_SCHEME_KEY, APPEARANCE_NAMESPACE, COLOR_SCHEME_KEY, CONTRAST_KEY, ColorScheme, Contrast, Namespace, }, }, zbus::object_server::SignalEmitter, zvariant::{OwnedValue, Value}, }; #[async_trait] pub trait SettingsSignalEmitter: Send + Sync { #[doc(alias = "SettingChanged")] async fn emit_changed(&self, namespace: &str, key: &str, value: Value<'_>) -> zbus::Result<()>; async fn emit_contrast_changed(&self, contrast: Contrast) -> zbus::Result<()>; async fn emit_accent_color_changed(&self, color: Color) -> zbus::Result<()>; async fn emit_color_scheme_changed(&self, scheme: ColorScheme) -> zbus::Result<()>; } #[async_trait] pub trait SettingsImpl: Send + Sync { #[doc(alias = "ReadAll")] async fn read_all( &self, namespaces: Vec, ) -> Result, PortalError>; #[doc(alias = "Read")] async fn read(&self, namespace: &str, key: &str) -> Result; // Set the signal emitter, allowing to notify of changes. fn set_signal_emitter(&mut self, signal_emitter: Arc); } pub(crate) struct SettingsInterface { imp: Arc, cnx: zbus::Connection, #[allow(dead_code)] spawn: Arc, } impl SettingsInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } pub async fn changed(&self, namespace: &str, key: &str, value: Value<'_>) -> zbus::Result<()> { let object_server = self.cnx.object_server(); let iface_ref = object_server .interface::<_, Self>(crate::proxy::DESKTOP_PATH) .await?; Self::setting_changed(iface_ref.signal_emitter(), namespace, key, value).await } pub async fn contrast_changed(&self, contrast: Contrast) -> zbus::Result<()> { self.changed( APPEARANCE_NAMESPACE, CONTRAST_KEY, OwnedValue::from(contrast).into(), ) .await } pub async fn accent_color_changed(&self, color: Color) -> zbus::Result<()> { self.changed( APPEARANCE_NAMESPACE, ACCENT_COLOR_SCHEME_KEY, OwnedValue::try_from(color).unwrap().into(), ) .await } pub async fn color_scheme_changed(&self, scheme: ColorScheme) -> zbus::Result<()> { self.changed( APPEARANCE_NAMESPACE, COLOR_SCHEME_KEY, OwnedValue::from(scheme).into(), ) .await } } #[async_trait] impl SettingsSignalEmitter for SettingsInterface { async fn emit_changed(&self, namespace: &str, key: &str, value: Value<'_>) -> zbus::Result<()> { self.changed(namespace, key, value).await } async fn emit_contrast_changed(&self, contrast: Contrast) -> zbus::Result<()> { self.contrast_changed(contrast).await } async fn emit_accent_color_changed(&self, color: Color) -> zbus::Result<()> { self.accent_color_changed(color).await } async fn emit_color_scheme_changed(&self, scheme: ColorScheme) -> zbus::Result<()> { self.color_scheme_changed(scheme).await } } #[zbus::interface(name = "org.freedesktop.impl.portal.Settings")] impl SettingsInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 2 } #[zbus(out_args("value"))] async fn read_all( &self, namespaces: Vec, ) -> Result, PortalError> { #[cfg(feature = "tracing")] tracing::debug!("Settings::ReadAll"); let response = self.imp.read_all(namespaces).await; #[cfg(feature = "tracing")] tracing::debug!("Settings::ReadAll returned {:#?}", response); response } #[zbus(out_args("value"))] async fn read(&self, namespace: &str, key: &str) -> Result { #[cfg(feature = "tracing")] tracing::debug!("Settings::Read"); let response = self.imp.read(namespace, key).await; #[cfg(feature = "tracing")] tracing::debug!("Settings::Read returned {:#?}", response); response } #[zbus(signal)] async fn setting_changed( signal_ctxt: &SignalEmitter<'_>, namespace: &str, key: &str, value: Value<'_>, ) -> zbus::Result<()>; } ashpd-0.13.11/src/backend/spawn.rs000064400000000000000000000005251046102023000147440ustar 00000000000000#[cfg(feature = "tokio")] pub struct TokioSpawner; #[cfg(feature = "tokio")] impl futures_util::task::Spawn for TokioSpawner { fn spawn_obj( &self, future: futures_util::task::FutureObj<'static, ()>, ) -> std::result::Result<(), futures_util::task::SpawnError> { tokio::spawn(future); Ok(()) } } ashpd-0.13.11/src/backend/usb.rs000064400000000000000000000054501046102023000144070ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use crate::{ MaybeAppID, WindowIdentifierType, backend::{ Result, request::{Request, RequestImpl}, }, desktop::{HandleToken, request::Response, usb::UsbDevice}, zvariant::{Optional, OwnedObjectPath, Type, as_value::optional}, }; #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] pub struct AcquireDevicesOptions {} #[derive(Debug, Serialize, Deserialize, Type)] #[zvariant(signature = "dict")] pub struct AccessOptions { #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] writable: Option, } impl AccessOptions { pub fn new(is_writable: bool) -> Self { Self { writable: Some(is_writable), } } pub fn is_writable(&self) -> Option { self.writable } } #[async_trait] pub trait UsbImpl: RequestImpl { #[doc(alias = "AcquireDevices")] async fn acquire_devices( &self, token: HandleToken, window_identifier: Option, app_id: Option, devices: Vec<(String, UsbDevice, AccessOptions)>, options: AcquireDevicesOptions, ) -> Result>; } pub(crate) struct UsbInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl UsbInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Usb")] impl UsbInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 1 } #[zbus(name = "AcquireDevices")] #[zbus(out_args("response", "results"))] async fn acquire_devices( &self, handle: OwnedObjectPath, window_identifier: Optional, app_id: Optional, devices: Vec<(String, UsbDevice, AccessOptions)>, options: AcquireDevicesOptions, ) -> Result>> { let imp = Arc::clone(&self.imp); Request::spawn( "Usb::AcquireDevices", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.acquire_devices( HandleToken::try_from(&handle).unwrap(), window_identifier.into(), app_id.into(), devices, options, ) .await }, ) .await } } ashpd-0.13.11/src/backend/wallpaper.rs000064400000000000000000000042201046102023000155770ustar 00000000000000use std::sync::Arc; use async_trait::async_trait; use crate::{ MaybeAppID, Uri, WindowIdentifierType, backend::{ Result, request::{Request, RequestImpl}, }, desktop::{HandleToken, request::ResponseType, wallpaper::WallpaperOptions}, zvariant::{Optional, OwnedObjectPath}, }; #[async_trait] pub trait WallpaperImpl: RequestImpl { #[doc(alias = "SetWallpaperURI")] async fn with_uri( &self, token: HandleToken, app_id: Option, window_identifier: Option, uri: Uri, options: WallpaperOptions, ) -> Result<()>; } pub(crate) struct WallpaperInterface { imp: Arc, spawn: Arc, cnx: zbus::Connection, } impl WallpaperInterface { pub fn new( imp: Arc, cnx: zbus::Connection, spawn: Arc, ) -> Self { Self { imp, cnx, spawn } } } #[zbus::interface(name = "org.freedesktop.impl.portal.Wallpaper")] impl WallpaperInterface { #[zbus(property(emits_changed_signal = "const"), name = "version")] fn version(&self) -> u32 { 1 } #[zbus(name = "SetWallpaperURI")] #[zbus(out_args("response"))] async fn set_wallpaper_uri( &self, handle: OwnedObjectPath, app_id: Optional, window_identifier: Optional, uri: Uri, options: WallpaperOptions, ) -> Result { let imp = Arc::clone(&self.imp); Request::spawn( "Wallpaper::SetWallpaperURI", &self.cnx, handle.clone(), Arc::clone(&self.imp), Arc::clone(&self.spawn), async move { imp.with_uri( HandleToken::try_from(&handle).unwrap(), app_id.into(), window_identifier.into(), uri, options, ) .await }, ) .await .map(|r| r.response_type()) } } ashpd-0.13.11/src/desktop/account.rs000064400000000000000000000136201046102023000153320ustar 00000000000000//! Access to the current logged user information such as the id, name //! or their avatar uri. //! //! Wrapper of the DBus interface: [`org.freedesktop.portal.Account`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Account.html). //! //! ### Examples //! //! ```rust, no_run //! use ashpd::desktop::account::UserInformation; //! //! async fn run() -> ashpd::Result<()> { //! let response = UserInformation::request() //! .reason("App would like to access user information") //! .send() //! .await? //! .response()?; //! //! println!("Name: {}", response.name()); //! println!("ID: {}", response.id()); //! //! Ok(()) //! } //! ``` use serde::{Deserialize, Serialize}; use zbus::zvariant::{ Optional, Type, as_value::{self, optional}, }; use super::HandleToken; use crate::{Error, Uri, WindowIdentifier, desktop::request::Request, proxy::Proxy}; /// Options for requesting user information. #[derive(Serialize, Deserialize, Type, Debug, Default)] #[zvariant(signature = "dict")] pub struct UserInformationOptions { #[serde(with = "as_value", skip_deserializing)] handle_token: HandleToken, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] reason: Option, } impl UserInformationOptions { /// Sets a user-visible reason for the request. #[must_use] pub fn set_reason<'a>(mut self, reason: impl Into>) -> Self { self.reason = reason.into().map(ToOwned::to_owned); self } /// Gets the user-visible reason for the request. #[cfg(feature = "backend")] pub fn reason(&self) -> Option<&str> { self.reason.as_deref() } } #[derive(Debug, Serialize, Deserialize, Type)] /// The response of a [`UserInformationRequest`] request. #[zvariant(signature = "dict")] pub struct UserInformation { #[serde(with = "as_value")] id: String, #[serde(with = "as_value")] name: String, #[serde(with = "as_value")] image: Uri, } impl UserInformation { #[cfg(feature = "backend")] #[cfg_attr(docsrs, doc(cfg(feature = "backend")))] /// Create a new instance of [`UserInformation`]. pub fn new(id: &str, name: &str, image: Uri) -> Self { Self { id: id.to_owned(), name: name.to_owned(), image, } } /// User identifier. pub fn id(&self) -> &str { &self.id } /// User name. pub fn name(&self) -> &str { &self.name } /// User image uri. pub fn image(&self) -> &Uri { &self.image } /// Creates a new builder-pattern struct instance to construct /// [`UserInformation`]. /// /// This method returns an instance of [`UserInformationRequest`]. pub fn request() -> UserInformationRequest { UserInformationRequest::default() } } /// Wrapper of the DBus interface: [`org.freedesktop.portal.Account`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Account.html). #[doc(alias = "org.freedesktop.portal.Account")] pub struct AccountProxy(Proxy<'static>); impl AccountProxy { /// Create a new instance of [`AccountProxy`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.Account").await?; Ok(Self(proxy)) } /// Create a new instance of [`AccountProxy`] with a specific connection. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Account") .await?; Ok(Self(proxy)) } /// Returns the portal interface version. pub fn version(&self) -> u32 { self.0.version() } /// Requests user information. #[doc(alias = "GetUserInformation")] pub async fn user_information( &self, identifier: Option<&WindowIdentifier>, options: UserInformationOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .request( &options.handle_token, "GetUserInformation", (identifier, &options), ) .await } } impl std::ops::Deref for AccountProxy { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } #[doc(alias = "xdp_portal_get_user_information")] #[doc(alias = "org.freedesktop.portal.Account")] #[derive(Debug, Default)] /// A [builder-pattern] type to construct [`UserInformation`]. /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html pub struct UserInformationRequest { options: UserInformationOptions, identifier: Option, connection: Option, } impl UserInformationRequest { #[must_use] /// Sets a user-visible reason for the request. pub fn reason<'a>(mut self, reason: impl Into>) -> Self { self.options.reason = reason.into().map(ToOwned::to_owned); self } #[must_use] /// Sets a window identifier. pub fn identifier(mut self, identifier: impl Into>) -> Self { self.identifier = identifier.into(); self } #[must_use] /// Sets a connection to use other than the internal one. pub fn connection(mut self, connection: Option) -> Self { self.connection = connection; self } /// Build the [`UserInformation`]. pub async fn send(self) -> Result, Error> { let proxy = if let Some(connection) = self.connection { AccountProxy::with_connection(connection).await? } else { AccountProxy::new().await? }; proxy .user_information(self.identifier.as_ref(), self.options) .await } } ashpd-0.13.11/src/desktop/background.rs000064400000000000000000000226771046102023000160310ustar 00000000000000//! Request to run in the background or started automatically when the user //! logs in. //! //! **Note** This portal only works for sandboxed applications. //! //! Wrapper of the DBus interface: [`org.freedesktop.portal.Background`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Background.html). //! //! ### Examples //! //! ```rust,no_run //! use ashpd::desktop::background::Background; //! //! async fn run() -> ashpd::Result<()> { //! let response = Background::request() //! .reason("Automatically fetch your latest mails") //! .auto_start(true) //! .command(&["geary"]) //! .dbus_activatable(false) //! .send() //! .await? //! .response()?; //! //! println!("{}", response.auto_start()); //! println!("{}", response.run_in_background()); //! //! Ok(()) //! } //! ``` //! //! If no `command` is provided, the [`Exec`](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables) line from the [desktop //! file](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#introduction) will be used. use serde::{Deserialize, Serialize}; use zbus::zvariant::{ Optional, Type, as_value::{self, optional}, }; use super::{HandleToken, Request}; use crate::{Error, WindowIdentifier, proxy::Proxy}; #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`BackgroundProxy::request_background`] request. pub struct BackgroundRequestOptions { #[serde(with = "as_value")] handle_token: HandleToken, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] reason: Option, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] autostart: Option, #[serde( with = "optional", rename = "dbus-activatable", skip_serializing_if = "Option::is_none" )] dbus_activatable: Option, #[serde( with = "as_value", rename = "commandline", skip_serializing_if = "Vec::is_empty" )] command: Vec, } impl BackgroundRequestOptions { #[must_use] /// Sets whether to auto start the application or not. pub fn set_auto_start(mut self, auto_start: impl Into>) -> Self { self.autostart = auto_start.into(); self } #[must_use] /// Sets whether the application is dbus activatable. pub fn set_dbus_activatable(mut self, dbus_activatable: impl Into>) -> Self { self.dbus_activatable = dbus_activatable.into(); self } #[must_use] /// Specifies the command line to execute. /// If this is not specified, the [`Exec`](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables) line from the [desktop /// file](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#introduction) pub fn set_command, I: AsRef + Type + Serialize>( mut self, command: P, ) -> Self { self.command = command.into_iter().map(|s| s.as_ref().to_owned()).collect(); self } #[must_use] /// Sets a user-visible reason for the request. pub fn set_reason<'a>(mut self, reason: impl Into>) -> Self { self.reason = reason.into().map(ToOwned::to_owned); self } } #[derive(Deserialize, Type, Debug)] /// The response of a [`BackgroundRequest`] request. #[zvariant(signature = "dict")] pub struct Background { #[serde(with = "as_value")] background: bool, #[serde(with = "as_value")] autostart: bool, } impl Background { /// Creates a new builder-pattern struct instance to construct /// [`Background`]. /// /// This method returns an instance of [`BackgroundRequest`]. pub fn request() -> BackgroundRequest { BackgroundRequest::default() } /// If the application is allowed to run in the background. pub fn run_in_background(&self) -> bool { self.background } /// If the application will be auto-started. pub fn auto_start(&self) -> bool { self.autostart } } #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`BackgroundProxy::set_status`] request. pub struct SetStatusOptions { #[serde(with = "as_value")] message: String, } impl SetStatusOptions { /// Sets the message to be displayed to the user. pub fn set_message(mut self, message: &str) -> Self { self.message = message.to_owned(); self } } /// The interface lets sandboxed applications request that the application /// is allowed to run in the background or started automatically when the user /// logs in. /// /// Wrapper of the DBus interface: [`org.freedesktop.portal.Background`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Background.html). #[doc(alias = "org.freedesktop.portal.Background")] pub struct BackgroundProxy(Proxy<'static>); impl BackgroundProxy { /// Create a new instance of [`BackgroundProxy`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.Background").await?; Ok(Self(proxy)) } /// Create a new instance of [`BackgroundProxy`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Background") .await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Sets the status of the application running in background. /// /// # Arguments /// /// * `message` - A string that will be used as the status message of the /// application. /// /// # Required version /// /// The method requires the 2nd version implementation of the portal and /// would fail with [`Error::RequiresVersion`] otherwise. /// /// # Specifications /// /// See also [`SetStatus`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Background.html#org-freedesktop-portal-background-setstatus). #[doc(alias = "SetStatus")] pub async fn set_status(&self, options: SetStatusOptions) -> Result<(), Error> { self.0.call_versioned("SetStatus", &(options), 2).await } /// Request background access. /// /// # Specifications /// /// See also [`RequestBackground`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Background.html#org-freedesktop-portal-background-requestbackground). #[doc(alias = "RequestBackground")] pub async fn request_background( &self, identifier: Option<&WindowIdentifier>, options: BackgroundRequestOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .request( &options.handle_token, "RequestBackground", (identifier, &options), ) .await } } impl std::ops::Deref for BackgroundProxy { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } #[doc(alias = "xdp_portal_request_background")] /// A [builder-pattern] type to construct [`Background`]. /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html #[derive(Debug, Default)] pub struct BackgroundRequest { identifier: Option, options: BackgroundRequestOptions, } impl BackgroundRequest { #[must_use] /// Sets a window identifier. pub fn identifier(mut self, identifier: impl Into>) -> Self { self.identifier = identifier.into(); self } #[must_use] /// Sets whether to auto start the application or not. pub fn auto_start(mut self, auto_start: impl Into>) -> Self { self.options.autostart = auto_start.into(); self } #[must_use] /// Sets whether the application is dbus activatable. pub fn dbus_activatable(mut self, dbus_activatable: impl Into>) -> Self { self.options.dbus_activatable = dbus_activatable.into(); self } #[must_use] /// Specifies the command line to execute. /// If this is not specified, the [`Exec`](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables) line from the [desktop /// file](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#introduction) pub fn command, I: AsRef + Type + Serialize>( mut self, command: P, ) -> Self { self.options.command = command.into_iter().map(|s| s.as_ref().to_owned()).collect(); self } #[must_use] /// Sets a user-visible reason for the request. pub fn reason<'a>(mut self, reason: impl Into>) -> Self { self.options.reason = reason.into().map(ToOwned::to_owned); self } /// Build the [`Background`]. pub async fn send(self) -> Result, Error> { let proxy = BackgroundProxy::new().await?; proxy .request_background(self.identifier.as_ref(), self.options) .await } } ashpd-0.13.11/src/desktop/camera.rs000064400000000000000000000240741046102023000151330ustar 00000000000000//! Check if a camera is available, request access to it and open a PipeWire //! remote stream. //! //! ### Examples //! //! ```rust,no_run //! use ashpd::desktop::camera::Camera; //! //! pub async fn run() -> ashpd::Result<()> { //! let camera = Camera::new().await?; //! if camera.is_present().await? { //! camera.request_access(Default::default()).await?; //! let remote_fd = camera.open_pipe_wire_remote(Default::default()).await?; //! // pass the remote fd to GStreamer for example //! } //! Ok(()) //! } //! ``` //! An example on how to connect with Pipewire can be found [here](https://github.com/bilelmoussaoui/ashpd/blob/main/examples/screen_cast_pw.rs), and with GStreamer [here](https://github.com/bilelmoussaoui/ashpd/blob/main/examples/screen_cast_gstreamer.rs). //! Although the examples are primary focus is screen casting, stream connection //! logic remains the same -- with one accessibility change: //! ```rust,ignore //! let stream = pw::stream::Stream::new( //! &core, //! "video-test", //! properties! { //! *pw::keys::MEDIA_TYPE => "Video", //! *pw::keys::MEDIA_CATEGORY => "Capture", //! *pw::keys::MEDIA_ROLE => "Screen", // <-- make this 'Camera' //! }, //! )?; //! ``` use std::{collections::HashMap, os::fd::OwnedFd}; use serde::Serialize; use zbus::zvariant::{self, Type, as_value}; use super::{HandleToken, Request}; use crate::{Error, proxy::Proxy}; #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`Camera::request_access`] request. pub struct CameraAccessOptions { #[serde(with = "as_value")] handle_token: HandleToken, } #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`Camera::open_pipe_wire_remote`] request. pub struct OpenPipeWireRemoteOptions {} /// The interface lets sandboxed applications access camera devices, such as web /// cams. /// /// Wrapper of the DBus interface: [`org.freedesktop.portal.Camera`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html). #[derive(Debug)] #[doc(alias = "org.freedesktop.portal.Camera")] pub struct Camera(Proxy<'static>); impl Camera { /// Create a new instance of [`Camera`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.Camera").await?; Ok(Self(proxy)) } /// Create a new instance of [`Camera`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Camera").await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Requests an access to the camera. /// /// # Specifications /// /// See also [`AccessCamera`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html#org-freedesktop-portal-camera-accesscamera). #[doc(alias = "AccessCamera")] #[doc(alias = "xdp_portal_access_camera")] pub async fn request_access(&self, options: CameraAccessOptions) -> Result, Error> { self.0 .empty_request(&options.handle_token, "AccessCamera", &options) .await } /// Open a file descriptor to the PipeWire remote where the camera nodes are /// available. /// /// # Returns /// /// File descriptor of an open PipeWire remote. /// /// # Specifications /// /// See also [`OpenPipeWireRemote`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html#org-freedesktop-portal-camera-openpipewireremote). #[doc(alias = "OpenPipeWireRemote")] #[doc(alias = "xdp_portal_open_pipewire_remote_for_camera")] pub async fn open_pipe_wire_remote( &self, options: OpenPipeWireRemoteOptions, ) -> Result { let fd = self .0 .call::("OpenPipeWireRemote", &options) .await?; Ok(fd.into()) } /// A boolean stating whether there is any cameras available. /// /// # Specifications /// /// See also [`IsCameraPresent`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html#org-freedesktop-portal-camera-iscamerapresent). #[doc(alias = "IsCameraPresent")] #[doc(alias = "xdp_portal_is_camera_present")] pub async fn is_present(&self) -> Result { self.0.property("IsCameraPresent").await } } impl std::ops::Deref for Camera { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } #[cfg(feature = "pipewire")] /// A PipeWire camera stream returned by [`pipewire_streams`]. #[derive(Debug)] pub struct Stream { node_id: u32, properties: HashMap, } #[cfg(feature = "pipewire")] impl Stream { /// The id of the PipeWire node. pub fn node_id(&self) -> u32 { self.node_id } /// The node properties. pub fn properties(&self) -> HashMap { self.properties.clone() } } #[cfg(feature = "pipewire")] fn pipewire_streams_inner( fd: OwnedFd, callback: F, done_callback: G, ) -> Result<(), pipewire::Error> { let mainloop = pipewire::main_loop::MainLoopRc::new(None)?; let context = pipewire::context::ContextRc::new(&mainloop, None)?; let core = context.connect_fd(fd, None)?; let registry = core.get_registry()?; let pending = core.sync(0).expect("sync failed"); let loop_clone = mainloop.clone(); let _listener_reg = registry .add_listener_local() .global(move |global| { if let Some(props) = &global.props && props.get("media.role") == Some("Camera") { #[cfg(feature = "tracing")] tracing::info!("found camera: {:#?}", props); let mut properties = HashMap::new(); for (key, value) in props.iter() { properties.insert(key.to_string(), value.to_string()); } let node_id = global.id; let stream = Stream { node_id, properties, }; callback.clone()(stream); } }) .register(); let _listener_core = core .add_listener_local() .done(move |id, seq| { if id == pipewire::core::PW_ID_CORE && seq == pending { loop_clone.quit(); done_callback.clone()(); } }) .register(); mainloop.run(); Ok(()) } /// A helper to get a list of PipeWire streams to use with the camera file /// descriptor returned by [`Camera::open_pipe_wire_remote`]. /// /// Currently, the camera portal only gives us a file descriptor. Not passing a /// node id may cause the media session controller to auto-connect the client to /// an incorrect node. /// /// The method looks for the available output streams of a `media.role` type of /// `Camera` and return a list of `Stream`s. /// /// *Note* The socket referenced by `fd` must not be used while this function is /// running. #[cfg(feature = "pipewire")] #[cfg_attr(docsrs, doc(cfg(feature = "pipewire")))] pub async fn pipewire_streams(fd: OwnedFd) -> Result, pipewire::Error> { let (sender, receiver) = futures_channel::oneshot::channel(); let (streams_sender, mut streams_receiver) = futures_channel::mpsc::unbounded(); let sender = std::sync::Arc::new(std::sync::Mutex::new(Some(sender))); let streams_sender = std::sync::Arc::new(std::sync::Mutex::new(streams_sender)); std::thread::spawn(move || { let inner_sender = sender.clone(); if let Err(err) = pipewire_streams_inner( fd, move |stream| { let inner_streams_sender = streams_sender.clone(); if let Ok(mut sender) = inner_streams_sender.lock() { let _result = sender.start_send(stream); }; }, move || { if let Ok(mut guard) = inner_sender.lock() && let Some(inner_sender) = guard.take() { let _result = inner_sender.send(Ok(())); } }, ) { #[cfg(feature = "tracing")] tracing::error!("Failed to get pipewire streams {:#?}", err); let mut guard = sender.lock().unwrap(); if let Some(sender) = guard.take() { let _ = sender.send(Err(err)); } } }); receiver.await.unwrap()?; let mut streams = vec![]; let mut seen_node_ids = std::collections::HashSet::new(); while let Ok(stream) = streams_receiver.try_recv() { if seen_node_ids.insert(stream.node_id) { streams.push(stream); } } Ok(streams) } #[cfg(not(feature = "pipewire"))] #[cfg_attr(docsrs, doc(cfg(not(feature = "pipewire"))))] /// Request access to the camera and return a file descriptor if one is /// available. pub async fn request() -> Result, Error> { let proxy = Camera::new().await?; proxy.request_access(Default::default()).await?; if proxy.is_present().await? { Ok(Some(proxy.open_pipe_wire_remote(Default::default()).await?)) } else { Ok(None) } } #[cfg(feature = "pipewire")] #[cfg_attr(docsrs, doc(cfg(feature = "pipewire")))] /// Request access to the camera and return a file descriptor and a list of the /// available streams, one per camera. pub async fn request() -> Result)>, Error> { let proxy = Camera::new().await?; proxy.request_access(Default::default()).await?; if proxy.is_present().await? { let fd = proxy.open_pipe_wire_remote(Default::default()).await?; let streams = pipewire_streams(fd.try_clone()?).await?; Ok(Some((fd, streams))) } else { Ok(None) } } ashpd-0.13.11/src/desktop/clipboard.rs000064400000000000000000000166751046102023000156520ustar 00000000000000//! Interact with the clipboard. //! //! The portal is mostly meant to be used along with //! [`RemoteDesktop`](crate::desktop::remote_desktop::RemoteDesktop) or //! [`InputCapture`](crate::desktop::input_capture::InputCapture). use futures_util::{Stream, StreamExt}; use serde::{Deserialize, Serialize}; use zbus::zvariant::{OwnedFd, OwnedObjectPath, Type, as_value, as_value::optional}; use super::{Session, SessionPortal}; use crate::{Result, proxy::Proxy}; /// A session that is compatible with the Clipboard portal pub trait IsClipboardSession: SessionPortal {} #[derive(Debug, Type, Serialize, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`Clipboard::set_selection`] request. pub struct SetSelectionOptions<'a> { #[serde(borrow)] #[serde(with = "as_value", skip_serializing_if = "<[_]>::is_empty")] mime_types: &'a [&'a str], } impl<'a> SetSelectionOptions<'a> { /// Sets the mime types for the clipboard selection. pub fn set_mime_types(mut self, mime_types: &'a [&'a str]) -> Self { self.mime_types = mime_types; self } } #[derive(Debug, Type, Serialize, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`Clipboard::request`] request. pub struct RequestClipboardOptions {} #[derive(Debug, Type, Deserialize, Default)] #[zvariant(signature = "dict")] /// The details of a new clipboard selection. pub struct SelectionOwnerChanged { #[serde(default, with = "as_value")] mime_types: Vec, #[serde(default, with = "optional")] session_is_owner: Option, } impl SelectionOwnerChanged { /// Whether the session is the owner of the clipboard selection or not. pub fn session_is_owner(&self) -> Option { self.session_is_owner } /// A list of mime types the new clipboard has content for. pub fn mime_types(&self) -> &[String] { &self.mime_types } } #[doc(alias = "org.freedesktop.portal.Clipboard")] /// Wrapper of the DBus interface: [`org.freedesktop.portal.Clipboard`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Clipboard.html). pub struct Clipboard(Proxy<'static>); impl Clipboard { /// Create a new instance of [`Clipboard`]. pub async fn new() -> Result { Ok(Self( Proxy::new_desktop("org.freedesktop.portal.Clipboard").await?, )) } /// Create a new instance of [`Clipboard`]. pub async fn with_connection(connection: zbus::Connection) -> Result { Ok(Self( Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Clipboard") .await?, )) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// # Specifications /// /// See also [`RequestClipboard`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Clipboard.html#org-freedesktop-portal-clipboard-requestclipboard). #[doc(alias = "RequestClipboard")] pub async fn request( &self, session: &Session, options: RequestClipboardOptions, ) -> Result<()> { self.0 .call_method("RequestClipboard", &(session, options)) .await?; Ok(()) } /// # Specifications /// /// See also [`SetSelection`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Clipboard.html#org-freedesktop-portal-clipboard-setselection). #[doc(alias = "SetSelection")] pub async fn set_selection<'a, T: IsClipboardSession>( &self, session: &Session, options: SetSelectionOptions<'a>, ) -> Result<()> { self.0 .call::<()>("SetSelection", &(session, options)) .await?; Ok(()) } /// # Specifications /// /// See also [`SelectionWrite`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Clipboard.html#org-freedesktop-portal-clipboard-selectionwrite). #[doc(alias = "SelectionWrite")] pub async fn selection_write( &self, session: &Session, serial: u32, ) -> Result { let fd = self .0 .call::("SelectionWrite", &(session, serial)) .await?; Ok(fd) } /// # Specifications /// /// See also [`SelectionWriteDone`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Clipboard.html#org-freedesktop-portal-clipboard-selectionwritedone). #[doc(alias = "SelectionWriteDone")] pub async fn selection_write_done( &self, session: &Session, serial: u32, success: bool, ) -> Result<()> { self.0 .call("SelectionWriteDone", &(session, serial, success)) .await } /// # Specifications /// /// See also [`SelectionRead`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Clipboard.html#org-freedesktop-portal-clipboard-selectionread). #[doc(alias = "SelectionRead")] pub async fn selection_read( &self, session: &Session, mime_type: &str, ) -> Result { let fd = self .0 .call::("SelectionRead", &(session, mime_type)) .await?; Ok(fd) } /// Notifies the session that the clipboard selection has changed. /// # Specifications /// /// See also [`SelectionOwnerChanged`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Clipboard.html#org-freedesktop-portal-clipboard-selectionownerchanged). #[doc(alias = "SelectionOwnerChanged")] pub async fn receive_selection_owner_changed( &self, ) -> Result, SelectionOwnerChanged)>> { let connection = self.connection().clone(); Ok(self .0 .signal::<(OwnedObjectPath, SelectionOwnerChanged)>("SelectionOwnerChanged") .await? .filter_map(move |(p, o)| { let connection = connection.clone(); async move { Session::with_connection(connection, p) .await .map(|s| (s, o)) .ok() } })) } /// # Specifications /// /// See also [`SelectionTransfer`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Clipboard.html#org-freedesktop-portal-clipboard-selectiontransfer). #[doc(alias = "SelectionTransfer")] pub async fn receive_selection_transfer( &self, ) -> Result, String, u32)>> { let connection = self.connection().clone(); Ok(self .0 .signal::<(OwnedObjectPath, String, u32)>("SelectionTransfer") .await? .filter_map(move |(p, mime_type, serial)| { let connection = connection.clone(); async move { Session::with_connection(connection, p) .await .map(|session| (session, mime_type, serial)) .ok() } })) } } impl std::ops::Deref for Clipboard { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } ashpd-0.13.11/src/desktop/color.rs000064400000000000000000000033661046102023000150220ustar 00000000000000use serde::{Deserialize, Serialize}; use crate::zvariant::{self, Type, as_value}; #[derive( Serialize, Deserialize, Clone, Copy, PartialEq, Type, zvariant::Value, zvariant::OwnedValue, )] /// A color as a RGB tuple. /// /// **Note** the values are normalized in the [0.0, 1.0] range. #[zvariant(signature = "dict")] pub struct Color { #[serde(with = "as_value")] color: (f64, f64, f64), } impl From<(f64, f64, f64)> for Color { fn from(value: (f64, f64, f64)) -> Self { Self::new(value.0, value.1, value.2) } } impl Color { /// Create a new instance of Color. pub fn new(red: f64, green: f64, blue: f64) -> Self { Self { color: (red, green, blue), } } /// Red. pub fn red(&self) -> f64 { self.color.0 } /// Green. pub fn green(&self) -> f64 { self.color.1 } /// Blue. pub fn blue(&self) -> f64 { self.color.2 } } #[cfg(feature = "gtk4")] impl From for gtk4::gdk::RGBA { fn from(color: Color) -> Self { gtk4::gdk::RGBA::builder() .red(color.red() as f32) .green(color.green() as f32) .blue(color.blue() as f32) .build() } } impl std::fmt::Debug for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Color") .field("red", &self.red()) .field("green", &self.green()) .field("blue", &self.blue()) .finish() } } impl std::fmt::Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&format!( "({}, {}, {})", self.red(), self.green(), self.blue() )) } } ashpd-0.13.11/src/desktop/dynamic_launcher.rs000064400000000000000000000334341046102023000172100ustar 00000000000000//! Install launchers like Web Application from your browser or Steam. //! //! # Examples //! //! ```rust,no_run //! use std::io::Read; //! use ashpd::{ //! desktop::{ //! dynamic_launcher::DynamicLauncherProxy, //! Icon, //! }, //! WindowIdentifier, //! }; //! //! async fn run() -> ashpd::Result<()> { //! let proxy = DynamicLauncherProxy::new().await?; //! //! let filename = "/home/bilalelmoussaoui/Projects/ashpd/ashpd-demo/data/icons/com.belmoussaoui.ashpd.demo.svg"; //! let mut f = std::fs::File::open(&filename).expect("no file found"); //! let metadata = std::fs::metadata(&filename).expect("unable to read metadata"); //! let mut buffer = vec![0; metadata.len() as usize]; //! f.read(&mut buffer).expect("buffer overflow"); //! //! let icon = Icon::Bytes(buffer); //! let response = proxy //! .prepare_install( //! None, //! "SomeApp", //! icon, //! Default::default() //! ) //! .await? //! .response()?; //! let token = response.token(); //! //! //! // Name and Icon will be overwritten from what we provided above //! // Exec will be overridden to call `flatpak run our-app` if the application is sandboxed //! let desktop_entry = r#" //! [Desktop Entry] //! Comment=My Web App //! Type=Application //! "#; //! proxy //! .install(&token, "some_file.desktop", desktop_entry, Default::default()) //! .await?; //! //! proxy.uninstall("some_file.desktop", Default::default()).await?; //! Ok(()) //! } //! ``` use enumflags2::{BitFlags, bitflags}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use zbus::zvariant::{ self, Optional, OwnedValue, Type, Value, as_value::{self, optional}, }; use super::{HandleToken, Icon, Request}; use crate::{ActivationToken, Error, WindowIdentifier, proxy::Proxy}; #[bitflags] #[derive(Default, Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Copy, Clone, Type)] #[repr(u32)] #[doc(alias = "XdpLauncherType")] /// The type of the launcher. pub enum LauncherType { #[doc(alias = "XDP_LAUNCHER_APPLICATION")] #[default] /// A launcher that represents an application Application, #[doc(alias = "XDP_LAUNCHER_WEBAPP")] /// A launcher that represents a web application WebApplication, } #[cfg_attr(feature = "glib", derive(glib::Enum))] #[cfg_attr(feature = "glib", enum_type(name = "AshpdIconType"))] #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Type)] #[zvariant(signature = "s")] #[serde(rename_all = "lowercase")] /// The icon format. pub enum IconType { /// PNG. Png, /// JPEG. Jpeg, /// SVG. Svg, } #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "(vsu)")] /// The icon of the launcher. pub struct LauncherIcon(zvariant::OwnedValue, IconType, u32); impl LauncherIcon { /// The actual icon. pub fn icon(&self) -> Icon { Icon::try_from(&self.0).unwrap() } /// The icon type. pub fn type_(&self) -> IconType { self.1 } /// The icon size. pub fn size(&self) -> u32 { self.2 } } #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Options to pass to [`DynamicLauncherProxy::prepare_install`] pub struct PrepareInstallOptions { #[serde(with = "as_value")] handle_token: HandleToken, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] modal: Option, #[serde(with = "as_value")] launcher_type: LauncherType, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] target: Option, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] editable_name: Option, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] editable_icon: Option, } impl PrepareInstallOptions { /// Sets whether the dialog should be a modal. pub fn set_modal(mut self, modal: impl Into>) -> Self { self.modal = modal.into(); self } /// Sets the launcher type. pub fn set_launcher_type(mut self, launcher_type: LauncherType) -> Self { self.launcher_type = launcher_type; self } /// The URL for a [`LauncherType::WebApplication`] otherwise it is not /// needed. pub fn set_target<'a>(mut self, target: impl Into>) -> Self { self.target = target.into().map(ToOwned::to_owned); self } /// Sets whether the name should be editable. pub fn set_editable_name(mut self, editable_name: impl Into>) -> Self { self.editable_name = editable_name.into(); self } /// Sets whether the icon should be editable. pub fn set_editable_icon(mut self, editable_icon: impl Into>) -> Self { self.editable_icon = editable_icon.into(); self } } #[derive(Deserialize, Type)] #[zvariant(signature = "dict")] /// A response of [`DynamicLauncherProxy::prepare_install`] pub struct PrepareInstallResponse { #[serde(with = "as_value")] name: String, #[serde(with = "as_value")] icon: OwnedValue, #[serde(with = "as_value")] token: String, } impl PrepareInstallResponse { /// The user defined name or a predefined one pub fn name(&self) -> &str { &self.name } /// A token to pass to [`DynamicLauncherProxy::install`] pub fn token(&self) -> &str { &self.token } /// The user selected icon or a predefined one pub fn icon(&self) -> Icon { let inner = self.icon.downcast_ref::().unwrap(); Icon::try_from(inner).unwrap() } } impl std::fmt::Debug for PrepareInstallResponse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PrepareInstallResponse") .field("name", &self.name()) .field("icon", &self.icon()) .field("token", &self.token()) .finish() } } #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Options to pass to [`DynamicLauncherProxy::launch`] pub struct LaunchOptions { #[serde(with = "optional", skip_serializing_if = "Option::is_none")] activation_token: Option, } impl LaunchOptions { /// Sets the token that can be used to activate the chosen application. #[must_use] pub fn activation_token( mut self, activation_token: impl Into>, ) -> Self { self.activation_token = activation_token.into(); self } } #[derive(Serialize, Type, Debug, Default)] /// Specified options for a [`DynamicLauncherProxy::install`] request. #[zvariant(signature = "dict")] pub struct InstallOptions {} #[derive(Serialize, Type, Debug, Default)] /// Specified options for a [`DynamicLauncherProxy::uninstall`] request. #[zvariant(signature = "dict")] pub struct UninstallOptions {} #[derive(Serialize, Type, Debug, Default)] /// Specified options for a [`DynamicLauncherProxy::request_install_token`] /// request. #[zvariant(signature = "dict")] pub struct RequestInstallTokenOptions {} #[derive(Debug)] /// Wrong type of [`crate::desktop::Icon`] was used. pub struct UnexpectedIconError; impl std::error::Error for UnexpectedIconError {} impl std::fmt::Display for UnexpectedIconError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("Unexpected icon type. Only Icon::Bytes is supported") } } /// The interface lets sandboxed applications install launchers like Web /// Application from your browser or Steam. /// /// Wrapper of the DBus interface: [`org.freedesktop.portal.DynamicLauncher`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher.html). #[derive(Debug)] #[doc(alias = "org.freedesktop.portal.DynamicLauncher")] pub struct DynamicLauncherProxy(Proxy<'static>); impl DynamicLauncherProxy { /// Create a new instance of [`DynamicLauncherProxy`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.DynamicLauncher").await?; Ok(Self(proxy)) } /// Create a new instance of [`DynamicLauncherProxy`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection( connection, "org.freedesktop.portal.DynamicLauncher", ) .await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// *Note* Only `Icon::Bytes` is accepted. /// /// # Specifications /// /// See also [`PrepareInstall`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher.html#org-freedesktop-portal-dynamiclauncher-prepareinstall). #[doc(alias = "PrepareInstall")] #[doc(alias = "xdp_portal_dynamic_launcher_prepare_install")] #[doc(alias = "xdp_portal_dynamic_launcher_prepare_install_finish")] pub async fn prepare_install( &self, identifier: Option<&WindowIdentifier>, name: &str, icon: Icon, options: PrepareInstallOptions, ) -> Result, Error> { if !icon.is_bytes() { return Err(UnexpectedIconError {}.into()); } let identifier = Optional::from(identifier); self.0 .request( &options.handle_token, "PrepareInstall", &(identifier, name, icon.as_value(), &options), ) .await } /// *Note* Only `Icon::Bytes` is accepted. /// /// # Specifications /// /// See also [`RequestInstallToken`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher.html#org-freedesktop-portal-dynamiclauncher-requestinstalltoken). #[doc(alias = "RequestInstallToken")] #[doc(alias = "xdp_portal_dynamic_launcher_request_install_token")] pub async fn request_install_token( &self, name: &str, icon: Icon, options: RequestInstallTokenOptions, ) -> Result { if !icon.is_bytes() { return Err(UnexpectedIconError {}.into()); } self.0 .call::("RequestInstallToken", &(name, icon.as_value(), options)) .await } /// # Specifications /// /// See also [`Install`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher.html#org-freedesktop-portal-dynamiclauncher-install). #[doc(alias = "Install")] #[doc(alias = "xdp_portal_dynamic_launcher_install")] pub async fn install( &self, token: &str, desktop_file_id: &str, desktop_entry: &str, options: InstallOptions, ) -> Result<(), Error> { self.0 .call::<()>("Install", &(token, desktop_file_id, desktop_entry, options)) .await } /// # Specifications /// /// See also [`Uninstall`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher.html#org-freedesktop-portal-dynamiclauncher-uninstall). #[doc(alias = "Uninstall")] #[doc(alias = "xdp_portal_dynamic_launcher_uninstall")] pub async fn uninstall( &self, desktop_file_id: &str, options: UninstallOptions, ) -> Result<(), Error> { self.0 .call::<()>("Uninstall", &(desktop_file_id, options)) .await } /// # Specifications /// /// See also [`GetDesktopEntry`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher.html#org-freedesktop-portal-dynamiclauncher-getdesktopentry). #[doc(alias = "GetDesktopEntry")] #[doc(alias = "xdp_portal_dynamic_launcher_get_desktop_entry")] pub async fn desktop_entry(&self, desktop_file_id: &str) -> Result { self.0.call("GetDesktopEntry", &(desktop_file_id)).await } /// # Specifications /// /// See also [`GetIcon`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher.html#org-freedesktop-portal-dynamiclauncher-geticon). #[doc(alias = "GetIcon")] #[doc(alias = "xdp_portal_dynamic_launcher_get_icon")] pub async fn icon(&self, desktop_file_id: &str) -> Result { self.0.call("GetIcon", &(desktop_file_id)).await } /// # Specifications /// /// See also [`Launch`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher.html#org-freedesktop-portal-dynamiclauncher-launch). #[doc(alias = "Launch")] #[doc(alias = "xdp_portal_dynamic_launcher_launch")] pub async fn launch(&self, desktop_file_id: &str, options: LaunchOptions) -> Result<(), Error> { self.0.call("Launch", &(desktop_file_id, &options)).await } /// # Specifications /// /// See also [`SupportedLauncherTypes`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.DynamicLauncher.html#org-freedesktop-portal-dynamiclauncher-supportedlaunchertypes). #[doc(alias = "SupportedLauncherTypes")] pub async fn supported_launcher_types(&self) -> Result, Error> { self.0 .property::>("SupportedLauncherTypes") .await } } impl std::ops::Deref for DynamicLauncherProxy { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } #[cfg(test)] mod test { use super::*; #[test] fn test_icon_signature() { assert_eq!(LauncherIcon::SIGNATURE, "(vsu)"); let icon = vec![IconType::Png]; assert_eq!(serde_json::to_string(&icon).unwrap(), "[\"png\"]"); } } ashpd-0.13.11/src/desktop/email.rs000064400000000000000000000227361046102023000147750ustar 00000000000000//! Compose an email. //! //! Wrapper of the DBus interface: [`org.freedesktop.portal.Email`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Email.html). //! //! # Examples //! //! Compose an email //! //! ```rust,no_run //! use std::{fs::File, os::fd::OwnedFd}; //! //! use ashpd::desktop::email::EmailRequest; //! //! async fn run() -> ashpd::Result<()> { //! let file = File::open("/home/bilelmoussaoui/Downloads/adwaita-night.jpg").unwrap(); //! EmailRequest::default() //! .address("test@gmail.com") //! .subject("email subject") //! .body("the pre-filled email body") //! .attach(OwnedFd::from(file)) //! .send() //! .await; //! Ok(()) //! } //! ``` use std::os::fd::OwnedFd; use serde::{Deserialize, Serialize}; use zbus::zvariant::{ self, Optional, Type, as_value::{self, optional}, }; use super::{HandleToken, Request}; use crate::{ActivationToken, Error, Uri, WindowIdentifier, proxy::Proxy}; /// Options for composing an email. #[derive(Serialize, Deserialize, Type, Debug, Default)] #[zvariant(signature = "dict")] pub struct EmailOptions { #[serde(with = "as_value", skip_deserializing)] handle_token: HandleToken, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] address: Option, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] addresses: Vec, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] cc: Vec, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] bcc: Vec, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] subject: Option, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] body: Option, #[serde( with = "as_value", skip_serializing_if = "Vec::is_empty", skip_deserializing )] attachment_fds: Vec, #[serde(default, with = "as_value", skip_serializing)] #[cfg_attr(not(feature = "backend"), allow(dead_code))] attachments: Vec, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] activation_token: Option, } impl EmailOptions { /// Gets the email address. #[cfg(feature = "backend")] pub fn address(&self) -> Option<&str> { self.address.as_deref() } /// Gets the list of email addresses. #[cfg(feature = "backend")] pub fn addresses(&self) -> &[String] { &self.addresses } /// Gets the list of CC email addresses. #[cfg(feature = "backend")] pub fn cc(&self) -> &[String] { &self.cc } /// Gets the list of BCC email addresses. #[cfg(feature = "backend")] pub fn bcc(&self) -> &[String] { &self.bcc } /// Gets the email subject. #[cfg(feature = "backend")] pub fn subject(&self) -> Option<&str> { self.subject.as_deref() } /// Gets the email body. #[cfg(feature = "backend")] pub fn body(&self) -> Option<&str> { self.body.as_deref() } /// Gets the list of attachment URIs. #[cfg(feature = "backend")] pub fn attachments(&self) -> &[Uri] { &self.attachments } /// Gets the activation token. #[cfg(feature = "backend")] pub fn activation_token(&self) -> Option<&ActivationToken> { self.activation_token.as_ref() } } /// Wrapper of the DBus interface: [`org.freedesktop.portal.Email`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Email.html). #[derive(Debug)] #[doc(alias = "org.freedesktop.portal.Email")] pub struct EmailProxy(Proxy<'static>); impl EmailProxy { /// Create a new instance of [`EmailProxy`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.Email").await?; Ok(Self(proxy)) } /// Create a new instance of [`EmailProxy`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Email").await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Presents a window that lets the user compose an email. /// /// **Note** the default email client for the host will need to support /// `mailto:` URIs following RFC 2368. /// /// # Arguments /// /// * `identifier` - Identifier for the application window. /// * `options` - An [`EmailOptions`]. /// /// # Specifications /// /// See also [`ComposeEmail`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Email.html#org-freedesktop-portal-email-composeemail). #[doc(alias = "ComposeEmail")] pub async fn compose( &self, identifier: Option<&WindowIdentifier>, options: EmailOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .empty_request( &options.handle_token, "ComposeEmail", &(identifier, &options), ) .await } } impl std::ops::Deref for EmailProxy { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Debug, Default)] #[doc(alias = "xdp_portal_compose_email")] /// A [builder-pattern] type to compose an email. /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html pub struct EmailRequest { identifier: Option, options: EmailOptions, connection: Option, } impl EmailRequest { /// Sets a window identifier. #[must_use] pub fn identifier(mut self, identifier: impl Into>) -> Self { self.identifier = identifier.into(); self } /// Sets the email address to send the email to. #[must_use] pub fn address<'a>(mut self, address: impl Into>) -> Self { self.options.address = address.into().map(ToOwned::to_owned); self } /// Sets a list of email addresses to send the email to. #[must_use] pub fn addresses, I: AsRef + Type + Serialize>( mut self, addresses: impl Into>, ) -> Self { if let Some(a) = addresses.into() { self.options.addresses = a.into_iter().map(|s| s.as_ref().to_owned()).collect(); } self } /// Sets a list of email addresses to BCC. #[must_use] pub fn bcc, I: AsRef + Type + Serialize>( mut self, bcc: impl Into>, ) -> Self { if let Some(a) = bcc.into() { self.options.bcc = a.into_iter().map(|s| s.as_ref().to_owned()).collect(); } self } /// Sets a list of email addresses to CC. #[must_use] pub fn cc, I: AsRef + Type + Serialize>( mut self, cc: impl Into>, ) -> Self { if let Some(a) = cc.into() { self.options.cc = a.into_iter().map(|s| s.as_ref().to_owned()).collect(); } self } /// Sets the email subject. #[must_use] pub fn subject<'a>(mut self, subject: impl Into>) -> Self { self.options.subject = subject.into().map(ToOwned::to_owned); self } /// Sets the email body. #[must_use] pub fn body<'a>(mut self, body: impl Into>) -> Self { self.options.body = body.into().map(ToOwned::to_owned); self } #[must_use] /// Attaches multiple files to the email. pub fn attach_multiple( mut self, attachments: impl IntoIterator>, ) -> Self { self.options.attachment_fds = attachments .into_iter() .map(|fd| zvariant::OwnedFd::from(fd.into())) .collect(); self } /// Attaches a file to the email. #[must_use] pub fn attach(mut self, attachment: OwnedFd) -> Self { self.add_attachment(attachment); self } /// Sets the token that can be used to activate the chosen application. #[must_use] pub fn activation_token( mut self, activation_token: impl Into>, ) -> Self { self.options.activation_token = activation_token.into(); self } /// A different variant of [`Self::attach`]. pub fn add_attachment(&mut self, attachment: OwnedFd) { let attachment = zvariant::OwnedFd::from(attachment); self.options.attachment_fds.push(attachment); } #[must_use] /// Sets a connection to use other than the internal one. pub fn connection(mut self, connection: Option) -> Self { self.connection = connection; self } /// Send the request. pub async fn send(mut self) -> Result, Error> { let proxy = if let Some(connection) = self.connection { EmailProxy::with_connection(connection).await? } else { EmailProxy::new().await? }; // activation_token requires version 4 if proxy.version() < 4 { self.options.activation_token = None; } proxy.compose(self.identifier.as_ref(), self.options).await } } ashpd-0.13.11/src/desktop/file_chooser.rs000064400000000000000000000776101046102023000163500ustar 00000000000000//! The interface lets sandboxed applications ask the user for access to files //! outside the sandbox. The portal backend will present the user with a file //! chooser dialog. //! //! Wrapper of the DBus interface: [`org.freedesktop.portal.FileChooser`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html). //! //! ### Examples //! //! #### Opening a file //! //! ```rust,no_run //! use ashpd::desktop::file_chooser::{Choice, FileFilter, SelectedFiles}; //! //! async fn run() -> ashpd::Result<()> { //! let files = SelectedFiles::open_file() //! .title("open a file to read") //! .accept_label("read") //! .modal(true) //! .multiple(true) //! .choice( //! Choice::new("encoding", "Encoding", "latin15") //! .insert("utf8", "Unicode (UTF-8)") //! .insert("latin15", "Western"), //! ) //! // A trick to have a checkbox //! .choice(Choice::boolean("re-encode", "Re-encode", false)) //! .filter(FileFilter::new("SVG Image").mimetype("image/svg+xml")) //! .send() //! .await? //! .response()?; //! //! println!("{:#?}", files); //! //! Ok(()) //! } //! ``` //! //! #### Ask to save a file //! //! ```rust,no_run //! use ashpd::desktop::file_chooser::{FileFilter, SelectedFiles}; //! //! async fn run() -> ashpd::Result<()> { //! let files = SelectedFiles::save_file() //! .title("open a file to write") //! .accept_label("write") //! .current_name("image.jpg") //! .modal(true) //! .filter(FileFilter::new("JPEG Image").glob("*.jpg")) //! .send() //! .await? //! .response()?; //! //! println!("{:#?}", files); //! //! Ok(()) //! } //! ``` //! //! #### Ask to save multiple files //! //! ```rust,no_run //! use ashpd::desktop::file_chooser::SelectedFiles; //! //! async fn run() -> ashpd::Result<()> { //! let files = SelectedFiles::save_files() //! .title("open files to write") //! .accept_label("write files") //! .modal(true) //! .current_folder("/home/bilelmoussaoui/Pictures")? //! .files(&["test.jpg", "awesome.png"])? //! .send() //! .await? //! .response()?; //! //! println!("{:#?}", files); //! //! Ok(()) //! } //! ``` use std::path::Path; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use zbus::zvariant::{ Optional, Type, as_value::{self, optional}, }; use super::{HandleToken, Request}; use crate::{Error, FilePath, Uri, WindowIdentifier, proxy::Proxy}; #[derive(Clone, Serialize, Deserialize, Type, Debug, PartialEq)] /// A file filter, to limit the available file choices to a mimetype or a glob /// pattern. pub struct FileFilter(String, Vec<(FilterType, String)>); #[derive(Clone, Serialize_repr, Deserialize_repr, Debug, Type, PartialEq)] #[repr(u32)] enum FilterType { GlobPattern = 0, MimeType = 1, } impl FilterType { /// Whether it is a mime type filter. fn is_mimetype(&self) -> bool { matches!(self, FilterType::MimeType) } /// Whether it is a glob pattern type filter. fn is_pattern(&self) -> bool { matches!(self, FilterType::GlobPattern) } } impl FileFilter { /// Create a new file filter /// /// # Arguments /// /// * `label` - user-visible name of the file filter. pub fn new(label: &str) -> Self { Self(label.to_owned(), vec![]) } /// Adds a mime type to the file filter. #[must_use] pub fn mimetype(mut self, mimetype: &str) -> Self { self.1.push((FilterType::MimeType, mimetype.to_owned())); self } /// Adds a glob pattern to the file filter. #[must_use] pub fn glob(mut self, pattern: &str) -> Self { self.1.push((FilterType::GlobPattern, pattern.to_owned())); self } } impl FileFilter { /// The label of the filter. pub fn label(&self) -> &str { &self.0 } /// List of mimetypes filters. pub fn mimetype_filters(&self) -> Vec<&str> { self.1 .iter() .filter_map(|(type_, string)| type_.is_mimetype().then_some(string.as_str())) .collect() } /// List of glob patterns filters. pub fn pattern_filters(&self) -> Vec<&str> { self.1 .iter() .filter_map(|(type_, string)| type_.is_pattern().then_some(string.as_str())) .collect() } } #[derive(Clone, Serialize, Deserialize, Type, Debug)] /// Presents the user with a choice to select from or as a checkbox. pub struct Choice(String, String, Vec<(String, String)>, String); impl Choice { /// Creates a checkbox choice. /// /// # Arguments /// /// * `id` - A unique identifier of the choice. /// * `label` - user-visible name of the choice. /// * `state` - the initial state value. pub fn boolean(id: &str, label: &str, state: bool) -> Self { Self::new(id, label, &state.to_string()) } /// Creates a new choice. /// /// # Arguments /// /// * `id` - A unique identifier of the choice. /// * `label` - user-visible name of the choice. /// * `initial_selection` - the initially selected value. pub fn new(id: &str, label: &str, initial_selection: &str) -> Self { Self( id.to_owned(), label.to_owned(), vec![], initial_selection.to_owned(), ) } /// Adds a (key, value) as a choice. #[must_use] pub fn insert(mut self, key: &str, value: &str) -> Self { self.2.push((key.to_owned(), value.to_owned())); self } /// The choice's unique id pub fn id(&self) -> &str { &self.0 } /// The user visible label of the choice. pub fn label(&self) -> &str { &self.1 } /// Pairs of choices. pub fn pairs(&self) -> Vec<(&str, &str)> { self.2 .iter() .map(|(x, y)| (x.as_str(), y.as_str())) .collect::>() } /// The initially selected value. pub fn initial_selection(&self) -> &str { &self.3 } } /// Options for opening a file. #[derive(Serialize, Deserialize, Type, Debug, Default)] #[zvariant(signature = "dict")] pub struct OpenFileOptions { #[serde(with = "as_value", skip_deserializing)] handle_token: HandleToken, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] accept_label: Option, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] modal: Option, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] multiple: Option, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] directory: Option, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] filters: Vec, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] current_filter: Option, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] choices: Vec, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] current_folder: Option, } impl OpenFileOptions { /// Sets the accept label. #[must_use] pub fn set_accept_label<'a>(mut self, accept_label: impl Into>) -> Self { self.accept_label = accept_label.into().map(ToOwned::to_owned); self } /// Gets the accept label. #[cfg(feature = "backend")] pub fn accept_label(&self) -> Option<&str> { self.accept_label.as_deref() } /// Sets whether the dialog should be modal. #[must_use] pub fn set_modal(mut self, modal: impl Into>) -> Self { self.modal = modal.into(); self } /// Gets whether the dialog should be modal. #[cfg(feature = "backend")] pub fn modal(&self) -> Option { self.modal } /// Sets whether multiple files can be selected. #[must_use] pub fn set_multiple(mut self, multiple: impl Into>) -> Self { self.multiple = multiple.into(); self } /// Gets whether multiple files can be selected. #[cfg(feature = "backend")] pub fn multiple(&self) -> Option { self.multiple } /// Sets whether to select directories instead of files. #[must_use] pub fn set_directory(mut self, directory: impl Into>) -> Self { self.directory = directory.into(); self } /// Gets whether to select directories instead of files. #[cfg(feature = "backend")] pub fn directory(&self) -> Option { self.directory } /// Sets the file filters. #[must_use] pub fn set_filters(mut self, filters: impl IntoIterator) -> Self { self.filters = filters.into_iter().collect(); self } /// Gets the file filters. #[cfg(feature = "backend")] pub fn filters(&self) -> &[FileFilter] { &self.filters } /// Sets the current filter. #[must_use] pub fn set_current_filter(mut self, current_filter: impl Into>) -> Self { self.current_filter = current_filter.into(); self } /// Gets the current filter. #[cfg(feature = "backend")] pub fn current_filter(&self) -> Option<&FileFilter> { self.current_filter.as_ref() } /// Sets the choices. #[must_use] pub fn set_choices(mut self, choices: impl IntoIterator) -> Self { self.choices = choices.into_iter().collect(); self } /// Gets the choices. #[cfg(feature = "backend")] pub fn choices(&self) -> &[Choice] { &self.choices } /// Sets the current folder. #[must_use] pub fn set_current_folder(mut self, current_folder: impl Into>) -> Self { self.current_folder = current_folder.into(); self } /// Gets the current folder. #[cfg(feature = "backend")] pub fn current_folder(&self) -> Option<&FilePath> { self.current_folder.as_ref() } } /// Options for saving a file. #[derive(Serialize, Deserialize, Type, Debug, Default)] #[zvariant(signature = "dict")] pub struct SaveFileOptions { #[serde(with = "as_value", skip_deserializing)] handle_token: HandleToken, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] accept_label: Option, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] modal: Option, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] current_name: Option, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] current_folder: Option, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] current_file: Option, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] filters: Vec, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] current_filter: Option, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] choices: Vec, } impl SaveFileOptions { /// Sets the accept label. #[must_use] pub fn set_accept_label<'a>(mut self, accept_label: impl Into>) -> Self { self.accept_label = accept_label.into().map(ToOwned::to_owned); self } /// Gets the accept label. #[cfg(feature = "backend")] pub fn accept_label(&self) -> Option<&str> { self.accept_label.as_deref() } /// Sets whether the dialog should be modal. #[must_use] pub fn set_modal(mut self, modal: impl Into>) -> Self { self.modal = modal.into(); self } /// Gets whether the dialog should be modal. #[cfg(feature = "backend")] pub fn modal(&self) -> Option { self.modal } /// Sets the current name. #[must_use] pub fn set_current_name<'a>(mut self, current_name: impl Into>) -> Self { self.current_name = current_name.into().map(ToOwned::to_owned); self } /// Gets the current name. #[cfg(feature = "backend")] pub fn current_name(&self) -> Option<&str> { self.current_name.as_deref() } /// Sets the current folder. #[must_use] pub fn set_current_folder(mut self, current_folder: impl Into>) -> Self { self.current_folder = current_folder.into(); self } /// Gets the current folder. #[cfg(feature = "backend")] pub fn current_folder(&self) -> Option<&FilePath> { self.current_folder.as_ref() } /// Sets the current file. #[must_use] pub fn set_current_file(mut self, current_file: impl Into>) -> Self { self.current_file = current_file.into(); self } /// Gets the current file. #[cfg(feature = "backend")] pub fn current_file(&self) -> Option<&FilePath> { self.current_file.as_ref() } /// Sets the file filters. #[must_use] pub fn set_filters(mut self, filters: impl IntoIterator) -> Self { self.filters = filters.into_iter().collect(); self } /// Gets the file filters. #[cfg(feature = "backend")] pub fn filters(&self) -> &[FileFilter] { &self.filters } /// Sets the current filter. #[must_use] pub fn set_current_filter(mut self, current_filter: impl Into>) -> Self { self.current_filter = current_filter.into(); self } /// Gets the current filter. #[cfg(feature = "backend")] pub fn current_filter(&self) -> Option<&FileFilter> { self.current_filter.as_ref() } /// Sets the choices. #[must_use] pub fn set_choices(mut self, choices: impl IntoIterator) -> Self { self.choices = choices.into_iter().collect(); self } /// Gets the choices. #[cfg(feature = "backend")] pub fn choices(&self) -> &[Choice] { &self.choices } } /// Options for saving multiple files. #[derive(Serialize, Deserialize, Type, Debug, Default)] #[zvariant(signature = "dict")] pub struct SaveFilesOptions { #[serde(with = "as_value", skip_deserializing)] handle_token: HandleToken, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] accept_label: Option, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] modal: Option, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] choices: Vec, #[serde(default, with = "optional", skip_serializing_if = "Option::is_none")] current_folder: Option, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] files: Vec, } impl SaveFilesOptions { /// Sets the accept label. #[must_use] pub fn set_accept_label<'a>(mut self, accept_label: impl Into>) -> Self { self.accept_label = accept_label.into().map(ToOwned::to_owned); self } /// Gets the accept label. #[cfg(feature = "backend")] pub fn accept_label(&self) -> Option<&str> { self.accept_label.as_deref() } /// Sets whether the dialog should be modal. #[must_use] pub fn set_modal(mut self, modal: impl Into>) -> Self { self.modal = modal.into(); self } /// Gets whether the dialog should be modal. #[cfg(feature = "backend")] pub fn modal(&self) -> Option { self.modal } /// Sets the choices. #[must_use] pub fn set_choices(mut self, choices: impl IntoIterator) -> Self { self.choices = choices.into_iter().collect(); self } /// Gets the choices. #[cfg(feature = "backend")] pub fn choices(&self) -> &[Choice] { &self.choices } /// Sets the current folder. #[must_use] pub fn set_current_folder(mut self, current_folder: impl Into>) -> Self { self.current_folder = current_folder.into(); self } /// Gets the current folder. #[cfg(feature = "backend")] pub fn current_folder(&self) -> Option<&FilePath> { self.current_folder.as_ref() } /// Sets the files. #[must_use] pub fn set_files(mut self, files: impl IntoIterator) -> Self { self.files = files.into_iter().collect(); self } /// Gets the files. #[cfg(feature = "backend")] pub fn files(&self) -> &[FilePath] { &self.files } } #[derive(Serialize, Deserialize, Type, Debug, Default)] /// A response of [`OpenFileRequest`], [`SaveFileRequest`] or /// [`SaveFilesRequest`]. #[zvariant(signature = "dict")] pub struct SelectedFiles { #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] uris: Vec, #[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")] choices: Vec<(String, String)>, // Backend-only fields /// Not relevant for SaveFiles #[serde( default, with = "optional", skip_serializing_if = "Option::is_none", skip_deserializing )] current_filter: Option, /// Only relevant for OpenFile #[serde( default, with = "optional", skip_serializing_if = "Option::is_none", skip_deserializing )] writable: Option, } impl SelectedFiles { /// Start an open file request. pub fn open_file() -> OpenFileRequest { OpenFileRequest::default() } /// Start a save file request. pub fn save_file() -> SaveFileRequest { SaveFileRequest::default() } /// Start a save files request. pub fn save_files() -> SaveFilesRequest { SaveFilesRequest::default() } /// The selected files uris. pub fn uris(&self) -> &[Uri] { self.uris.as_slice() } /// The selected value of each choice as a tuple of (key, value) pub fn choices(&self) -> &[(String, String)] { &self.choices } /// Adds a URI to the selected files. #[cfg(feature = "backend")] pub fn uri(mut self, value: Uri) -> Self { self.uris.push(value); self } /// Adds a choice to the selected files. #[cfg(feature = "backend")] pub fn choice(mut self, choice_key: &str, choice_value: &str) -> Self { self.choices .push((choice_key.to_owned(), choice_value.to_owned())); self } /// Sets the current filter. #[cfg(feature = "backend")] pub fn current_filter(mut self, value: impl Into>) -> Self { self.current_filter = value.into(); self } /// Sets whether the file is writable. #[cfg(feature = "backend")] pub fn writable(mut self, value: impl Into>) -> Self { self.writable = value.into(); self } } /// Wrapper of the DBus interface: [`org.freedesktop.portal.FileChooser`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html). #[doc(alias = "org.freedesktop.portal.FileChooser")] pub struct FileChooserProxy(Proxy<'static>); impl FileChooserProxy { /// Create a new instance of [`FileChooserProxy`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.FileChooser").await?; Ok(Self(proxy)) } /// Create a new instance of [`FileChooserProxy`] with a specific /// connection. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.FileChooser") .await?; Ok(Self(proxy)) } /// Returns the portal interface version. pub fn version(&self) -> u32 { self.0.version() } /// Asks to open one or more files. #[doc(alias = "OpenFile")] pub async fn open_file( &self, identifier: Option<&WindowIdentifier>, title: &str, options: OpenFileOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .request( &options.handle_token, "OpenFile", &(identifier, title, &options), ) .await } /// Asks for a location to save a file. #[doc(alias = "SaveFile")] pub async fn save_file( &self, identifier: Option<&WindowIdentifier>, title: &str, options: SaveFileOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .request( &options.handle_token, "SaveFile", &(identifier, title, &options), ) .await } /// Asks for a location to save one or more files. #[doc(alias = "SaveFiles")] pub async fn save_files( &self, identifier: Option<&WindowIdentifier>, title: &str, options: SaveFilesOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .request( &options.handle_token, "SaveFiles", &(identifier, title, &options), ) .await } } impl std::ops::Deref for FileChooserProxy { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Debug, Default)] #[doc(alias = "xdp_portal_open_file")] /// A [builder-pattern] type to open a file. /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html pub struct OpenFileRequest { identifier: Option, title: String, options: OpenFileOptions, connection: Option, } impl OpenFileRequest { #[must_use] /// Sets a window identifier. pub fn identifier(mut self, identifier: impl Into>) -> Self { self.identifier = identifier.into(); self } /// Sets a title for the file chooser dialog. #[must_use] pub fn title<'a>(mut self, title: impl Into>) -> Self { self.title = title.into().map(ToOwned::to_owned).unwrap_or_default(); self } /// Sets a user-visible string to the "accept" button. #[must_use] pub fn accept_label<'a>(mut self, accept_label: impl Into>) -> Self { self.options.accept_label = accept_label.into().map(ToOwned::to_owned); self } /// Sets whether the dialog should be a modal. #[must_use] pub fn modal(mut self, modal: impl Into>) -> Self { self.options.modal = modal.into(); self } /// Sets whether to allow multiple files selection. #[must_use] pub fn multiple(mut self, multiple: impl Into>) -> Self { self.options.multiple = multiple.into(); self } /// Sets whether to select directories or not. #[must_use] pub fn directory(mut self, directory: impl Into>) -> Self { self.options.directory = directory.into(); self } /// Adds a files filter. #[must_use] pub fn filter(mut self, filter: FileFilter) -> Self { self.options.filters.push(filter); self } #[must_use] /// Adds a list of files filters. pub fn filters(mut self, filters: impl IntoIterator) -> Self { self.options.filters = filters.into_iter().collect(); self } /// Specifies the default filter. #[must_use] pub fn current_filter(mut self, current_filter: impl Into>) -> Self { self.options.current_filter = current_filter.into(); self } /// Adds a choice. #[must_use] pub fn choice(mut self, choice: Choice) -> Self { self.options.choices.push(choice); self } #[must_use] /// Adds a list of choices. pub fn choices(mut self, choices: impl IntoIterator) -> Self { self.options.choices = choices.into_iter().collect(); self } /// Specifies the current folder path. pub fn current_folder>( mut self, current_folder: impl Into>, ) -> Result { self.options.current_folder = current_folder .into() .map(|c| FilePath::new(c)) .transpose()?; Ok(self) } #[must_use] /// Sets a connection to use other than the internal one. pub fn connection(mut self, connection: Option) -> Self { self.connection = connection; self } /// Send the request. pub async fn send(self) -> Result, Error> { let proxy = if let Some(connection) = self.connection { FileChooserProxy::with_connection(connection).await? } else { FileChooserProxy::new().await? }; proxy .open_file(self.identifier.as_ref(), &self.title, self.options) .await } } #[derive(Debug, Default)] #[doc(alias = "xdp_portal_save_files")] /// A [builder-pattern] type to save multiple files. /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html pub struct SaveFilesRequest { identifier: Option, title: String, options: SaveFilesOptions, connection: Option, } impl SaveFilesRequest { #[must_use] /// Sets a window identifier. pub fn identifier(mut self, identifier: impl Into>) -> Self { self.identifier = identifier.into(); self } /// Sets a title for the file chooser dialog. #[must_use] pub fn title<'a>(mut self, title: impl Into>) -> Self { self.title = title.into().map(ToOwned::to_owned).unwrap_or_default(); self } /// Sets a user-visible string to the "accept" button. #[must_use] pub fn accept_label<'a>(mut self, accept_label: impl Into>) -> Self { self.options.accept_label = accept_label.into().map(ToOwned::to_owned); self } /// Sets whether the dialog should be a modal. #[must_use] pub fn modal(mut self, modal: impl Into>) -> Self { self.options.modal = modal.into(); self } /// Adds a choice. #[must_use] pub fn choice(mut self, choice: Choice) -> Self { self.options.choices.push(choice); self } #[must_use] /// Adds a list of choices. pub fn choices(mut self, choices: impl IntoIterator) -> Self { self.options.choices = choices.into_iter().collect(); self } /// Specifies the current folder path. pub fn current_folder>( mut self, current_folder: impl Into>, ) -> Result { self.options.current_folder = current_folder .into() .map(|c| FilePath::new(c)) .transpose()?; Ok(self) } /// Sets a list of files to save. pub fn files>>( mut self, files: impl Into>, ) -> Result { if let Some(f) = files.into() { self.options.files = f .into_iter() .map(|s| FilePath::new(s)) .collect::, _>>()?; } Ok(self) } #[must_use] /// Sets a connection to use other than the internal one. pub fn connection(mut self, connection: Option) -> Self { self.connection = connection; self } /// Send the request. pub async fn send(self) -> Result, Error> { let proxy = if let Some(connection) = self.connection { FileChooserProxy::with_connection(connection).await? } else { FileChooserProxy::new().await? }; proxy .save_files(self.identifier.as_ref(), &self.title, self.options) .await } } #[derive(Debug, Default)] #[doc(alias = "xdp_portal_save_file")] /// A [builder-pattern] type to save a file. /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html pub struct SaveFileRequest { identifier: Option, title: String, options: SaveFileOptions, connection: Option, } impl SaveFileRequest { #[must_use] /// Sets a window identifier. pub fn identifier(mut self, identifier: impl Into>) -> Self { self.identifier = identifier.into(); self } /// Sets a title for the file chooser dialog. #[must_use] pub fn title<'a>(mut self, title: impl Into>) -> Self { self.title = title.into().map(ToOwned::to_owned).unwrap_or_default(); self } /// Sets a user-visible string to the "accept" button. #[must_use] pub fn accept_label<'a>(mut self, accept_label: impl Into>) -> Self { self.options.accept_label = accept_label.into().map(ToOwned::to_owned); self } /// Sets whether the dialog should be a modal. #[must_use] pub fn modal(mut self, modal: impl Into>) -> Self { self.options.modal = modal.into(); self } /// Sets the current file name. #[must_use] pub fn current_name<'a>(mut self, current_name: impl Into>) -> Self { self.options.current_name = current_name.into().map(ToOwned::to_owned); self } /// Sets the current folder. pub fn current_folder>( mut self, current_folder: impl Into>, ) -> Result { self.options.current_folder = current_folder .into() .map(|c| FilePath::new(c)) .transpose()?; Ok(self) } /// Sets the absolute path of the file. pub fn current_file>( mut self, current_file: impl Into>, ) -> Result { self.options.current_file = current_file.into().map(|c| FilePath::new(c)).transpose()?; Ok(self) } /// Adds a files filter. #[must_use] pub fn filter(mut self, filter: FileFilter) -> Self { self.options.filters.push(filter); self } #[must_use] /// Adds a list of files filters. pub fn filters(mut self, filters: impl IntoIterator) -> Self { self.options.filters = filters.into_iter().collect(); self } /// Sets the default filter. #[must_use] pub fn current_filter(mut self, current_filter: impl Into>) -> Self { self.options.current_filter = current_filter.into(); self } /// Adds a choice. #[must_use] pub fn choice(mut self, choice: Choice) -> Self { self.options.choices.push(choice); self } #[must_use] /// Adds a list of choices. pub fn choices(mut self, choices: impl IntoIterator) -> Self { self.options.choices = choices.into_iter().collect(); self } #[must_use] /// Sets a connection to use other than the internal one. pub fn connection(mut self, connection: Option) -> Self { self.connection = connection; self } /// Send the request. pub async fn send(self) -> Result, Error> { let proxy = if let Some(connection) = self.connection { FileChooserProxy::with_connection(connection).await? } else { FileChooserProxy::new().await? }; proxy .save_file(self.identifier.as_ref(), &self.title, self.options) .await } } ashpd-0.13.11/src/desktop/game_mode.rs000064400000000000000000000310051046102023000156100ustar 00000000000000//! # Examples //! //! ```rust,no_run //! use ashpd::desktop::game_mode::GameMode; //! //! async fn run() -> ashpd::Result<()> { //! let proxy = GameMode::new().await?; //! //! println!("{:#?}", proxy.register(246612).await?); //! println!("{:#?}", proxy.query_status(246612).await?); //! println!("{:#?}", proxy.unregister(246612).await?); //! println!("{:#?}", proxy.query_status(246612).await?); //! //! Ok(()) //! } //! ``` use std::{fmt::Debug, os::fd::AsFd}; use serde_repr::Deserialize_repr; use zbus::zvariant::{Fd, Type}; use crate::{Error, Pid, error::PortalError, proxy::Proxy}; #[cfg_attr(feature = "glib", derive(glib::Enum))] #[cfg_attr(feature = "glib", enum_type(name = "AshpdGameModeStatus"))] #[derive(Deserialize_repr, PartialEq, Eq, Debug, Clone, Copy, Type)] #[repr(i32)] /// The status of the game mode. pub enum Status { /// GameMode is inactive. Inactive = 0, /// GameMode is active. Active = 1, /// GameMode is active and `pid` is registered. Registered = 2, /// The query failed inside GameMode. Rejected = -1, } #[derive(Deserialize_repr, PartialEq, Eq, Debug, Type)] #[repr(i32)] /// The status of a (un-)register game mode request. enum RegisterStatus { /// If the game was successfully (un-)registered. Success = 0, /// If the request was rejected by GameMode. Rejected = -1, } /// The interface lets sandboxed applications access GameMode from within the /// sandbox. /// /// It is analogous to the `com.feralinteractive.GameMode` interface and will /// proxy request there, but with additional permission checking and pid /// mapping. The latter is necessary in the case that sandbox has pid namespace /// isolation enabled. See the man page for pid_namespaces(7) for more details, /// but briefly, it means that the sandbox has its own process id namespace /// which is separated from the one on the host. Thus there will be two separate /// process ids (pids) within two different namespaces that both identify same /// process. One id from the pid namespace inside the sandbox and one id from /// the host pid namespace. Since GameMode expects pids from the host pid /// namespace but programs inside the sandbox can only know pids from the /// sandbox namespace, process ids need to be translated from the portal to the /// host namespace. The portal will do that transparently for all calls where /// this is necessary. /// /// Note: GameMode will monitor active clients, i.e. games and other programs /// that have successfully called [`GameMode::register`]. In the event /// that a client terminates without a call to the /// [`GameMode::unregister`] method, GameMode will automatically /// un-register the client. This might happen with a (small) delay. /// /// Wrapper of the DBus interface: [`org.freedesktop.portal.GameMode`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html). #[derive(Debug)] #[doc(alias = "org.freedesktop.portal.GameMode")] pub struct GameMode(Proxy<'static>); impl GameMode { /// Create a new instance of [`GameMode`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.GameMode").await?; Ok(Self(proxy)) } /// Create a new instance of [`GameMode`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.GameMode") .await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Query the GameMode status for a process. /// If the caller is running inside a sandbox with pid namespace isolation, /// the pid will be translated to the respective host pid. /// /// # Arguments /// /// * `pid` - Process id to query the GameMode status of. /// /// # Specifications /// /// See also [`QueryStatus`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-querystatus). #[doc(alias = "QueryStatus")] pub async fn query_status(&self, pid: Pid) -> Result { self.0.call("QueryStatus", &(pid as i32)).await } /// Query the GameMode status for a process. /// /// # Arguments /// /// * `target` - Pid file descriptor to query the GameMode status of. /// * `requester` - Pid file descriptor of the process requesting the /// information. /// /// # Specifications /// /// See also [`QueryStatusByPIDFd`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-querystatusbypidfd). #[doc(alias = "QueryStatusByPIDFd")] pub async fn query_status_by_pidfd( &self, target: &impl AsFd, requester: &impl AsFd, ) -> Result { self.0 .call( "QueryStatusByPIDFd", &(Fd::from(target), Fd::from(requester)), ) .await } /// Query the GameMode status for a process. /// /// # Arguments /// /// * `target` - Process id to query the GameMode status of. /// * `requester` - Process id of the process requesting the information. /// /// # Specifications /// /// See also [`QueryStatusByPid`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-querystatusbypid). #[doc(alias = "QueryStatusByPid")] pub async fn query_status_by_pid(&self, target: Pid, requester: Pid) -> Result { self.0 .call("QueryStatusByPid", &(target as i32, requester as i32)) .await } /// Register a game with GameMode and thus request GameMode to be activated. /// If the caller is running inside a sandbox with pid namespace isolation, /// the pid will be translated to the respective host pid. See the general /// introduction for details. If the GameMode has already been requested /// for pid before, this call will fail. /// /// # Arguments /// /// * `pid` - Process id of the game to register. /// /// # Specifications /// /// See also [`RegisterGame`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-registergame). #[doc(alias = "RegisterGame")] pub async fn register(&self, pid: Pid) -> Result<(), Error> { let status = self.0.call("RegisterGame", &(pid as i32)).await?; match status { RegisterStatus::Success => Ok(()), RegisterStatus::Rejected => Err(Error::Portal(PortalError::Failed(format!( "Failed to register game for `{pid}`" )))), } } /// Register a game with GameMode. /// /// # Arguments /// /// * `target` - Process file descriptor of the game to register. /// * `requester` - Process file descriptor of the process requesting the /// registration. /// /// # Specifications /// /// See also [`RegisterGameByPIDFd`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-registergamebypidfd). #[doc(alias = "RegisterGameByPIDFd")] pub async fn register_by_pidfd( &self, target: &impl AsFd, requester: &impl AsFd, ) -> Result<(), Error> { let status = self .0 .call( "RegisterGameByPIDFd", &(Fd::from(target), Fd::from(requester)), ) .await?; match status { RegisterStatus::Success => Ok(()), RegisterStatus::Rejected => Err(Error::Portal(PortalError::Failed( "Failed to register by pidfd".to_string(), ))), } } /// Register a game with GameMode. /// /// # Arguments /// /// * `target` - Process id of the game to register. /// * `requester` - Process id of the process requesting the registration. /// /// # Specifications /// /// See also [`RegisterGameByPid`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-registergamebypid). #[doc(alias = "RegisterGameByPid")] pub async fn register_by_pid(&self, target: Pid, requester: Pid) -> Result<(), Error> { let status = self .0 .call("RegisterGameByPid", &(target as i32, requester as i32)) .await?; match status { RegisterStatus::Success => Ok(()), RegisterStatus::Rejected => Err(Error::Portal(PortalError::Failed(format!( "Failed to register by pid for target=`{target}` requester=`{requester}`" )))), } } /// Un-register a game from GameMode. /// if the call is successful and there are no other games or clients /// registered, GameMode will be deactivated. If the caller is running /// inside a sandbox with pid namespace isolation, the pid will be /// translated to the respective host pid. /// /// # Arguments /// /// * `pid` - Process id of the game to un-register. /// /// # Specifications /// /// See also [`UnregisterGame`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-unregistergame). #[doc(alias = "UnregisterGame")] pub async fn unregister(&self, pid: Pid) -> Result<(), Error> { let status = self.0.call("UnregisterGame", &(pid as i32)).await?; match status { RegisterStatus::Success => Ok(()), RegisterStatus::Rejected => Err(Error::Portal(PortalError::Failed(format!( "Failed to unregister for `{pid}`" )))), } } /// Un-register a game from GameMode. /// /// # Arguments /// /// * `target` - Pid file descriptor of the game to un-register. /// * `requester` - Pid file descriptor of the process requesting the /// un-registration. /// /// # Specifications /// /// See also [`UnregisterGameByPIDFd`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-unregistergamebypidfd). #[doc(alias = "UnregisterGameByPIDFd")] pub async fn unregister_by_pidfd( &self, target: &impl AsFd, requester: &impl AsFd, ) -> Result<(), Error> { let status = self .0 .call( "UnregisterGameByPIDFd", &(Fd::from(target), Fd::from(requester)), ) .await?; match status { RegisterStatus::Success => Ok(()), RegisterStatus::Rejected => Err(Error::Portal(PortalError::Failed( "Failed to unregister by pidfd`".to_string(), ))), } } /// Un-register a game from GameMode. /// /// # Arguments /// /// * `target` - Process id of the game to un-register. /// * `requester` - Process id of the process requesting the /// un-registration. /// /// # Specifications /// /// See also [`UnregisterGameByPid`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-unregistergamebypid). #[doc(alias = "UnregisterGameByPid")] pub async fn unregister_by_pid(&self, target: Pid, requester: Pid) -> Result<(), Error> { let status = self .0 .call("UnregisterGameByPid", &(target as i32, requester as i32)) .await?; match status { RegisterStatus::Success => Ok(()), RegisterStatus::Rejected => Err(Error::Portal(PortalError::Failed(format!( "Failed to unregister by pid for target=`{target}` requester=`{requester}`" )))), } } /// Whether any pid on the system has enabled Game Mode. /// /// # Specifications /// /// See also [`Active`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GameMode.html#org-freedesktop-portal-gamemode-active). #[doc(alias = "Active")] pub async fn is_active(&self) -> Result { let is_active = self.0.property("Active").await?; Ok(is_active) } } impl std::ops::Deref for GameMode { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } ashpd-0.13.11/src/desktop/global_shortcuts.rs000064400000000000000000000304231046102023000172540ustar 00000000000000//! Register global shortcuts use std::{collections::HashMap, fmt::Debug, time::Duration}; use futures_util::Stream; use serde::{Deserialize, Serialize}; use zbus::zvariant::{ ObjectPath, Optional, OwnedObjectPath, OwnedValue, Type, as_value::{self, optional}, }; use super::{HandleToken, Request, Session, session::SessionPortal}; use crate::{ ActivationToken, Error, WindowIdentifier, desktop::session::{CreateSessionOptions, CreateSessionResponse}, proxy::Proxy, }; #[derive(Clone, Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] struct NewShortcutInfo { /// User-readable text describing what the shortcut does. #[serde(with = "as_value")] description: String, /// The preferred shortcut trigger, defined as described by the "shortcuts" /// XDG specification. Optional. #[serde(with = "optional", skip_serializing_if = "Option::is_none")] preferred_trigger: Option, } /// Shortcut descriptor used to bind new shortcuts in /// [`GlobalShortcuts::bind_shortcuts`] #[derive(Clone, Serialize, Type, Debug)] pub struct NewShortcut(String, NewShortcutInfo); impl NewShortcut { /// Construct new shortcut pub fn new(id: impl Into, description: impl Into) -> Self { Self( id.into(), NewShortcutInfo { description: description.into(), preferred_trigger: None, }, ) } /// Sets the preferred shortcut trigger, defined as described by the /// "shortcuts" XDG specification. #[must_use] pub fn preferred_trigger<'a>(mut self, preferred_trigger: impl Into>) -> Self { self.1.preferred_trigger = preferred_trigger.into().map(ToOwned::to_owned); self } } #[derive(Clone, Deserialize, Type, Debug, Default)] #[zvariant(signature = "dict")] struct ShortcutInfo { /// User-readable text describing what the shortcut does. #[serde(with = "as_value")] description: String, /// User-readable text describing how to trigger the shortcut for the client /// to render. #[serde(with = "as_value")] trigger_description: String, } /// Struct that contains information about existing binded shortcut. /// /// If you need to create a new shortcuts, take a look at [`NewShortcut`] /// instead. #[derive(Clone, Deserialize, Type, Debug)] pub struct Shortcut(String, ShortcutInfo); impl Shortcut { /// Shortcut id pub fn id(&self) -> &str { &self.0 } /// User-readable text describing what the shortcut does. pub fn description(&self) -> &str { &self.1.description } /// User-readable text describing how to trigger the shortcut for the client /// to render. pub fn trigger_description(&self) -> &str { &self.1.trigger_description } } #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`GlobalShortcuts::bind_shortcuts`] request. pub struct BindShortcutsOptions { /// A string that will be used as the last element of the handle. #[serde(with = "as_value")] handle_token: HandleToken, } /// A response to a [`GlobalShortcuts::bind_shortcuts`] request. #[derive(Deserialize, Type, Debug)] #[zvariant(signature = "dict")] pub struct BindShortcuts { #[serde(default, with = "as_value")] shortcuts: Vec, } impl BindShortcuts { /// A list of shortcuts. pub fn shortcuts(&self) -> &[Shortcut] { &self.shortcuts } } #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`GlobalShortcuts::configure_shortcuts`] request. pub struct ConfigureShortcutsOptions { #[serde(with = "optional", skip_serializing_if = "Option::is_none")] activation_token: Option, } impl ConfigureShortcutsOptions { /// Sets the token that can be used to activate the configuration dialog. #[must_use] pub fn set_activation_token( mut self, activation_token: impl Into>, ) -> Self { self.activation_token = activation_token.into(); self } } #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`GlobalShortcuts::list_shortcuts`] request. pub struct ListShortcutsOptions { /// A string that will be used as the last element of the handle. #[serde(with = "as_value")] handle_token: HandleToken, } /// A response to a [`GlobalShortcuts::list_shortcuts`] request. #[derive(Deserialize, Type, Debug)] #[zvariant(signature = "dict")] pub struct ListShortcuts { /// A list of shortcuts. #[serde(default, with = "as_value")] shortcuts: Vec, } impl ListShortcuts { /// A list of shortcuts. pub fn shortcuts(&self) -> &[Shortcut] { &self.shortcuts } } /// Notifies about a shortcut becoming active. #[derive(Debug, Deserialize, Type)] pub struct Activated(OwnedObjectPath, String, u64, HashMap); impl Activated { /// Session that requested the shortcut. pub fn session_handle(&self) -> ObjectPath<'_> { self.0.as_ref() } /// The application-provided ID for the shortcut. pub fn shortcut_id(&self) -> &str { &self.1 } /// The timestamp, as seconds and microseconds since the Unix epoch. pub fn timestamp(&self) -> Duration { Duration::from_millis(self.2) } /// Optional information pub fn options(&self) -> &HashMap { &self.3 } } /// Notifies that a shortcut is not active anymore. #[derive(Debug, Deserialize, Type)] pub struct Deactivated(OwnedObjectPath, String, u64, HashMap); impl Deactivated { /// Session that requested the shortcut. pub fn session_handle(&self) -> ObjectPath<'_> { self.0.as_ref() } /// The application-provided ID for the shortcut. pub fn shortcut_id(&self) -> &str { &self.1 } /// The timestamp, as seconds and microseconds since the Unix epoch. pub fn timestamp(&self) -> Duration { Duration::from_millis(self.2) } /// Optional information pub fn options(&self) -> &HashMap { &self.3 } } /// Indicates that the information associated with some of the shortcuts has /// changed. #[derive(Debug, Deserialize, Type)] pub struct ShortcutsChanged(OwnedObjectPath, Vec); impl ShortcutsChanged { /// Session that requested the shortcut. pub fn session_handle(&self) -> ObjectPath<'_> { self.0.as_ref() } /// Shortcuts that have been registered. pub fn shortcuts(&self) -> &[Shortcut] { &self.1 } } /// Wrapper of the DBus interface: [`org.freedesktop.portal.GlobalShortcuts`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html). #[derive(Debug)] #[doc(alias = "org.freedesktop.portal.GlobalShortcuts")] pub struct GlobalShortcuts(Proxy<'static>); impl GlobalShortcuts { /// Create a new instance of [`GlobalShortcuts`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.GlobalShortcuts").await?; Ok(Self(proxy)) } /// Create a new instance of [`GlobalShortcuts`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection( connection, "org.freedesktop.portal.GlobalShortcuts", ) .await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Create a global shortcuts session. /// /// # Specifications /// /// See also [`CreateSession`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-createsession). #[doc(alias = "CreateSession")] pub async fn create_session( &self, options: CreateSessionOptions, ) -> Result, Error> { let (request, proxy) = futures_util::try_join!( self.0.request::( &options.handle_token, "CreateSession", &options ), Session::from_unique_name(self.0.connection().clone(), &options.session_handle_token), )?; assert_eq!(proxy.path(), &request.response()?.session_handle.as_ref()); Ok(proxy) } /// Bind the shortcuts. /// /// # Specifications /// /// See also [`BindShortcuts`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-bindshortcuts). #[doc(alias = "BindShortcuts")] pub async fn bind_shortcuts( &self, session: &Session, shortcuts: &[NewShortcut], identifier: Option<&WindowIdentifier>, options: BindShortcutsOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .request( &options.handle_token, "BindShortcuts", &(session, shortcuts, identifier, &options), ) .await } /// Lists all shortcuts. /// /// # Specifications /// /// See also [`ListShortcuts`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-listshortcuts). #[doc(alias = "ListShortcuts")] pub async fn list_shortcuts( &self, session: &Session, options: ListShortcutsOptions, ) -> Result, Error> { self.0 .request(&options.handle_token, "ListShortcuts", &(session, &options)) .await } /// Request showing a configuration UI so the user is able to configure all /// shortcuts of this session. /// /// # Specifications /// /// See also [`ConfigureShortcuts`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-configureshortcuts). #[doc(alias = "ConfigureShortcuts")] pub async fn configure_shortcuts( &self, session: &Session, identifier: Option<&WindowIdentifier>, options: ConfigureShortcutsOptions, ) -> Result<(), Error> { let identifier = Optional::from(identifier); self.0 .call_versioned::<()>("ConfigureShortcuts", &(session, identifier, options), 2) .await } /// Signal emitted when shortcut becomes active. /// /// # Specifications /// /// See also [`Activated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-activated). #[doc(alias = "Activated")] pub async fn receive_activated(&self) -> Result + use<>, Error> { self.0.signal("Activated").await } /// Signal emitted when shortcut is not active anymore. /// /// # Specifications /// /// See also [`Deactivated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-deactivated). #[doc(alias = "Deactivated")] pub async fn receive_deactivated( &self, ) -> Result + use<>, Error> { self.0.signal("Deactivated").await } /// Signal emitted when information associated with some of the shortcuts /// has changed. /// /// # Specifications /// /// See also [`ShortcutsChanged`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-shortcutschanged). #[doc(alias = "ShortcutsChanged")] pub async fn receive_shortcuts_changed( &self, ) -> Result + use<>, Error> { self.0.signal("ShortcutsChanged").await } } impl std::ops::Deref for GlobalShortcuts { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } impl crate::Sealed for GlobalShortcuts {} impl SessionPortal for GlobalShortcuts {} ashpd-0.13.11/src/desktop/handle_token.rs000064400000000000000000000076741046102023000163450ustar 00000000000000use std::{ convert::TryFrom, fmt::{self, Debug, Display}, }; use serde::{Deserialize, Serialize}; use zbus::{names::OwnedMemberName, zvariant::Type}; /// A handle token is a DBus Object Path element. /// /// Specified in the [`Request`](crate::desktop::Request) or /// [`Session`](crate::desktop::Session) object path following this format /// `/org/freedesktop/portal/desktop/request/SENDER/TOKEN` where sender is the /// caller's unique name and token is the [`HandleToken`]. /// /// A valid object path element must only contain the ASCII characters /// `[A-Z][a-z][0-9]_` #[derive(Serialize, Type, PartialEq, Eq, Hash, Clone)] pub struct HandleToken(OwnedMemberName); impl Display for HandleToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0) } } impl Debug for HandleToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("HandleToken") .field(&self.0.as_str()) .finish() } } impl Default for HandleToken { fn default() -> Self { const ALPHANUMERIC: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let mut token = String::with_capacity(16); // "ashpd_" + 10 chars token.push_str("ashpd_"); let mut rnd_bytes = [0u8; 10]; getrandom::fill(&mut rnd_bytes).expect("failed to generate random bytes"); for byte in rnd_bytes.iter() { let idx = (*byte as usize) % ALPHANUMERIC.len(); token.push(ALPHANUMERIC[idx] as char); } Self(OwnedMemberName::try_from(token).unwrap()) } } #[derive(Debug)] pub struct HandleInvalidCharacter(char); impl std::fmt::Display for HandleInvalidCharacter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("Invalid Character {}", self.0)) } } impl std::error::Error for HandleInvalidCharacter {} impl std::str::FromStr for HandleToken { type Err = HandleInvalidCharacter; fn from_str(value: &str) -> Result { for char in value.chars() { if !char.is_ascii_alphanumeric() && char != '_' { return Err(HandleInvalidCharacter(char)); } } Ok(Self(OwnedMemberName::try_from(value).unwrap())) } } impl TryFrom for HandleToken { type Error = HandleInvalidCharacter; fn try_from(value: String) -> Result { value.parse::() } } impl TryFrom<&str> for HandleToken { type Error = HandleInvalidCharacter; fn try_from(value: &str) -> Result { value.parse::() } } #[cfg(feature = "backend")] impl TryFrom<&zbus::zvariant::OwnedObjectPath> for HandleToken { type Error = HandleInvalidCharacter; fn try_from(value: &zbus::zvariant::OwnedObjectPath) -> Result { let base_segment = value .as_str() .split('/') .next_back() .expect("A valid request ObjectPath"); HandleToken::try_from(base_segment) } } impl<'de> Deserialize<'de> for HandleToken { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let token = String::deserialize(deserializer)?; token .parse::() .map_err(|err| serde::de::Error::custom(err.to_string())) } } #[cfg(test)] mod test { use std::str::FromStr; use super::HandleToken; #[test] fn handle_token() { assert!(HandleToken::from_str("token").is_ok()); let token = HandleToken::from_str("token2").unwrap(); assert_eq!(token.to_string(), "token2".to_string()); assert!(HandleToken::from_str("/test").is_err()); assert!(HandleToken::from_str("تجربة").is_err()); assert!(HandleToken::from_str("test_token").is_ok()); HandleToken::default(); // ensure we don't panic } } ashpd-0.13.11/src/desktop/icon.rs000064400000000000000000000212101046102023000146200ustar 00000000000000use std::os::fd::AsFd; use serde::{ Deserialize, de, ser::{Serialize, SerializeTuple}, }; use zbus::zvariant::{self, OwnedValue, Type, Value}; use crate::{Error, Uri}; #[derive(Debug, Type)] #[zvariant(signature = "(sv)")] /// A representation of an icon. /// /// Used by both the Notification & Dynamic launcher portals. pub enum Icon { /// An icon URI. Uri(Uri), /// A list of icon names. Names(Vec), /// Icon bytes. Bytes(Vec), /// A file descriptor. FileDescriptor(std::os::fd::OwnedFd), } impl Icon { /// Create an icon from a list of names. pub fn with_names(names: impl IntoIterator) -> Self where N: ToString, { Self::Names(names.into_iter().map(|name| name.to_string()).collect()) } pub(crate) fn is_bytes(&self) -> bool { matches!(self, Self::Bytes(_)) } pub(crate) fn inner_bytes<'a>(&'a self) -> Value<'a> { match self { Self::Bytes(bytes) => { let mut array = zvariant::Array::new(u8::SIGNATURE); for byte in bytes.iter() { // Safe to unwrap because we are sure it is of the correct type array.append(Value::from(*byte)).unwrap(); } Value::from(array) } _ => panic!("Only bytes icons can be converted to a bytes variant"), } } pub(crate) fn as_value<'a>(&'a self) -> Value<'a> { let tuple = match self { Self::Uri(uri) => ("file", Value::from(uri.as_str())), Self::Names(names) => { let mut array = zvariant::Array::new(String::SIGNATURE); for name in names.iter() { // Safe to unwrap because we are sure it is of the correct type array.append(Value::from(name)).unwrap(); } ("themed", Value::from(array)) } Self::Bytes(_) => ("bytes", self.inner_bytes()), Self::FileDescriptor(fd) => ("file-descriptor", zvariant::Fd::from(fd).into()), }; Value::new(tuple) } } impl Serialize for Icon { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut tuple = serializer.serialize_tuple(2)?; match self { Self::Uri(uri) => { tuple.serialize_element("file")?; tuple.serialize_element(&Value::from(uri.as_str()))?; } Self::Names(names) => { tuple.serialize_element("themed")?; let mut array = zvariant::Array::new(String::SIGNATURE); for name in names.iter() { // Safe to unwrap because we are sure it is of the correct type array.append(Value::from(name)).unwrap(); } tuple.serialize_element(&Value::from(array))?; } Self::Bytes(_) => { tuple.serialize_element("bytes")?; tuple.serialize_element(&self.inner_bytes())?; } Self::FileDescriptor(fd) => { tuple.serialize_element("file-descriptor")?; tuple.serialize_element(&Value::from(zvariant::Fd::from(fd)))?; } } tuple.end() } } impl<'de> Deserialize<'de> for Icon { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let (type_, data) = <(String, OwnedValue)>::deserialize(deserializer)?; match type_.as_str() { "file" => { let uri_str = data.downcast_ref::().unwrap(); let uri = Uri::parse(uri_str.as_str()) .map_err(|e| serde::de::Error::custom(format!("Invalid URI: {}", e)))?; Ok(Self::Uri(uri)) } "bytes" => { let array = data.downcast_ref::().unwrap(); let mut bytes = Vec::with_capacity(array.len()); for byte in array.inner() { bytes.push(byte.downcast_ref::().unwrap()); } Ok(Self::Bytes(bytes)) } "themed" => { let array = data.downcast_ref::().unwrap(); let mut names = Vec::with_capacity(array.len()); for value in array.inner() { let name = value.downcast_ref::().unwrap(); names.push(name.as_str().to_owned()); } Ok(Self::Names(names)) } "file-descriptor" => { let fd = data.downcast_ref::().unwrap(); Ok(Self::FileDescriptor( fd.as_fd() .try_clone_to_owned() .expect("Failed to clone file descriptor"), )) } _ => Err(de::Error::custom("Invalid Icon type")), } } } impl TryFrom<&OwnedValue> for Icon { type Error = crate::Error; fn try_from(value: &OwnedValue) -> Result { let structure = value.downcast_ref::().unwrap(); let fields = structure.fields(); let type_ = fields[0].downcast_ref::().unwrap(); match type_.as_str() { "file" => { let uri_str = fields[1] .downcast_ref::() .unwrap() .to_owned(); let uri = Uri::parse(uri_str.as_str()).map_err(crate::Error::Uri)?; Ok(Self::Uri(uri)) } "bytes" => { let array = fields[1].downcast_ref::().unwrap(); let mut bytes = Vec::with_capacity(array.len()); for byte in array.inner() { bytes.push(byte.downcast_ref::().unwrap()); } Ok(Self::Bytes(bytes)) } "themed" => { let array = fields[1].downcast_ref::().unwrap(); let mut names = Vec::with_capacity(array.len()); for value in array.inner() { let name = value.downcast_ref::().unwrap(); names.push(name.as_str().to_owned()); } Ok(Self::Names(names)) } "file-descriptor" => { let fd = fields[1].downcast_ref::().unwrap(); Ok(Self::FileDescriptor( fd.as_fd() .try_clone_to_owned() .expect("Failed to clone file descriptor"), )) } _ => Err(Error::ParseError("Invalid Icon type")), } } } impl TryFrom for Icon { type Error = crate::Error; fn try_from(value: OwnedValue) -> Result { Self::try_from(&value) } } impl TryFrom> for Icon { type Error = crate::Error; fn try_from(value: Value<'_>) -> Result { Self::try_from(&value) } } impl TryFrom<&Value<'_>> for Icon { type Error = crate::Error; fn try_from(value: &Value<'_>) -> Result { Self::try_from(value.try_to_owned()?) } } #[cfg(test)] mod test { use zbus::zvariant::{Endian, serialized::Context, to_bytes}; use super::*; #[test] fn check_icon_signature() { assert_eq!(Icon::SIGNATURE, "(sv)"); } #[test] fn serialize_deserialize() { let ctxt = Context::new_dbus(Endian::Little, 0); let icon = Icon::with_names(["dialog-symbolic"]); let encoded = to_bytes(ctxt, &icon).unwrap(); let decoded: Icon = encoded.deserialize().unwrap().0; assert!(matches!(decoded, Icon::Names(_))); let icon = Icon::Uri(Uri::parse("file://some/icon.png").unwrap()); let encoded = to_bytes(ctxt, &icon).unwrap(); let decoded: Icon = encoded.deserialize().unwrap().0; assert!(matches!(decoded, Icon::Uri(_))); let icon = Icon::Bytes(vec![1, 0, 1, 0]); let encoded = to_bytes(ctxt, &icon).unwrap(); let decoded: Icon = encoded.deserialize().unwrap().0; assert!(matches!(decoded, Icon::Bytes(_))); let fd = std::fs::File::open("/tmp").unwrap(); let icon = Icon::FileDescriptor(fd.as_fd().try_clone_to_owned().unwrap()); let encoded = to_bytes(ctxt, &icon).unwrap(); let decoded: Icon = encoded.deserialize().unwrap().0; assert!(matches!(decoded, Icon::FileDescriptor(_))); } } ashpd-0.13.11/src/desktop/inhibit.rs000064400000000000000000000222571046102023000153320ustar 00000000000000//! # Examples //! //! How to inhibit logout/user switch //! //! ```rust,no_run //! use std::{thread, time}; //! //! use ashpd::desktop::inhibit::{InhibitFlags, InhibitOptions, InhibitProxy, SessionState}; //! use futures_util::StreamExt; //! //! async fn run() -> ashpd::Result<()> { //! let proxy = InhibitProxy::new().await?; //! let session = proxy.create_monitor(None, Default::default()).await?; //! //! let state = proxy.receive_state_changed().await?.next().await.unwrap(); //! match state.session_state() { //! SessionState::Running => (), //! SessionState::QueryEnd => { //! proxy //! .inhibit( //! None, //! InhibitFlags::Logout | InhibitFlags::UserSwitch, //! InhibitOptions::default() //! .set_reason("please save the opened project first"), //! ) //! .await?; //! thread::sleep(time::Duration::from_secs(1)); //! proxy.query_end_response(&session).await?; //! } //! SessionState::Ending => { //! println!("ending the session"); //! } //! } //! Ok(()) //! } //! ``` use enumflags2::{BitFlags, bitflags}; use futures_util::Stream; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use zbus::zvariant::{ ObjectPath, Optional, OwnedObjectPath, Type, as_value::{self, optional}, }; use super::{HandleToken, Request, Session, session::SessionPortal}; use crate::{Error, WindowIdentifier, desktop::session::CreateSessionResponse, proxy::Proxy}; #[derive(Serialize, Type, Debug, Default)] /// Specified options for a [`InhibitProxy::create_monitor`] request. #[zvariant(signature = "dict")] pub struct CreateMonitorOptions { #[serde(with = "as_value")] handle_token: HandleToken, #[serde(with = "as_value")] session_handle_token: HandleToken, } #[derive(Serialize, Type, Debug, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`InhibitProxy::inhibit`] request. pub struct InhibitOptions { #[serde(with = "as_value")] handle_token: HandleToken, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] reason: Option, } impl InhibitOptions { /// Sets a user-visible reason for the request. #[must_use] pub fn set_reason<'a>(mut self, reason: impl Into>) -> Self { self.reason = reason.into().map(ToOwned::to_owned); self } } #[bitflags] #[derive(Serialize_repr, PartialEq, Eq, Debug, Clone, Copy, Type)] #[repr(u32)] #[doc(alias = "XdpInhibitFlags")] /// The actions to inhibit that can end the user's session pub enum InhibitFlags { #[doc(alias = "XDP_INHIBIT_FLAG_LOGOUT")] /// Logout. Logout, #[doc(alias = "XDP_INHIBIT_FLAG_USER_SWITCH")] /// User switch. UserSwitch, #[doc(alias = "XDP_INHIBIT_FLAG_SUSPEND")] /// Suspend. Suspend, #[doc(alias = "XDP_INHIBIT_FLAG_IDLE")] /// Idle. Idle, } #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] #[serde(rename_all = "kebab-case")] struct State { #[serde(with = "as_value")] screensaver_active: bool, #[serde(with = "as_value")] session_state: SessionState, } #[derive(Debug, Deserialize, Type)] /// A response received when the `state_changed` signal is received. pub struct InhibitState(OwnedObjectPath, State); impl InhibitState { /// The session triggered the state change pub fn session_handle(&self) -> ObjectPath<'_> { self.0.as_ref() } /// Whether screensaver is active or not. pub fn screensaver_active(&self) -> bool { self.1.screensaver_active } /// The session state. pub fn session_state(&self) -> SessionState { self.1.session_state } } #[cfg_attr(feature = "glib", derive(glib::Enum))] #[cfg_attr(feature = "glib", enum_type(name = "AshpdSessionState"))] #[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Clone, Copy, Type)] #[doc(alias = "XdpLoginSessionState")] #[repr(u32)] /// The current state of the user's session. pub enum SessionState { #[doc(alias = "XDP_LOGIN_SESSION_RUNNING")] /// Running. Running = 1, #[doc(alias = "XDP_LOGIN_SESSION_QUERY_END")] /// The user asked to end the session e.g logout. QueryEnd = 2, #[doc(alias = "XDP_LOGIN_SESSION_ENDING")] /// The session is ending. Ending = 3, } /// The interface lets sandboxed applications inhibit the user session from /// ending, suspending, idling or getting switched away. /// /// Wrapper of the DBus interface: [`org.freedesktop.portal.Inhibit`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Inhibit.html). #[derive(Debug)] #[doc(alias = "org.freedesktop.portal.Inhibit")] pub struct InhibitProxy(Proxy<'static>); impl InhibitProxy { /// Create a new instance of [`InhibitProxy`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.Inhibit").await?; Ok(Self(proxy)) } /// Create a new instance of [`InhibitProxy`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Inhibit") .await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Creates a monitoring session. /// While this session is active, the caller will receive `state_changed` /// signals with updates on the session state. /// /// # Arguments /// /// * `identifier` - The application window identifier. /// /// # Specifications /// /// See also [`CreateMonitor`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Inhibit.html#org-freedesktop-portal-inhibit-createmonitor). #[doc(alias = "CreateMonitor")] #[doc(alias = "xdp_portal_session_monitor_start")] pub async fn create_monitor( &self, identifier: Option<&WindowIdentifier>, options: CreateMonitorOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); let body = &(identifier, &options); let (monitor, proxy) = futures_util::try_join!( self.0 .request::(&options.handle_token, "CreateMonitor", body), Session::from_unique_name(self.0.connection().clone(), &options.session_handle_token), )?; assert_eq!(proxy.path(), &monitor.response()?.session_handle.as_ref()); Ok(proxy) } /// Inhibits a session status changes. /// /// # Arguments /// /// * `identifier` - The application window identifier. /// * `flags` - The flags determine what changes are inhibited. /// /// # Specifications /// /// See also [`Inhibit`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Inhibit.html#org-freedesktop-portal-inhibit-inhibit). #[doc(alias = "Inhibit")] #[doc(alias = "xdp_portal_session_inhibit")] pub async fn inhibit( &self, identifier: Option<&WindowIdentifier>, flags: BitFlags, options: InhibitOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .empty_request( &options.handle_token, "Inhibit", &(identifier, flags, &options), ) .await } /// Signal emitted when the session state changes. /// /// # Specifications /// /// See also [`StateChanged`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Inhibit.html#org-freedesktop-portal-inhibit-statechanged). #[doc(alias = "StateChanged")] #[doc(alias = "XdpPortal::session-state-changed")] pub async fn receive_state_changed( &self, ) -> Result + use<>, Error> { self.0.signal("StateChanged").await } /// Acknowledges that the caller received the "state_changed" signal. /// This method should be called within one second after receiving a /// [`receive_state_changed()`][`InhibitProxy::receive_state_changed`] /// signal with the [`SessionState::QueryEnd`] state. /// /// # Arguments /// /// * `session` - A [`Session`], created with /// [`create_monitor()`][`InhibitProxy::create_monitor`]. /// /// # Specifications /// /// See also [`QueryEndResponse`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Inhibit.html#org-freedesktop-portal-inhibit-queryendresponse). #[doc(alias = "QueryEndResponse")] #[doc(alias = "xdp_portal_session_monitor_query_end_response")] pub async fn query_end_response(&self, session: &Session) -> Result<(), Error> { self.0.call("QueryEndResponse", &(session)).await } } impl std::ops::Deref for InhibitProxy { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } impl crate::Sealed for InhibitProxy {} impl SessionPortal for InhibitProxy {} ashpd-0.13.11/src/desktop/input_capture.rs000064400000000000000000001117571046102023000165720ustar 00000000000000//! # Examples //! //! ## A Note of Warning Regarding the GNOME Portal Implementation //! //! `xdg-desktop-portal-gnome` in version 46.0 has a //! [bug](https://gitlab.gnome.org/GNOME/xdg-desktop-portal-gnome/-/issues/126) //! that prevents reenabling a disabled session. //! //! Since changing barrier locations requires a session to be disabled, //! it is currently (as of GNOME 46) not possible to change barriers //! after a session has been enabled! //! //! (the [official documentation](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-setpointerbarriers) //! states that a //! [`InputCapture::set_pointer_barriers()`][set_pointer_barriers] //! request suspends the capture session but in reality the GNOME //! desktop portal enforces a //! [`InputCapture::disable()`][disable] //! request //! in order to use //! [`InputCapture::set_pointer_barriers()`][set_pointer_barriers] //! ) //! //! [set_pointer_barriers]: crate::desktop::input_capture::InputCapture::set_pointer_barriers //! [disable]: crate::desktop::input_capture::InputCapture::disable //! //! ## Retrieving an Ei File Descriptor //! //! The input capture portal is used to negotiate the input capture //! triggers and enable input capturing. //! //! Actual input capture events are then communicated over a unix //! stream using the [libei protocol](https://gitlab.freedesktop.org/libinput/libei). //! //! The lifetime of an ei file descriptor is bound by a capture session. //! //! ```rust,no_run //! use std::os::fd::AsRawFd; //! //! use ashpd::desktop::input_capture::{Capabilities, CreateSessionOptions, InputCapture}; //! //! async fn run() -> ashpd::Result<()> { //! let input_capture = InputCapture::new().await?; //! let (session, capabilities) = input_capture //! .create_session( //! None, //! CreateSessionOptions::default().set_capabilities( //! Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen, //! ), //! ) //! .await?; //! eprintln!("capabilities: {capabilities}"); //! //! let eifd = input_capture //! .connect_to_eis(&session, Default::default()) //! .await?; //! eprintln!("eifd: {}", eifd.as_raw_fd()); //! Ok(()) //! } //! ``` //! //! //! ## Selecting Pointer Barriers. //! //! Input capture is triggered through pointer barriers that are provided //! by the client. //! //! The provided barriers need to be positioned at the edges of outputs //! (monitors) and can be denied by the compositor for various reasons, such as //! wrong placement. //! //! For debugging why a barrier placement failed, the logs of the //! active portal implementation can be useful, e.g.: //! //! ```sh //! journalctl --user -xeu xdg-desktop-portal-gnome.service //! ``` //! //! The following example sets up barriers according to `pos` //! (either `Left`, `Right`, `Top` or `Bottom`). //! //! Note that barriers positioned between two monitors will be denied //! and returned in the `failed_barrier_ids` vector. //! //! ```rust,no_run //! use ashpd::desktop::input_capture::{ //! Barrier, BarrierID, BarrierPosition, Capabilities, CreateSessionOptions, InputCapture, //! }; //! //! #[allow(unused)] //! enum Position { //! Left, //! Right, //! Top, //! Bottom, //! } //! //! async fn run() -> ashpd::Result<()> { //! let input_capture = InputCapture::new().await?; //! let (session, _capabilities) = input_capture //! .create_session( //! None, //! CreateSessionOptions::default().set_capabilities( //! Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen, //! ), //! ) //! .await?; //! //! let pos = Position::Left; //! let zones = input_capture //! .zones(&session, Default::default()) //! .await? //! .response()?; //! eprintln!("zones: {zones:?}"); //! let barriers = zones //! .regions() //! .iter() //! .enumerate() //! .map(|(n, r)| { //! let id = BarrierID::new((n + 1) as u32).expect("barrier-id must be non-zero"); //! let (x, y) = (r.x_offset(), r.y_offset()); //! let (width, height) = (r.width() as i32, r.height() as i32); //! let barrier_pos = match pos { //! Position::Left => BarrierPosition::new(x, y, x, y + height - 1), // start pos, end pos, inclusive //! Position::Right => BarrierPosition::new(x + width, y, x + width, y + height - 1), //! Position::Top => BarrierPosition::new(x, y, x + width - 1, y), //! Position::Bottom => BarrierPosition::new(x, y + height, x + width - 1, y + height), //! }; //! Barrier::new(id, barrier_pos) //! }) //! .collect::>(); //! //! eprintln!("requested barriers: {barriers:?}"); //! //! let request = input_capture //! .set_pointer_barriers(&session, &barriers, zones.zone_set(), Default::default()) //! .await?; //! let response = request.response()?; //! let failed_barrier_ids = response.failed_barriers(); //! //! eprintln!("failed barrier ids: {:?}", failed_barrier_ids); //! //! Ok(()) //! } //! ``` //! //! ## Enabling Input Capture and Retrieving Captured Input Events. //! //! The following full example uses the [reis crate](https://docs.rs/reis/0.2.0/reis/) //! for libei communication. //! //! Input Capture can be released using ESC. //! //! ```rust,no_run //! use std::{collections::HashMap, os::unix::net::UnixStream, sync::OnceLock, time::Duration}; //! //! use ashpd::desktop::input_capture::{ //! Barrier, BarrierID, BarrierPosition, Capabilities, CreateSessionOptions, InputCapture, ReleaseOptions, //! }; //! use futures_util::StreamExt; //! use reis::{ //! ei::{self, keyboard::KeyState}, //! event::{DeviceCapability, EiEvent, KeyboardKey}, //! }; //! //! #[allow(unused)] //! enum Position { //! Left, //! Right, //! Top, //! Bottom, //! } //! //! static INTERFACES: OnceLock> = OnceLock::new(); //! //! async fn run() -> ashpd::Result<()> { //! let input_capture = InputCapture::new().await?; //! //! let (session, _cap) = input_capture //! .create_session( //! None, //! CreateSessionOptions::default().set_capabilities( //! Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen, //! ), //! ) //! .await?; //! //! // connect to eis server //! let fd = input_capture //! .connect_to_eis(&session, Default::default()) //! .await?; //! //! // create unix stream from fd //! let stream = UnixStream::from(fd); //! stream.set_nonblocking(true)?; //! //! // create ei context //! let context = ei::Context::new(stream)?; //! context.flush().unwrap(); //! //! let (_connection, mut event_stream) = context //! .handshake_tokio("ashpd-mre", ei::handshake::ContextType::Receiver) //! .await //! .expect("ei handshake failed"); //! //! let pos = Position::Left; //! let zones = input_capture //! .zones(&session, Default::default()) //! .await? //! .response()?; //! eprintln!("zones: {zones:?}"); //! let barriers = zones //! .regions() //! .iter() //! .enumerate() //! .map(|(n, r)| { //! let id = BarrierID::new((n + 1) as u32).expect("barrier-id must be non-zero"); //! let (x, y) = (r.x_offset(), r.y_offset()); //! let (width, height) = (r.width() as i32, r.height() as i32); //! let barrier_pos = match pos { //! Position::Left => BarrierPosition::new(x, y, x, y + height - 1), // start pos, end pos, inclusive //! Position::Right => BarrierPosition::new(x + width, y, x + width, y + height - 1), //! Position::Top => BarrierPosition::new(x, y, x + width - 1, y), //! Position::Bottom => BarrierPosition::new(x, y + height, x + width - 1, y + height), //! }; //! Barrier::new(id, barrier_pos) //! }) //! .collect::>(); //! //! eprintln!("requested barriers: {barriers:?}"); //! //! let request = input_capture //! .set_pointer_barriers(&session, &barriers, zones.zone_set(), Default::default()) //! .await?; //! let response = request.response()?; //! let failed_barrier_ids = response.failed_barriers(); //! //! eprintln!("failed barrier ids: {:?}", failed_barrier_ids); //! //! input_capture.enable(&session, Default::default()).await?; //! //! let mut activate_stream = input_capture.receive_activated().await?; //! //! loop { //! let activated = activate_stream.next().await.unwrap(); //! //! eprintln!("activated: {activated:?}"); //! loop { //! let ei_event = event_stream.next().await.unwrap().unwrap(); //! eprintln!("ei event: {ei_event:?}"); //! if let EiEvent::SeatAdded(seat_event) = &ei_event { //! seat_event.seat.bind_capabilities( //! DeviceCapability::Pointer //! | DeviceCapability::PointerAbsolute //! | DeviceCapability::Keyboard //! | DeviceCapability::Touch //! | DeviceCapability::Scroll //! | DeviceCapability::Button, //! ); //! context.flush().unwrap(); //! } //! if let EiEvent::DeviceAdded(_) = ei_event { //! // new device added -> restart capture //! break; //! }; //! if let EiEvent::KeyboardKey(KeyboardKey { key, state, .. }) = ei_event { //! if key == 1 && state == KeyState::Press { //! // esc pressed //! break; //! } //! } //! } //! //! eprintln!("releasing input capture"); //! let (x, y) = activated.cursor_position().unwrap(); //! let (x, y) = (x as f64, y as f64); //! let cursor_pos = match pos { //! Position::Left => (x + 1., y), //! Position::Right => (x - 1., y), //! Position::Top => (x, y - 1.), //! Position::Bottom => (x, y + 1.), //! }; //! input_capture //! .release( //! &session, //! ReleaseOptions::default() //! .set_activation_id(activated.activation_id()) //! .set_cursor_position(cursor_pos), //! ) //! .await?; //! } //! } //! ``` use std::{collections::HashMap, num::NonZeroU32, os::fd::OwnedFd}; use enumflags2::{BitFlags, bitflags}; use futures_util::Stream; use serde::{Deserialize, Serialize, de::Visitor}; use serde_repr::{Deserialize_repr, Serialize_repr}; use zbus::zvariant::{ self, ObjectPath, Optional, OwnedObjectPath, OwnedValue, Type, as_value::{self, optional}, }; use super::{HandleToken, PersistMode, Request, Session, session::SessionPortal}; use crate::{Error, WindowIdentifier, proxy::Proxy}; #[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Copy, Clone, Type)] #[bitflags] #[repr(u32)] /// Supported capabilities pub enum Capabilities { /// Keyboard Keyboard, /// Pointer Pointer, /// Touchscreen Touchscreen, } #[derive(Debug, Serialize, Type, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`InputCapture::create_session`] request. pub struct CreateSessionOptions { #[serde(with = "as_value")] handle_token: HandleToken, #[serde(with = "as_value")] session_handle_token: HandleToken, #[serde(with = "as_value")] capabilities: BitFlags, } impl CreateSessionOptions { /// Request the specified capabilities. pub fn set_capabilities(mut self, capabilities: BitFlags) -> Self { self.capabilities = capabilities; self } } #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] struct CreateSessionResponse { #[serde(with = "as_value")] session_handle: OwnedObjectPath, #[serde(with = "as_value")] capabilities: BitFlags, } #[derive(Debug, Serialize, Type, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`InputCapture::create_session2`] request. pub struct CreateSession2Options { #[serde(with = "as_value")] session_handle_token: HandleToken, } #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] struct CreateSession2Results { #[serde(with = "as_value")] session_handle: OwnedObjectPath, } #[derive(Debug, Serialize, Type, Default)] #[zvariant(signature = "dict")] /// Specified options for a [`InputCapture::start`] request. pub struct StartOptions { #[serde(with = "as_value")] handle_token: HandleToken, #[serde(with = "as_value")] capabilities: BitFlags, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] restore_token: Option, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] persist_mode: Option, } impl StartOptions { /// Request the specified capabilities. pub fn set_capabilities(mut self, capabilities: BitFlags) -> Self { self.capabilities = capabilities; self } /// Set the token to restore a previous persistent session. pub fn set_restore_token(mut self, restore_token: impl Into>) -> Self { self.restore_token = restore_token.into(); self } /// Set the persist mode for this session. pub fn set_persist_mode(mut self, persist_mode: impl Into>) -> Self { self.persist_mode = persist_mode.into(); self } } #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] /// Response of [`InputCapture::create_session`] request. pub struct StartResponse { #[serde(with = "as_value")] capabilities: BitFlags, #[serde(default, with = "optional")] clipboard_enabled: Option, #[serde(default, with = "optional")] restore_token: Option, } impl StartResponse { /// The capabilities available to this session. pub fn capabilities(&self) -> BitFlags { self.capabilities } /// Whether the clipboard was enabled. pub fn is_clipboard_enabled(&self) -> bool { self.clipboard_enabled.unwrap_or(false) } /// The session restore token. pub fn restore_token(&self) -> Option<&str> { self.restore_token.as_deref() } } #[derive(Default, Debug, Serialize, Type)] #[zvariant(signature = "dict")] /// Specified options for a [`InputCapture::zones`] request. pub struct GetZonesOptions { #[serde(with = "as_value")] handle_token: HandleToken, } #[derive(Default, Debug, Serialize, Type)] #[zvariant(signature = "dict")] /// Specified options for a [`InputCapture::set_pointer_barriers`] request. pub struct SetPointerBarriersOptions { #[serde(with = "as_value")] handle_token: HandleToken, } #[derive(Default, Debug, Serialize, Type)] #[zvariant(signature = "dict")] /// Specified options for a [`InputCapture::enable`] request. pub struct EnableOptions {} #[derive(Default, Debug, Serialize, Type)] #[zvariant(signature = "dict")] /// Specified options for a [`InputCapture::disable`] request. pub struct DisableOptions {} #[derive(Default, Debug, Serialize, Type)] #[zvariant(signature = "dict")] /// Specified options for a [`InputCapture::release`] request. pub struct ReleaseOptions { #[serde(with = "optional", skip_serializing_if = "Option::is_none")] activation_id: Option, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] cursor_position: Option<(f64, f64)>, } impl ReleaseOptions { /// The same activation_id number as in the corresponding "Activated" /// signal. pub fn set_activation_id(mut self, activation_id: impl Into>) -> Self { self.activation_id = activation_id.into(); self } /// The suggested cursor position within the Zones available in this /// session. pub fn set_cursor_position(mut self, cursor_position: impl Into>) -> Self { self.cursor_position = cursor_position.into(); self } } #[derive(Default, Debug, Serialize, Type)] #[zvariant(signature = "dict")] /// Specified options for a [`InputCapture::connect_to_eis`] request. pub struct ConnectToEISOptions {} /// Indicates that an input capturing session was disabled. #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "(oa{sv})")] pub struct Disabled(OwnedObjectPath, HashMap); impl Disabled { /// Session that was disabled. pub fn session_handle(&self) -> ObjectPath<'_> { self.0.as_ref() } /// Optional information pub fn options(&self) -> &HashMap { &self.1 } } #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] struct DeactivatedOptions { #[serde(default, with = "optional")] activation_id: Option, } /// Indicates that an input capturing session was deactivated. #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "(oa{sv})")] pub struct Deactivated(OwnedObjectPath, DeactivatedOptions); impl Deactivated { /// Session that was deactivated. pub fn session_handle(&self) -> ObjectPath<'_> { self.0.as_ref() } /// The same activation_id number as in the corresponding "Activated" /// signal. pub fn activation_id(&self) -> Option { self.1.activation_id } } #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] struct ActivatedOptions { #[serde(default, with = "optional")] activation_id: Option, #[serde(default, with = "optional")] cursor_position: Option<(f32, f32)>, #[serde(default, with = "optional")] barrier_id: Option, } /// Indicates that an input capturing session was activated. #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "(oa{sv})")] pub struct Activated(OwnedObjectPath, ActivatedOptions); impl Activated { /// Session that was activated. pub fn session_handle(&self) -> ObjectPath<'_> { self.0.as_ref() } /// A number that can be used to synchronize with the transport-layer. pub fn activation_id(&self) -> Option { self.1.activation_id } /// The current cursor position in the same coordinate space as the zones. pub fn cursor_position(&self) -> Option<(f32, f32)> { self.1.cursor_position } /// The barrier that was triggered or None, /// if the input-capture was not triggered by a barrier pub fn barrier_id(&self) -> Option { self.1.barrier_id } } #[derive(Clone, Copy, Debug, Type)] #[zvariant(signature = "u")] /// information about an activation barrier pub enum ActivatedBarrier { /// [`BarrierID`] of the triggered barrier Barrier(BarrierID), /// The id of the triggered barrier could not be determined, /// e.g. because of multiple barriers at the same location. UnknownBarrier, } impl<'de> Deserialize<'de> for ActivatedBarrier { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let visitor = ActivatedBarrierVisitor {}; deserializer.deserialize_u32(visitor) } } struct ActivatedBarrierVisitor {} impl Visitor<'_> for ActivatedBarrierVisitor { type Value = ActivatedBarrier; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "an unsigned 32bit integer (u32)") } fn visit_u32(self, v: u32) -> Result where E: serde::de::Error, { match BarrierID::new(v) { Some(v) => Ok(ActivatedBarrier::Barrier(v)), None => Ok(ActivatedBarrier::UnknownBarrier), } } } #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] struct ZonesChangedOptions { #[serde(default, with = "optional")] zone_set: Option, } /// Indicates that zones available to this session changed. #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "(oa{sv})")] pub struct ZonesChanged(OwnedObjectPath, ZonesChangedOptions); impl ZonesChanged { /// Session that was deactivated. pub fn session_handle(&self) -> ObjectPath<'_> { self.0.as_ref() } /// The zone_set ID of the invalidated zone. pub fn zone_set(&self) -> Option { self.1.zone_set } } /// A region of a [`Zones`]. #[derive(Debug, Clone, Copy, Deserialize, Type)] #[zvariant(signature = "(uuii)")] pub struct Region(u32, u32, i32, i32); impl Region { /// The width. pub fn width(self) -> u32 { self.0 } /// The height pub fn height(self) -> u32 { self.1 } /// The x offset. pub fn x_offset(self) -> i32 { self.2 } /// The y offset. pub fn y_offset(self) -> i32 { self.3 } } /// A response of [`InputCapture::zones`]. #[derive(Debug, Type, Deserialize)] #[zvariant(signature = "dict")] pub struct Zones { #[serde(default, with = "as_value")] zones: Vec, #[serde(default, with = "as_value")] zone_set: u32, } impl Zones { /// A list of regions. pub fn regions(&self) -> &[Region] { &self.zones } /// A unique ID to be used in [`InputCapture::set_pointer_barriers`]. pub fn zone_set(&self) -> u32 { self.zone_set } } /// A barrier ID. pub type BarrierID = NonZeroU32; /// Position of a barrier defined by two points (x1, y1) and (x2, y2). /// /// Barriers are typically placed along screen edges. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Type)] #[zvariant(signature = "(iiii)")] pub struct BarrierPosition { /// x coordinate of the first point x1: i32, /// y coordinate of the first point y1: i32, /// x coordinate of the second point x2: i32, /// y coordinate of the second point y2: i32, } impl BarrierPosition { /// Create a new barrier position represented by the /// points x1/y1 to x2/y2. pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Self { Self { x1, y1, x2, y2 } } /// Convert to a tuple (x1, y1, x2, y2). pub fn as_tuple(&self) -> (i32, i32, i32, i32) { (self.x1, self.y1, self.x2, self.y2) } /// The x coordinate of the first point of the barrier. pub fn x1(&self) -> i32 { self.x1 } /// The y coordinate of the second point of the barrier. pub fn y1(&self) -> i32 { self.y1 } /// The x coordinate of the second point of the barrier. pub fn x2(&self) -> i32 { self.x2 } /// The y coordinate of the second point of the barrier. pub fn y2(&self) -> i32 { self.y2 } } impl From<(i32, i32, i32, i32)> for BarrierPosition { fn from(pos: (i32, i32, i32, i32)) -> Self { Self { x1: pos.0, y1: pos.1, x2: pos.2, y2: pos.3, } } } impl From for (i32, i32, i32, i32) { fn from(pos: BarrierPosition) -> Self { pos.as_tuple() } } impl Serialize for BarrierPosition { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.as_tuple().serialize(serializer) } } #[derive(Debug, Serialize, Type)] #[zvariant(signature = "dict")] /// Input Barrier. pub struct Barrier { #[serde(with = "as_value")] barrier_id: BarrierID, #[serde(with = "as_value")] position: BarrierPosition, } impl Barrier { /// Create a new barrier. pub fn new(barrier_id: BarrierID, position: impl Into) -> Self { Self { barrier_id, position: position.into(), } } /// Get the barrier ID. pub fn barrier_id(&self) -> BarrierID { self.barrier_id } /// Get the barrier position. pub fn position(&self) -> BarrierPosition { self.position } } /// A response to [`InputCapture::set_pointer_barriers`] #[derive(Debug, Deserialize, Type)] #[zvariant(signature = "dict")] pub struct SetPointerBarriersResponse { #[serde(default, with = "as_value")] failed_barriers: Vec, } impl SetPointerBarriersResponse { /// List of pointer barriers that have been denied pub fn failed_barriers(&self) -> &[BarrierID] { &self.failed_barriers } } /// Wrapper of the DBus interface: [`org.freedesktop.portal.InputCapture`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html). #[doc(alias = "org.freedesktop.portal.InputCapture")] pub struct InputCapture(Proxy<'static>); impl InputCapture { /// Create a new instance of [`InputCapture`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.InputCapture").await?; Ok(Self(proxy)) } /// Create a new instance of [`InputCapture`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.InputCapture") .await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Create an input capture session. /// /// # Specifications /// /// See also [`CreateSession`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-createsession). #[doc(alias = "CreateSession")] pub async fn create_session( &self, identifier: Option<&WindowIdentifier>, options: CreateSessionOptions, ) -> Result<(Session, BitFlags), Error> { let identifier = Optional::from(identifier); let (request, proxy) = futures_util::try_join!( self.0.request::( &options.handle_token, "CreateSession", (identifier, &options) ), Session::from_unique_name(self.0.connection().clone(), &options.session_handle_token), )?; let response = request.response()?; assert_eq!(proxy.path(), &response.session_handle.as_ref()); Ok((proxy, response.capabilities)) } /// Create an input capture session. /// /// The session must be started with [`start`][`InputCapture::start`] /// before using methods which take a session. /// /// This method was added in version 2 of the interface. /// /// # Example /// /// Use the following approach to start a session and fall back to /// the legacy [`create_session`][`InputCapture::create_session`] /// for portals that only implement version 1. /// /// ```rust,no_run /// use ashpd::{ /// Error, /// desktop::{ /// PersistMode, /// input_capture::{Capabilities, InputCapture, StartOptions}, /// }, /// }; /// /// # async fn run() -> ashpd::Result<()> { /// let input_capture = InputCapture::new().await?; /// let opts = ashpd::desktop::input_capture::CreateSession2Options::default(); /// /// let session = match input_capture.create_session2(opts).await { /// Ok(sess) => { /// // Version 2: explicitly start the session /// let opts = StartOptions::default() /// .set_capabilities(Capabilities::Keyboard | Capabilities::Pointer); /// input_capture.start(&sess, None, opts).await?; /// sess /// } /// Err(Error::RequiresVersion(_, _)) => { /// // Version 1: fallback to legacy API, starts implicitly /// let opts = ashpd::desktop::input_capture::CreateSessionOptions::default() /// .set_capabilities(Capabilities::Keyboard | Capabilities::Pointer); /// let (session, _capabilities) = input_capture.create_session(None, opts).await?; /// session /// } /// Err(e) => return Err(e), /// }; /// # Ok(()) /// # } /// ``` /// /// # Specifications /// /// See also [`CreateSession2`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-createsession2). #[doc(alias = "CreateSession2")] pub async fn create_session2( &self, options: CreateSession2Options, ) -> Result, Error> { let proxy = Session::from_unique_name(self.0.connection().clone(), &options.session_handle_token) .await?; let response = self .0 .call_versioned::("CreateSession2", &options, 2) .await?; assert_eq!(proxy.path(), &response.session_handle.as_ref()); Ok(proxy) } /// Start the input capture session. /// /// This will typically result in the portal presenting a dialog letting /// the user decide whether they want to allow the input of the session /// to be captured, and what capabilities to support. /// /// This method may only be called once on a session previously created /// with [`create_session2`][`InputCapture::create_session2`]. /// /// This method was added in version 2 of the interface. /// /// # Arguments /// /// * `session` - A [`Session`], created with /// [`create_session2()`][`InputCapture::create_session2`]. /// * `identifier` - Identifier for the application window. /// * `capabilities` - Bitmask of requested capabilities. /// * `restore_token` - The token to restore a previous session. /// * `persist_mode` - How this session should persist. /// /// # Specifications /// /// See also [`Start`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-start). #[doc(alias = "Start")] pub async fn start( &self, session: &Session, identifier: Option<&WindowIdentifier>, options: StartOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .request_versioned( &options.handle_token, "Start", (session, identifier, &options), 2, ) .await } /// A set of currently available input zones for this session. /// /// # Specifications /// /// See also [`GetZones`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-getzones). #[doc(alias = "GetZones")] pub async fn zones( &self, session: &Session, options: GetZonesOptions, ) -> Result, Error> { self.0 .request(&options.handle_token, "GetZones", (session, &options)) .await } /// Set up zero or more pointer barriers. /// /// # Specifications /// /// See also [`SetPointerBarriers`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-setpointerbarriers). #[doc(alias = "SetPointerBarriers")] pub async fn set_pointer_barriers( &self, session: &Session, barriers: &[Barrier], zone_set: u32, options: SetPointerBarriersOptions, ) -> Result, Error> { self.0 .request( &options.handle_token, "SetPointerBarriers", &(session, &options, barriers, zone_set), ) .await } /// Enable input capturing. /// /// # Specifications /// /// See also [`Enable`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-enable). #[doc(alias = "Enable")] pub async fn enable( &self, session: &Session, options: EnableOptions, ) -> Result<(), Error> { self.0.call("Enable", &(session, &options)).await } /// Disable input capturing. /// /// # Specifications /// /// See also [`Disable`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-disable). #[doc(alias = "Disable")] pub async fn disable( &self, session: &Session, options: DisableOptions, ) -> Result<(), Error> { self.0.call("Disable", &(session, &options)).await } /// Release any ongoing input capture. /// /// # Specifications /// /// See also [`Release`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-release). #[doc(alias = "Release")] pub async fn release( &self, session: &Session, options: ReleaseOptions, ) -> Result<(), Error> { self.0.call("Release", &(session, &options)).await } /// Connect to EIS. /// /// # Specifications /// /// See also [`ConnectToEIS`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-connecttoeis). #[doc(alias = "ConnectToEIS")] pub async fn connect_to_eis( &self, session: &Session, options: ConnectToEISOptions, ) -> Result { let fd = self .0 .call::("ConnectToEIS", &(session, options)) .await?; Ok(fd.into()) } /// Signal emitted when the application will no longer receive captured /// events. /// /// # Specifications /// /// See also [`Disabled`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-disabled). #[doc(alias = "Disabled")] pub async fn receive_disabled(&self) -> Result, Error> { self.0.signal("Disabled").await } /// Signal emitted when input capture starts and /// input events are about to be sent to the application. /// /// # Specifications /// /// See also [`Activated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-activated). #[doc(alias = "Activated")] pub async fn receive_activated(&self) -> Result, Error> { self.0.signal("Activated").await } /// Signal emitted when input capture stopped and input events /// are no longer sent to the application. /// /// # Specifications /// /// See also [`Deactivated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-deactivated). #[doc(alias = "Deactivated")] pub async fn receive_deactivated(&self) -> Result, Error> { self.0.signal("Deactivated").await } /// Signal emitted when the set of zones available to this session change. /// /// # Specifications /// /// See also [`ZonesChanged`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-zoneschanged). #[doc(alias = "ZonesChanged")] pub async fn receive_zones_changed(&self) -> Result, Error> { self.0.signal("ZonesChanged").await } /// Supported capabilities. /// /// # Specifications /// /// See also [`SupportedCapabilities`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-supportedcapabilities). #[doc(alias = "SupportedCapabilities")] pub async fn supported_capabilities(&self) -> Result, Error> { self.0.property("SupportedCapabilities").await } } impl std::ops::Deref for InputCapture { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } impl crate::Sealed for InputCapture {} impl SessionPortal for InputCapture {} #[cfg(feature = "clipboard")] impl crate::desktop::clipboard::IsClipboardSession for InputCapture {} #[cfg(test)] mod tests { use super::BarrierPosition; #[test] fn test_barrier_position() { let pos = BarrierPosition::new(1, 2, 3, 4); assert_eq!(pos.as_tuple(), (1, 2, 3, 4)); assert_eq!(pos.x1(), 1); assert_eq!(pos.y1(), 2); assert_eq!(pos.x2(), 3); assert_eq!(pos.y2(), 4); let string = serde_json::to_string(&pos).unwrap(); assert_eq!(string, "[1,2,3,4]"); let pos2 = BarrierPosition::from((1, 2, 3, 4)); assert_eq!(pos, pos2); } } ashpd-0.13.11/src/desktop/location.rs000064400000000000000000000241231046102023000155060ustar 00000000000000//! # Examples //! //! ```rust,no_run //! use ashpd::desktop::location::{Accuracy, CreateSessionOptions, LocationProxy}; //! use futures_util::{FutureExt, StreamExt}; //! //! async fn run() -> ashpd::Result<()> { //! let proxy = LocationProxy::new().await?; //! let session = proxy //! .create_session(CreateSessionOptions::default().set_accuracy(Accuracy::Street)) //! .await?; //! let mut stream = proxy.receive_location_updated().await?; //! let (_, location) = futures_util::join!( //! proxy //! .start(&session, None, Default::default()) //! .map(|e| e.expect("Couldn't start session")), //! stream.next().map(|e| e.expect("Stream is exhausted")) //! ); //! println!("{}", location.accuracy()); //! println!("{}", location.longitude()); //! println!("{}", location.latitude()); //! session.close().await?; //! Ok(()) //! } //! ``` use std::fmt::Debug; use futures_util::Stream; use serde::{Deserialize, Serialize}; use serde_repr::Serialize_repr; use zbus::zvariant::{ ObjectPath, Optional, OwnedObjectPath, Type, as_value::{self, optional}, }; use super::{HandleToken, Request, Session, session::SessionPortal}; use crate::{Error, WindowIdentifier, proxy::Proxy}; #[cfg_attr(feature = "glib", derive(glib::Enum))] #[cfg_attr(feature = "glib", enum_type(name = "AshpdLocationAccuracy"))] #[derive(Serialize_repr, PartialEq, Eq, Clone, Copy, Debug, Type)] #[doc(alias = "XdpLocationAccuracy")] #[repr(u32)] /// The accuracy of the location. pub enum Accuracy { #[doc(alias = "XDP_LOCATION_ACCURACY_NONE")] /// None. None = 0, #[doc(alias = "XDP_LOCATION_ACCURACY_COUNTRY")] /// Country. Country = 1, #[doc(alias = "XDP_LOCATION_ACCURACY_CITY")] /// City. City = 2, #[doc(alias = "XDP_LOCATION_ACCURACY_NEIGHBORHOOD")] /// Neighborhood. Neighborhood = 3, #[doc(alias = "XDP_LOCATION_ACCURACY_STREET")] /// Street. Street = 4, #[doc(alias = "XDP_LOCATION_ACCURACY_EXACT")] /// The exact location. Exact = 5, } #[derive(Serialize, Type, Debug, Default)] /// Specified options for a [`LocationProxy::create_session`] request. #[zvariant(signature = "dict")] pub struct CreateSessionOptions { #[serde(with = "as_value")] session_handle_token: HandleToken, #[serde( rename = "distance-threshold", with = "optional", skip_serializing_if = "Option::is_none" )] distance_threshold: Option, #[serde( rename = "time-threshold", with = "optional", skip_serializing_if = "Option::is_none" )] time_threshold: Option, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] accuracy: Option, } impl CreateSessionOptions { /// Distance threshold in meters. Default is 0. pub fn set_distance_threshold(mut self, distance_threshold: impl Into>) -> Self { self.distance_threshold = distance_threshold.into(); self } /// Time threshold in seconds. Default is 0. pub fn set_time_threshold(mut self, time_threshold: impl Into>) -> Self { self.time_threshold = time_threshold.into(); self } /// Requested accuracy. Default is `Accuracy::Exact`. pub fn set_accuracy(mut self, accuracy: impl Into>) -> Self { self.accuracy = accuracy.into(); self } } #[derive(Serialize, Type, Debug, Default)] /// Specified options for a [`LocationProxy::start`] request. #[zvariant(signature = "dict")] pub struct StartOptions { #[serde(with = "as_value")] handle_token: HandleToken, } #[derive(Deserialize, Type)] /// The response received on a `location_updated` signal. pub struct Location(OwnedObjectPath, LocationInner); impl Location { /// The associated session. pub fn session_handle(&self) -> ObjectPath<'_> { self.0.as_ref() } /// The accuracy, in meters. pub fn accuracy(&self) -> f64 { self.1.accuracy } /// The altitude, in meters. pub fn altitude(&self) -> Option { if self.1.altitude == -f64::MAX { None } else { Some(self.1.altitude) } } /// The speed, in meters per second. pub fn speed(&self) -> Option { if self.1.speed == -1f64 { None } else { Some(self.1.speed) } } /// The heading, in degrees, going clockwise. North 0, East 90, South 180, /// West 270. pub fn heading(&self) -> Option { if self.1.heading == -1f64 { None } else { Some(self.1.heading) } } /// The location description. pub fn description(&self) -> Option<&str> { if self.1.description.is_empty() { None } else { Some(&self.1.description) } } /// The latitude, in degrees. pub fn latitude(&self) -> f64 { self.1.latitude } /// The longitude, in degrees. pub fn longitude(&self) -> f64 { self.1.longitude } /// The timestamp when the location was retrieved. pub fn timestamp(&self) -> std::time::Duration { std::time::Duration::from_secs(self.1.timestamp.0) } } impl Debug for Location { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Location") .field("accuracy", &self.accuracy()) .field("altitude", &self.altitude()) .field("speed", &self.speed()) .field("heading", &self.heading()) .field("description", &self.description()) .field("latitude", &self.latitude()) .field("longitude", &self.longitude()) .field("timestamp", &self.timestamp()) .finish() } } #[derive(Debug, Serialize, Deserialize, Type)] #[zvariant(signature = "dict")] #[serde(rename_all = "PascalCase")] struct LocationInner { #[serde(with = "as_value")] accuracy: f64, #[serde(with = "as_value")] altitude: f64, #[serde(with = "as_value")] speed: f64, #[serde(with = "as_value")] heading: f64, #[serde(with = "as_value")] description: String, #[serde(with = "as_value")] latitude: f64, #[serde(with = "as_value")] longitude: f64, #[serde(with = "as_value")] timestamp: (u64, u64), } /// The interface lets sandboxed applications query basic information about the /// location. /// /// Wrapper of the DBus interface: [`org.freedesktop.portal.Location`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Location.html). #[derive(Debug, Clone)] #[doc(alias = "org.freedesktop.portal.Location")] pub struct LocationProxy(Proxy<'static>); impl LocationProxy { /// Create a new instance of [`LocationProxy`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.Location").await?; Ok(Self(proxy)) } /// Create a new instance of [`LocationProxy`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.Location") .await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Signal emitted when the user location is updated. /// /// # Specifications /// /// See also [`LocationUpdated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Location.html#org-freedesktop-portal-location-locationupdated). #[doc(alias = "LocationUpdated")] #[doc(alias = "XdpPortal::location-updated")] pub async fn receive_location_updated( &self, ) -> Result + use<>, Error> { self.0.signal("LocationUpdated").await } /// Create a location session. /// /// # Arguments /// /// * `distance_threshold` - Sets the distance threshold in meters, default /// to `0`. /// * `time_threshold` - Sets the time threshold in seconds, default to `0`. /// * `accuracy` - Sets the location accuracy, default to /// [`Accuracy::Exact`]. /// /// # Specifications /// /// See also [`CreateSession`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Location.html#org-freedesktop-portal-location-createsession). #[doc(alias = "CreateSession")] pub async fn create_session( &self, options: CreateSessionOptions, ) -> Result, Error> { let (path, proxy) = futures_util::try_join!( self.0.call::("CreateSession", &(options)), Session::from_unique_name(self.0.connection().clone(), &options.session_handle_token), )?; assert_eq!(proxy.path(), &path.into_inner()); Ok(proxy) } /// Start the location session. /// An application can only attempt start a session once. /// /// # Arguments /// /// * `session` - A [`Session`], created with /// [`create_session()`][`LocationProxy::create_session`]. /// * `identifier` - Identifier for the application window. /// /// # Specifications /// /// See also [`Start`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Location.html#org-freedesktop-portal-location-start). #[doc(alias = "Start")] #[doc(alias = "xdp_portal_location_monitor_start")] pub async fn start( &self, session: &Session, identifier: Option<&WindowIdentifier>, options: StartOptions, ) -> Result, Error> { let identifier = Optional::from(identifier); self.0 .empty_request( &options.handle_token, "Start", &(session, identifier, &options), ) .await } } impl crate::Sealed for LocationProxy {} impl SessionPortal for LocationProxy {} impl std::ops::Deref for LocationProxy { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } ashpd-0.13.11/src/desktop/memory_monitor.rs000064400000000000000000000045531046102023000167620ustar 00000000000000//! # Examples //! //! ```rust,no_run //! use ashpd::desktop::memory_monitor::MemoryMonitor; //! use futures_util::StreamExt; //! //! async fn run() -> ashpd::Result<()> { //! let proxy = MemoryMonitor::new().await?; //! let level = proxy //! .receive_low_memory_warning() //! .await? //! .next() //! .await //! .expect("Stream exhausted"); //! println!("{}", level); //! Ok(()) //! } //! ``` use futures_util::Stream; use crate::{Error, proxy::Proxy}; /// The interface provides information about low system memory to sandboxed /// applications. /// /// It is not a portal in the strict sense, since it does not involve user /// interaction. /// /// Wrapper of the DBus interface: [`org.freedesktop.portal.MemoryMonitor`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.MemoryMonitor.html). #[derive(Debug)] #[doc(alias = "org.freedesktop.portal.MemoryMonitor")] pub struct MemoryMonitor(Proxy<'static>); impl MemoryMonitor { /// Create a new instance of [`MemoryMonitor`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.MemoryMonitor").await?; Ok(Self(proxy)) } /// Create a new instance of [`MemoryMonitor`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.MemoryMonitor") .await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Signal emitted when a particular low memory situation happens /// with 0 being the lowest level of memory availability warning, and 255 /// being the highest. /// /// # Specifications /// /// See also [`LowMemoryWarning`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.MemoryMonitor.html#org-freedesktop-portal-memorymonitor-lowmemorywarning). #[doc(alias = "LowMemoryWarning")] pub async fn receive_low_memory_warning(&self) -> Result, Error> { self.0.signal("LowMemoryWarning").await } } impl std::ops::Deref for MemoryMonitor { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } ashpd-0.13.11/src/desktop/mod.rs000064400000000000000000000130471046102023000144600ustar 00000000000000mod handle_token; pub(crate) mod request; mod session; #[cfg_attr(docsrs, doc(cfg(feature = "backend")))] #[cfg(feature = "backend")] pub use self::handle_token::HandleToken; #[cfg(not(feature = "backend"))] pub(crate) use self::handle_token::HandleToken; pub use self::{ request::{Request, Response, ResponseError, ResponseType}, session::{CreateSessionOptions, Session, SessionPortal}, }; #[cfg(any(feature = "screenshot", feature = "settings"))] mod color; #[cfg_attr(docsrs, doc(cfg(any(feature = "screenshot", feature = "settings",))))] #[cfg(any(feature = "screenshot", feature = "settings",))] pub use color::Color; #[cfg(any( feature = "notification", feature = "dynamic_launcher", feature = "backend" ))] mod icon; #[cfg_attr( docsrs, doc(cfg(any( feature = "notification", feature = "dynamic_launcher", feature = "backend" ))) )] #[cfg(any( feature = "notification", feature = "dynamic_launcher", feature = "backend" ))] pub use icon::Icon; #[cfg_attr(docsrs, doc(cfg(feature = "account")))] #[cfg(feature = "account")] pub mod account; #[cfg_attr(docsrs, doc(cfg(feature = "background")))] #[cfg(feature = "background")] pub mod background; #[cfg_attr(docsrs, doc(cfg(feature = "camera")))] #[cfg(feature = "camera")] pub mod camera; #[cfg_attr(docsrs, doc(cfg(feature = "clipboard")))] #[cfg(feature = "clipboard")] pub mod clipboard; #[cfg_attr(docsrs, doc(cfg(feature = "dynamic_launcher")))] #[cfg(feature = "dynamic_launcher")] pub mod dynamic_launcher; #[cfg_attr(docsrs, doc(cfg(feature = "email")))] #[cfg(feature = "email")] pub mod email; /// Open/save file(s) chooser. #[cfg_attr(docsrs, doc(cfg(feature = "file_chooser")))] #[cfg(feature = "file_chooser")] pub mod file_chooser; /// Enable/disable/query the status of Game Mode. #[cfg_attr(docsrs, doc(cfg(feature = "game_mode")))] #[cfg(feature = "game_mode")] pub mod game_mode; /// Register global shortcuts #[cfg_attr(docsrs, doc(cfg(feature = "global_shortcuts")))] #[cfg(feature = "global_shortcuts")] pub mod global_shortcuts; /// Inhibit the session from being restarted or the user from logging out. #[cfg_attr(docsrs, doc(cfg(feature = "inhibit")))] #[cfg(feature = "inhibit")] pub mod inhibit; /// Capture input events from physical or logical devices. #[cfg_attr(docsrs, doc(cfg(feature = "input_capture")))] #[cfg(feature = "input_capture")] pub mod input_capture; /// Query the user's GPS location. #[cfg_attr(docsrs, doc(cfg(feature = "location")))] #[cfg(feature = "location")] pub mod location; /// Monitor memory level. #[cfg_attr(docsrs, doc(cfg(feature = "memory_monitor")))] #[cfg(feature = "memory_monitor")] pub mod memory_monitor; /// Check the status of the network on a user's machine. #[cfg_attr(docsrs, doc(cfg(feature = "network_monitor")))] #[cfg(feature = "network_monitor")] pub mod network_monitor; /// Send/withdraw notifications. #[cfg_attr(docsrs, doc(cfg(feature = "notification")))] #[cfg(feature = "notification")] pub mod notification; #[cfg_attr(docsrs, doc(cfg(feature = "open_uri")))] #[cfg(feature = "open_uri")] pub mod open_uri; /// Power profile monitoring. #[cfg_attr(docsrs, doc(cfg(feature = "power_profile_monitor")))] #[cfg(feature = "power_profile_monitor")] pub mod power_profile_monitor; /// Print a document. #[cfg_attr(docsrs, doc(cfg(feature = "print")))] #[cfg(feature = "print")] pub mod print; /// Proxy information. #[cfg_attr(docsrs, doc(cfg(feature = "proxy_resolver")))] #[cfg(feature = "proxy_resolver")] pub mod proxy_resolver; #[cfg_attr(docsrs, doc(cfg(feature = "realtime")))] #[cfg(feature = "realtime")] pub mod realtime; /// Start a remote desktop session and interact with it. #[cfg_attr(docsrs, doc(cfg(feature = "remote_desktop")))] #[cfg(feature = "remote_desktop")] pub mod remote_desktop; #[cfg_attr(docsrs, doc(cfg(feature = "screencast")))] #[cfg(feature = "screencast")] pub mod screencast; #[cfg_attr(docsrs, doc(cfg(feature = "screenshot")))] #[cfg(feature = "screenshot")] pub mod screenshot; /// Retrieve a per-application secret used to encrypt confidential data inside /// the sandbox. #[cfg_attr(docsrs, doc(cfg(feature = "secret")))] #[cfg(feature = "secret")] pub mod secret; /// Read & listen to system settings changes. #[cfg_attr(docsrs, doc(cfg(feature = "settings")))] #[cfg(feature = "settings")] pub mod settings; #[cfg_attr(docsrs, doc(cfg(feature = "trash")))] #[cfg(feature = "trash")] pub mod trash; #[cfg_attr(docsrs, doc(cfg(feature = "usb")))] #[cfg(feature = "usb")] pub mod usb; #[cfg_attr(docsrs, doc(cfg(feature = "wallpaper")))] #[cfg(feature = "wallpaper")] pub mod wallpaper; #[cfg_attr(feature = "glib", derive(glib::Enum))] #[cfg_attr(feature = "glib", enum_type(name = "AshpdPersistMode"))] #[derive( Default, serde_repr::Deserialize_repr, serde_repr::Serialize_repr, PartialEq, Eq, Debug, Copy, Clone, zbus::zvariant::Type, )] #[cfg_attr( docsrs, doc(cfg(any( feature = "screencast", feature = "remote_desktop", feature = "input_capture" ))) )] #[cfg(any( feature = "screencast", feature = "remote_desktop", feature = "input_capture" ))] #[doc(alias = "XdpPersistMode")] #[repr(u32)] /// Persistence mode for a screencast, remote desktop, or input capture session. pub enum PersistMode { #[doc(alias = "XDP_PERSIST_MODE_NONE")] #[default] /// Do not persist. DoNot = 0, #[doc(alias = "XDP_PERSIST_MODE_TRANSIENT")] /// Persist while the application is running. Application = 1, #[doc(alias = "XDP_PERSIST_MODE_PERSISTENT")] /// Persist until explicitly revoked. ExplicitlyRevoked = 2, } ashpd-0.13.11/src/desktop/network_monitor.rs000064400000000000000000000203051046102023000171340ustar 00000000000000//! **Note** This portal doesn't work for sandboxed applications. //! # Examples //! //! ```rust,no_run //! use ashpd::desktop::network_monitor::NetworkMonitor; //! //! async fn run() -> ashpd::Result<()> { //! let proxy = NetworkMonitor::new().await?; //! //! println!("{}", proxy.can_reach("www.google.com", 80).await?); //! println!("{}", proxy.is_available().await?); //! println!("{:#?}", proxy.connectivity().await?); //! println!("{}", proxy.is_metered().await?); //! println!("{:#?}", proxy.status().await?); //! //! Ok(()) //! } //! ``` use std::fmt; use futures_util::Stream; use serde::Deserialize; use serde_repr::Deserialize_repr; use zbus::zvariant::{Type, as_value}; use crate::{Error, proxy::Proxy}; #[derive(Deserialize, Type, Debug)] /// The network status, composed of the availability, metered & connectivity #[zvariant(signature = "dict")] pub struct NetworkStatus { /// Whether the network is considered available. #[serde(with = "as_value")] available: bool, /// Whether the network is considered metered. #[serde(with = "as_value")] metered: bool, /// More detailed information about the host's network connectivity #[serde(with = "as_value")] connectivity: Connectivity, } impl NetworkStatus { /// Returns whether the network is considered available. pub fn is_available(&self) -> bool { self.available } /// Returns whether the network is considered metered. pub fn is_metered(&self) -> bool { self.metered } /// Returns more detailed information about the host's network connectivity. pub fn connectivity(&self) -> Connectivity { self.connectivity } } #[cfg_attr(feature = "glib", derive(glib::Enum))] #[cfg_attr(feature = "glib", enum_type(name = "AshpdConnectivity"))] #[derive(Deserialize_repr, PartialEq, Eq, Debug, Clone, Copy, Type)] #[repr(u32)] /// Host's network activity pub enum Connectivity { /// The host is not configured with a route to the internet. Local = 1, /// The host is connected to a network, but can't reach the full internet. Limited = 2, /// The host is behind a captive portal and cannot reach the full internet. CaptivePortal = 3, /// The host connected to a network, and can reach the full internet. FullNetwork = 4, } impl fmt::Display for Connectivity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let connectivity = match self { Self::Local => "local", Self::Limited => "limited", Self::CaptivePortal => "captive portal", Self::FullNetwork => "full network", }; f.write_str(connectivity) } } /// The interface provides network status information to sandboxed applications. /// /// It is not a portal in the strict sense, since it does not involve user /// interaction. Applications are expected to use this interface indirectly, /// via a library API such as the GLib [`gio::NetworkMonitor`](https://gtk-rs.org/gtk-rs-core/stable/latest/docs/gio/struct.NetworkMonitor.html) interface. /// /// Wrapper of the DBus interface: [`org.freedesktop.portal.NetworkMonitor`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.NetworkMonitor.html). #[derive(Debug)] #[doc(alias = "org.freedesktop.portal.NetworkMonitor")] pub struct NetworkMonitor(Proxy<'static>); impl NetworkMonitor { /// Create a new instance of [`NetworkMonitor`]. pub async fn new() -> Result { let proxy = Proxy::new_desktop("org.freedesktop.portal.NetworkMonitor").await?; Ok(Self(proxy)) } /// Create a new instance of [`NetworkMonitor`]. pub async fn with_connection(connection: zbus::Connection) -> Result { let proxy = Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.NetworkMonitor") .await?; Ok(Self(proxy)) } /// Returns the version of the portal interface. pub fn version(&self) -> u32 { self.0.version() } /// Returns whether the given hostname is believed to be reachable. /// /// # Arguments /// /// * `hostname` - The hostname to reach. /// * `port` - The port to reach. /// /// # Required version /// /// The method requires the 3nd version implementation of the portal and /// would fail with [`Error::RequiresVersion`] otherwise. /// /// # Specifications /// /// See also [`CanReach`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.NetworkMonitor.html#org-freedesktop-portal-networkmonitor-canreach). #[doc(alias = "CanReach")] pub async fn can_reach(&self, hostname: &str, port: u32) -> Result { self.0 .call_versioned("CanReach", &(hostname, port), 3) .await } /// Returns whether the network is considered available. /// That is, whether the system as a default route for at least one of IPv4 /// or IPv6. /// /// # Required version /// /// The method requires the 2nd version implementation of the portal and /// would fail with [`Error::RequiresVersion`] otherwise. /// /// # Specifications /// /// See also [`GetAvailable`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.NetworkMonitor.html#org-freedesktop-portal-networkmonitor-getavailable). #[doc(alias = "GetAvailable")] #[doc(alias = "get_available")] pub async fn is_available(&self) -> Result { self.0.call_versioned("GetAvailable", &(), 2).await } /// Returns more detailed information about the host's network connectivity. /// /// # Required version /// /// The method requires the 2nd version implementation of the portal and /// would fail with [`Error::RequiresVersion`] otherwise. /// /// # Specifications /// /// See also [`GetConnectivity`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.NetworkMonitor.html#org-freedesktop-portal-networkmonitor-getconnectivity). #[doc(alias = "GetConnectivity")] #[doc(alias = "get_connectivity")] pub async fn connectivity(&self) -> Result { self.0.call_versioned("GetConnectivity", &(), 2).await } /// Returns whether the network is considered metered. /// That is, whether the system as traffic flowing through the default /// connection that is subject to limitations by service providers. /// /// # Required version /// /// The method requires the 2nd version implementation of the portal and /// would fail with [`Error::RequiresVersion`] otherwise. /// /// # Specifications /// /// See also [`GetMetered`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.NetworkMonitor.html#org-freedesktop-portal-networkmonitor-getmetered). #[doc(alias = "GetMetered")] #[doc(alias = "get_metered")] pub async fn is_metered(&self) -> Result { self.0.call_versioned("GetMetered", &(), 2).await } /// Returns the three values all at once. /// /// # Required version /// /// The method requires the 3nd version implementation of the portal and /// would fail with [`Error::RequiresVersion`] otherwise. /// /// # Specifications /// /// See also [`GetStatus`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.NetworkMonitor.html#org-freedesktop-portal-networkmonitor-getstatus). #[doc(alias = "GetStatus")] #[doc(alias = "get_status")] pub async fn status(&self) -> Result { self.0.call_versioned("GetStatus", &(), 3).await } /// Emitted when the network configuration changes. /// /// # Specifications /// /// See also [`changed`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.NetworkMonitor.html#org-freedesktop-portal-networkmonitor-changed). #[doc(alias = "changed")] pub async fn receive_changed(&self) -> Result, Error> { self.0.signal("changed").await } } impl std::ops::Deref for NetworkMonitor { type Target = zbus::Proxy<'static>; fn deref(&self) -> &Self::Target { &self.0 } } ashpd-0.13.11/src/desktop/notification.rs000064400000000000000000000541341046102023000163710ustar 00000000000000//! # Examples //! //! ```rust,no_run //! use std::{thread, time}; //! //! use ashpd::desktop::{ //! Icon, //! notification::{Action, Button, Notification, NotificationProxy, Priority}, //! }; //! use futures_util::StreamExt; //! use zbus::zvariant::Value; //! //! async fn run() -> ashpd::Result<()> { //! let proxy = NotificationProxy::new().await?; //! //! let notification_id = "org.gnome.design.Contrast"; //! proxy //! .add_notification( //! notification_id, //! Notification::new("Contrast") //! .default_action("open") //! .default_action_target(100) //! .body("color copied to clipboard") //! .priority(Priority::High) //! .icon(Icon::with_names(&["dialog-question-symbolic"])) //! .button(Button::new("Copy", "copy").target(32)) //! .button(Button::new("Delete", "delete").target(40)), //! ) //! .await?; //! //! let action = proxy //! .receive_action_invoked() //! .await? //! .next() //! .await //! .expect("Stream exhausted"); //! match action.name() { //! "copy" => (), // Copy something to clipboard //! "delete" => (), // Delete the file //! _ => (), //! }; //! println!("{:#?}", action.id()); //! println!( //! "{:#?}", //! action.parameter().get(0).unwrap().downcast_ref::() //! ); //! //! proxy.remove_notification(notification_id).await?; //! Ok(()) //! } //! ``` use std::{fmt, os::fd::AsFd, str::FromStr}; use futures_util::Stream; use serde::{self, Deserialize, Serialize}; use zbus::zvariant::{ OwnedValue, Type, Value, as_value::{self, optional}, }; use super::Icon; use crate::{Error, proxy::Proxy}; #[derive(Debug, Clone, PartialEq, Eq, Type)] #[zvariant(signature = "s")] /// The content of a notification. pub enum Category { /// Instant messaging apps message. #[doc(alias = "im.message")] ImMessage, /// Ringing alarm. #[doc(alias = "alarm.ringing")] AlarmRinging, /// Incoming call. #[doc(alias = "call.incoming")] IncomingCall, /// Ongoing call. #[doc(alias = "call.ongoing")] OngoingCall, /// Missed call. #[doc(alias = "call.missed")] MissedCall, /// Extreme weather warning. #[doc(alias = "weather.warning.extreme")] ExtremeWeather, /// Extreme danger broadcast. #[doc(alias = "cellbroadcast.danger.extreme")] CellNetworkExtremeDanger, /// Severe danger broadcast. #[doc(alias = "cellbroadcast.danger.severe")] CellNetworkSevereDanger, /// Amber alert broadcast. #[doc(alias = "cellbroadcast.amber-alert")] CellNetworkAmberAlert, /// Test broadcast. #[doc(alias = "cellbroadcast.test")] CellNetworkBroadcastTest, /// Low battery. #[doc(alias = "os.battery.low")] LowBattery, /// Browser websites notifications. #[doc(alias = "browser.web-notification")] WebNotification, /// Vendor specific. Other(String), } impl Serialize for Category { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let category_str = match self { Self::ImMessage => "im.message", Self::AlarmRinging => "alarm.ringing", Self::IncomingCall => "call.incoming", Self::OngoingCall => "call.ongoing", Self::MissedCall => "call.missed", Self::ExtremeWeather => "weather.warning.extreme", Self::CellNetworkExtremeDanger => "cellbroadcast.danger.extreme", Self::CellNetworkSevereDanger => "cellbroadcast.danger.severe", Self::CellNetworkAmberAlert => "cellbroadcast.amber-alert", Self::CellNetworkBroadcastTest => "cellbroadcast.test", Self::LowBattery => "os.battery.low", Self::WebNotification => "browser.web-notification", Self::Other(other) => other.as_str(), }; serializer.serialize_str(category_str) } } impl FromStr for Category { type Err = crate::Error; fn from_str(s: &str) -> Result { match s { "im.message" => Ok(Self::ImMessage), "alarm.ringing" => Ok(Self::AlarmRinging), "call.incoming" => Ok(Self::IncomingCall), "call.ongoing" => Ok(Self::OngoingCall), "call.missed" => Ok(Self::MissedCall), "weather.warning.extreme" => Ok(Self::ExtremeWeather), "cellbroadcast.danger.extreme" => Ok(Self::CellNetworkExtremeDanger), "cellbroadcast.danger.severe" => Ok(Self::CellNetworkSevereDanger), "cellbroadcast.amber-alert" => Ok(Self::CellNetworkAmberAlert), "cellbroadcast.test" => Ok(Self::CellNetworkBroadcastTest), "os.battery.low" => Ok(Self::LowBattery), "browser.web-notification" => Ok(Self::WebNotification), _ => Ok(Self::Other(s.to_owned())), } } } impl<'de> Deserialize<'de> for Category { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let category = String::deserialize(deserializer)?; category .parse::() .map_err(|_e| serde::de::Error::custom("Failed to parse category")) } } #[cfg_attr(feature = "glib", derive(glib::Enum))] #[cfg_attr(feature = "glib", enum_type(name = "AshpdPriority"))] #[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq, Type)] #[zvariant(signature = "s")] #[serde(rename_all = "lowercase")] /// The notification priority pub enum Priority { /// Low. Low, /// Normal. Normal, /// High. High, /// Urgent. Urgent, } impl fmt::Display for Priority { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Low => write!(f, "Low"), Self::Normal => write!(f, "Normal"), Self::High => write!(f, "High"), Self::Urgent => write!(f, "Urgent"), } } } impl AsRef for Priority { fn as_ref(&self) -> &str { match self { Self::Low => "Low", Self::Normal => "Normal", Self::High => "High", Self::Urgent => "Urgent", } } } impl From for &'static str { fn from(d: Priority) -> Self { match d { Priority::Low => "Low", Priority::Normal => "Normal", Priority::High => "High", Priority::Urgent => "Urgent", } } } impl FromStr for Priority { type Err = Error; fn from_str(s: &str) -> Result { match s { "Low" | "low" => Ok(Priority::Low), "Normal" | "normal" => Ok(Priority::Normal), "High" | "high" => Ok(Priority::High), "Urgent" | "urgent" => Ok(Priority::Urgent), _ => Err(Error::ParseError("Failed to parse priority, invalid value")), } } } #[cfg_attr(feature = "glib", derive(glib::Enum))] #[cfg_attr(feature = "glib", enum_type(name = "AshpdNotificationDisplayHint"))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Type, Serialize)] #[zvariant(signature = "s")] #[serde(rename_all = "kebab-case")] /// Ways to display a notification. pub enum DisplayHint { /// Transient. #[doc(alias = "transient")] Transient, /// Tray. #[doc(alias = "tray")] Tray, /// Persistent. #[doc(alias = "persistent")] Persistent, /// Hide on lockscreen. #[doc(alias = "hide-on-lockscreen")] HideOnLockScreen, /// Enable speakerphone. #[doc(alias = "hide-content-on-lockscreen")] HideContentOnLockScreen, /// Show as new. #[doc(alias = "show-as-new")] ShowAsNew, } #[derive(Serialize, Type, Debug)] /// A notification #[zvariant(signature = "dict")] #[serde(rename_all = "kebab-case")] pub struct Notification { /// User-visible string to display as the title. #[serde(with = "as_value")] title: String, /// User-visible string to display as the body. #[serde(with = "optional", skip_serializing_if = "Option::is_none")] body: Option, #[serde(with = "optional", skip_serializing_if = "Option::is_none")] markup_body: Option, /// Serialized icon (e.g using gio::Icon::serialize). #[serde(with = "optional", skip_serializing_if = "Option::is_none")] icon: Option, /// The priority for the notification. #[serde(with = "optional", skip_serializing_if = "Option::is_none")] priority: Option, /// Name of an action that is exported by the application. /// This action will be activated when the user clicks on the notification. #[serde(with = "optional", skip_serializing_if = "Option::is_none")] default_action: Option, /// Target parameter to send along when activating the default action. #[serde(with = "optional", skip_serializing_if = "Option::is_none")] default_action_target: Option, /// Array of buttons to add to the notification. #[serde(with = "as_value", skip_serializing_if = "Vec::is_empty")] buttons: Vec