1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// SPDX-License-Identifier: GPL-3.0-or-later
use gtk::{gdk, glib, glib::closure_local, prelude::*, subclass::prelude::*};
use matrix_sdk::encryption::verification::QrVerificationData;

mod camera;
mod qr_code_detector;

pub use camera::{Camera, CameraExt};

mod imp {
    use std::cell::RefCell;

    use adw::subclass::prelude::*;
    use glib::subclass::{InitializingObject, Signal};
    use gtk::CompositeTemplate;
    use once_cell::sync::Lazy;

    use super::*;

    #[derive(Debug, CompositeTemplate, Default)]
    #[template(resource = "/org/gnome/Fractal/ui/contrib/qr_code_scanner/mod.ui")]
    pub struct QrCodeScanner {
        #[template_child]
        pub stack: TemplateChild<gtk::Stack>,
        #[template_child]
        pub picture: TemplateChild<gtk::Picture>,
        pub handler: RefCell<Option<glib::SignalHandlerId>>,
    }

    #[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: &InitializingObject<Self>) {
            obj.init_template();
        }
    }
    impl ObjectImpl for QrCodeScanner {
        fn signals() -> &'static [Signal] {
            static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
                vec![Signal::builder("code-detected")
                    .param_types([QrVerificationDataBoxed::static_type()])
                    .run_first()
                    .build()]
            });
            SIGNALS.as_ref()
        }
    }
    impl WidgetImpl for QrCodeScanner {
        fn unmap(&self) {
            self.parent_unmap();
            self.obj().stop();
        }
    }
    impl BinImpl for QrCodeScanner {}
}

glib::wrapper! {
    pub struct QrCodeScanner(ObjectSubclass<imp::QrCodeScanner>) @extends gtk::Widget, adw::Bin;
}

impl QrCodeScanner {
    pub fn new() -> Self {
        glib::Object::new()
    }

    pub fn stop(&self) {
        let imp = self.imp();

        if let Some(paintable) = imp.picture.paintable() {
            imp.picture.set_paintable(gdk::Paintable::NONE);
            if let Some(handler) = imp.handler.take() {
                paintable.disconnect(handler);
            }
        }
    }

    pub async fn start(&self) {
        let imp = self.imp();
        let camera = camera::Camera::default();

        if let Some(paintable) = camera.paintable().await {
            self.stop();

            imp.picture.set_paintable(Some(&paintable));

            let callback = glib::clone!(@weak self as obj => @default-return None, move |args: &[glib::Value]| {
                let code = args.get(1).unwrap().get::<QrVerificationDataBoxed>().unwrap();
                obj.emit_by_name::<()>("code-detected", &[&code]);

                None
            });
            let handler = paintable.connect_local("code-detected", false, callback);

            imp.handler.replace(Some(handler));
            imp.stack.set_visible_child_name("camera");
        } else {
            imp.stack.set_visible_child_name("no-camera");
        }
    }

    pub fn connect_code_detected<F: Fn(&Self, QrVerificationData) + 'static>(
        &self,
        f: F,
    ) -> glib::SignalHandlerId {
        self.connect_closure(
            "code-detected",
            true,
            closure_local!(move |obj: Self, data: QrVerificationDataBoxed| {
                f(&obj, data.0);
            }),
        )
    }
}

#[derive(Clone, Debug, PartialEq, Eq, glib::Boxed)]
#[boxed_type(name = "QrVerificationDataBoxed")]
struct QrVerificationDataBoxed(QrVerificationData);