From ff2a3ca741f18a3545e304904da082b6ca53a8e0 Mon Sep 17 00:00:00 2001 From: Julian Sparber Date: Tue, 16 Nov 2021 18:26:40 +0100 Subject: [PATCH] verification: Add qr-code scanning for verification This also moves the error handling to the `IdentityVerification` --- Cargo.lock | 860 +++++++++++++++++- Cargo.toml | 5 + build-aux/org.gnome.FractalNext.Devel.json | 7 +- data/resources/resources.gresource.xml | 1 + data/resources/ui/qr-code-scanner.ui | 13 + data/resources/ui/session-verification.ui | 171 ++++ src/contrib/mod.rs | 2 + .../qr_code_scanner/camera_paintable.rs | 461 ++++++++++ src/contrib/qr_code_scanner/mod.rs | 199 ++++ .../qr_code_scanner/qr_code_detector.rs | 140 +++ src/contrib/qr_code_scanner/screenshot.rs | 14 + src/main.rs | 1 + src/meson.build | 4 + .../verification/identity_verification.rs | 338 +++++-- .../verification/session_verification.rs | 144 +-- 15 files changed, 2213 insertions(+), 147 deletions(-) create mode 100644 data/resources/ui/qr-code-scanner.ui create mode 100644 src/contrib/qr_code_scanner/camera_paintable.rs create mode 100644 src/contrib/qr_code_scanner/mod.rs create mode 100644 src/contrib/qr_code_scanner/qr_code_detector.rs create mode 100644 src/contrib/qr_code_scanner/screenshot.rs diff --git a/Cargo.lock b/Cargo.lock index 59741759..e7ce6e49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,15 @@ dependencies = [ "url", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -131,12 +140,77 @@ version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "ashpd" +version = "0.2.0-alpha-5" +source = "git+https://github.com/bilelmoussaoui/ashpd?rev=66d4dc0020181a7174451150ecc711344082b5ce#66d4dc0020181a7174451150ecc711344082b5ce" +dependencies = [ + "enumflags2", + "futures", + "gdk4-wayland", + "gdk4-x11", + "gtk4", + "libc", + "pipewire", + "rand 0.8.4", + "serde", + "serde_repr", + "tracing", + "zbus 2.0.0-beta.7", + "zbus_macros 2.0.0-beta.7", + "zbus_names", + "zvariant", + "zvariant_derive", +] + [[package]] name = "assign" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002" +[[package]] +name = "async-broadcast" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90622698a1218e0b2fb846c97b5f19a0831f6baddee73d9454156365ccfa473b" +dependencies = [ + "easy-parallel", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab", +] + [[package]] name = "async-io" version = "1.6.0" @@ -156,6 +230,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "async-lock" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-recursion" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2" +dependencies = [ + "proc-macro2 1.0.30", + "quote 1.0.10", + "syn 1.0.80", +] + [[package]] name = "async-stream" version = "0.3.2" @@ -177,6 +271,12 @@ dependencies = [ "syn 1.0.80", ] +[[package]] +name = "async-task" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" + [[package]] name = "async-trait" version = "0.1.51" @@ -197,6 +297,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -223,11 +334,46 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bindgen" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2 1.0.30", + "quote 1.0.10", + "regex", + "rustc-hash", + "shlex", + "which", +] + [[package]] name = "bitflags" -version = "1.3.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] [[package]] name = "block" @@ -320,6 +466,15 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +[[package]] +name = "cexpr" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" +dependencies = [ + "nom", +] + [[package]] name = "cfg-expr" version = "0.8.1" @@ -411,6 +566,32 @@ dependencies = [ "generic-array", ] +[[package]] +name = "clang-sys" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term 0.11.0", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cmake" version = "0.1.46" @@ -441,6 +622,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + [[package]] name = "core-foundation" version = "0.9.2" @@ -610,6 +797,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "easy-parallel" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd4afd79212583ff429b913ad6605242ed7eec277e950b1438f300748f948f4" + [[package]] name = "ed25519" version = "1.2.0" @@ -669,6 +877,40 @@ dependencies = [ "syn 1.0.80", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.1" @@ -729,11 +971,16 @@ dependencies = [ name = "fractal" version = "0.1.0" dependencies = [ + "ashpd", "futures", "gettext-rs", + "gstreamer", + "gstreamer-base", + "gstreamer-video", "gtk-macros", "gtk4", "html2pango", + "image", "indexmap", "libadwaita", "log", @@ -760,6 +1007,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futf" version = "0.1.4" @@ -993,6 +1246,58 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "gdk4-wayland" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c930875d2f466207eae96d0110a3233c22744c16087cd0035f73da507f1a1bf5" +dependencies = [ + "gdk4", + "gdk4-wayland-sys", + "gio", + "glib", + "libc", + "wayland-client", +] + +[[package]] +name = "gdk4-wayland-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c321379df46fc983d2a6aa0b639832e22ea0f85d64222a10e985b4378565ac" +dependencies = [ + "glib-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "gdk4-x11" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb34d456170b6293d8d195090e3fd2fa0cb6f44d46b27bb7b729ada679cfa742" +dependencies = [ + "gdk4", + "gdk4-x11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdk4-x11-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3e38c047b930780e687774a050f65e10cc1186494b36ef0e8ec09271a19fa4" +dependencies = [ + "gdk4-sys", + "glib-sys", + "libc", + "system-deps 5.0.0", + "x11", +] + [[package]] name = "generic-array" version = "0.14.4" @@ -1141,6 +1446,12 @@ dependencies = [ "system-deps 3.2.0", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "gloo-timers" version = "0.2.1" @@ -1220,6 +1531,99 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "gstreamer" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a255f142048ba2c4a4dce39106db1965abe355d23f4b5335edea43a553faa4" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "gstreamer-sys", + "libc", + "muldiv", + "num-integer", + "num-rational 0.4.0", + "once_cell", + "paste", + "pretty-hex", + "thiserror", +] + +[[package]] +name = "gstreamer-base" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0c1d8c62eb5d08fb80173609f2eea71d385393363146e4e78107facbd67715" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "glib", + "gstreamer", + "gstreamer-base-sys", + "libc", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28169a7b58edb93ad8ac766f0fa12dcd36a2af4257a97ee10194c7103baf3e27" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps 3.2.0", +] + +[[package]] +name = "gstreamer-sys" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a81704feeb3e8599913bdd1e738455c2991a01ff4a1780cb62200993e454cc3e" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 3.2.0", +] + +[[package]] +name = "gstreamer-video" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3447ee95c8e79daec0b163260cf6a3de9bc19ff47a01b533787f900074a3476" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "futures-channel", + "glib", + "gstreamer", + "gstreamer-base", + "gstreamer-video-sys", + "libc", + "once_cell", +] + +[[package]] +name = "gstreamer-video-sys" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81608f4182bdddd5bd33aaaa341d5544eda12b067a3dab75b1b7d2de01a3ba7" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-base-sys", + "gstreamer-sys", + "libc", + "system-deps 3.2.0", +] + [[package]] name = "gtk-macros" version = "0.3.0" @@ -1330,6 +1734,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.10.0" @@ -1424,6 +1834,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.13" @@ -1484,7 +1900,7 @@ dependencies = [ "gif", "jpeg-decoder", "num-iter", - "num-rational", + "num-rational 0.3.2", "num-traits", "png", "scoped_threadpool", @@ -1577,6 +1993,25 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + [[package]] name = "libadwaita" version = "0.1.0-alpha-6" @@ -1598,24 +2033,60 @@ dependencies = [ name = "libadwaita-sys" version = "0.1.0-alpha-6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3127d83c55f64c466925b9d1e27a964187f193e94c7c8820ad6b29d6e5f487d8" +checksum = "3127d83c55f64c466925b9d1e27a964187f193e94c7c8820ad6b29d6e5f487d8" +dependencies = [ + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps 4.0.0", +] + +[[package]] +name = "libc" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" + +[[package]] +name = "libloading" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "libspa" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb373e8b03740369c5fe48a557c6408b6898982d57e17940de144375d472743" dependencies = [ - "gdk-pixbuf-sys", - "gdk4-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk4-sys", + "bitflags", + "cc", + "cookie-factory", + "errno", "libc", - "pango-sys", - "system-deps 4.0.0", + "libspa-sys", + "nom", + "system-deps 3.2.0", ] [[package]] -name = "libc" -version = "0.2.104" +name = "libspa-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" +checksum = "d301a2fc2fed0a97c13836408a4d98f419af0c2695ecf74e634a214c17beefa6" +dependencies = [ + "bindgen", + "system-deps 3.2.0", +] [[package]] name = "linkify" @@ -1901,6 +2372,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "muldiv" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5136edda114182728ccdedb9f5eda882781f35fa6e80cc360af12a8932507f3" + [[package]] name = "native-tls" version = "0.2.8" @@ -1935,6 +2412,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nix" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", + "void", +] + [[package]] name = "nix" version = "0.17.0" @@ -1948,6 +2438,45 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -1967,7 +2496,7 @@ dependencies = [ "num-complex", "num-integer", "num-iter", - "num-rational", + "num-rational 0.3.2", "num-traits", ] @@ -2024,6 +2553,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -2210,6 +2750,12 @@ dependencies = [ "crypto-mac 0.11.1", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -2295,6 +2841,35 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pipewire" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de050d879e7b8d9313429ec314b88b26fe48ba29a6ecc3bc8289d3673fee6c8" +dependencies = [ + "anyhow", + "bitflags", + "errno", + "libc", + "libspa", + "libspa-sys", + "once_cell", + "pipewire-sys", + "signal", + "thiserror", +] + +[[package]] +name = "pipewire-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4aa5ef9f3afef7dbb335106f69bd6bb541259e8796c693810cde20db1eb949" +dependencies = [ + "bindgen", + "libspa-sys", + "system-deps 3.2.0", +] + [[package]] name = "pkcs8" version = "0.7.6" @@ -2372,6 +2947,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty-hex" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2484,6 +3065,12 @@ dependencies = [ "proc-macro2 1.0.30", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.7.3" @@ -2905,6 +3492,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2962,17 +3555,17 @@ dependencies = [ "rand 0.8.4", "serde", "sha2", - "zbus", - "zbus_macros", + "zbus 1.9.1", + "zbus_macros 1.9.1", "zvariant", "zvariant_derive", ] [[package]] name = "security-framework" -version = "2.4.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" dependencies = [ "bitflags", "core-foundation", @@ -3069,6 +3662,12 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + [[package]] name = "sha2" version = "0.9.8" @@ -3091,6 +3690,22 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f6ce83b159ab6984d2419f495134972b48754d13ff2e3f8c998339942b56ed9" +dependencies = [ + "libc", + "nix 0.14.1", +] + [[package]] name = "signature" version = "1.4.0" @@ -3125,6 +3740,15 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.7.0" @@ -3216,6 +3840,12 @@ dependencies = [ "quote 1.0.10", ] +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "strum" version = "0.21.0" @@ -3318,6 +3948,12 @@ dependencies = [ "version-compare", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "temp-dir" version = "0.1.11" @@ -3349,6 +3985,24 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.30" @@ -3550,7 +4204,7 @@ version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ - "ansi_term", + "ansi_term 0.12.1", "chrono", "lazy_static", "matchers", @@ -3614,6 +4268,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + [[package]] name = "unicode-xid" version = "0.1.0" @@ -3676,6 +4336,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version-compare" version = "0.0.11" @@ -3788,6 +4454,55 @@ version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +[[package]] +name = "wayland-client" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ab332350e502f159382201394a78e3cc12d0f04db863429260164ea40e0355" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.20.2", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21817947c7011bbd0a27e11b17b337bfd022e8544b071a2641232047966fbda" +dependencies = [ + "nix 0.20.2", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-scanner" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce923eb2deb61de332d1f356ec7b6bf37094dc5573952e1c8936db03b54c03f1" +dependencies = [ + "proc-macro2 1.0.30", + "quote 1.0.10", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d841fca9aed7febf9bed2e9796c49bf58d4152ceda8ac949ebe00868d8f0feb8" +dependencies = [ + "dlib", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.55" @@ -3813,6 +4528,15 @@ dependencies = [ "cc", ] +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + [[package]] name = "wildmatch" version = "2.1.0" @@ -3835,6 +4559,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3850,6 +4583,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + +[[package]] +name = "x11" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + [[package]] name = "xml5ever" version = "0.16.2" @@ -3875,13 +4630,48 @@ dependencies = [ "fastrand", "futures", "nb-connect", - "nix", + "nix 0.17.0", "once_cell", "polling", "scoped-tls", "serde", "serde_repr", - "zbus_macros", + "zbus_macros 1.9.1", + "zvariant", +] + +[[package]] +name = "zbus" +version = "2.0.0-beta.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b2e14e7c15f04af391e91950081f5ac19c6a595a8906bc156f5d914ab57b681" +dependencies = [ + "async-broadcast", + "async-channel", + "async-executor", + "async-io", + "async-lock", + "async-recursion", + "async-task", + "async-trait", + "byteorder", + "derivative", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.21.2", + "once_cell", + "rand 0.8.4", + "serde", + "serde_repr", + "sha1", + "slotmap", + "static_assertions", + "zbus_macros 2.0.0-beta.7", + "zbus_names", "zvariant", ] @@ -3897,6 +4687,30 @@ dependencies = [ "syn 1.0.80", ] +[[package]] +name = "zbus_macros" +version = "2.0.0-beta.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d54aa0d29b3e36e112361c54bc3b750e12a45d704f86ca543e3101b338834ad" +dependencies = [ + "proc-macro-crate 1.1.0", + "proc-macro2 1.0.30", + "quote 1.0.10", + "regex", + "syn 1.0.80", +] + +[[package]] +name = "zbus_names" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45b644a32f28e5fc17974d63d3d8ee9f9f7985f9f4fb6f4e12d8be2fa3eaa31" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zeroize" version = "1.4.2" diff --git a/Cargo.toml b/Cargo.toml index 82fcbe53..48719f1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,11 @@ futures = "0.3" rand = "0.8" indexmap = "1.6.2" qrcode = "0.12.0" +ashpd = {git = "https://github.com/bilelmoussaoui/ashpd", rev="66d4dc0020181a7174451150ecc711344082b5ce", features=["feature_gtk4", "feature_pipewire", "log"]} +gst = {version = "0.17", package = "gstreamer"} +gst_base = {version = "0.17", package = "gstreamer-base"} +gst_video = {version = "0.17", package = "gstreamer-video"} +image = {version = "0.23", default-features = false, features=["png"]} [dependencies.sourceview] package = "sourceview5" diff --git a/build-aux/org.gnome.FractalNext.Devel.json b/build-aux/org.gnome.FractalNext.Devel.json index b6f08bdc..6c3c8f98 100644 --- a/build-aux/org.gnome.FractalNext.Devel.json +++ b/build-aux/org.gnome.FractalNext.Devel.json @@ -4,13 +4,15 @@ "runtime-version" : "master", "sdk" : "org.gnome.Sdk", "sdk-extensions" : [ - "org.freedesktop.Sdk.Extension.rust-stable" + "org.freedesktop.Sdk.Extension.rust-stable", + "org.freedesktop.Sdk.Extension.llvm12" ], "command" : "fractal", "finish-args" : [ "--socket=fallback-x11", "--socket=wayland", "--share=network", + "--share=ipc", "--device=dri", "--talk-name=org.a11y.Bus", "--talk-name=org.freedesktop.secrets", @@ -19,7 +21,8 @@ "--env=RUST_BACKTRACE=1" ], "build-options" : { - "append-path" : "/usr/lib/sdk/rust-stable/bin", + "append-ld-library-path": "/usr/lib/sdk/llvm12/lib", + "append-path" : "/usr/lib/sdk/llvm12/bin:/usr/lib/sdk/rust-stable/bin", "build-args" : [ "--share=network" ], diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index c613e5f8..7ea0ad7c 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -44,6 +44,7 @@ ui/room-creation.ui ui/session-verification.ui ui/verification-emoji.ui + ui/qr-code-scanner.ui style.css icons/scalable/actions/send-symbolic.svg icons/scalable/status/welcome.svg diff --git a/data/resources/ui/qr-code-scanner.ui b/data/resources/ui/qr-code-scanner.ui new file mode 100644 index 00000000..fbc58034 --- /dev/null +++ b/data/resources/ui/qr-code-scanner.ui @@ -0,0 +1,13 @@ + + + + diff --git a/data/resources/ui/session-verification.ui b/data/resources/ui/session-verification.ui index ff062c7c..e4af1e08 100644 --- a/data/resources/ui/session-verification.ui +++ b/data/resources/ui/session-verification.ui @@ -140,6 +140,177 @@ + + + scan-qr-code + + + 400 + 300 + + + vertical + 18 + center + center + + + Verify Session + True + word-char + center + + + + + + Scan the Qr code with this session from another session logged into this account + True + word-char + center + + + + + 24 + 24 + True + True + + + + + Can't scan QR code? + True + word-char + center + + + + + Compare Emoji + center + + + + + + Take a Screenshot of a Qr Code + center + + + + + + + + + + + + qr-code-scanned + + + 400 + 300 + + + vertical + 18 + center + center + + + Scan Complete + True + word-char + center + + + + + + resource:///org/gnome/FractalNext/icons/scalable/status/setup-complete.svg + + + + + You scanned to qr code successfully. You may need to confirm the verification in the other session. + True + word-char + center + + + + + + + + + + + no-camera + + + 400 + 300 + + + vertical + 18 + center + center + + + Verify Session + True + word-char + center + + + + + + Select an option to verify the new session. + True + word-char + center + + + + + Compare Emoji + center + + + + + + Take a Screenshot of a Qr Code + center + + + + + + + + + qrcode diff --git a/src/contrib/mod.rs b/src/contrib/mod.rs index d5fc7251..ab641686 100644 --- a/src/contrib/mod.rs +++ b/src/contrib/mod.rs @@ -1,3 +1,5 @@ mod qr_code; +mod qr_code_scanner; pub use self::qr_code::{QRCode, QRCodeExt}; +pub use self::qr_code_scanner::{screenshot, QrCodeScanner}; diff --git a/src/contrib/qr_code_scanner/camera_paintable.rs b/src/contrib/qr_code_scanner/camera_paintable.rs new file mode 100644 index 00000000..7938079d --- /dev/null +++ b/src/contrib/qr_code_scanner/camera_paintable.rs @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Fancy Camera with QR code detection +// +// Pipeline: +// queue -- videoconvert -- QrCodeDetector sink +// / +// pipewiresrc -- tee +// \ +// queue -- videoconvert -- our fancy sink + +use glib::{clone, Receiver, Sender}; +use gst::prelude::*; +use gtk::glib; +use gtk::prelude::*; +use gtk::subclass::prelude::*; +use once_cell::sync::Lazy; + +use std::os::unix::io::AsRawFd; +use std::sync::{Arc, Mutex}; + +use crate::contrib::qr_code_scanner::qr_code_detector::QrCodeDetector; +use crate::contrib::qr_code_scanner::QrVerificationDataBoxed; +use gtk::{gdk, graphene}; +use matrix_sdk::encryption::verification::QrVerificationData; + +pub enum Action { + FrameChanged, + QrCodeDetected(QrVerificationData), +} + +mod camera_sink { + use std::convert::AsRef; + + #[derive(Debug)] + pub struct Frame(pub gst_video::VideoFrame); + + impl AsRef<[u8]> for Frame { + fn as_ref(&self) -> &[u8] { + self.0.plane_data(0).unwrap() + } + } + + impl From for gdk::Paintable { + fn from(f: Frame) -> gdk::Paintable { + let format = match f.0.format() { + gst_video::VideoFormat::Bgra => gdk::MemoryFormat::B8g8r8a8, + gst_video::VideoFormat::Argb => gdk::MemoryFormat::A8r8g8b8, + gst_video::VideoFormat::Rgba => gdk::MemoryFormat::R8g8b8a8, + gst_video::VideoFormat::Abgr => gdk::MemoryFormat::A8b8g8r8, + gst_video::VideoFormat::Rgb => gdk::MemoryFormat::R8g8b8, + gst_video::VideoFormat::Bgr => gdk::MemoryFormat::B8g8r8, + _ => unreachable!(), + }; + let width = f.0.width() as i32; + let height = f.0.height() as i32; + let rowstride = f.0.plane_stride()[0] as usize; + + gdk::MemoryTexture::new( + width, + height, + format, + &glib::Bytes::from_owned(f), + rowstride, + ) + .upcast() + } + } + + impl Frame { + pub fn new(buffer: &gst::Buffer, info: &gst_video::VideoInfo) -> Self { + let video_frame = + gst_video::VideoFrame::from_buffer_readable(buffer.clone(), &info).unwrap(); + Self(video_frame) + } + + pub fn width(&self) -> u32 { + self.0.width() + } + + pub fn height(&self) -> u32 { + self.0.height() + } + } + + use super::*; + + mod imp { + use std::sync::Mutex; + + use gst::subclass::prelude::*; + use gst_base::subclass::prelude::*; + use gst_video::subclass::prelude::*; + use once_cell::sync::Lazy; + + use super::*; + + #[derive(Default)] + pub struct CameraSink { + pub info: Mutex>, + pub sender: Mutex>>, + pub pending_frame: Mutex>, + } + + #[glib::object_subclass] + impl ObjectSubclass for CameraSink { + const NAME: &'static str = "CameraSink"; + type Type = super::CameraSink; + type ParentType = gst_video::VideoSink; + } + + impl ObjectImpl for CameraSink {} + impl ElementImpl for CameraSink { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + gst::subclass::ElementMetadata::new( + "GTK Camera Sink", + "Sink/Camera/Video", + "A GTK Camera sink", + "Bilal Elmoussaoui ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + let caps = gst_video::video_make_raw_caps(&[ + gst_video::VideoFormat::Bgra, + gst_video::VideoFormat::Argb, + gst_video::VideoFormat::Rgba, + gst_video::VideoFormat::Abgr, + gst_video::VideoFormat::Rgb, + gst_video::VideoFormat::Bgr, + ]) + .any_features() + .build(); + + vec![gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + ) + .unwrap()] + }); + + PAD_TEMPLATES.as_ref() + } + } + impl BaseSinkImpl for CameraSink { + fn set_caps( + &self, + _element: &Self::Type, + caps: &gst::Caps, + ) -> Result<(), gst::LoggableError> { + let video_info = gst_video::VideoInfo::from_caps(caps).unwrap(); + let mut info = self.info.lock().unwrap(); + info.replace(video_info); + + Ok(()) + } + } + impl VideoSinkImpl for CameraSink { + fn show_frame( + &self, + _element: &Self::Type, + buffer: &gst::Buffer, + ) -> Result { + if let Some(info) = &*self.info.lock().unwrap() { + let frame = Frame::new(buffer, info); + let mut last_frame = self.pending_frame.lock().unwrap(); + + last_frame.replace(frame); + let sender = self.sender.lock().unwrap(); + + sender.as_ref().unwrap().send(Action::FrameChanged).unwrap(); + } + Ok(gst::FlowSuccess::Ok) + } + } + } + + glib::wrapper! { + pub struct CameraSink(ObjectSubclass) @extends gst_video::VideoSink, gst_base::BaseSink, gst::Element, gst::Object; + } + unsafe impl Send for CameraSink {} + unsafe impl Sync for CameraSink {} + + impl CameraSink { + pub fn new(sender: Sender) -> Self { + let sink = glib::Object::new(&[]).expect("Failed to create a CameraSink"); + let priv_ = imp::CameraSink::from_instance(&sink); + priv_.sender.lock().unwrap().replace(sender); + sink + } + + pub fn pending_frame(&self) -> Option { + let self_ = imp::CameraSink::from_instance(self); + self_.pending_frame.lock().unwrap().take() + } + } +} + +mod imp { + use glib::subclass; + use std::cell::RefCell; + + use super::*; + + pub struct CameraPaintable { + pub sink: camera_sink::CameraSink, + pub detector: QrCodeDetector, + pub pipeline: RefCell>, + pub sender: Sender, + pub image: RefCell>, + pub size: RefCell>, + pub receiver: RefCell>>, + } + + impl Default for CameraPaintable { + fn default() -> Self { + let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + let receiver = RefCell::new(Some(r)); + + Self { + pipeline: RefCell::default(), + sink: camera_sink::CameraSink::new(sender.clone()), + detector: QrCodeDetector::new(sender.clone()), + image: RefCell::new(None), + sender, + receiver, + size: RefCell::new(None), + } + } + } + + #[glib::object_subclass] + impl ObjectSubclass for CameraPaintable { + const NAME: &'static str = "CameraPaintable"; + type Type = super::CameraPaintable; + type ParentType = glib::Object; + type Interfaces = (gdk::Paintable,); + } + + impl ObjectImpl for CameraPaintable { + fn constructed(&self, obj: &Self::Type) { + obj.init_widgets(); + self.parent_constructed(obj); + } + fn dispose(&self, paintable: &Self::Type) { + paintable.close_pipeline(); + } + + fn signals() -> &'static [subclass::Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![subclass::Signal::builder( + "code-detected", + &[QrVerificationDataBoxed::static_type().into()], + glib::Type::UNIT.into(), + ) + .flags(glib::SignalFlags::RUN_FIRST) + .build()] + }); + SIGNALS.as_ref() + } + } + + impl PaintableImpl for CameraPaintable { + fn intrinsic_height(&self, _paintable: &Self::Type) -> i32 { + if let Some((_, height)) = *self.size.borrow() { + height as i32 + } else { + 0 + } + } + fn intrinsic_width(&self, _paintable: &Self::Type) -> i32 { + if let Some((width, _)) = *self.size.borrow() { + width as i32 + } else { + 0 + } + } + + fn snapshot( + &self, + _paintable: &Self::Type, + snapshot: &gdk::Snapshot, + width: f64, + height: f64, + ) { + let snapshot = snapshot.downcast_ref::().unwrap(); + + if let Some(ref image) = *self.image.borrow() { + // Transformation to avoid stretching the camera. We translate and scale the image + // under a clip. + let clip = graphene::Rect::new(0.0, 0.0, width as f32, height as f32); + + let aspect = width / height.max(std::f64::EPSILON); // Do not divide by zero. + let image_aspect = image.intrinsic_aspect_ratio(); + + snapshot.push_clip(&clip); + + if image_aspect == 0.0 { + image.snapshot(snapshot.upcast_ref(), width, height); + return; + }; + + let (new_width, new_height) = match aspect <= image_aspect { + true => (height * image_aspect, height), // Mobile view + false => (width, width / image_aspect), // Landscape + }; + + let p = graphene::Point::new( + ((width - new_width) / 2.0) as f32, + ((height - new_height) / 2.0) as f32, + ); + snapshot.translate(&p); + + image.snapshot(snapshot.upcast_ref(), new_width, new_height); + + snapshot.pop(); + } else { + snapshot.append_color( + &gdk::RGBA::black(), + &graphene::Rect::new(0f32, 0f32, width as f32, height as f32), + ); + } + } + } +} + +glib::wrapper! { + pub struct CameraPaintable(ObjectSubclass) @implements gdk::Paintable; +} + +impl Default for CameraPaintable { + fn default() -> Self { + glib::Object::new(&[]).expect("Failed to create a CameraPaintable") + } +} + +impl CameraPaintable { + pub fn set_pipewire_fd(&self, fd: F, node_id: u32) { + let pipewire_element = gst::ElementFactory::make("pipewiresrc", None).unwrap(); + pipewire_element + .set_property("fd", &fd.as_raw_fd()) + .unwrap(); + pipewire_element + .set_property("path", &node_id.to_string()) + .unwrap(); + self.init_pipeline(pipewire_element); + } + + fn init_pipeline(&self, pipewire_src: gst::Element) { + let self_ = imp::CameraPaintable::from_instance(self); + let pipeline = gst::Pipeline::new(None); + + let tee = gst::ElementFactory::make("tee", None).unwrap(); + let queue = gst::ElementFactory::make("queue", None).unwrap(); + let videoconvert1 = gst::ElementFactory::make("videoconvert", None).unwrap(); + let videoconvert2 = gst::ElementFactory::make("videoconvert", None).unwrap(); + let src_pad = queue.static_pad("src").unwrap(); + + // Reduce the number of frames we use to get the qrcode from + let start = Arc::new(Mutex::new(std::time::Instant::now())); + src_pad.add_probe(gst::PadProbeType::BUFFER, move |_, _| { + let mut start = start.lock().unwrap(); + if start.elapsed() < std::time::Duration::from_millis(500) { + gst::PadProbeReturn::Drop + } else { + *start = std::time::Instant::now(); + gst::PadProbeReturn::Ok + } + }); + + let queue2 = gst::ElementFactory::make("queue", None).unwrap(); + + pipeline + .add_many(&[ + &pipewire_src, + &tee, + &queue, + &videoconvert1, + self_.detector.upcast_ref(), + &queue2, + &videoconvert2, + self_.sink.upcast_ref(), + ]) + .unwrap(); + + gst::Element::link_many(&[ + &pipewire_src, + &tee, + &queue, + &videoconvert1, + self_.detector.upcast_ref(), + ]) + .unwrap(); + + tee.link_pads(None, &queue2, None).unwrap(); + gst::Element::link_many(&[&queue2, &videoconvert2, self_.sink.upcast_ref()]).unwrap(); + let bus = pipeline.bus().unwrap(); + bus.add_watch_local( + clone!(@weak self as paintable => @default-return glib::Continue(false), move |_, msg| { + match msg.view() { + gst::MessageView::Error(err) => { + log::error!( + "Error from {:?}: {} ({:?})", + err.src().map(|s| s.path_string()), + err.error(), + err.debug() + ); + }, + _ => (), + } + glib::Continue(true) + }), + ) + .expect("Failed to add bus watch"); + pipeline.set_state(gst::State::Playing).ok(); + self_.pipeline.replace(Some(pipeline)); + } + + pub fn close_pipeline(&self) { + let self_ = imp::CameraPaintable::from_instance(self); + if let Some(pipeline) = self_.pipeline.borrow_mut().take() { + pipeline.set_state(gst::State::Null).unwrap(); + } + } + + pub fn init_widgets(&self) { + let self_ = imp::CameraPaintable::from_instance(self); + + let receiver = self_.receiver.borrow_mut().take().unwrap(); + receiver.attach( + None, + glib::clone!(@weak self as paintable => @default-return glib::Continue(false), move |action| paintable.do_action(action)), + ); + } + + fn do_action(&self, action: Action) -> glib::Continue { + let self_ = imp::CameraPaintable::from_instance(self); + match action { + Action::FrameChanged => { + if let Some(frame) = self_.sink.pending_frame() { + let (width, height) = (frame.width(), frame.height()); + self_.size.replace(Some((width, height))); + self_.image.replace(Some(frame.into())); + self.invalidate_contents(); + } + } + Action::QrCodeDetected(code) => { + self.emit_by_name("code-detected", &[&QrVerificationDataBoxed(code)]) + .unwrap(); + } + } + glib::Continue(true) + } +} diff --git a/src/contrib/qr_code_scanner/mod.rs b/src/contrib/qr_code_scanner/mod.rs new file mode 100644 index 00000000..e5a646f3 --- /dev/null +++ b/src/contrib/qr_code_scanner/mod.rs @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use crate::spawn; +use ashpd::{desktop::camera, zbus}; +use glib::clone; +use glib::subclass; +use gtk::glib; +use gtk::prelude::*; +use gtk::subclass::prelude::*; +use matrix_sdk::encryption::verification::QrVerificationData; +use std::os::unix::prelude::RawFd; + +mod camera_paintable; +mod qr_code_detector; +pub mod screenshot; + +use camera_paintable::CameraPaintable; + +mod imp { + use adw::subclass::prelude::*; + use gtk::CompositeTemplate; + use once_cell::sync::Lazy; + use std::cell::Cell; + + use super::*; + + #[derive(Debug, CompositeTemplate, Default)] + #[template(resource = "/org/gnome/FractalNext/qr-code-scanner.ui")] + pub struct QrCodeScanner { + pub paintable: CameraPaintable, + #[template_child] + pub picture: TemplateChild, + pub has_camera: Cell, + pub is_started: Cell, + } + + #[glib::object_subclass] + impl ObjectSubclass for QrCodeScanner { + const NAME: &'static str = "QrCodeScanner"; + type Type = super::QrCodeScanner; + type ParentType = adw::Bin; + + fn class_init(klass: &mut Self::Class) { + Self::bind_template(klass); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + impl ObjectImpl for QrCodeScanner { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![glib::ParamSpec::new_boolean( + "has-camera", + "Has Camera", + "Whether we have a working camera", + false, + glib::ParamFlags::READABLE, + )] + }); + + PROPERTIES.as_ref() + } + + fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "has-camera" => obj.has_camera().to_value(), + _ => unimplemented!(), + } + } + + fn constructed(&self, obj: &Self::Type) { + self.picture.set_paintable(Some(&self.paintable)); + + let callback = glib::clone!(@weak obj => @default-return None, move |args: &[glib::Value]| { + let code = args.get(1).unwrap().get::().unwrap(); + obj.emit_by_name("code-detected", &[&code]).unwrap(); + + None + }); + self.paintable + .connect_local("code-detected", false, callback) + .unwrap(); + obj.init_has_camera(); + } + + fn signals() -> &'static [subclass::Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![subclass::Signal::builder( + "code-detected", + &[QrVerificationDataBoxed::static_type().into()], + glib::Type::UNIT.into(), + ) + .flags(glib::SignalFlags::RUN_FIRST) + .build()] + }); + SIGNALS.as_ref() + } + } + impl WidgetImpl for QrCodeScanner { + fn unmap(&self, widget: &Self::Type) { + self.parent_unmap(widget); + widget.stop(); + } + } + impl BinImpl for QrCodeScanner {} +} + +glib::wrapper! { + pub struct QrCodeScanner(ObjectSubclass) @extends gtk::Widget, adw::Bin; +} + +impl QrCodeScanner { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + glib::Object::new(&[]).expect("Failed to create a QrCodeScanner") + } + + pub fn stop(&self) { + let self_ = imp::QrCodeScanner::from_instance(self); + + self_.paintable.close_pipeline(); + } + + async fn start_internal(&self) -> bool { + let self_ = imp::QrCodeScanner::from_instance(self); + if let Ok(Some(stream_fd)) = stream().await { + if let Ok(node_id) = camera::pipewire_node_id(stream_fd).await { + self_.paintable.set_pipewire_fd(stream_fd, node_id); + self_.has_camera.set(true); + self.notify("has-camera"); + return true; + } + } + self_.has_camera.set(false); + self.notify("has-camera"); + false + } + + pub async fn start(&self) -> bool { + let priv_ = imp::QrCodeScanner::from_instance(self); + let is_started = self.start_internal().await; + priv_.is_started.set(is_started); + is_started + } + + fn init_has_camera(&self) { + spawn!(clone!(@weak self as obj => async move { + let priv_ = imp::QrCodeScanner::from_instance(&obj); + let has_camera = if obj.start_internal().await { + if !priv_.is_started.get() { + obj.stop(); + } + true + } else { + false + }; + priv_.has_camera.set(has_camera); + obj.notify("has-camera"); + })); + } + + pub fn has_camera(&self) -> bool { + let priv_ = imp::QrCodeScanner::from_instance(self); + priv_.has_camera.get() + } + + /// Connects the prepared signals to the function f given in input + pub fn connect_code_detected( + &self, + f: F, + ) -> glib::SignalHandlerId { + self.connect_local("code-detected", true, move |values| { + let obj = values[0].get::().unwrap(); + let data = values[1].get::().unwrap(); + + f(&obj, data.0); + + None + }) + .unwrap() + } +} + +async fn stream() -> Result, ashpd::Error> { + let connection = zbus::Connection::session().await?; + let proxy = camera::CameraProxy::new(&connection).await?; + + if proxy.is_camera_present().await? { + proxy.access_camera().await?; + Ok(Some(proxy.open_pipe_wire_remote().await?)) + } else { + Ok(None) + } +} + +#[derive(Clone, Debug, PartialEq, glib::GBoxed)] +#[gboxed(type_name = "QrVerificationDataBoxed")] +struct QrVerificationDataBoxed(QrVerificationData); diff --git a/src/contrib/qr_code_scanner/qr_code_detector.rs b/src/contrib/qr_code_scanner/qr_code_detector.rs new file mode 100644 index 00000000..edbb6470 --- /dev/null +++ b/src/contrib/qr_code_scanner/qr_code_detector.rs @@ -0,0 +1,140 @@ +use crate::contrib::qr_code_scanner::camera_paintable::Action; +use matrix_sdk::encryption::verification::QrVerificationData; + +use glib::Sender; +use gst_video::{video_frame::VideoFrameRef, VideoInfo}; +use log::debug; +use std::convert::AsRef; + +use super::*; + +mod imp { + use std::sync::Mutex; + + use gst::subclass::prelude::*; + use gst_base::subclass::prelude::*; + use gst_video::subclass::prelude::*; + use once_cell::sync::Lazy; + + use super::*; + + #[derive(Default)] + pub struct QrCodeDetector { + pub info: Mutex>, + pub sender: Mutex>>, + pub code: Mutex>, + } + + #[glib::object_subclass] + impl ObjectSubclass for QrCodeDetector { + const NAME: &'static str = "QrCodeDetector"; + type Type = super::QrCodeDetector; + type ParentType = gst_video::VideoSink; + } + + impl ObjectImpl for QrCodeDetector {} + impl ElementImpl for QrCodeDetector { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + gst::subclass::ElementMetadata::new( + "Matrix Qr Code detector Sink", + "Sink/Video/QrCode/Matrix", + "A Qr code detector for Matrix", + "Julian Sparber ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + let caps = gst_video::video_make_raw_caps(&[gst_video::VideoFormat::Gray8]) + .any_features() + .build(); + + vec![gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + ) + .unwrap()] + }); + + PAD_TEMPLATES.as_ref() + } + } + impl BaseSinkImpl for QrCodeDetector { + fn set_caps( + &self, + _element: &Self::Type, + caps: &gst::Caps, + ) -> Result<(), gst::LoggableError> { + let video_info = gst_video::VideoInfo::from_caps(caps).unwrap(); + let mut info = self.info.lock().unwrap(); + info.replace(video_info); + + Ok(()) + } + } + impl VideoSinkImpl for QrCodeDetector { + fn show_frame( + &self, + _element: &Self::Type, + buffer: &gst::Buffer, + ) -> Result { + let now = std::time::Instant::now(); + + if let Some(info) = &*self.info.lock().unwrap() { + let frame = VideoFrameRef::from_buffer_ref_readable(buffer, info).unwrap(); + + let mut samples = image::FlatSamples::> { + samples: frame.plane_data(0).unwrap().to_vec(), + layout: image::flat::SampleLayout { + channels: 1, + channel_stride: 1, + width: frame.width(), + width_stride: 1, + height: frame.height(), + height_stride: frame.plane_stride()[0] as usize, + }, + color_hint: Some(image::ColorType::L8), + }; + + let image = samples.as_view_mut::>().unwrap(); + + if let Ok(code) = QrVerificationData::from_luma(image) { + let mut previous_code = self.code.lock().unwrap(); + if previous_code.as_ref() != Some(&code) { + previous_code.replace(code.clone()); + let sender = self.sender.lock().unwrap(); + sender + .as_ref() + .unwrap() + .send(Action::QrCodeDetected(code)) + .unwrap(); + } + } + } + debug!("Spend {}ms to detect qr code", now.elapsed().as_millis()); + + Ok(gst::FlowSuccess::Ok) + } + } +} + +glib::wrapper! { + pub struct QrCodeDetector(ObjectSubclass) @extends gst_video::VideoSink, gst_base::BaseSink, gst::Element, gst::Object; +} +unsafe impl Send for QrCodeDetector {} +unsafe impl Sync for QrCodeDetector {} + +impl QrCodeDetector { + pub fn new(sender: Sender) -> Self { + let sink = glib::Object::new(&[]).expect("Failed to create a QrCodeDetector"); + let priv_ = imp::QrCodeDetector::from_instance(&sink); + priv_.sender.lock().unwrap().replace(sender); + sink + } +} diff --git a/src/contrib/qr_code_scanner/screenshot.rs b/src/contrib/qr_code_scanner/screenshot.rs new file mode 100644 index 00000000..a0a11ff3 --- /dev/null +++ b/src/contrib/qr_code_scanner/screenshot.rs @@ -0,0 +1,14 @@ +use ashpd::desktop::screenshot; +use gtk::gio; +use gtk::prelude::*; +use matrix_sdk::encryption::verification::QrVerificationData; + +pub async fn capture(root: >k::Root) -> Option { + let identifier = ashpd::WindowIdentifier::from_native(root).await; + let uri = screenshot::take(&identifier, true, true).await.ok()?; + let screenshot = gio::File::for_uri(&uri); + let (data, _) = screenshot.load_contents(gio::NONE_CANCELLABLE).ok()?; + let image = image::load_from_memory(&data).ok()?; + + QrVerificationData::from_image(image).ok() +} diff --git a/src/main.rs b/src/main.rs index c1c13967..6bd24367 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,6 +48,7 @@ fn main() { gtk::init().expect("Unable to start GTK4"); adw::init(); + gst::init().expect("Failed to initalize gst"); let res = gio::Resource::load(RESOURCES_FILE).expect("Could not load gresource file"); gio::resources_register(&res); diff --git a/src/meson.build b/src/meson.build index bd5b9bad..74d0465f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -21,6 +21,10 @@ run_command( sources = files( 'application.rs', 'contrib/qr_code.rs', + 'contrib/qr_code_scanner/camera_paintable.rs', + 'contrib/qr_code_scanner/mod.rs', + 'contrib/qr_code_scanner/screenshot.rs', + 'contrib/qr_code_scanner/qr_code_detector.rs', 'components/avatar.rs', 'components/auth_dialog.rs', 'components/context_menu_bin.rs', diff --git a/src/session/verification/identity_verification.rs b/src/session/verification/identity_verification.rs index 53ccdb5d..084e94b8 100644 --- a/src/session/verification/identity_verification.rs +++ b/src/session/verification/identity_verification.rs @@ -1,18 +1,25 @@ use crate::session::user::UserExt; use crate::session::User; +use crate::spawn; use crate::spawn_tokio; +use crate::Error; +use gettextrs::gettext; use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*}; use log::error; +use log::warn; use matrix_sdk::{ encryption::{ identities::RequestVerificationError, verification::{ - CancelInfo, Emoji, QrVerification, SasVerification, Verification as MatrixVerification, - VerificationRequest, + CancelInfo, Emoji, QrVerification, QrVerificationData, SasVerification, + Verification as MatrixVerification, VerificationRequest, }, }, ruma::{ - api::client::r0::sync::sync_events::ToDevice, events::AnyToDeviceEvent, identifiers::UserId, + api::client::r0::sync::sync_events::ToDevice, + events::key::verification::{cancel::CancelCode, VerificationMethod}, + events::AnyToDeviceEvent, + identifiers::UserId, }, Client, Error as MatrixError, }; @@ -62,26 +69,30 @@ pub enum Mode { Unavailable, Requested, SasV1, - QrV1, + QrV1Show, + QrV1Scan, Completed, Cancelled, + Dismissed, + Error, } impl Default for Mode { fn default() -> Self { - Self::Unavailable + Self::Requested } } -#[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Clone)] pub enum UserAction { Match, NotMatch, Cancel, StartSas, + Scanned(QrVerificationData), } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, PartialEq)] pub enum Message { UserAction(UserAction), Sync((String, State)), @@ -99,9 +110,10 @@ mod imp { pub user: OnceCell>, pub mode: Cell, pub sync_sender: RefCell>>, - pub main_sender: OnceCell>, + pub main_sender: OnceCell>, pub request: RefCell>, pub source_id: RefCell>, + pub flow_id: RefCell>, } #[glib::object_subclass] @@ -137,6 +149,13 @@ mod imp { None, glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY, ), + glib::ParamSpec::new_string( + "flow-id", + "Flow Id", + "The flow id of this verification request", + None, + glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, + ), ] }); @@ -152,6 +171,7 @@ mod imp { ) { match pspec.name() { "user" => obj.set_user(value.get().unwrap()), + "flow-id" => obj.set_flow_id(value.get().unwrap()), _ => unimplemented!(), } } @@ -161,6 +181,7 @@ mod imp { "user" => obj.user().to_value(), "mode" => obj.mode().to_value(), "display-name" => obj.display_name().to_value(), + "flow-id" => obj.flow_id().to_value(), _ => unimplemented!(), } } @@ -169,16 +190,11 @@ mod imp { self.parent_constructed(obj); let (main_sender, main_receiver) = - glib::MainContext::sync_channel::(Default::default(), 100); + glib::MainContext::sync_channel::<(Verification, Mode)>(Default::default(), 100); let source_id = main_receiver.attach( None, - clone!(@weak obj => @default-return glib::Continue(false), move |verification| { - let mode = match verification { - Verification::QrV1(_) => Mode::QrV1, - Verification::SasV1(_) => Mode::SasV1, - Verification::Request(_) => Mode::Requested, - }; + clone!(@weak obj => @default-return glib::Continue(false), move |(verification, mode)| { obj.set_request(Some(verification)); obj.set_mode(mode); @@ -208,6 +224,38 @@ impl IdentityVerification { glib::Object::new(&[("user", user)]).expect("Failed to create IdentityVerification") } + pub fn accept_incoming(&self) { + let priv_ = imp::IdentityVerification::from_instance(self); + let user = self.user(); + let client = user.session().client(); + let user_id = user.user_id().clone(); + let main_sender = priv_.main_sender.get().unwrap().clone(); + let flow_id = self.flow_id().clone().unwrap(); + + self.set_request(None); + let (sync_sender, sync_receiver) = mpsc::channel(100); + priv_.sync_sender.replace(Some(sync_sender)); + + // TODO add timeout + + let handle = spawn_tokio!(async move { + start(client, user_id, flow_id, main_sender, sync_receiver).await + }); + + let weak_obj = self.downgrade(); + spawn!(async move { + let result = handle.await.unwrap(); + if let Some(obj) = weak_obj.upgrade() { + let priv_ = imp::IdentityVerification::from_instance(&obj); + match result { + Ok(result) => obj.set_mode(result), + Err(error) => error!("Verification failed: {}", error), + } + priv_.sync_sender.take(); + } + }); + } + pub fn user(&self) -> User { let priv_ = imp::IdentityVerification::from_instance(self); priv_.user.get().unwrap().upgrade().unwrap() @@ -220,7 +268,7 @@ impl IdentityVerification { /// Start an interactive identity verification /// Already in progress verifications are cancelled before starting a new one - pub async fn start(&self) -> Result<(), RequestVerificationError> { + pub fn start(&self) { let priv_ = imp::IdentityVerification::from_instance(self); let user = self.user(); let client = user.session().client(); @@ -235,15 +283,41 @@ impl IdentityVerification { // TODO add timeout - let result = - spawn_tokio!(async move { start(client, user_id, main_sender, sync_receiver).await }) - .await - .unwrap()?; - - priv_.sync_sender.take(); + let handle = spawn_tokio!(async move { + let identity = if let Some(identity) = + client.get_user_identity(&user_id).await.map_err(|error| { + RequestVerificationError::Sdk(MatrixError::CryptoStoreError(error)) + })? { + identity + } else { + return Ok(Mode::IdentityNotFound); + }; - self.set_mode(result); - Ok(()) + let request = identity + .request_verification_with_methods(vec![ + VerificationMethod::SasV1, + VerificationMethod::QrCodeScanV1, + VerificationMethod::QrCodeShowV1, + VerificationMethod::ReciprocateV1, + ]) + .await?; + let flow_id = request.flow_id().to_owned(); + + start(client, user_id, flow_id, main_sender, sync_receiver).await + }); + + let weak_obj = self.downgrade(); + spawn!(async move { + let result = handle.await.unwrap(); + if let Some(obj) = weak_obj.upgrade() { + let priv_ = imp::IdentityVerification::from_instance(&obj); + match result { + Ok(result) => obj.set_mode(result), + Err(error) => error!("Verification failed: {}", error), + } + priv_.sync_sender.take(); + } + }); } pub fn emoji_match(&self) { @@ -281,6 +355,19 @@ impl IdentityVerification { return; } + match mode { + Mode::SasV1 => { + if self.emoji().is_none() { + warn!("Failed to get emoji for SasVerification"); + self.show_error(); + } + } + Mode::Unavailable | Mode::Cancelled => { + self.show_error(); + } + _ => {} + } + priv_.mode.set(mode); self.notify("mode"); } @@ -291,11 +378,60 @@ impl IdentityVerification { priv_.request.replace(request); } + fn show_error(&self) { + self.set_mode(Mode::Error); + let error_message = if let Some(info) = self.cancel_info() { + match info.cancel_code() { + CancelCode::User => Some(gettext("You cancelled the verificaiton process.")), + CancelCode::Timeout => Some(gettext( + "The verification process failed because it reached a timeout.", + )), + CancelCode::Accepted => { + Some(gettext("You accepted the request from an other session.")) + } + _ => match info.cancel_code().as_str() { + "m.mismatched_sas" => Some(gettext("The emoji did not match.")), + _ => None, + }, + } + } else { + None + }; + + let error_message = error_message.unwrap_or_else(|| { + gettext("An unknown error occured during the verification process.") + }); + + let error = Error::new(move |_| { + let error_label = gtk::LabelBuilder::new() + .label(&error_message) + .wrap(true) + .build(); + Some(error_label.upcast()) + }); + + if let Some(window) = self.user().session().parent_window() { + window.append_error(&error); + } + } + pub fn display_name(&self) -> String { // TODO: give this request a name based on the device "Request".to_string() } + pub fn flow_id(&self) -> Option { + let priv_ = imp::IdentityVerification::from_instance(self); + priv_.flow_id.borrow().clone() + } + + pub fn set_flow_id(&self, flow_id: Option) { + let priv_ = imp::IdentityVerification::from_instance(self); + + priv_.flow_id.replace(flow_id); + self.notify("flow-id"); + } + /// Get the QrCode for this verification request /// /// This is only set once the request reached the `State::Ready` @@ -334,6 +470,18 @@ impl IdentityVerification { } } + pub fn scanned_qr_code(&self, data: QrVerificationData) { + let priv_ = imp::IdentityVerification::from_instance(self); + + if let Some(sync_sender) = &*priv_.sync_sender.borrow() { + let result = sync_sender.try_send(Message::UserAction(UserAction::Scanned(data))); + + if let Err(error) = result { + error!("Failed to send message to tokio runtime: {}", error); + } + } + } + pub fn cancel(&self) { let priv_ = imp::IdentityVerification::from_instance(self); if let Some(sync_sender) = &*priv_.sync_sender.borrow() { @@ -344,6 +492,10 @@ impl IdentityVerification { } } + pub fn dismiss(&self) { + self.set_mode(Mode::Dismissed); + } + /// Get information about why the request was cancelled pub fn cancel_info(&self) -> Option { let priv_ = imp::IdentityVerification::from_instance(self); @@ -394,62 +546,115 @@ impl IdentityVerification { async fn start( client: Client, user_id: UserId, - main_sender: glib::SyncSender, + flow_id: String, + main_sender: glib::SyncSender<(Verification, Mode)>, mut sync_receiver: mpsc::Receiver, ) -> Result { - let identity = if let Some(identity) = client - .get_user_identity(&user_id) - .await - .map_err(|error| RequestVerificationError::Sdk(MatrixError::CryptoStoreError(error)))? - { - identity - } else { - return Ok(Mode::IdentityNotFound); - }; - - let request = identity.request_verification().await?; - let flow_id = request.flow_id(); + let request = + if let Some(verification) = client.get_verification_request(&user_id, &flow_id).await { + verification + } else { + return Ok(Mode::Unavailable); + }; - let result = main_sender.send(Verification::Request(request.clone())); + let result = main_sender.send((Verification::Request(request.clone()), Mode::Requested)); if let Err(error) = result { error!("Failed to send message to the main context: {}", error); } - if wait_for_state(flow_id, State::Ready, &mut sync_receiver).await { - request.cancel().await?; - return Ok(Mode::Cancelled); + if !request.we_started() { + request + .accept_with_methods(vec![ + VerificationMethod::SasV1, + VerificationMethod::QrCodeScanV1, + VerificationMethod::QrCodeShowV1, + VerificationMethod::ReciprocateV1, + ]) + .await?; + } else { + if wait_for_state(&flow_id, State::Ready, &mut sync_receiver).await { + request.cancel().await?; + return Ok(Mode::Cancelled); + } } - let qr_verification = request - .generate_qr_code() - .await - .map_err(|error| RequestVerificationError::Sdk(error))?; + let supports_qr_code_scanning = request.their_supported_methods().map_or(false, |methods| { + methods + .iter() + .any(|method| method == &VerificationMethod::QrCodeScanV1) + }); + + let qr_verification = if supports_qr_code_scanning { + request + .generate_qr_code() + .await + .map_err(|error| RequestVerificationError::Sdk(error))? + } else { + None + }; let start_sas = if let Some(qr_verification) = qr_verification { - let result = main_sender.send(Verification::QrV1(qr_verification)); + let result = main_sender.send((Verification::QrV1(qr_verification), Mode::QrV1Show)); if let Err(error) = result { error!("Failed to send message to the main context: {}", error); } - let (start_sas, cancel) = loop { + loop { match sync_receiver.recv().await.unwrap() { - Message::Sync((id, State::Start)) if flow_id == &id => break (false, false), - Message::Sync((id, State::Cancel)) if flow_id == &id => break (false, true), - Message::UserAction(UserAction::Cancel) => break (false, true), - Message::UserAction(UserAction::StartSas) => break (true, false), + Message::Sync((id, State::Start)) if flow_id == id => break false, + Message::Sync((id, State::Cancel)) if flow_id == id => { + request.cancel().await?; + return Ok(Mode::Cancelled); + } + Message::UserAction(UserAction::Cancel) => { + request.cancel().await?; + return Ok(Mode::Cancelled); + } + Message::UserAction(UserAction::StartSas) => break true, + Message::UserAction(UserAction::Scanned(data)) => { + request.scan_qr_code(data).await?; + break false; + } _ => {} } - }; - - if cancel { - request.cancel().await?; - return Ok(Mode::Cancelled); } - start_sas } else { - true + let supports_qr_code_showing = request.their_supported_methods().map_or(false, |methods| { + methods + .iter() + .any(|method| method == &VerificationMethod::QrCodeShowV1) + }); + if supports_qr_code_showing { + let result = main_sender.send((Verification::Request(request.clone()), Mode::QrV1Scan)); + + if let Err(error) = result { + error!("Failed to send message to the main context: {}", error); + } + + loop { + match sync_receiver.recv().await.unwrap() { + Message::Sync((id, State::Start)) if flow_id == id => break false, + Message::Sync((id, State::Cancel)) if flow_id == id => { + request.cancel().await?; + return Ok(Mode::Cancelled); + } + Message::UserAction(UserAction::Cancel) => { + request.cancel().await?; + return Ok(Mode::Cancelled); + } + Message::UserAction(UserAction::StartSas) => break true, + Message::UserAction(UserAction::Scanned(data)) => { + request.scan_qr_code(data).await?; + break false; + } + _ => {} + } + } + } else { + true + } }; if start_sas { @@ -461,9 +666,9 @@ async fn start( { let cancel = loop { match sync_receiver.recv().await { - Some(Message::Sync((id, State::Start))) if flow_id == &id => break false, - Some(Message::Sync((id, State::Accept))) if flow_id == &id => break false, - Some(Message::Sync((id, State::Cancel))) if flow_id == &id => break true, + Some(Message::Sync((id, State::Start))) if flow_id == id => break false, + Some(Message::Sync((id, State::Accept))) if flow_id == id => break false, + Some(Message::Sync((id, State::Cancel))) if flow_id == id => break true, Some(Message::UserAction(UserAction::Cancel)) => break true, None => break true, _ => {} @@ -491,7 +696,7 @@ async fn start( MatrixVerification::QrV1(qr_verification) => { qr_verification.confirm().await?; - if wait_for_state(flow_id, State::Done, &mut sync_receiver).await { + if wait_for_state(&flow_id, State::Done, &mut sync_receiver).await { request.cancel().await?; return Ok(Mode::Cancelled); } @@ -499,25 +704,26 @@ async fn start( MatrixVerification::SasV1(sas_verification) => { sas_verification.accept().await?; - if wait_for_state(flow_id, State::Key, &mut sync_receiver).await { + if wait_for_state(&flow_id, State::Key, &mut sync_receiver).await { request.cancel().await?; return Ok(Mode::Cancelled); } - let result = main_sender.send(Verification::SasV1(sas_verification.clone())); + let result = + main_sender.send((Verification::SasV1(sas_verification.clone()), Mode::SasV1)); if let Err(error) = result { error!("Failed to send message to the main context: {}", error); } - if wait_for_match_action(flow_id, &mut sync_receiver).await { + if wait_for_match_action(&flow_id, &mut sync_receiver).await { request.cancel().await?; return Ok(Mode::Cancelled); } sas_verification.confirm().await?; - if wait_for_state(flow_id, State::Done, &mut sync_receiver).await { + if wait_for_state(&flow_id, State::Done, &mut sync_receiver).await { request.cancel().await?; return Ok(Mode::Cancelled); } diff --git a/src/session/verification/session_verification.rs b/src/session/verification/session_verification.rs index e14a46d6..df7f4e06 100644 --- a/src/session/verification/session_verification.rs +++ b/src/session/verification/session_verification.rs @@ -4,14 +4,16 @@ use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate use log::{debug, error, warn}; use crate::components::{AuthDialog, SpinnerButton}; +use crate::contrib::screenshot; use crate::contrib::QRCode; use crate::contrib::QRCodeExt; +use crate::contrib::QrCodeScanner; use crate::session::verification::{Emoji, IdentityVerification, VerificationMode}; use crate::session::Session; use crate::spawn; use crate::Error; use crate::Window; -use matrix_sdk::ruma::events::key::verification::cancel::CancelCode; +use matrix_sdk::encryption::verification::QrVerificationData; mod imp { use super::*; @@ -24,7 +26,7 @@ mod imp { #[derive(Debug, Default, CompositeTemplate)] #[template(resource = "/org/gnome/FractalNext/session-verification.ui")] pub struct SessionVerification { - pub request: OnceCell>, + pub request: OnceCell, pub session: OnceCell>, #[template_child] pub bootstrap_button: TemplateChild, @@ -41,7 +43,17 @@ mod imp { #[template_child] pub start_emoji_btn: TemplateChild, #[template_child] + pub start_emoji_btn2: TemplateChild, + #[template_child] + pub start_emoji_btn3: TemplateChild, + #[template_child] + pub take_screenshot_btn2: TemplateChild, + #[template_child] + pub take_screenshot_btn3: TemplateChild, + #[template_child] pub main_stack: TemplateChild, + #[template_child] + pub qr_code_scanner: TemplateChild, pub mode_handler: RefCell>, } @@ -145,11 +157,42 @@ mod imp { obj.request().start_sas(); })); + self.start_emoji_btn2 + .connect_clicked(clone!(@weak obj => move |button| { + let priv_ = imp::SessionVerification::from_instance(&obj); + button.set_loading(true); + priv_.take_screenshot_btn2.set_sensitive(false); + obj.request().start_sas(); + })); + self.start_emoji_btn3 + .connect_clicked(clone!(@weak obj => move |button| { + let priv_ = imp::SessionVerification::from_instance(&obj); + button.set_loading(true); + priv_.take_screenshot_btn3.set_sensitive(false); + obj.request().start_sas(); + })); + self.bootstrap_button .connect_clicked(clone!(@weak obj => move |button| { button.set_loading(true); obj.bootstrap_cross_signing(); })); + + self.take_screenshot_btn2 + .connect_clicked(clone!(@weak obj => move |button| { + let priv_ = imp::SessionVerification::from_instance(&obj); + button.set_loading(true); + priv_.start_emoji_btn2.set_sensitive(false); + obj.take_screenshot(); + })); + + self.take_screenshot_btn3 + .connect_clicked(clone!(@weak obj => move |button| { + let priv_ = imp::SessionVerification::from_instance(&obj); + button.set_loading(true); + priv_.start_emoji_btn3.set_sensitive(false); + obj.take_screenshot(); + })); } fn dispose(&self, obj: &Self::Type) { @@ -172,15 +215,15 @@ impl SessionVerification { .expect("Failed to create SessionVerification") } - pub fn request(&self) -> IdentityVerification { + pub fn request(&self) -> &IdentityVerification { let priv_ = imp::SessionVerification::from_instance(self); - priv_.request.get().unwrap().upgrade().unwrap() + priv_.request.get().unwrap() } fn set_request(&self, request: IdentityVerification) { let priv_ = imp::SessionVerification::from_instance(self); - priv_.request.set(request.downgrade()).unwrap() + priv_.request.set(request).unwrap(); } pub fn session(&self) -> Session { @@ -208,6 +251,12 @@ impl SessionVerification { priv_.start_emoji_btn.set_loading(false); priv_.start_emoji_btn.set_sensitive(true); priv_.bootstrap_button.set_loading(false); + priv_.start_emoji_btn2.set_loading(false); + priv_.start_emoji_btn2.set_sensitive(true); + priv_.take_screenshot_btn2.set_loading(false); + priv_.take_screenshot_btn2.set_sensitive(true); + priv_.take_screenshot_btn3.set_loading(false); + priv_.take_screenshot_btn3.set_sensitive(true); while let Some(child) = priv_.emoji_row_1.first_child() { priv_.emoji_row_1.remove(&child); @@ -233,15 +282,7 @@ impl SessionVerification { priv_.mode_handler.replace(Some(handler)); - let weak_obj = self.downgrade(); - spawn!(clone!(@weak request => async move { - if let Err(error) = request.start().await { - if let Some(obj) = weak_obj.upgrade() { - obj.show_error(); - } - error!("Verification failed: {}", error); - } - })); + request.start(); } /// Cancel the verification request without telling the user about it @@ -267,7 +308,7 @@ impl SessionVerification { VerificationMode::Requested => { priv_.main_stack.set_visible_child_name("wait-for-device"); } - VerificationMode::QrV1 => { + VerificationMode::QrV1Show => { if let Some(qrcode) = request.qr_code() { priv_.qrcode.set_qrcode(qrcode); priv_.main_stack.set_visible_child_name("qrcode"); @@ -276,6 +317,9 @@ impl SessionVerification { request.start_sas(); } } + VerificationMode::QrV1Scan => { + self.start_scanning(); + } VerificationMode::SasV1 => { // TODO: implement sas fallback when emojis arn't supported if let Some(emoji) = request.emoji() { @@ -287,56 +331,44 @@ impl SessionVerification { } } priv_.main_stack.set_visible_child_name("emoji"); - } else { - warn!("Failed to get emoji for SasVerification"); - self.show_error(); } } - VerificationMode::Unavailable => { - self.show_error(); - } VerificationMode::Completed => { priv_.main_stack.set_visible_child_name("completed"); } - VerificationMode::Cancelled => { - self.show_error(); + _ => { + warn!("Try to show a dismissed verification"); } } } - fn show_error(&self) { - let error_message = if let Some(info) = self.request().cancel_info() { - match info.cancel_code() { - CancelCode::User => Some(gettext("You cancelled the verificaiton process.")), - CancelCode::Timeout => Some(gettext( - "The verification process failed because it reached a timeout.", - )), - _ => match info.cancel_code().as_str() { - "m.mismatched_sas" => Some(gettext("The emoji did not match.")), - _ => None, - }, + fn start_scanning(&self) { + spawn!(clone!(@weak self as obj => async move { + let priv_ = imp::SessionVerification::from_instance(&obj); + if priv_.qr_code_scanner.start().await { + priv_.main_stack.set_visible_child_name("scan-qr-code"); + } else { + priv_.main_stack.set_visible_child_name("no-camera"); } - } else { - None - }; - - let error_message = error_message.unwrap_or_else(|| { - gettext("An unknown error occured during the verification process.") - }); - - let error = Error::new(move |_| { - let error_label = gtk::LabelBuilder::new() - .label(&error_message) - .wrap(true) - .build(); - Some(error_label.upcast()) - }); - - if let Some(window) = self.session().parent_window() { - window.append_error(&error); - } - self.silent_cancel(); - self.start_verification(); + })); + } + + fn take_screenshot(&self) { + spawn!(clone!(@weak self as obj => async move { + let root = obj.root().unwrap(); + if let Some(code) = screenshot::capture(&root).await { + obj.finish_scanning(code); + } else { + obj.reset(); + } + })); + } + + fn finish_scanning(&self, data: QrVerificationData) { + let priv_ = imp::SessionVerification::from_instance(self); + priv_.qr_code_scanner.stop(); + self.request().scanned_qr_code(data); + priv_.main_stack.set_visible_child_name("qr-code-scanned"); } fn show_recovery(&self) { @@ -362,7 +394,7 @@ impl SessionVerification { self.silent_cancel(); self.activate_action("session.logout", None); } - "emoji" | "qrcode" => { + "emoji" | "qrcode" | "scan-qr-code" | "no-camera" => { self.silent_cancel(); self.start_verification(); }