Skip to content
Draft
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
4 changes: 4 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod audio;
pub mod power;
pub mod commands;
pub mod db;
pub mod notifications;
Expand Down
40 changes: 40 additions & 0 deletions src-tauri/src/power.rs
Original file line number Diff line number Diff line change
@@ -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();
}
2 changes: 2 additions & 0 deletions src-tauri/src/settings/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
6 changes: 6 additions & 0 deletions src-tauri/src/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ pub struct Settings {
pub window_width: Option<u32>,
/// Last known window height (physical pixels). `None` = use OS default.
pub window_height: Option<u32>,
pub prevent_sleep: bool,
pub keep_screen_on: bool,
}

impl Default for Settings {
Expand Down Expand Up @@ -124,6 +126,8 @@ impl Default for Settings {
window_y: None,
window_width: None,
window_height: None,
prevent_sleep: false,
keep_screen_on: false,
}
}
}
Expand Down Expand Up @@ -249,6 +253,8 @@ pub fn load(conn: &Connection) -> Result<Settings> {
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),
})
}

Expand Down
33 changes: 33 additions & 0 deletions src-tauri/src/timer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 } => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 } => {
Expand All @@ -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 => {
Expand Down Expand Up @@ -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 } => {
Expand Down
17 changes: 17 additions & 0 deletions src/lib/components/settings/sections/SystemSection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,23 @@
{/if}
{/if}

<div class="group-heading">Power</div>

<SettingsToggle
label="Keep System Awake"
description="Prevent the PC from sleeping while a timer is running."
checked={$settings.prevent_sleep}
onclick={() => toggle('prevent_sleep', $settings.prevent_sleep)}
/>
{#if $settings.prevent_sleep}
<SettingsToggle
label="Keep Screen On"
description="Also prevent the display from turning off."
checked={$settings.keep_screen_on}
onclick={() => toggle('keep_screen_on', $settings.keep_screen_on)}
/>
{/if}

<div class="group-heading">{m.system_group_window()}</div>

<SettingsToggle
Expand Down
2 changes: 2 additions & 0 deletions src/lib/stores/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const defaults: Settings = {
local_shortcut_volume_up: 'ArrowUp',
local_shortcut_mute: 'm',
local_shortcut_fullscreen: 'F11',
prevent_sleep: false,
keep_screen_on: false,
};

export const settings = writable<Settings>(defaults);
2 changes: 2 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down