fractal/components/camera/linux/
viewfinder.rs1use ashpd::desktop::camera;
2use gtk::{
3 glib,
4 glib::{clone, subclass::prelude::*},
5 prelude::*,
6 subclass::prelude::*,
7};
8use matrix_sdk::encryption::verification::QrVerificationData;
9use tokio::task::AbortHandle;
10use tracing::{debug, error};
11
12use crate::{
13 components::camera::{
14 CameraViewfinder, CameraViewfinderExt, CameraViewfinderImpl, CameraViewfinderState,
15 },
16 spawn_tokio,
17};
18
19impl From<aperture::ViewfinderState> for CameraViewfinderState {
20 fn from(value: aperture::ViewfinderState) -> Self {
21 match value {
22 aperture::ViewfinderState::Loading => Self::Loading,
23 aperture::ViewfinderState::Ready => Self::Ready,
24 aperture::ViewfinderState::NoCameras => Self::NoCameras,
25 aperture::ViewfinderState::Error => Self::Error,
26 }
27 }
28}
29
30mod imp {
31 use std::cell::RefCell;
32
33 use matrix_sdk::encryption::verification::DecodingError;
34
35 use super::*;
36
37 #[derive(Debug)]
38 pub struct LinuxCameraViewfinder {
39 child: aperture::Viewfinder,
41 provider: aperture::DeviceProvider,
43 abort_handle: RefCell<Option<AbortHandle>>,
44 }
45
46 impl Default for LinuxCameraViewfinder {
47 fn default() -> Self {
48 Self {
49 child: Default::default(),
50 provider: aperture::DeviceProvider::instance().clone(),
51 abort_handle: Default::default(),
52 }
53 }
54 }
55
56 #[glib::object_subclass]
57 impl ObjectSubclass for LinuxCameraViewfinder {
58 const NAME: &'static str = "LinuxCameraViewfinder";
59 type Type = super::LinuxCameraViewfinder;
60 type ParentType = CameraViewfinder;
61
62 fn class_init(klass: &mut Self::Class) {
63 klass.set_layout_manager_type::<gtk::BinLayout>();
64 }
65 }
66
67 impl ObjectImpl for LinuxCameraViewfinder {
68 fn constructed(&self) {
69 self.parent_constructed();
70 let obj = self.obj();
71
72 self.child.set_parent(&*obj);
73 self.child.set_detect_codes(true);
74
75 self.child.connect_state_notify(glib::clone!(
76 #[weak(rename_to = imp)]
77 self,
78 move |_| {
79 imp.update_state();
80 }
81 ));
82 self.update_state();
83
84 self.child.connect_code_detected(clone!(
85 #[weak]
86 obj,
87 move |_, code| {
88 match QrVerificationData::from_bytes(&code) {
89 Ok(data) => obj.emit_qrcode_detected(data),
90 Err(error) => {
91 let code = String::from_utf8_lossy(&code);
92
93 if matches!(error, DecodingError::Header) {
94 debug!("Detected non-Matrix QR Code: {code}");
95 } else {
96 error!(
97 "Could not decode Matrix verification QR code {code}: {error}"
98 );
99 }
100 }
101 }
102 }
103 ));
104 }
105
106 fn dispose(&self) {
107 self.child.stop_stream();
108 self.child.unparent();
109
110 if let Some(abort_handle) = self.abort_handle.take() {
111 abort_handle.abort();
112 }
113 }
114 }
115
116 impl WidgetImpl for LinuxCameraViewfinder {}
117 impl CameraViewfinderImpl for LinuxCameraViewfinder {}
118
119 impl LinuxCameraViewfinder {
120 pub(super) async fn init(&self) -> Result<(), ()> {
122 if self.provider.started() {
123 return Ok(());
124 }
125
126 let handle = spawn_tokio!(camera::request());
127 self.set_abort_handle(Some(handle.abort_handle()));
128
129 let Ok(request_result) = handle.await else {
130 debug!("Camera request was aborted");
131 self.set_abort_handle(None);
132 return Err(());
133 };
134
135 self.set_abort_handle(None);
136
137 let fd = match request_result {
138 Ok(Some(fd)) => fd,
139 Ok(None) => {
140 error!("Could not access camera: no camera present");
141 return Err(());
142 }
143 Err(error) => {
144 error!("Could not access camera: {error}");
145 return Err(());
146 }
147 };
148
149 if let Err(error) = self.provider.set_fd(fd) {
150 error!("Could not access camera: {error}");
151 return Err(());
152 }
153
154 if let Err(error) = self.provider.start_with_default(|camera| {
155 matches!(camera.location(), aperture::CameraLocation::Back)
156 }) {
157 error!("Could not access camera: {error}");
158 return Err(());
159 }
160
161 Ok(())
162 }
163
164 fn update_state(&self) {
166 self.obj().set_state(self.child.state().into());
167 }
168
169 fn set_abort_handle(&self, abort_handle: Option<AbortHandle>) {
171 self.abort_handle.replace(abort_handle);
172 }
173 }
174}
175
176glib::wrapper! {
177 pub struct LinuxCameraViewfinder(ObjectSubclass<imp::LinuxCameraViewfinder>)
179 @extends gtk::Widget, CameraViewfinder,
180 @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
181}
182
183impl LinuxCameraViewfinder {
184 pub(super) async fn new() -> Option<Self> {
185 let obj = glib::Object::new::<Self>();
186
187 obj.imp().init().await.ok()?;
188
189 Some(obj)
190 }
191}