authenticator/widgets/backup/
camera_page.rs

1use std::{cell::Cell, rc::Rc};
2
3use adw::subclass::{navigation_page::*, prelude::*};
4use anyhow::Result;
5use gtk::{
6    gio,
7    glib::{self, clone},
8    prelude::*,
9};
10use tokio::{
11    select,
12    sync::oneshot,
13    time::{Duration, sleep},
14};
15
16use crate::{utils::spawn_tokio, widgets::Camera};
17
18mod imp {
19    use std::cell::OnceCell;
20
21    use super::*;
22
23    #[derive(Default, gtk::CompositeTemplate, glib::Properties)]
24    #[template(resource = "/com/belmoussaoui/Authenticator/preferences_camera_page.ui")]
25    #[properties(wrapper_type = super::CameraPage)]
26    pub struct CameraPage {
27        #[property(get, set, construct_only)]
28        pub actions: OnceCell<gio::SimpleActionGroup>,
29        #[template_child]
30        pub camera: TemplateChild<Camera>,
31    }
32
33    #[glib::object_subclass]
34    impl ObjectSubclass for CameraPage {
35        const NAME: &'static str = "CameraPage";
36        type Type = super::CameraPage;
37        type ParentType = adw::NavigationPage;
38
39        fn class_init(klass: &mut Self::Class) {
40            klass.bind_template();
41            klass.bind_template_instance_callbacks();
42        }
43
44        fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
45            obj.init_template();
46        }
47    }
48
49    #[glib::derived_properties]
50    impl ObjectImpl for CameraPage {}
51
52    impl WidgetImpl for CameraPage {}
53    impl NavigationPageImpl for CameraPage {}
54}
55
56glib::wrapper! {
57    pub struct CameraPage(ObjectSubclass<imp::CameraPage>)
58        @extends gtk::Widget, adw::NavigationPage,
59        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
60}
61
62#[gtk::template_callbacks]
63impl CameraPage {
64    pub fn new(actions: &gio::SimpleActionGroup) -> Self {
65        glib::Object::builder().property("actions", actions).build()
66    }
67
68    pub async fn scan_from_camera(&self) -> Result<String> {
69        let imp = self.imp();
70
71        let (tx, rx) = oneshot::channel();
72
73        // This is required because for whatever reason `glib::clone!` wouldn't let it
74        // be moved into the closure.
75        let tx = Rc::new(Cell::new(Some(tx)));
76
77        // This is to make it safe to access `src` inside of the connected closure to
78        // disconnect it after being called.
79        let src = Rc::new(Cell::new(None));
80
81        src.set(Some(imp.camera.connect_code_detected(clone!(
82            #[weak(rename_to = camera_page)]
83            self,
84            #[strong]
85            src,
86            #[strong]
87            tx,
88            move |_, code| {
89                match tx.take().unwrap().send(code) {
90                    Ok(()) => (),
91                    Err(_) => {
92                        tracing::error!(concat!(
93                            "CameraPage::scan_from_camera failed to send the resulting QR ",
94                            "code to the recipient because the recipient already received a ",
95                            "QR code or was dropped. This should never occur.",
96                        ));
97                    }
98                }
99                camera_page.imp().camera.disconnect(src.take().unwrap());
100            }
101        ))));
102
103        drop(tx);
104        drop(src);
105
106        imp.camera.scan_from_camera().await;
107
108        match rx.await {
109            Ok(code) => Ok(code),
110            Err(error) => {
111                tracing::error!(concat!(
112                    "CameraPage::scan_from_camera failed to receive the resulting QR code from ",
113                    "the sender because the sender was dropped without sending a QR code. This ",
114                    "should never occur."
115                ));
116                Err(error.into())
117            }
118        }
119    }
120
121    pub async fn scan_from_screenshot(&self) -> Result<String> {
122        let imp = self.imp();
123
124        let (tx, rx) = oneshot::channel();
125
126        // This is required because for whatever reason `glib::clone!` wouldn't let it
127        // be moved into the closure.
128        let tx = Rc::new(Cell::new(Some(tx)));
129
130        // This is to make it safe to access `src` inside of the connected closure to
131        // disconnect it after being called.
132        let src = Rc::new(Cell::new(None));
133
134        src.set(Some(imp.camera.connect_code_detected(clone!(
135            #[weak(rename_to = camera_page)]
136            self,
137            #[strong]
138            src,
139            #[strong]
140            tx,
141            move |_, code| {
142                match tx.take().unwrap().send(code) {
143                    Ok(()) => (),
144                    Err(_) => {
145                        tracing::error!(concat!(
146                            "CameraPage::scan_from_screenshot failed to send the resulting QR ",
147                            "code to the recipient because the recipient already received a ",
148                            "QR code or was dropped. This should never occur.",
149                        ));
150                    }
151                }
152                camera_page.imp().camera.disconnect(src.take().unwrap());
153            }
154        ))));
155
156        drop(tx);
157
158        select! {
159            biased;
160            result = rx => result.map_err(|error| {
161                tracing::error!(concat!(
162                    "CameraPage::scan_from_screenshot failed to receive the resulting QR code ",
163                    "from the sender because the sender was dropped without sending a QR ",
164                    "code. This should never occur.",
165                ));
166
167                error.into()
168            }),
169            result = async move {
170                imp.camera.scan_from_screenshot().await?;
171
172                // Give the GLib event loop a whole 2.5 seconds to dispatch the "code-detected"
173                // action before we assume that its not going to be dispatched at all.
174                spawn_tokio(async { sleep(Duration::from_millis(2500)).await; }).await;
175
176                // Disconnect the signal handler.
177                imp.camera.disconnect(src.take().unwrap());
178
179                anyhow::bail!(concat!(
180                    "CameraPage::scan_from_screenshot failed to receive the resulting QR code in ",
181                    "a reasonable amount of time."
182                ));
183            } => result,
184        }
185    }
186
187    #[template_callback]
188    fn on_camera_close(&self) {
189        self.actions().activate_action("close_page", None);
190    }
191}