Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## [Unreleased]

### Features

- **Full-screen break screen overlay** — adds an opt-in full-screen ambient break screen (screen shield) that covers the display during short and long breaks with a large countdown dial, relaxation prompt, and quick unlock/skip controls. Contributed by [@ArashZich](https://github.com/ArashZich).

## [v1.7.1] - 2026-05-11

### Bug Fixes
Expand Down
4 changes: 3 additions & 1 deletion src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main", "settings", "stats"],
"windows": ["main", "settings", "stats", "break"],
"permissions": [
"core:default",
"log:default",
"core:window:allow-close",
"core:window:allow-hide",
"core:window:allow-minimize",
"core:webview:allow-create-webview-window",
"core:window:allow-show",
Expand All @@ -17,6 +18,7 @@
"core:window:allow-is-maximized",
"core:window:allow-set-fullscreen",
"core:window:allow-is-fullscreen",
"core:window:allow-set-always-on-top",
"opener:default",
"dialog:default",
"dialog:allow-open",
Expand Down
15 changes: 14 additions & 1 deletion src-tauri/src/db/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ const MIGRATION_6: &str = "
INSERT INTO schema_version VALUES (6);
";

/// Seeds the `fullscreen_break_shield` setting for users upgrading from an older version.
/// Defaults to 'false' (opt-in break screen overlay).
const MIGRATION_7: &str = "
INSERT OR IGNORE INTO settings (key, value) VALUES ('fullscreen_break_shield', 'false');
INSERT INTO schema_version VALUES (7);
";

/// Apply any pending migrations. Each migration is wrapped in a transaction
/// so a partial failure leaves the database unchanged.
pub fn run(conn: &Connection) -> Result<()> {
Expand Down Expand Up @@ -131,6 +138,12 @@ pub fn run(conn: &Connection) -> Result<()> {
log::info!("[db/migrations] MIGRATION_6 complete");
}

if version < 7 {
log::info!("[db/migrations] applying MIGRATION_7: seed fullscreen_break_shield");
conn.execute_batch(&format!("BEGIN; {MIGRATION_7} COMMIT;"))?;
log::info!("[db/migrations] MIGRATION_7 complete");
}

Ok(())
}

Expand Down Expand Up @@ -166,7 +179,7 @@ mod tests {
let v: i64 = conn
.query_row("SELECT MAX(version) FROM schema_version", [], |r| r.get(0))
.unwrap();
assert_eq!(v, 6);
assert_eq!(v, 7);
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ pub fn run() {
let _ = win_for_close.hide();
} else {
// Main window is truly closing — close child windows if open.
for label in ["settings", "stats"] {
for label in ["settings", "stats", "break"] {
if let Some(win) = app_for_close.get_webview_window(label) {
let _ = win.close();
}
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/settings/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ pub const DEFAULTS: &[(&str, &str)] = &[
("local_shortcut_volume_up", "ArrowUp"),
("local_shortcut_mute", "m"),
("local_shortcut_fullscreen", "F11"),
("fullscreen_break_shield", "false"),
];
4 changes: 4 additions & 0 deletions src-tauri/src/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ pub struct Settings {
pub local_shortcut_volume_up: String,
pub local_shortcut_mute: String,
pub local_shortcut_fullscreen: String,
/// When true, a full-screen ambient break overlay covers the screen during breaks.
pub fullscreen_break_shield: bool,
/// Last known window X coordinate (physical pixels). `None` = use OS default.
pub window_x: Option<i32>,
/// Last known window Y coordinate (physical pixels). `None` = use OS default.
Expand Down Expand Up @@ -120,6 +122,7 @@ impl Default for Settings {
local_shortcut_volume_up: "ArrowUp".to_string(),
local_shortcut_mute: "m".to_string(),
local_shortcut_fullscreen: "F11".to_string(),
fullscreen_break_shield: false,
window_x: None,
window_y: None,
window_width: None,
Expand Down Expand Up @@ -245,6 +248,7 @@ pub fn load(conn: &Connection) -> Result<Settings> {
local_shortcut_volume_up: map.get("local_shortcut_volume_up").cloned().unwrap_or(d.local_shortcut_volume_up),
local_shortcut_mute: map.get("local_shortcut_mute").cloned().unwrap_or(d.local_shortcut_mute),
local_shortcut_fullscreen: map.get("local_shortcut_fullscreen").cloned().unwrap_or(d.local_shortcut_fullscreen),
fullscreen_break_shield: parse_bool(&map, "fullscreen_break_shield", d.fullscreen_break_shield),
window_x: parse_opt_i32(&map, "window_x"),
window_y: parse_opt_i32(&map, "window_y"),
window_width: parse_opt_u32(&map, "window_width"),
Expand Down
10 changes: 10 additions & 0 deletions src/lib/components/Timer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import type { UnlistenFn } from '@tauri-apps/api/event';
import * as m from '$paraglide/messages.js';
import { notificationShow } from '$lib/ipc';
import { openBreakShield, closeBreakShield } from '$lib/utils/breakShield';

interface Props {
isCompact?: boolean;
Expand Down Expand Up @@ -82,6 +83,14 @@
}),
await onRoundChange((snap) => {
timerState.set(snap);
if (snap.round_type === 'short-break' || snap.round_type === 'long-break') {
if ($settings.fullscreen_break_shield) {
openBreakShield();
}
} else {
closeBreakShield();
}

if ($settings.notifications_enabled) {
let title: string;
let body: string;
Expand All @@ -103,6 +112,7 @@
}),
await onTimerReset((snap) => {
timerState.set(snap);
closeBreakShield();
})
);
})();
Expand Down
6 changes: 6 additions & 0 deletions src/lib/components/settings/sections/TimerSection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@
checked={$settings.dial_countdown}
onclick={() => toggle('dial_countdown', $settings.dial_countdown)}
/>
<SettingsToggle
label={m.timer_toggle_break_shield()}
description={m.timer_toggle_break_shield_desc()}
checked={$settings.fullscreen_break_shield}
onclick={() => toggle('fullscreen_break_shield', $settings.fullscreen_break_shield)}
/>
</div>

<style>
Expand Down
1 change: 1 addition & 0 deletions src/lib/stores/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const defaults: Settings = {
local_shortcut_volume_up: 'ArrowUp',
local_shortcut_mute: 'm',
local_shortcut_fullscreen: 'F11',
fullscreen_break_shield: false,
};

export const settings = writable<Settings>(defaults);
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface Settings {
local_shortcut_volume_up: string;
local_shortcut_mute: string;
local_shortcut_fullscreen: string;
fullscreen_break_shield: boolean;
}

/** Returned by `check_update` — describes an available update. */
Expand Down
48 changes: 48 additions & 0 deletions src/lib/utils/breakShield.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Utility for managing the full-screen break screen overlay window ('break').

import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { info, error as logError } from '@tauri-apps/plugin-log';

/**
* Opens or restores the full-screen break overlay window.
* If the window already exists, it is focused and brought to the front.
*/
export async function openBreakShield(): Promise<void> {
try {
const existing = await WebviewWindow.getByLabel('break');
if (existing) {
await existing.show();
await existing.setFocus();
return;
}

new WebviewWindow('break', {
url: '/break',
title: 'Pomotroid — Break',
fullscreen: true,
alwaysOnTop: true,
decorations: false,
skipTaskbar: true,
focus: true,
visible: false, // will show after applying theme to avoid white flash
});
await info('[break-shield] created break window');
} catch (err) {
await logError(`[break-shield] failed to open break shield: ${err}`);
}
}

/**
* Closes the full-screen break overlay window if it is currently open.
*/
export async function closeBreakShield(): Promise<void> {
try {
const existing = await WebviewWindow.getByLabel('break');
if (existing) {
await existing.close();
await info('[break-shield] closed break window');
}
} catch (err) {
await logError(`[break-shield] failed to close break shield: ${err}`);
}
}
8 changes: 8 additions & 0 deletions src/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
"timer_toggle_long_breaks_desc": "Lange Pause am Ende jedes Zyklus überspringen.",
"timer_toggle_countdown": "Countdown-Zifferblatt",
"timer_toggle_countdown_desc": "Der Bogen beginnt voll und nimmt im Laufe der Zeit ab.",
"timer_toggle_break_shield": "Vollbild-Pausenbildschirm",
"timer_toggle_break_shield_desc": "Bedeckt den Bildschirm während der Pausen mit einem Timer.",
"break_shield_title_short": "Kurze Pause",
"break_shield_title_long": "Lange Pause",
"break_shield_subtitle": "Gönnen Sie Ihren Augen eine Pause und atmen Sie tief durch.",
"break_shield_btn_dismiss": "Entsperren",
"break_shield_btn_skip": "Pause überspringen",
"break_shield_hint_esc": "Esc drücken, um den Bildschirm zu entsperren",
"timer_reset": "Zurücksetzen",
"timer_reset_defaults": "Auf Standardwerte zurücksetzen",
"appearance_group_mode": "Modus",
Expand Down
8 changes: 8 additions & 0 deletions src/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
"timer_toggle_long_breaks_desc": "Skip the long break at the end of each cycle.",
"timer_toggle_countdown": "Countdown Dial",
"timer_toggle_countdown_desc": "Arc starts full and subtracts as time passes.",
"timer_toggle_break_shield": "Full-screen Break Screen",
"timer_toggle_break_shield_desc": "Cover the screen with an ambient timer during breaks.",
"break_shield_title_short": "Short Break",
"break_shield_title_long": "Long Break",
"break_shield_subtitle": "Rest your eyes, stretch, and take a deep breath.",
"break_shield_btn_dismiss": "Unlock",
"break_shield_btn_skip": "Skip Break",
"break_shield_hint_esc": "Press Esc to unlock screen",
"timer_reset": "Reset",
"timer_reset_defaults": "Reset to Defaults",
"appearance_group_mode": "Mode",
Expand Down
8 changes: 8 additions & 0 deletions src/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
"timer_toggle_long_breaks_desc": "Omitir el descanso largo al final de cada ciclo.",
"timer_toggle_countdown": "Dial de cuenta regresiva",
"timer_toggle_countdown_desc": "El arco comienza lleno y se reduce a medida que pasa el tiempo.",
"timer_toggle_break_shield": "Pantalla de descanso completa",
"timer_toggle_break_shield_desc": "Cubre la pantalla con un temporizador durante los descansos.",
"break_shield_title_short": "Descanso corto",
"break_shield_title_long": "Descanso largo",
"break_shield_subtitle": "Descansa la vista, estírate y respira hondo.",
"break_shield_btn_dismiss": "Desbloquear",
"break_shield_btn_skip": "Saltar descanso",
"break_shield_hint_esc": "Presiona Esc para desbloquear",
"timer_reset": "Restablecer",
"timer_reset_defaults": "Restablecer valores predeterminados",
"appearance_group_mode": "Modo",
Expand Down
8 changes: 8 additions & 0 deletions src/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
"timer_toggle_long_breaks_desc": "Ignorer la pause longue à la fin de chaque cycle.",
"timer_toggle_countdown": "Cadran à rebours",
"timer_toggle_countdown_desc": "L'arc commence plein et se réduit au fil du temps.",
"timer_toggle_break_shield": "Écran de pause plein écran",
"timer_toggle_break_shield_desc": "Couvre l'écran avec un minuteur pendant les pauses.",
"break_shield_title_short": "Courte pause",
"break_shield_title_long": "Longue pause",
"break_shield_subtitle": "Reposez vos yeux, étirez-vous et respirez profondément.",
"break_shield_btn_dismiss": "Déverrouiller",
"break_shield_btn_skip": "Passer la pause",
"break_shield_hint_esc": "Appuyez sur Échap pour déverrouiller",
"timer_reset": "Réinitialiser",
"timer_reset_defaults": "Rétablir les paramètres par défaut",
"appearance_group_mode": "Mode",
Expand Down
8 changes: 8 additions & 0 deletions src/messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
"timer_toggle_long_breaks_desc": "各サイクルの終わりに長い休憩をスキップします。",
"timer_toggle_countdown": "カウントダウンダイヤル",
"timer_toggle_countdown_desc": "アークが満タンから始まり、時間の経過とともに減少します。",
"timer_toggle_break_shield": "全画面休憩シールド",
"timer_toggle_break_shield_desc": "休憩中に全画面でタイマーを表示します。",
"break_shield_title_short": "短い休憩",
"break_shield_title_long": "長い休憩",
"break_shield_subtitle": "目を休め、ストレッチをして、深呼吸しましょう。",
"break_shield_btn_dismiss": "画面ロック解除",
"break_shield_btn_skip": "休憩をスキップ",
"break_shield_hint_esc": "Escキーで画面ロックを解除",
"timer_reset": "リセット",
"timer_reset_defaults": "デフォルトにリセット",
"appearance_group_mode": "モード",
Expand Down
8 changes: 8 additions & 0 deletions src/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
"timer_toggle_long_breaks_desc": "Pular a pausa longa ao final de cada ciclo.",
"timer_toggle_countdown": "Mostrador de Contagem Regressiva",
"timer_toggle_countdown_desc": "O arco começa cheio e diminui conforme o tempo passa.",
"timer_toggle_break_shield": "Tela Cheia de Pausa",
"timer_toggle_break_shield_desc": "Cobre a tela com um temporizador durante as pausas.",
"break_shield_title_short": "Pausa Curta",
"break_shield_title_long": "Pausa Longa",
"break_shield_subtitle": "Descanse os olhos, alongue-se e respire fundo.",
"break_shield_btn_dismiss": "Desbloquear",
"break_shield_btn_skip": "Pular Pausa",
"break_shield_hint_esc": "Pressione Esc para desbloquear",
"timer_reset": "Redefinir",
"timer_reset_defaults": "Redefinir para Padrões",
"appearance_group_mode": "Modo",
Expand Down
8 changes: 8 additions & 0 deletions src/messages/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
"timer_toggle_long_breaks_desc": "Her döngünün sonundaki uzun molayı atla.",
"timer_toggle_countdown": "Geri Sayım Kadranı",
"timer_toggle_countdown_desc": "Yay dolu başlar ve zaman geçtikçe azalır.",
"timer_toggle_break_shield": "Tam Ekran Mola Kalkanı",
"timer_toggle_break_shield_desc": "Molalar sırasında ekranı dinlendirici bir zamanlayıcıyla kaplayın.",
"break_shield_title_short": "Kısa Mola",
"break_shield_title_long": "Uzun Mola",
"break_shield_subtitle": "Gözlerinizi dinlendirin, esneyin ve derin bir nefes alın.",
"break_shield_btn_dismiss": "Kilidi Aç",
"break_shield_btn_skip": "Molayı Atla",
"break_shield_hint_esc": "Kilidi açmak için Esc tuşuna basın",
"timer_reset": "Sıfırla",
"timer_reset_defaults": "Varsayılanlara Sıfırla",
"appearance_group_mode": "Mod",
Expand Down
8 changes: 8 additions & 0 deletions src/messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
"timer_toggle_long_breaks_desc": "跳过每个周期结束时的长休息。",
"timer_toggle_countdown": "倒计时表盘",
"timer_toggle_countdown_desc": "表盘从满格开始,随时间推移减少。",
"timer_toggle_break_shield": "全屏休息护盾",
"timer_toggle_break_shield_desc": "休息期间以全屏计时器覆盖屏幕。",
"break_shield_title_short": "短暂休息",
"break_shield_title_long": "长时间休息",
"break_shield_subtitle": "放松双眼,舒展身体,深呼吸。",
"break_shield_btn_dismiss": "解锁屏幕",
"break_shield_btn_skip": "跳过休息",
"break_shield_hint_esc": "按 Esc 键解锁屏幕",
"timer_reset": "重置",
"timer_reset_defaults": "恢复默认设置",
"appearance_group_mode": "模式",
Expand Down
Loading