[fractal/fractal-next] verification: Add qr-code scanning for verification
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] verification: Add qr-code scanning for verification
- Date: Fri, 19 Nov 2021 11:37:58 +0000 (UTC)
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: >k::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]