You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
6.0 KiB
201 lines
6.0 KiB
// SPDX-License-Identifier: GPL-3.0-or-later |
|
use crate::spawn; |
|
use ashpd::{desktop::camera, zbus}; |
|
use glib::clone; |
|
use glib::subclass; |
|
use gtk::glib; |
|
use gtk::prelude::*; |
|
use gtk::subclass::prelude::*; |
|
use matrix_sdk::encryption::verification::QrVerificationData; |
|
use std::os::unix::prelude::RawFd; |
|
|
|
mod camera_paintable; |
|
mod qr_code_detector; |
|
pub mod screenshot; |
|
|
|
use camera_paintable::CameraPaintable; |
|
|
|
mod imp { |
|
use adw::subclass::prelude::*; |
|
use gtk::CompositeTemplate; |
|
use once_cell::sync::Lazy; |
|
use std::cell::Cell; |
|
use tokio::sync::OnceCell; |
|
|
|
use super::*; |
|
|
|
#[derive(Debug, CompositeTemplate, Default)] |
|
#[template(resource = "/org/gnome/FractalNext/qr-code-scanner.ui")] |
|
pub struct QrCodeScanner { |
|
pub paintable: CameraPaintable, |
|
#[template_child] |
|
pub picture: TemplateChild<gtk::Picture>, |
|
pub connection: OnceCell<zbus::Connection>, |
|
pub has_camera: Cell<bool>, |
|
} |
|
|
|
#[glib::object_subclass] |
|
impl ObjectSubclass for QrCodeScanner { |
|
const NAME: &'static str = "QrCodeScanner"; |
|
type Type = super::QrCodeScanner; |
|
type ParentType = adw::Bin; |
|
|
|
fn class_init(klass: &mut Self::Class) { |
|
Self::bind_template(klass); |
|
} |
|
|
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) { |
|
obj.init_template(); |
|
} |
|
} |
|
impl ObjectImpl for QrCodeScanner { |
|
fn properties() -> &'static [glib::ParamSpec] { |
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| { |
|
vec![glib::ParamSpecBoolean::new( |
|
"has-camera", |
|
"Has Camera", |
|
"Whether we have a working camera", |
|
false, |
|
glib::ParamFlags::READABLE, |
|
)] |
|
}); |
|
|
|
PROPERTIES.as_ref() |
|
} |
|
|
|
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { |
|
match pspec.name() { |
|
"has-camera" => obj.has_camera().to_value(), |
|
_ => unimplemented!(), |
|
} |
|
} |
|
|
|
fn constructed(&self, obj: &Self::Type) { |
|
self.picture.set_paintable(Some(&self.paintable)); |
|
|
|
let callback = glib::clone!(@weak obj => @default-return None, move |args: &[glib::Value]| { |
|
let code = args.get(1).unwrap().get::<QrVerificationDataBoxed>().unwrap(); |
|
obj.emit_by_name::<()>("code-detected", &[&code]); |
|
|
|
None |
|
}); |
|
self.paintable |
|
.connect_local("code-detected", false, callback); |
|
obj.init_has_camera(); |
|
} |
|
|
|
fn signals() -> &'static [subclass::Signal] { |
|
static SIGNALS: Lazy<Vec<subclass::Signal>> = Lazy::new(|| { |
|
vec![subclass::Signal::builder( |
|
"code-detected", |
|
&[QrVerificationDataBoxed::static_type().into()], |
|
glib::Type::UNIT.into(), |
|
) |
|
.flags(glib::SignalFlags::RUN_FIRST) |
|
.build()] |
|
}); |
|
SIGNALS.as_ref() |
|
} |
|
} |
|
impl WidgetImpl for QrCodeScanner { |
|
fn unmap(&self, widget: &Self::Type) { |
|
self.parent_unmap(widget); |
|
widget.stop(); |
|
} |
|
} |
|
impl BinImpl for QrCodeScanner {} |
|
} |
|
|
|
glib::wrapper! { |
|
pub struct QrCodeScanner(ObjectSubclass<imp::QrCodeScanner>) @extends gtk::Widget, adw::Bin; |
|
} |
|
|
|
impl QrCodeScanner { |
|
#[allow(clippy::new_without_default)] |
|
pub fn new() -> Self { |
|
glib::Object::new(&[]).expect("Failed to create a QrCodeScanner") |
|
} |
|
|
|
async fn connection(&self) -> Result<&zbus::Connection, ashpd::Error> { |
|
let priv_ = imp::QrCodeScanner::from_instance(self); |
|
|
|
Ok(priv_ |
|
.connection |
|
.get_or_try_init(zbus::Connection::session) |
|
.await?) |
|
} |
|
|
|
pub fn stop(&self) { |
|
let self_ = imp::QrCodeScanner::from_instance(self); |
|
|
|
self_.paintable.close_pipeline(); |
|
} |
|
|
|
pub async fn start(&self) -> bool { |
|
let priv_ = imp::QrCodeScanner::from_instance(self); |
|
if let Ok(stream_fd) = self.stream().await { |
|
if let Ok(node_id) = camera::pipewire_node_id(stream_fd).await { |
|
priv_.paintable.set_pipewire_fd(stream_fd, node_id); |
|
self.set_has_camera(true); |
|
return true; |
|
} |
|
} |
|
|
|
self.set_has_camera(false); |
|
false |
|
} |
|
|
|
async fn has_camera_internal(&self) -> Result<bool, ashpd::Error> { |
|
let proxy = camera::CameraProxy::new(self.connection().await?).await?; |
|
|
|
proxy.is_camera_present().await |
|
} |
|
|
|
async fn stream(&self) -> Result<RawFd, ashpd::Error> { |
|
let proxy = camera::CameraProxy::new(self.connection().await?).await?; |
|
|
|
proxy.access_camera().await?; |
|
proxy.open_pipe_wire_remote().await |
|
} |
|
|
|
fn init_has_camera(&self) { |
|
spawn!(clone!(@weak self as obj => async move { |
|
obj.set_has_camera(obj.has_camera_internal().await.unwrap_or_default()); |
|
})); |
|
} |
|
|
|
pub fn has_camera(&self) -> bool { |
|
let priv_ = imp::QrCodeScanner::from_instance(self); |
|
priv_.has_camera.get() |
|
} |
|
|
|
fn set_has_camera(&self, has_camera: bool) { |
|
let priv_ = imp::QrCodeScanner::from_instance(self); |
|
|
|
if has_camera == self.has_camera() { |
|
return; |
|
} |
|
|
|
priv_.has_camera.set(has_camera); |
|
self.notify("has-camera"); |
|
} |
|
|
|
/// Connects the prepared signals to the function f given in input |
|
pub fn connect_code_detected<F: Fn(&Self, QrVerificationData) + 'static>( |
|
&self, |
|
f: F, |
|
) -> glib::SignalHandlerId { |
|
self.connect_local("code-detected", true, move |values| { |
|
let obj = values[0].get::<Self>().unwrap(); |
|
let data = values[1].get::<QrVerificationDataBoxed>().unwrap(); |
|
|
|
f(&obj, data.0); |
|
|
|
None |
|
}) |
|
} |
|
} |
|
|
|
#[derive(Clone, Debug, PartialEq, glib::Boxed)] |
|
#[boxed_type(name = "QrVerificationDataBoxed")] |
|
struct QrVerificationDataBoxed(QrVerificationData);
|
|
|