fractal/components/camera/
qrcode_scanner.rs1use 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 #[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 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 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 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 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 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}