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

// 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);