From c5e656084e53b985cfd273a869c1ee7857ca024e Mon Sep 17 00:00:00 2001 From: todoterza <273458311+todoterza@users.noreply.github.com> Date: Fri, 22 May 2026 10:50:54 +0300 Subject: [PATCH] feat: prevent sleep and keep screen on during timer (Windows) --- src-tauri/Cargo.toml | 4 ++ src-tauri/src/lib.rs | 1 + src-tauri/src/power.rs | 40 +++++++++++++++++++ src-tauri/src/settings/defaults.rs | 2 + src-tauri/src/settings/mod.rs | 6 +++ src-tauri/src/timer/mod.rs | 33 +++++++++++++++ .../settings/sections/SystemSection.svelte | 17 ++++++++ src/lib/stores/settings.ts | 2 + src/lib/types.ts | 2 + 9 files changed, 107 insertions(+) create mode 100644 src-tauri/src/power.rs diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 979f185b..9eb209f5 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -39,6 +39,10 @@ futures-util = "0.3" rodio = { version = "0.22", default-features = false, features = ["playback", "wav", "mp3", "vorbis"] } symphonia-codec-adpcm = "0.5.5" tiny-skia = "0.12" +[target.'cfg(target_os = "windows")'.dependencies] +windows = { version = "0.61", features = [ + "Win32_System_Power", +] } notify = "8" [dev-dependencies] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3f4596d7..7eda17fc 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ pub mod audio; +pub mod power; pub mod commands; pub mod db; pub mod notifications; diff --git a/src-tauri/src/power.rs b/src-tauri/src/power.rs new file mode 100644 index 00000000..be0d1af9 --- /dev/null +++ b/src-tauri/src/power.rs @@ -0,0 +1,40 @@ +//! Keep-awake / prevent-sleep for Windows. +//! Uses SetThreadExecutionState — a stable, documented Win32 API. +//! No-ops on non-Windows builds. + +#[cfg(target_os = "windows")] +mod win { + use windows::Win32::System::Power::{ + SetThreadExecutionState, ES_CONTINUOUS, ES_DISPLAY_REQUIRED, ES_SYSTEM_REQUIRED, + EXECUTION_STATE, + }; + + pub fn acquire(keep_screen: bool) { + let mut flags: EXECUTION_STATE = ES_CONTINUOUS | ES_SYSTEM_REQUIRED; + if keep_screen { + flags |= ES_DISPLAY_REQUIRED; + } + unsafe { + SetThreadExecutionState(flags); + } + log::debug!("[power] wakelock acquired keep_screen={keep_screen}"); + } + + pub fn release() { + unsafe { + SetThreadExecutionState(ES_CONTINUOUS); + } + log::debug!("[power] wakelock released"); + } +} + +#[allow(unused_variables)] +pub fn acquire_wakelock(keep_screen: bool) { + #[cfg(target_os = "windows")] + win::acquire(keep_screen); +} + +pub fn release_wakelock() { + #[cfg(target_os = "windows")] + win::release(); +} \ No newline at end of file diff --git a/src-tauri/src/settings/defaults.rs b/src-tauri/src/settings/defaults.rs index 2edea8ff..b9654851 100644 --- a/src-tauri/src/settings/defaults.rs +++ b/src-tauri/src/settings/defaults.rs @@ -20,6 +20,8 @@ pub const DEFAULTS: &[(&str, &str)] = &[ ("theme_dark", "Pomotroid"), ("tick_sounds_work", "false"), ("tick_sounds_break", "false"), + ("prevent_sleep", "false"), + ("keep_screen_on", "false"), ("time_work_secs", "1500"), ("time_short_break_secs", "300"), ("time_long_break_secs", "900"), diff --git a/src-tauri/src/settings/mod.rs b/src-tauri/src/settings/mod.rs index 268c76c1..80f31b29 100644 --- a/src-tauri/src/settings/mod.rs +++ b/src-tauri/src/settings/mod.rs @@ -65,6 +65,8 @@ pub struct Settings { pub window_width: Option, /// Last known window height (physical pixels). `None` = use OS default. pub window_height: Option, + pub prevent_sleep: bool, + pub keep_screen_on: bool, } impl Default for Settings { @@ -124,6 +126,8 @@ impl Default for Settings { window_y: None, window_width: None, window_height: None, + prevent_sleep: false, + keep_screen_on: false, } } } @@ -249,6 +253,8 @@ pub fn load(conn: &Connection) -> Result { window_y: parse_opt_i32(&map, "window_y"), window_width: parse_opt_u32(&map, "window_width"), window_height: parse_opt_u32(&map, "window_height"), + prevent_sleep: parse_bool(&map, "prevent_sleep", d.prevent_sleep), + keep_screen_on: parse_bool(&map, "keep_screen_on", d.keep_screen_on), }) } diff --git a/src-tauri/src/timer/mod.rs b/src-tauri/src/timer/mod.rs index c7d6c904..dd9e08a5 100644 --- a/src-tauri/src/timer/mod.rs +++ b/src-tauri/src/timer/mod.rs @@ -12,6 +12,7 @@ use crate::db::{queries, DbState}; use crate::settings::Settings; use crate::tray::{self, TrayState}; use crate::websocket::{self, WsState}; +use crate::power; use engine::{EngineHandle, TimerCommand, TimerEvent}; use sequence::{RoundType, SequenceState}; @@ -252,6 +253,13 @@ fn listen_events( websocket::broadcast_started(&ws, total_secs); } tray::update_menu_items(&tray, true, false); + + { + let s = settings.lock().unwrap(); + if s.prevent_sleep { + power::acquire_wakelock(s.keep_screen_on); + } + } } TimerEvent::Tick { elapsed_secs, total_secs } => { @@ -387,6 +395,13 @@ fn listen_events( RoundType::Work => auto_start_work, _ => auto_start_break, }; + // Release wakelock when idle (auto-start will re-acquire via Started event). + if !should_auto { + let s = settings.lock().unwrap(); + if s.prevent_sleep { + power::release_wakelock(); + } + } if should_auto { log::debug!("[timer] auto-starting {}", next_round.as_str()); engine.send(TimerCommand::Start); @@ -416,6 +431,12 @@ fn listen_events( let progress = if total > 0 { elapsed_secs as f32 / total as f32 } else { 0.0 }; tray::update_icon(&tray, &rt, true, progress); tray::update_menu_items(&tray, false, true); + { + let s = settings.lock().unwrap(); + if s.prevent_sleep { + power::release_wakelock(); + } + } } TimerEvent::Resumed { elapsed_secs } => { @@ -437,6 +458,12 @@ fn listen_events( tray::update_icon(&tray, &rt, false, progress); last_tray_progress = progress; tray::update_menu_items(&tray, true, false); + { + let s = settings.lock().unwrap(); + if s.prevent_sleep { + power::acquire_wakelock(s.keep_screen_on); + } + } } TimerEvent::Reset => { @@ -472,6 +499,12 @@ fn listen_events( tray::update_icon(&tray, &rt, false, 0.0); last_tray_progress = -1.0; tray::update_menu_items(&tray, false, false); + { + let s = settings.lock().unwrap(); + if s.prevent_sleep { + power::release_wakelock(); + } + } } TimerEvent::Suspended { elapsed_secs } => { diff --git a/src/lib/components/settings/sections/SystemSection.svelte b/src/lib/components/settings/sections/SystemSection.svelte index d417425a..45ff577d 100644 --- a/src/lib/components/settings/sections/SystemSection.svelte +++ b/src/lib/components/settings/sections/SystemSection.svelte @@ -220,6 +220,23 @@ {/if} {/if} +
Power
+ + toggle('prevent_sleep', $settings.prevent_sleep)} + /> + {#if $settings.prevent_sleep} + toggle('keep_screen_on', $settings.keep_screen_on)} + /> + {/if} +
{m.system_group_window()}
(defaults); diff --git a/src/lib/types.ts b/src/lib/types.ts index 88a82594..3c678b75 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -48,6 +48,8 @@ export interface Settings { language: string; // 'auto' | 'en' | 'es' | 'fr' | 'de' | 'ja' verbose_logging: boolean; check_for_updates: boolean; + prevent_sleep: boolean; + keep_screen_on: boolean; global_shortcuts_enabled: boolean; local_shortcut_toggle: string; local_shortcut_reset: string;