authenticator/widgets/backup/
camera_page.rs1use 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 let tx = Rc::new(Cell::new(Some(tx)));
76
77 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 let tx = Rc::new(Cell::new(Some(tx)));
129
130 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 spawn_tokio(async { sleep(Duration::from_millis(2500)).await; }).await;
175
176 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}