Skip to main content

fractal/components/camera/
qrcode_scanner.rs

1use adw::{prelude::*, subclass::prelude::*};
2use gettextrs::gettext;
3use gtk::{
4    glib,
5    glib::{clone, closure_local},
6};
7use matrix_sdk::encryption::verification::QrVerificationData;
8
9use super::{Camera, CameraExt, CameraViewfinder, CameraViewfinderExt, CameraViewfinderState};
10use crate::utils::BoundConstructOnlyObject;
11
12#[derive(Clone, Debug, PartialEq, Eq, glib::Boxed)]
13#[boxed_type(name = "QrVerificationDataBoxed")]
14pub(super) struct QrVerificationDataBoxed(pub(super) QrVerificationData);
15
16mod imp {
17    use std::sync::LazyLock;
18
19    use glib::subclass::{InitializingObject, Signal};
20
21    use super::*;
22
23    #[derive(Debug, gtk::CompositeTemplate, Default, glib::Properties)]
24    #[template(resource = "/org/gnome/Fractal/ui/components/camera/qrcode_scanner.ui")]
25    #[properties(wrapper_type = super::QrCodeScanner)]
26    pub struct QrCodeScanner {
27        #[template_child]
28        stack: TemplateChild<gtk::Stack>,
29        /// The viewfinder to use to scan the QR code.
30        #[property(get, set = Self::set_viewfinder, construct_only)]
31        viewfinder: BoundConstructOnlyObject<CameraViewfinder>,
32    }
33
34    #[glib::object_subclass]
35    impl ObjectSubclass for QrCodeScanner {
36        const NAME: &'static str = "QrCodeScanner";
37        type Type = super::QrCodeScanner;
38        type ParentType = adw::Bin;
39
40        fn class_init(klass: &mut Self::Class) {
41            Self::bind_template(klass);
42        }
43
44        fn instance_init(obj: &InitializingObject<Self>) {
45            obj.init_template();
46        }
47    }
48
49    #[glib::derived_properties]
50    impl ObjectImpl for QrCodeScanner {
51        fn signals() -> &'static [Signal] {
52            static SIGNALS: LazyLock<Vec<Signal>> = LazyLock::new(|| {
53                vec![
54                    Signal::builder("qrcode-detected")
55                        .param_types([QrVerificationDataBoxed::static_type()])
56                        .run_first()
57                        .build(),
58                ]
59            });
60            SIGNALS.as_ref()
61        }
62    }
63
64    impl WidgetImpl for QrCodeScanner {}
65    impl BinImpl for QrCodeScanner {}
66
67    impl QrCodeScanner {
68        /// Set the viewfinder to use to scan the QR code.
69        fn set_viewfinder(&self, viewfinder: CameraViewfinder) {
70            let obj = self.obj();
71
72            let state_handler = viewfinder.connect_state_notify(clone!(
73                #[weak(rename_to = imp)]
74                self,
75                move |_| {
76                    imp.update_visible_page();
77                }
78            ));
79            let qrcode_detected_handler = viewfinder.connect_qrcode_detected(clone!(
80                #[weak]
81                obj,
82                move |_, data| {
83                    obj.emit_by_name::<()>("qrcode-detected", &[&QrVerificationDataBoxed(data)]);
84                }
85            ));
86
87            viewfinder.set_overflow(gtk::Overflow::Hidden);
88            viewfinder.add_css_class("card");
89
90            self.stack
91                .add_titled(&viewfinder, Some("camera"), &gettext("Camera"));
92
93            self.viewfinder
94                .set(viewfinder, vec![state_handler, qrcode_detected_handler]);
95
96            self.update_visible_page();
97        }
98
99        /// Update the visible page according to the current state.
100        fn update_visible_page(&self) {
101            let name = match self.viewfinder.obj().state() {
102                CameraViewfinderState::Loading => "loading",
103                CameraViewfinderState::Ready => "camera",
104                CameraViewfinderState::NoCameras | CameraViewfinderState::Error => "no-camera",
105            };
106
107            self.stack.set_visible_child_name(name);
108        }
109    }
110}
111
112glib::wrapper! {
113    /// A widget to show the output of the camera and detect QR codes with it.
114    pub struct QrCodeScanner(ObjectSubclass<imp::QrCodeScanner>)
115        @extends gtk::Widget, adw::Bin,
116        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
117}
118
119impl QrCodeScanner {
120    /// Try to construct a new `QrCodeScanner`.
121    ///
122    /// Returns `None` if we could not get a [`CameraViewfinder`].
123    pub async fn new() -> Option<Self> {
124        let viewfinder = Camera::viewfinder().await?;
125
126        let obj = glib::Object::builder()
127            .property("viewfinder", viewfinder)
128            .build();
129
130        Some(obj)
131    }
132
133    /// Connect to the signal emitted when a QR code is detected.
134    pub fn connect_qrcode_detected<F: Fn(&Self, QrVerificationData) + 'static>(
135        &self,
136        f: F,
137    ) -> glib::SignalHandlerId {
138        self.connect_closure(
139            "qrcode-detected",
140            true,
141            closure_local!(move |obj: Self, data: QrVerificationDataBoxed| {
142                f(&obj, data.0);
143            }),
144        )
145    }
146}