[fractal/fractal-next] verification: Add qr-code scanning for verification



commit ff2a3ca741f18a3545e304904da082b6ca53a8e0
Author: Julian Sparber <julian sparber net>
Date:   Tue Nov 16 18:26:40 2021 +0100

    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 +
 src/contrib/qr_code_scanner/camera_paintable.rs   | 461 ++++++++++++
 src/contrib/qr_code_scanner/mod.rs                | 199 +++++
 src/contrib/qr_code_scanner/qr_code_detector.rs   | 140 ++++
 src/contrib/qr_code_scanner/screenshot.rs         |  14 +
 src/main.rs                                       |   1 +
 src/meson.build                                   |   4 +
 src/session/verification/identity_verification.rs | 338 +++++++--
 src/session/verification/session_verification.rs  | 144 ++--
 15 files changed, 2213 insertions(+), 147 deletions(-)
---
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 @@
     <file compressed="true" preprocess="xml-stripblanks" alias="room-creation.ui">ui/room-creation.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="session-verification.ui">ui/session-verification.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="verification-emoji.ui">ui/verification-emoji.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="qr-code-scanner.ui">ui/qr-code-scanner.ui</file>
     <file compressed="true">style.css</file>
     <file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
     <file preprocess="xml-stripblanks">icons/scalable/status/welcome.svg</file>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="QrCodeScanner" parent="AdwBin">
+    <property name="child">
+      <object class="GtkPicture" id="picture">
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <property name="keep-aspect-ratio">False</property>
+        <property name="height-request">400</property>
+      </object>
+    </property>
+  </template>
+</interface>
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 @@
                 </property>
               </object>
             </child>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">scan-qr-code</property>
