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.
This commit is contained in:
parent
fa1637fe51
commit
bb087578df
10 changed files with 331 additions and 40 deletions
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{
|
||||
cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry},
|
||||
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry},
|
||||
theme,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ use std::str::FromStr;
|
|||
use std::sync::OnceLock;
|
||||
|
||||
use i18n_embed::{
|
||||
fluent::{fluent_language_loader, FluentLanguageLoader},
|
||||
DefaultLocalizer, LanguageLoader, Localizer,
|
||||
fluent::{FluentLanguageLoader, fluent_language_loader},
|
||||
};
|
||||
use icu_collator::{Collator, CollatorOptions, Numeric};
|
||||
use icu_provider::DataLocale;
|
||||
|
|
|
|||
58
src/main.rs
58
src/main.rs
|
|
@ -2,23 +2,25 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{
|
||||
app::{command, message, Command, Core, Settings},
|
||||
Application, ApplicationExt, Element,
|
||||
app::{Command, Core, Settings, command, message},
|
||||
cosmic_config::{self, CosmicConfigEntry},
|
||||
cosmic_theme, executor, font,
|
||||
iced::{
|
||||
Alignment, Background, Border, Color, ContentFit, Length, Limits,
|
||||
event::{self, Event},
|
||||
keyboard::{Event as KeyEvent, Key, Modifiers},
|
||||
mouse::{Event as MouseEvent, ScrollDelta},
|
||||
subscription::Subscription,
|
||||
window, Alignment, Background, Border, Color, ContentFit, Length, Limits,
|
||||
window,
|
||||
},
|
||||
iced_style, theme,
|
||||
widget::{self, menu::action::MenuAction, nav_bar, segmented_button, Slider},
|
||||
Application, ApplicationExt, Element,
|
||||
widget::{self, Slider, menu::action::MenuAction, nav_bar, segmented_button},
|
||||
};
|
||||
use iced_video_player::{
|
||||
Video, VideoPlayer,
|
||||
gst::{self, prelude::*},
|
||||
gst_pbutils, Video, VideoPlayer,
|
||||
gst_pbutils,
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
|
|
@ -32,8 +34,8 @@ use std::{
|
|||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
config::{Config, ConfigState, CONFIG_VERSION},
|
||||
key_bind::{key_binds, KeyBind},
|
||||
config::{CONFIG_VERSION, Config, ConfigState},
|
||||
key_bind::{KeyBind, key_binds},
|
||||
project::ProjectNode,
|
||||
};
|
||||
|
||||
|
|
@ -47,6 +49,8 @@ mod mpris;
|
|||
mod project;
|
||||
mod thumbnail;
|
||||
mod video;
|
||||
#[cfg(feature = "xdg-portal")]
|
||||
mod xdg_portals;
|
||||
|
||||
static CONTROLS_TIMEOUT: Duration = Duration::new(2, 0);
|
||||
|
||||
|
|
@ -141,6 +145,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
settings = settings.theme(config.app_theme.theme());
|
||||
settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
|
||||
|
||||
|
||||
let flags = Flags {
|
||||
config,
|
||||
config_state_handler,
|
||||
|
|
@ -313,6 +318,8 @@ pub struct App {
|
|||
current_audio: i32,
|
||||
text_codes: Vec<TextCode>,
|
||||
current_text: Option<i32>,
|
||||
#[cfg(feature = "xdg-portal")]
|
||||
inhibit: tokio::sync::watch::Sender<bool>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -339,6 +346,7 @@ impl App {
|
|||
self.current_text = None;
|
||||
self.update_mpris_meta();
|
||||
self.update_nav_bar_active();
|
||||
self.allow_idle();
|
||||
was_open
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +371,7 @@ impl App {
|
|||
|
||||
let video = match video::new_video(&url) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return err
|
||||
Err(err) => return err,
|
||||
};
|
||||
|
||||
self.duration = video.duration().as_secs_f64();
|
||||
|
|
@ -420,6 +428,7 @@ impl App {
|
|||
self.current_text = None;
|
||||
}
|
||||
|
||||
self.inhibit_idle();
|
||||
self.update_flags();
|
||||
self.update_mpris_meta();
|
||||
self.update_title()
|
||||
|
|
@ -792,6 +801,20 @@ impl App {
|
|||
let title = "COSMIC Media Player";
|
||||
self.set_window_title(title.to_string())
|
||||
}
|
||||
|
||||
/// Allow screen to dim or turn off if there is no input from the user.
|
||||
///
|
||||
/// Basically, undo [`Self::inhibit_idle`].
|
||||
fn allow_idle(&self) {
|
||||
#[cfg(feature = "xdg-portal")]
|
||||
let _ = self.inhibit.send(false);
|
||||
}
|
||||
|
||||
/// Prevent screen from dimming or turning off if there is no keyboard/mouse input.
|
||||
fn inhibit_idle(&self) {
|
||||
#[cfg(feature = "xdg-portal")]
|
||||
let _ = self.inhibit.send(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||
|
|
@ -820,6 +843,13 @@ impl Application for App {
|
|||
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||
core.window.content_container = false;
|
||||
|
||||
#[cfg(feature = "xdg-portal")]
|
||||
let inhibit = {
|
||||
let (tx, rx) = tokio::sync::watch::channel(false);
|
||||
std::mem::drop(tokio::spawn(crate::xdg_portals::inhibit(rx)));
|
||||
tx
|
||||
};
|
||||
|
||||
let mut app = App {
|
||||
core,
|
||||
flags,
|
||||
|
|
@ -843,6 +873,8 @@ impl Application for App {
|
|||
current_audio: -1,
|
||||
text_codes: Vec::new(),
|
||||
current_text: None,
|
||||
#[cfg(feature = "xdg-portal")]
|
||||
inhibit,
|
||||
};
|
||||
|
||||
// Do not show nav bar by default. Will be opened by open_project if needed
|
||||
|
|
@ -1176,12 +1208,18 @@ impl Application for App {
|
|||
self.dropdown_opt = None;
|
||||
|
||||
if let Some(video) = &mut self.video_opt {
|
||||
video.set_paused(match message {
|
||||
let pause = match message {
|
||||
Message::Play => false,
|
||||
Message::Pause => true,
|
||||
_ => !video.paused(),
|
||||
});
|
||||
};
|
||||
video.set_paused(pause);
|
||||
self.update_controls(true);
|
||||
if pause {
|
||||
self.allow_idle();
|
||||
} else {
|
||||
self.inhibit_idle();
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Scrolled(delta) => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{
|
||||
theme,
|
||||
widget::menu::{self, key_bind::KeyBind, ItemHeight, ItemWidth, MenuBar},
|
||||
Element,
|
||||
Element, theme,
|
||||
widget::menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind},
|
||||
};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use crate::{fl, Action, Config, ConfigState, Message};
|
||||
use crate::{Action, Config, ConfigState, Message, fl};
|
||||
|
||||
pub fn menu_bar<'a>(
|
||||
_config: &Config,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ use cosmic::iced::{
|
|||
subscription::{self, Subscription},
|
||||
};
|
||||
use mpris_server::{
|
||||
zbus::{fdo, Result},
|
||||
LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface,
|
||||
Server, Signal, Time, TrackId, Volume,
|
||||
zbus::{Result, fdo},
|
||||
};
|
||||
use std::{any::TypeId, future, process};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
|
||||
use crate::{Message, MprisEvent, MprisMeta, MprisState};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use cosmic::iced_core::image::Data;
|
||||
use iced_video_player::{Position};
|
||||
use iced_video_player::Position;
|
||||
use image::{DynamicImage, ImageFormat, RgbaImage};
|
||||
use std::{error::Error, num::NonZero, path::Path, time::Duration};
|
||||
use url::Url;
|
||||
|
|
@ -15,7 +15,7 @@ pub fn main(
|
|||
let thumbnails = {
|
||||
let mut video = match video::new_video(input) {
|
||||
Ok(ok) => ok,
|
||||
Err(_err) => return Err(Into::into(format!("missing required plugin")))
|
||||
Err(_err) => return Err(Into::into(format!("missing required plugin"))),
|
||||
};
|
||||
|
||||
let duration = video.duration();
|
||||
|
|
|
|||
15
src/video.rs
15
src/video.rs
|
|
@ -1,12 +1,14 @@
|
|||
|
||||
use iced_video_player::{
|
||||
Video,
|
||||
gst::{self, prelude::*},
|
||||
gst_app, gst_pbutils, Video,
|
||||
gst_app, gst_pbutils,
|
||||
};
|
||||
|
||||
use cosmic::app::{Command, message};
|
||||
|
||||
pub fn new_video(url: &url::Url) -> Result<Video, cosmic::Command<cosmic::app::Message<super::Message>>> {
|
||||
pub fn new_video(
|
||||
url: &url::Url,
|
||||
) -> Result<Video, cosmic::Command<cosmic::app::Message<super::Message>>> {
|
||||
//TODO: this code came from iced_video_player::Video::new and has been modified to stop the pipeline on error
|
||||
//TODO: remove unwraps and enable playback of files with only audio.
|
||||
gst::init().unwrap();
|
||||
|
|
@ -26,7 +28,10 @@ pub fn new_video(url: &url::Url) -> Result<Video, cosmic::Command<cosmic::app::M
|
|||
};
|
||||
if let Some(factory) = elem.factory() {
|
||||
if factory.name() == "souphttpsrc" {
|
||||
elem.set_property("user-agent", "Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0");
|
||||
elem.set_property(
|
||||
"user-agent",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0",
|
||||
);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
|
@ -71,4 +76,4 @@ pub fn new_video(url: &url::Url) -> Result<Video, cosmic::Command<cosmic::app::M
|
|||
Err(Command::batch(commands))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
src/xdg_portals.rs
Normal file
61
src/xdg_portals.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// 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(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue