Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ default-members = ["crates/*"]

[workspace.package]
edition = "2024"
version = "0.16.0"
version = "0.16.1"
license = "MIT"

[profile.release]
Expand Down
8 changes: 4 additions & 4 deletions crates/flex-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ anyhow = "1.0.104"
chrono = { version = "0.4.45", default-features = false, features = ["clock"] }
eframe = { version = "0.36", default-features = false, features = ["glow", "default_fonts"] }
env_logger = "0.11.11"
flex-core = { version = "0.16.0", path = "../flex-core" }
flex-plugin = { version = "0.16.0", path = "../flex-plugin" }
flex-web = { version = "0.16.0", path = "../flex-web" }
flex-core = { version = "0.16.1", path = "../flex-core" }
flex-plugin = { version = "0.16.1", path = "../flex-plugin" }
flex-web = { version = "0.16.1", path = "../flex-web" }
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp"] }
libloading = "0.9"
log = "0.4.34"
Expand All @@ -21,7 +21,7 @@ ureq = "3"
winit = { version = "0.30", default-features = false }

[target."cfg(windows)".dependencies]
windows-sys = { version = "0.61.2", features = ["Win32_UI_WindowsAndMessaging", "Win32_UI_Input_KeyboardAndMouse", "Win32_Foundation", "Win32_Storage_FileSystem", "Win32_UI_Input", "Win32_System_Diagnostics_Debug", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Com", "Win32_System_Registry", "Win32_System_Threading"] }
windows-sys = { version = "0.61.2", features = ["Win32_UI_WindowsAndMessaging", "Win32_UI_Input_KeyboardAndMouse", "Win32_Foundation", "Win32_Storage_FileSystem", "Win32_UI_Input", "Win32_System_Diagnostics_Debug", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Com", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Power"] }

[target."cfg(target_os = \"linux\")".dependencies]
# Remote buttons egui doesn't report (media keys), read from /dev/input.
Expand Down
107 changes: 107 additions & 0 deletions crates/flex-app/src/awake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! Keeps the computer from sleeping while something plays. mpv would do this for its own
//! window, but Flex draws mpv's picture itself, so it has to ask for itself.

/// What playback needs kept on.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Need {
/// A video: the screen and the computer.
Screen,
/// Music: only the computer; the screen may go off.
System,
}

/// Holds the request while playback needs it. Dropping it lets the computer sleep again.
#[derive(Default)]
pub struct KeepAwake {
held: Option<(Need, imp::Hold)>,
failed: bool,
}

impl KeepAwake {
/// Asks for `need` (or lets go with `None`); only does anything when it changes.
pub fn set(&mut self, need: Option<Need>) {
if self.held.as_ref().map(|(n, _)| *n) == need {
return;
}
// Let go of the old one first: on Windows they're the same switch.
self.held = None;
let Some(need) = need else { return };
match imp::Hold::new(need) {
Ok(hold) => self.held = Some((need, hold)),
// Once is enough in the log; this runs at every play and pause.
Err(e) if !self.failed => {
self.failed = true;
log::warn!("couldn't keep the computer awake while playing: {e}");
}
Err(_) => {}
}
}
}

#[cfg(windows)]
mod imp {
use super::Need;
use windows_sys::Win32::System::Power::{ES_CONTINUOUS, ES_DISPLAY_REQUIRED, ES_SYSTEM_REQUIRED, SetThreadExecutionState};

/// Windows' own setting for media players. It belongs to the thread that set it, which
/// is always Flex's UI thread.
pub struct Hold;

impl Hold {
pub fn new(need: Need) -> Result<Self, String> {
let display = if need == Need::Screen { ES_DISPLAY_REQUIRED } else { 0 };
let previous = unsafe { SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | display) };
if previous == 0 { Err("Windows refused".into()) } else { Ok(Hold) }
}
}

impl Drop for Hold {
fn drop(&mut self) {
unsafe { SetThreadExecutionState(ES_CONTINUOUS) };
}
}
}