+                <property name="child">
+                  <object class="AdwClamp">
+                    <property name="maximum-size">400</property>
+                    <property name="tightening-threshold">300</property>
+                    <property name="child">
+                      <object class="GtkBox">
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">18</property>
+                        <property name="valign">center</property>
+                        <property name="halign">center</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="label" translatable="yes">Verify Session</property>
+                            <property name="wrap">True</property>
+                            <property name="wrap-mode">word-char</property>
+                            <property name="justify">center</property>
+                            <style>
+                              <class name="title-1"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="label" translatable="yes">Scan the Qr code with this session 
from another session logged into this account</property>
+                            <property name="wrap">True</property>
+                            <property name="wrap-mode">word-char</property>
+                            <property name="justify">center</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="QrCodeScanner" id="qr_code_scanner">
+                            <property name="margin-top">24</property>
+                            <property name="margin-bottom">24</property>
+                            <property name="vexpand">True</property>
+                            <property name="hexpand">True</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="label" translatable="yes">Can't scan QR code?</property>
+                            <property name="wrap">True</property>
+                            <property name="wrap-mode">word-char</property>
+                            <property name="justify">center</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="SpinnerButton" id="start_emoji_btn2">
+                            <property name="label" translatable="yes">Compare Emoji</property>
+                            <property name="halign">center</property>
+                            <style>
+                              <class name="pill"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="SpinnerButton" id="take_screenshot_btn2">
+                            <property name="label" translatable="yes">Take a Screenshot of a Qr 
Code</property>
+                            <property name="halign">center</property>
+                            <style>
+                              <class name="pill"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </property>
+                  </object>
+                </property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">qr-code-scanned</property>
+                <property name="child">
+                  <object class="AdwClamp">
+                    <property name="maximum-size">400</property>
+                    <property name="tightening-threshold">300</property>
+                    <property name="child">
+                      <object class="GtkBox">
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">18</property>
+                        <property name="valign">center</property>
+                        <property name="halign">center</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="label" translatable="yes">Scan Complete</property>
+                            <property name="wrap">True</property>
+                            <property name="wrap-mode">word-char</property>
+                            <property name="justify">center</property>
+                            <style>
+                              <class name="title-1"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkPicture">
+                            <property 
name="file">resource:///org/gnome/FractalNext/icons/scalable/status/setup-complete.svg</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="label" translatable="yes">You scanned to qr code successfully. 
You may need to confirm the verification in the other session.</property>
+                            <property name="wrap">True</property>
+                            <property name="wrap-mode">word-char</property>
+                            <property name="justify">center</property>
+                          </object>
+                        </child>
+                      </object>
+                    </property>
+                  </object>
+                </property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">no-camera</property>
+                <property name="child">
+                  <object class="AdwClamp">
+                    <property name="maximum-size">400</property>
+                    <property name="tightening-threshold">300</property>
+                    <property name="child">
+                      <object class="GtkBox">
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">18</property>
+                        <property name="valign">center</property>
+                        <property name="halign">center</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="label" translatable="yes">Verify Session</property>
+                            <property name="wrap">True</property>
+                            <property name="wrap-mode">word-char</property>
+                            <property name="justify">center</property>
+                            <style>
+                              <class name="title-1"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="label" translatable="yes">Select an option to verify the new 
session.</property>
+                            <property name="wrap">True</property>
+                            <property name="wrap-mode">word-char</property>
+                            <property name="justify">center</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="SpinnerButton" id="start_emoji_btn3">
+                            <property name="label" translatable="yes">Compare Emoji</property>
+                            <property name="halign">center</property>
+                            <style>
+                              <class name="pill"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="SpinnerButton" id="take_screenshot_btn3">
+                            <property name="label" translatable="yes">Take a Screenshot of a Qr 
Code</property>
+                            <property name="halign">center</property>
+                            <style>
+                              <class name="pill"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </property>
+                  </object>
+                </property>
+              </object>
+            </child>
             <child>
               <object class="GtkStackPage">
                 <property name="name">qrcode</property>
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<gst_video::video_frame::Readable>);
+
+    impl AsRef<[u8]> for Frame {
+        fn as_ref(&self) -> &[u8] {
+            self.0.plane_data(0).unwrap()
+        }
+    }
+
+    impl From<Frame> 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<Option<gst_video::VideoInfo>>,
+            pub sender: Mutex<Option<Sender<Action>>>,
+            pub pending_frame: Mutex<Option<Frame>>,
+        }
+
+        #[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<gst::subclass::ElementMetadata> = Lazy::new(|| {
+                    gst::subclass::ElementMetadata::new(
+                        "GTK Camera Sink",
+                        "Sink/Camera/Video",
+                        "A GTK Camera sink",
+                        "Bilal Elmoussaoui <bil elmoussaoui gmail com>",
+                    )
+                });
+
+                Some(&*ELEMENT_METADATA)
+            }
+
+            fn pad_templates() -> &'static [gst::PadTemplate] {
+                static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = 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<gst::FlowSuccess, gst::FlowError> {
+                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<imp::CameraSink>) @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<Action>) -> 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<Frame> {
+            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<Option<gst::Pipeline>>,
+        pub sender: Sender<Action>,
+        pub image: RefCell<Option<gdk::Paintable>>,
+        pub size: RefCell<Option<(u32, u32)>>,
+        pub receiver: RefCell<Option<Receiver<Action>>>,
+    }
+
+    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<Vec<subclass::Signal>> = 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::<gtk::Snapshot>().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<imp::CameraPaintable>) @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<F: AsRawFd>(&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<gtk::Picture>,
+        pub has_camera: Cell<bool>,
+        pub is_started: Cell<bool>,
+    }
+
+    #[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<Self>) {
+            obj.init_template();
+        }
+    }
+    impl ObjectImpl for QrCodeScanner {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = 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::<QrVerificationDataBoxed>().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<Vec<subclass::Signal>> = 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<imp::QrCodeScanner>) @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<F: Fn(&Self, QrVerificationData) + 'static>(
+        &self,
+        f: F,
+    ) -> glib::SignalHandlerId {
+        self.connect_local("code-detected", true, move |values| {
+            let obj = values[0].get::<Self>().unwrap();
+            let data = values[1].get::<QrVerificationDataBoxed>().unwrap();
+
+            f(&obj, data.0);
+
+            None
+        })
+        .unwrap()
+    }
+}
+
+async fn stream() -> Result<Option<RawFd>, 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<Option<VideoInfo>>,
+        pub sender: Mutex<Option<Sender<Action>>>,
+        pub code: Mutex<Option<QrVerificationData>>,
+    }
+
+    #[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<gst::subclass::ElementMetadata> = Lazy::new(|| {
+                gst::subclass::ElementMetadata::new(
+                    "Matrix Qr Code detector Sink",
+                    "Sink/Video/QrCode/Matrix",
+                    "A Qr code detector for Matrix",
+                    "Julian Sparber <julian sparber net>",
+                )
+            });
+
+            Some(&*ELEMENT_METADATA)
+        }
+
+        fn pad_templates() -> &'static [gst::PadTemplate] {
+            static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = 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<gst::FlowSuccess, gst::FlowError> {
+            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::<Vec<u8>> {
+                    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::<image::Luma<u8>>().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<imp::QrCodeDetector>) @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<Action>) -> 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: &gtk::Root) -> Option<QrVerificationData> {
+    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<WeakRef<User>>,
         pub mode: Cell<Mode>,
         pub sync_sender: RefCell<Option<mpsc::Sender<Message>>>,
-        pub main_sender: OnceCell<glib::SyncSender<Verification>>,
+        pub main_sender: OnceCell<glib::SyncSender<(Verification, Mode)>>,
         pub request: RefCell<Option<Verification>>,
         pub source_id: RefCell<Option<SourceId>>,
+        pub flow_id: RefCell<Option<String>>,
     }
 
     #[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::<Verification>(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<String> {
+        let priv_ = imp::IdentityVerification::from_instance(self);
+        priv_.flow_id.borrow().clone()
+    }
+
+    pub fn set_flow_id(&self, flow_id: Option<String>) {
+        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<CancelInfo> {
         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<Verification>,
+    flow_id: String,
+    main_sender: glib::SyncSender<(Verification, Mode)>,
     mut sync_receiver: mpsc::Receiver<Message>,
 ) -> Result<Mode, RequestVerificationError> {
-    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<WeakRef<IdentityVerification>>,
+        pub request: OnceCell<IdentityVerification>,
         pub session: OnceCell<WeakRef<Session>>,
         #[template_child]
         pub bootstrap_button: TemplateChild<SpinnerButton>,
@@ -41,7 +43,17 @@ mod imp {
         #[template_child]
         pub start_emoji_btn: TemplateChild<SpinnerButton>,
         #[template_child]
+        pub start_emoji_btn2: TemplateChild<SpinnerButton>,
+        #[template_child]
+        pub start_emoji_btn3: TemplateChild<SpinnerButton>,
+        #[template_child]
+        pub take_screenshot_btn2: TemplateChild<SpinnerButton>,
+        #[template_child]
+        pub take_screenshot_btn3: TemplateChild<SpinnerButton>,
+        #[template_child]
         pub main_stack: TemplateChild<gtk::Stack>,
+        #[template_child]
+        pub qr_code_scanner: TemplateChild<QrCodeScanner>,
         pub mode_handler: RefCell<Option<SignalHandlerId>>,
     }
 
@@ -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();
             }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]