use ashpd::desktop::settings::{ColorScheme, Settings};
use calloop::channel::Channel;
use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
use smol::stream::StreamExt;
use crate::media_capture::{
CaptureBackend, CaptureConfig, CaptureDeviceInfo, CaptureDeviceKind, CaptureSession,
CaptureSessionState, DeviceEnumerator, FrameCallback,
};
use crate::{BackgroundExecutor, WindowAppearance};
use anyhow::{Result, anyhow};
use std::sync::atomic::{AtomicU64, Ordering};
pub enum Event {
WindowAppearance(WindowAppearance),
#[cfg_attr(feature = "x11", allow(dead_code))]
CursorTheme(String),
#[cfg_attr(feature = "x11", allow(dead_code))]
CursorSize(u32),
}
pub struct XDPEventSource {
channel: Channel<Event>,
}
impl XDPEventSource {
pub fn new(executor: &BackgroundExecutor) -> Self {
let (sender, channel) = calloop::channel::channel();
let background = executor.clone();
executor
.spawn(async move {
let settings = Settings::new().await?;
if let Ok(initial_appearance) = settings.color_scheme().await {
sender.send(Event::WindowAppearance(WindowAppearance::from_native(
initial_appearance,
)))?;
}
if let Ok(initial_theme) = settings
.read::<String>("org.gnome.desktop.interface", "cursor-theme")
.await
{
sender.send(Event::CursorTheme(initial_theme))?;
}
if let Ok(initial_size) = settings
.read::<i32>("org.gnome.desktop.interface", "cursor-size")
.await
{
sender.send(Event::CursorSize(initial_size as u32))?;
}
if let Ok(mut cursor_theme_changed) = settings
.receive_setting_changed_with_args(
"org.gnome.desktop.interface",
"cursor-theme",
)
.await
{
let sender = sender.clone();
background
.spawn(async move {
while let Some(theme) = cursor_theme_changed.next().await {
let theme = theme?;
sender.send(Event::CursorTheme(theme))?;
}
anyhow::Ok(())
})
.detach();
}
if let Ok(mut cursor_size_changed) = settings
.receive_setting_changed_with_args::<i32>(
"org.gnome.desktop.interface",
"cursor-size",
)
.await
{
let sender = sender.clone();
background
.spawn(async move {
while let Some(size) = cursor_size_changed.next().await {
let size = size?;
sender.send(Event::CursorSize(size as u32))?;
}
anyhow::Ok(())
})
.detach();
}
let mut appearance_changed = settings.receive_color_scheme_changed().await?;
while let Some(scheme) = appearance_changed.next().await {
sender.send(Event::WindowAppearance(WindowAppearance::from_native(
scheme,
)))?;
}
anyhow::Ok(())
})
.detach();
Self { channel }
}
}
impl EventSource for XDPEventSource {
type Event = Event;
type Metadata = ();
type Ret = ();
type Error = anyhow::Error;
fn process_events<F>(
&mut self,
readiness: Readiness,
token: Token,
mut callback: F,
) -> Result<PostAction, Self::Error>
where
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
{
self.channel.process_events(readiness, token, |evt, _| {
if let calloop::channel::Event::Msg(msg) = evt {
(callback)(msg, &mut ())
}
})?;
Ok(PostAction::Continue)
}
fn register(
&mut self,
poll: &mut Poll,
token_factory: &mut TokenFactory,
) -> calloop::Result<()> {
self.channel.register(poll, token_factory)?;
Ok(())
}
fn reregister(
&mut self,
poll: &mut Poll,
token_factory: &mut TokenFactory,
) -> calloop::Result<()> {
self.channel.reregister(poll, token_factory)?;
Ok(())
}
fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
self.channel.unregister(poll)?;
Ok(())
}
}
impl WindowAppearance {
fn from_native(cs: ColorScheme) -> WindowAppearance {
match cs {
ColorScheme::PreferDark => WindowAppearance::Dark,
ColorScheme::PreferLight => WindowAppearance::Light,
ColorScheme::NoPreference => WindowAppearance::Light,
}
}
#[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
fn set_native(&mut self, cs: ColorScheme) {
*self = Self::from_native(cs);
}
}
pub struct XdgDesktopPortalCaptureBackend;
impl XdgDesktopPortalCaptureBackend {
pub fn new() -> Self {
Self
}
}
impl DeviceEnumerator for XdgDesktopPortalCaptureBackend {
fn devices(&self, kind: CaptureDeviceKind) -> Result<Vec<CaptureDeviceInfo>> {
match kind {
CaptureDeviceKind::Screen => Ok(vec![CaptureDeviceInfo {
id: "portal-screen-0".to_string(),
name: "XDG Desktop Portal Screen".to_string(),
kind: CaptureDeviceKind::Screen,
is_available: true,
}]),
CaptureDeviceKind::Window => Ok(vec![]),
_ => Ok(vec![]),
}
}
}
impl CaptureBackend for XdgDesktopPortalCaptureBackend {
fn create_session(&self, config: &CaptureConfig) -> Result<Box<dyn CaptureSession>> {
match config.kind {
CaptureDeviceKind::Screen | CaptureDeviceKind::Window => Ok(Box::new(
XdgDesktopPortalCaptureSession::new(config.clone()),
)),
_ => Err(anyhow!(
"XdgDesktopPortalCaptureBackend does not support {:?}",
config.kind
)),
}
}
}
struct XdgDesktopPortalCaptureSession {
config: CaptureConfig,
state: CaptureSessionState,
dropped: AtomicU64,
latency_ms: AtomicU64,
callback: Option<FrameCallback>,
}
impl XdgDesktopPortalCaptureSession {
fn new(config: CaptureConfig) -> Self {
Self {
config,
state: CaptureSessionState::Idle,
dropped: AtomicU64::new(0),
latency_ms: AtomicU64::new(0),
callback: None,
}
}
}
impl CaptureSession for XdgDesktopPortalCaptureSession {
fn start(&mut self, config: CaptureConfig, callback: FrameCallback) -> Result<()> {
self.config = config;
self.state = CaptureSessionState::Starting;
self.callback = Some(callback);
Err(anyhow!(
"XDG Desktop Portal screen capture requires runtime initialization"
))
}
fn pause(&mut self) -> Result<()> {
self.state = CaptureSessionState::Paused;
Ok(())
}
fn resume(&mut self) -> Result<()> {
self.state = CaptureSessionState::Running;
Ok(())
}
fn stop(&mut self) -> Result<()> {
self.state = CaptureSessionState::Stopped;
self.callback = None;
Ok(())
}
fn state(&self) -> CaptureSessionState {
self.state
}
fn dropped_frame_count(&self) -> u64 {
self.dropped.load(Ordering::Relaxed)
}
fn latency_ms(&self) -> u64 {
self.latency_ms.load(Ordering::Relaxed)
}
}