12 changed files with 413 additions and 166 deletions
@ -1,123 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
use std::time::Duration; |
||||
|
||||
use ashpd::desktop::camera; |
||||
use gtk::{glib, subclass::prelude::*}; |
||||
use once_cell::sync::Lazy; |
||||
use tracing::error; |
||||
|
||||
use super::camera_paintable::CameraPaintable; |
||||
use crate::{spawn_tokio, utils::timeout_future}; |
||||
|
||||
mod imp { |
||||
use super::*; |
||||
|
||||
#[derive(Debug, Default)] |
||||
pub struct Camera { |
||||
pub paintable: glib::WeakRef<CameraPaintable>, |
||||
} |
||||
|
||||
#[glib::object_subclass] |
||||
impl ObjectSubclass for Camera { |
||||
const NAME: &'static str = "Camera"; |
||||
type Type = super::Camera; |
||||
} |
||||
|
||||
impl ObjectImpl for Camera {} |
||||
} |
||||
|
||||
glib::wrapper! { |
||||
pub struct Camera(ObjectSubclass<imp::Camera>); |
||||
} |
||||
|
||||
impl Camera { |
||||
/// Create a new `Camera`.
|
||||
///
|
||||
/// Use `Camera::default()` to get a shared GObject.
|
||||
fn new() -> Self { |
||||
glib::Object::new() |
||||
} |
||||
|
||||
/// Ask the system whether cameras are available.
|
||||
pub async fn has_cameras(&self) -> bool { |
||||
let handle = spawn_tokio!(async move { |
||||
let camera = match camera::Camera::new().await { |
||||
Ok(camera) => camera, |
||||
Err(error) => { |
||||
error!("Failed to create instance of camera proxy: {error}"); |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
match camera.is_present().await { |
||||
Ok(is_present) => is_present, |
||||
Err(error) => { |
||||
error!("Failed to check whether system has cameras: {error}"); |
||||
false |
||||
} |
||||
} |
||||
}); |
||||
let abort_handle = handle.abort_handle(); |
||||
|
||||
match timeout_future(Duration::from_secs(1), handle).await { |
||||
Ok(is_present) => is_present.expect("The task should not have been aborted"), |
||||
Err(_) => { |
||||
abort_handle.abort(); |
||||
error!("Failed to check whether system has cameras: the request timed out"); |
||||
false |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Get the a `gdk::Paintable` displaying the content of a camera.
|
||||
///
|
||||
/// Panics if not called from the `MainContext` where GTK is running.
|
||||
pub async fn paintable(&self) -> Option<CameraPaintable> { |
||||
// We need to make sure that the Paintable is taken only from the MainContext
|
||||
assert!(glib::MainContext::default().is_owner()); |
||||
let imp = self.imp(); |
||||
|
||||
if let Some(paintable) = imp.paintable.upgrade() { |
||||
return Some(paintable); |
||||
} |
||||
|
||||
let handle = spawn_tokio!(async move { camera::request().await }); |
||||
let abort_handle = handle.abort_handle(); |
||||
|
||||
match timeout_future(Duration::from_secs(1), handle).await { |
||||
Ok(tokio_res) => match tokio_res.expect("The task should not have been aborted") { |
||||
Ok(Some((fd, streams))) => { |
||||
let paintable = CameraPaintable::new(fd, streams).await; |
||||
imp.paintable.set(Some(&paintable)); |
||||
|
||||
Some(paintable) |
||||
} |
||||
Ok(None) => { |
||||
error!("Failed to request access to cameras: the response is empty"); |
||||
None |
||||
} |
||||
Err(error) => { |
||||
error!("Failed to request access to cameras: {error}"); |
||||
None |
||||
} |
||||
}, |
||||
Err(_) => { |
||||
// Error because we reached the timeout.
|
||||
abort_handle.abort(); |
||||
error!("Failed to request access to cameras: the request timed out"); |
||||
None |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Default for Camera { |
||||
fn default() -> Self { |
||||
static CAMERA: Lazy<Camera> = Lazy::new(Camera::new); |
||||
|
||||
CAMERA.to_owned() |
||||
} |
||||
} |
||||
|
||||
unsafe impl Send for Camera {} |
||||
unsafe impl Sync for Camera {} |
||||
@ -0,0 +1,101 @@
|
||||
/// Subclassable camera paintable.
|
||||
use gtk::{gdk, glib, glib::closure_local, prelude::*, subclass::prelude::*}; |
||||
use matrix_sdk::encryption::verification::QrVerificationData; |
||||
|
||||
#[cfg(target_os = "linux")] |
||||
pub mod linux; |
||||
|
||||
use crate::contrib::qr_code_scanner::QrVerificationDataBoxed; |
||||
|
||||
pub enum Action { |
||||
QrCodeDetected(QrVerificationData), |
||||
} |
||||
|
||||
mod imp { |
||||
use glib::subclass::Signal; |
||||
use once_cell::sync::Lazy; |
||||
|
||||
use super::*; |
||||
|
||||
#[repr(C)] |
||||
pub struct CameraPaintableClass { |
||||
pub parent_class: glib::object::Class<adw::Bin>, |
||||
} |
||||
|
||||
unsafe impl ClassStruct for CameraPaintableClass { |
||||
type Type = CameraPaintable; |
||||
} |
||||
|
||||
#[derive(Debug, Default)] |
||||
pub struct CameraPaintable; |
||||
|
||||
#[glib::object_subclass] |
||||
impl ObjectSubclass for CameraPaintable { |
||||
const NAME: &'static str = "CameraPaintable"; |
||||
type Type = super::CameraPaintable; |
||||
type Class = CameraPaintableClass; |
||||
type Interfaces = (gdk::Paintable,); |
||||
} |
||||
|
||||
impl ObjectImpl for CameraPaintable { |
||||
fn signals() -> &'static [Signal] { |
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| { |
||||
vec![Signal::builder("code-detected") |
||||
.param_types([QrVerificationDataBoxed::static_type()]) |
||||
.run_first() |
||||
.build()] |
||||
}); |
||||
SIGNALS.as_ref() |
||||
} |
||||
} |
||||
|
||||
impl PaintableImpl for CameraPaintable { |
||||
fn snapshot(&self, _snapshot: &gdk::Snapshot, _width: f64, _height: f64) { |
||||
// Nothing to do
|
||||
} |
||||
} |
||||
} |
||||
|
||||
glib::wrapper! { |
||||
/// A subclassable paintable to display the output of a camera.
|
||||
pub struct CameraPaintable(ObjectSubclass<imp::CameraPaintable>) |
||||
@implements gdk::Paintable; |
||||
} |
||||
|
||||
pub trait CameraPaintableExt: 'static { |
||||
/// Connect to the signal emitted when a code is detected.
|
||||
fn connect_code_detected<F: Fn(&Self, QrVerificationData) + 'static>( |
||||
&self, |
||||
f: F, |
||||
) -> glib::SignalHandlerId; |
||||
} |
||||
|
||||
impl<O: IsA<CameraPaintable>> CameraPaintableExt for O { |
||||
fn connect_code_detected<F: Fn(&Self, QrVerificationData) + 'static>( |
||||
&self, |
||||
f: F, |
||||
) -> glib::SignalHandlerId { |
||||
self.connect_closure( |
||||
"activated", |
||||
true, |
||||
closure_local!(move |obj: Self, data: QrVerificationDataBoxed| { |
||||
f(&obj, data.0); |
||||
}), |
||||
) |
||||
} |
||||
} |
||||
|
||||
/// Public trait that must be implemented for everything that derives from
|
||||
/// `CameraPaintable`.
|
||||
///
|
||||
/// Overriding a method from this Trait overrides also its behavior in
|
||||
/// `CameraPaintableExt`.
|
||||
#[allow(async_fn_in_trait)] |
||||
pub trait CameraPaintableImpl: ObjectImpl + PaintableImpl {} |
||||
|
||||
unsafe impl<T> IsSubclassable<T> for CameraPaintable |
||||
where |
||||
T: CameraPaintableImpl, |
||||
T::Type: IsA<CameraPaintable>, |
||||
{ |
||||
} |
||||
@ -0,0 +1,119 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
use std::time::Duration; |
||||
|
||||
use ashpd::desktop::camera; |
||||
use gtk::{glib, prelude::*, subclass::prelude::*}; |
||||
use tracing::error; |
||||
|
||||
use super::{ |
||||
camera_paintable::{linux::LinuxCameraPaintable, CameraPaintable}, |
||||
Camera, CameraImpl, |
||||
}; |
||||
use crate::{spawn_tokio, utils::timeout_future}; |
||||
|
||||
mod imp { |
||||
use super::*; |
||||
|
||||
#[derive(Debug, Default)] |
||||
pub struct LinuxCamera { |
||||
pub paintable: glib::WeakRef<LinuxCameraPaintable>, |
||||
} |
||||
|
||||
#[glib::object_subclass] |
||||
impl ObjectSubclass for LinuxCamera { |
||||
const NAME: &'static str = "LinuxCamera"; |
||||
type Type = super::LinuxCamera; |
||||
type ParentType = Camera; |
||||
} |
||||
|
||||
impl ObjectImpl for LinuxCamera {} |
||||
|
||||
impl CameraImpl for LinuxCamera { |
||||
async fn has_cameras(&self) -> bool { |
||||
let handle = spawn_tokio!(async move { |
||||
let camera = match camera::Camera::new().await { |
||||
Ok(camera) => camera, |
||||
Err(error) => { |
||||
error!("Failed to create instance of camera proxy: {error}"); |
||||
return false; |
||||
} |
||||
}; |
||||
|
||||
match camera.is_present().await { |
||||
Ok(is_present) => is_present, |
||||
Err(error) => { |
||||
error!("Failed to check whether system has cameras: {error}"); |
||||
false |
||||
} |
||||
} |
||||
}); |
||||
let abort_handle = handle.abort_handle(); |
||||
|
||||
match timeout_future(Duration::from_secs(1), handle).await { |
||||
Ok(is_present) => is_present.expect("The task should not have been aborted"), |
||||
Err(_) => { |
||||
abort_handle.abort(); |
||||
error!("Failed to check whether system has cameras: the request timed out"); |
||||
false |
||||
} |
||||
} |
||||
} |
||||
|
||||
async fn paintable(&self) -> Option<CameraPaintable> { |
||||
// We need to make sure that the Paintable is taken only from the MainContext
|
||||
assert!(glib::MainContext::default().is_owner()); |
||||
|
||||
if let Some(paintable) = self.paintable.upgrade() { |
||||
return Some(paintable.upcast()); |
||||
} |
||||
|
||||
let handle = spawn_tokio!(async move { camera::request().await }); |
||||
let abort_handle = handle.abort_handle(); |
||||
|
||||
match timeout_future(Duration::from_secs(1), handle).await { |
||||
Ok(tokio_res) => match tokio_res.expect("The task should not have been aborted") { |
||||
Ok(Some((fd, streams))) => { |
||||
let paintable = LinuxCameraPaintable::new(fd, streams).await; |
||||
self.paintable.set(Some(&paintable)); |
||||
|
||||
Some(paintable.upcast()) |
||||
} |
||||
Ok(None) => { |
||||
error!("Failed to request access to cameras: the response is empty"); |
||||
None |
||||
} |
||||
Err(error) => { |
||||
error!("Failed to request access to cameras: {error}"); |
||||
None |
||||
} |
||||
}, |
||||
Err(_) => { |
||||
// Error because we reached the timeout.
|
||||
abort_handle.abort(); |
||||
error!("Failed to request access to cameras: the request timed out"); |
||||
None |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
glib::wrapper! { |
||||
pub struct LinuxCamera(ObjectSubclass<imp::LinuxCamera>) @extends Camera; |
||||
} |
||||
|
||||
impl LinuxCamera { |
||||
/// Create a new `LinuxCamera`.
|
||||
pub fn new() -> Self { |
||||
glib::Object::new() |
||||
} |
||||
} |
||||
|
||||
impl Default for LinuxCamera { |
||||
fn default() -> Self { |
||||
Self::new() |
||||
} |
||||
} |
||||
|
||||
unsafe impl Send for LinuxCamera {} |
||||
unsafe impl Sync for LinuxCamera {} |
||||
@ -0,0 +1,155 @@
|
||||
//! Camera API.
|
||||
|
||||
use futures_util::{future::LocalBoxFuture, FutureExt}; |
||||
use gtk::{glib, prelude::*, subclass::prelude::*}; |
||||
use once_cell::sync::Lazy; |
||||
use tracing::error; |
||||
|
||||
mod camera_paintable; |
||||
#[cfg(target_os = "linux")] |
||||
mod linux; |
||||
|
||||
pub use self::camera_paintable::Action; |
||||
use self::camera_paintable::CameraPaintable; |
||||
|
||||
mod imp { |
||||
|
||||
use super::*; |
||||
|
||||
#[repr(C)] |
||||
pub struct CameraClass { |
||||
pub parent_class: glib::object::Class<adw::Bin>, |
||||
pub has_cameras: fn(&super::Camera) -> LocalBoxFuture<bool>, |
||||
pub paintable: fn(&super::Camera) -> LocalBoxFuture<Option<CameraPaintable>>, |
||||
} |
||||
|
||||
unsafe impl ClassStruct for CameraClass { |
||||
type Type = Camera; |
||||
} |
||||
|
||||
pub(super) async fn camera_has_cameras(this: &super::Camera) -> bool { |
||||
let klass = this.class(); |
||||
(klass.as_ref().has_cameras)(this).await |
||||
} |
||||
|
||||
pub(super) async fn camera_paintable(this: &super::Camera) -> Option<CameraPaintable> { |
||||
let klass = this.class(); |
||||
(klass.as_ref().paintable)(this).await |
||||
} |
||||
|
||||
#[derive(Debug, Default)] |
||||
pub struct Camera; |
||||
|
||||
#[glib::object_subclass] |
||||
impl ObjectSubclass for Camera { |
||||
const NAME: &'static str = "Camera"; |
||||
type Type = super::Camera; |
||||
type Class = CameraClass; |
||||
} |
||||
|
||||
impl ObjectImpl for Camera {} |
||||
} |
||||
|
||||
glib::wrapper! { |
||||
/// Subclassable Camera API.
|
||||
///
|
||||
/// The default implementation, for unsupported platforms, makes sure the camera support is disabled.
|
||||
pub struct Camera(ObjectSubclass<imp::Camera>); |
||||
} |
||||
|
||||
impl Camera { |
||||
/// Create a new `Camera`.
|
||||
///
|
||||
/// Use `Camera::default()` to get a shared GObject.
|
||||
fn new() -> Self { |
||||
#[cfg(target_os = "linux")] |
||||
let obj = linux::LinuxCamera::new().upcast(); |
||||
|
||||
#[cfg(not(target_os = "linux"))] |
||||
let obj = glib::Object::new(); |
||||
|
||||
obj |
||||
} |
||||
} |
||||
|
||||
impl Default for Camera { |
||||
fn default() -> Self { |
||||
static CAMERA: Lazy<Camera> = Lazy::new(Camera::new); |
||||
|
||||
CAMERA.to_owned() |
||||
} |
||||
} |
||||
|
||||
unsafe impl Send for Camera {} |
||||
unsafe impl Sync for Camera {} |
||||
|
||||
pub trait CameraExt: 'static { |
||||
/// Whether any cameras are available.
|
||||
async fn has_cameras(&self) -> bool; |
||||
|
||||
/// The paintable displaying the camera.
|
||||
async fn paintable(&self) -> Option<CameraPaintable>; |
||||
} |
||||
|
||||
impl<O: IsA<Camera>> CameraExt for O { |
||||
async fn has_cameras(&self) -> bool { |
||||
imp::camera_has_cameras(self.upcast_ref()).await |
||||
} |
||||
|
||||
async fn paintable(&self) -> Option<CameraPaintable> { |
||||
imp::camera_paintable(self.upcast_ref()).await |
||||
} |
||||
} |
||||
|
||||
/// Public trait that must be implemented for everything that derives from
|
||||
/// `Camera`.
|
||||
///
|
||||
/// Overriding a method from this Trait overrides also its behavior in
|
||||
/// `CameraExt`.
|
||||
#[allow(async_fn_in_trait)] |
||||
pub trait CameraImpl: ObjectImpl { |
||||
/// Whether any cameras are available.
|
||||
async fn has_cameras(&self) -> bool { |
||||
false |
||||
} |
||||
|
||||
/// The paintable displaying the camera.
|
||||
async fn paintable(&self) -> Option<CameraPaintable> { |
||||
error!("The camera API is not supported on this platform"); |
||||
None |
||||
} |
||||
} |
||||
|
||||
unsafe impl<T> IsSubclassable<T> for Camera |
||||
where |
||||
T: CameraImpl, |
||||
T::Type: IsA<Camera>, |
||||
{ |
||||
fn class_init(class: &mut glib::Class<Self>) { |
||||
Self::parent_class_init::<T>(class.upcast_ref_mut()); |
||||
|
||||
let klass = class.as_mut(); |
||||
|
||||
klass.has_cameras = has_cameras_trampoline::<T>; |
||||
klass.paintable = paintable_trampoline::<T>; |
||||
} |
||||
} |
||||
|
||||
// Virtual method implementation trampolines.
|
||||
fn has_cameras_trampoline<T>(this: &Camera) -> LocalBoxFuture<bool> |
||||
where |
||||
T: ObjectSubclass + CameraImpl, |
||||
T::Type: IsA<Camera>, |
||||
{ |
||||
let this = this.downcast_ref::<T::Type>().unwrap(); |
||||
this.imp().has_cameras().boxed_local() |
||||
} |
||||
|
||||
fn paintable_trampoline<T>(this: &Camera) -> LocalBoxFuture<Option<CameraPaintable>> |
||||
where |
||||
T: ObjectSubclass + CameraImpl, |
||||
T::Type: IsA<Camera>, |
||||
{ |
||||
let this = this.downcast_ref::<T::Type>().unwrap(); |
||||
this.imp().paintable().boxed_local() |
||||
} |
||||
Loading…
Reference in new issue