cosmic-player/src/xdg_portals.rs
Josh Megnauth bb087578df
Prevent screen turning off during playback
Closes: #157

XDG portals expose a D-Bus interface allowing apps to prevent screen
idling, user switching, and other actions. That interface,
org.freedesktop.portal.Inhibit, does all of the heavy lifting here.

Idling = the screen dimming or shutting off.

Idling is inhibited when a video is actively playing.
Idling is NOT inhibited when videos aren't playing - this includes
paused or stopped videos.
2025-11-13 13:26:23 -05:00

61 lines
2.5 KiB
Rust

// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
//! Integrations with XDG portals.
use ashpd::{
desktop::inhibit::{InhibitFlags, InhibitProxy},
enumflags2::{BitFlags, make_bitflags},
};
use log::{debug, warn};
use tokio::sync::watch::Receiver;
// Actions to inhibit. Currently, COSMIC defaults to the GTK portal for Inhibit. That
// implementation only supports inhibiting idling and trying to inhibit anything else causes the
// D-Bus call to silently fail. We will only inhibit idling until COSMIC gets a bespoke Inhibit.
const INHIBIT_FLAGS: BitFlags<InhibitFlags> = make_bitflags!(InhibitFlags::{Idle});
/// Inhibit idle and user switching while media is played.
///
/// # Usage
/// Enable the inhibitor by setting the watcher to `true`. Disable the inhibitor by sending a
/// `false`. Sending multiple consecutive trues/falses is safe and guarded internally.
///
/// Portal:
/// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Inhibit.html
pub async fn inhibit(mut signal: Receiver<bool>) -> ashpd::Result<()> {
let proxy = InhibitProxy::new().await?;
// Mark the watcher's value as unseen so a temporary or mutable bool isn't needed in the loop.
signal.mark_changed();
loop {
if signal.wait_for(|&status| status).await.is_err() {
// The watcher will likely only be closed when the app is closed.
debug!("Inhibit task's watcher is closed");
break;
}
// Copying the bool is important or else we would needlessly hold the lock below.
let should_inhibit = *signal.borrow_and_update();
if should_inhibit
&& let Some(inhibit_handle) = proxy
.inhibit(None, INHIBIT_FLAGS, "")
.await
.inspect_err(|e| warn!("Failed to call inhibit portal endpoint: {e}"))
.ok()
{
// We don't have to check the bool because it's already checked to be false in the
// closure. We also don't have to break on error because the next iteration of the loop
// would break anyway.
let _ = signal.wait_for(|&status| !status).await;
if let Err(e) = inhibit_handle.close().await {
// This should only happen if the inhibit portal silently fails which GTK (and
// others!) apparently do.
warn!("Removing the inhibitor failed: {e}");
break;
}
}
}
Ok(())
}