Skip to main content

fractal/components/camera/linux/
viewfinder.rs

1use 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        /// The child viewfinder.
40        child: aperture::Viewfinder,
41        /// The device provider for the viewfinder.
42        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        /// Initialize the viewfinder.
121        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        /// Update the current state.
165        fn update_state(&self) {
166            self.obj().set_state(self.child.state().into());
167        }
168
169        /// Set the current abort handle.
170        fn set_abort_handle(&self, abort_handle: Option<AbortHandle>) {
171            self.abort_handle.replace(abort_handle);
172        }
173    }
174}
175
176glib::wrapper! {
177    /// A camera viewfinder widget for Linux.
178    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}