#[cfg(unix)]
mod imp {
use super::Need;
use std::process::{Child, Command, Stdio};

/// A helper that holds the request for as long as it runs: macOS's `caffeinate`, or
/// systemd's `systemd-inhibit` elsewhere.
pub struct Hold(Child);

impl Hold {
pub fn new(need: Need) -> Result<Self, String> {
let mut cmd = command(need);
// If Flex dies without stopping it, the helper goes too: `caffeinate -w` watches
// Flex, and `cat` ends when Flex's end of its stdin closes.
cmd.stdin(Stdio::piped()).stdout(Stdio::null()).stderr(Stdio::null());
let name = cmd.get_program().to_string_lossy().into_owned();
cmd.spawn().map(Hold).map_err(|e| format!("{name}: {e}"))
}
}

#[cfg(target_os = "macos")]
fn command(need: Need) -> Command {
let mut cmd = Command::new("caffeinate");
cmd.arg(if need == Need::Screen { "-di" } else { "-i" }).args(["-w", &std::process::id().to_string()]);
cmd
}

#[cfg(not(target_os = "macos"))]
fn command(need: Need) -> Command {
// "idle" also stops logind blanking the screen; music only needs "sleep".
let what = if need == Need::Screen { "idle:sleep" } else { "sleep" };
let mut cmd = Command::new("systemd-inhibit");
cmd.args([&format!("--what={what}"), "--who=Flex", "--why=Playing", "--mode=block", "cat"]);
cmd
}

impl Drop for Hold {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
}
1 change: 1 addition & 0 deletions crates/flex-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

mod ambient;
mod app;
mod awake;
#[cfg(windows)]
mod autostart;
mod images;
Expand Down
10 changes: 9 additions & 1 deletion crates/flex-app/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ pub struct Shared {
playback_sent: Option<Playback>,
/// The latest playback seen (with its library file), for where a stop happened.
playback_last: Option<(Playback, Option<(i64, i64)>)>,
/// Stops the computer sleeping while something plays.
awake: crate::awake::KeepAwake,
pub youtube: crate::youtube::YouTube,
/// Bumped when something is removed from Continue Watching, so Home refreshes.
pub continue_version: u64,
Expand Down Expand Up @@ -390,6 +392,7 @@ impl Shared {
ambient,
playback_sent: None,
playback_last: None,
awake: Default::default(),
youtube: crate::youtube::YouTube::new(ctx),
continue_version: 0,
sync: None,
Expand Down Expand Up @@ -1828,7 +1831,8 @@ impl Shared {
}
}

/// Handles plugin messages, tells plugins about playback and updates lights.
/// Handles plugin messages, tells plugins about playback, keeps the computer awake
/// while something plays and updates lights.
/// `playing`: what the top screen is playing, if anything, and the library file.
pub fn poll_plugins(&mut self, playing: Option<(Playback, Option<(i64, i64)>)>) {
if let Some(web) = &self.web {
Expand All @@ -1842,6 +1846,10 @@ impl Shared {
let youtube = self.youtube.tools.is_some() && self.youtube_enabled();
web.set_status(flex_web::remote::Status { playing: now_playing, youtube });
}
let need = playing.as_ref().filter(|(p, _)| p.state == PlaybackState::Playing).map(|(p, _)| {
if p.kind == "video" { crate::awake::Need::Screen } else { crate::awake::Need::System }
});
self.awake.set(need);
let mut wait = self.plugins.poll(&self.db);
let plugin_toasts = self.plugins.take_toasts();
self.toasts.extend(plugin_toasts);
Expand Down
2 changes: 1 addition & 1 deletion crates/flex-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ publish = false
[dependencies]
anyhow = "1.0.104"
directories = "6.0.0"
flex-plugin = { version = "0.16.0", path = "../flex-plugin" }
flex-plugin = { version = "0.16.1", path = "../flex-plugin" }
hunch = "2"
lofty = "0.25.4"
log = "0.4.34"
Expand Down
2 changes: 1 addition & 1 deletion crates/flex-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ publish = false
[dependencies]
anyhow = "1.0.104"
axum = "0.8.9"
flex-core = { version = "0.16.0", path = "../flex-core" }
flex-core = { version = "0.16.1", path = "../flex-core" }
log = "0.4.34"
serde = { version = "1.0.229", features = ["derive", "rc"] }
serde_json = "1.0.151"
Expand Down
2 changes: 1 addition & 1 deletion plugins/anilist/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ license.workspace = true
publish = false

[dependencies]
flex-plugin = { version = "0.16.0", path = "../../crates/flex-plugin" }
flex-plugin = { version = "0.16.1", path = "../../crates/flex-plugin" }
ureq = { version = "3", features = ["json"] }
2 changes: 1 addition & 1 deletion plugins/anilist/plugin.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "AniList"
description = "Updates your AniList progress as you watch anime in Flex"
version = "0.16.0"
version = "0.16.1"
command = "flex-anilist"
subscribe = ["playback"]

Expand Down
2 changes: 1 addition & 1 deletion plugins/lotus-lamp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ license.workspace = true
publish = false

[dependencies]
flex-plugin = { version = "0.16.0", path = "../../crates/flex-plugin" }
flex-plugin = { version = "0.16.1", path = "../../crates/flex-plugin" }
lotus-lamp = { git = "https://github.com/bombpersons/lotus-lamp-rs", rev = "59cf5ccaa0007f006f1812fd02cc5991aff11e72" }
tokio = { version = "1.53.2", features = ["rt", "time", "sync", "macros"] }
2 changes: 1 addition & 1 deletion plugins/lotus-lamp/plugin.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "LED strip (Lotus Lamp)"
description = "Lights a Bluetooth LED strip behind the TV with the colours on screen"
version = "0.16.0"
version = "0.16.1"
command = "flex-lotus-lamp"
subscribe = ["light"]

Expand Down
2 changes: 1 addition & 1 deletion plugins/trakt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ license.workspace = true
publish = false

[dependencies]
flex-plugin = { version = "0.16.0", path = "../../crates/flex-plugin" }
flex-plugin = { version = "0.16.1", path = "../../crates/flex-plugin" }
ureq = { version = "3", features = ["json"] }
2 changes: 1 addition & 1 deletion plugins/trakt/plugin.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Trakt"
description = "Keeps your Trakt history up to date with what you watch in Flex"
version = "0.16.0"
version = "0.16.1"
command = "flex-trakt"
subscribe = ["playback"]

Expand Down
Loading