# BreakReminder **Repository Path**: mfar/break-reminder ## Basic Information - **Project Name**: BreakReminder - **Description**: 专注电脑工作的休息提醒神器 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-03 - **Last Updated**: 2026-07-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 休息提醒 (BreakReminder) 一个运行于 Windows 11 的本地常驻程序,按时间规律弹窗提醒正在工作的用户休息。 以系统托盘程序形态运行,提供图形化配置窗口,支持开机自启。 ## 功能特性 - **可配置的时间参数**:工作开始/结束时间、单次工作时长、单次休息时长、午休开始时间与时长 - **5 类弹窗**:工作开始、休息提醒、休息结束、午休开始、午休结束,文案与显示时长均可自定义 - **智能首弹**:正常开机显示 5 分钟;迟到开机(开机时间晚于工作开始时间)只显示 10 秒 - **今日休息**:一键暂停当天所有提醒,次日自动恢复 - **午休插入**:到午休时间自动暂停工作/休息循环,午休结束后恢复 - **跨天自恢复**:每天工作结束后进入待机,次日工作开始时间自动重启循环 - **配置即时生效**:修改设置保存后立即重算排程,无需重启 - **系统托盘**:自定义图标,托盘菜单可打开设置、今日休息、切换开机自启、退出 - **开机自启**:通过注册表实现,可在托盘菜单切换 - **现代化 UI**:卡片式弹窗(图标 + 倒计时进度条)、卡片式配置界面(可滚动 + 多行文案编辑) ## 弹窗循环逻辑 ``` A(工作开始) → [工作时长] → B(休息提醒) → [休息时长] → C(休息结束) → [工作时长] → B → ... ↑ | └──────────────────────────────────────────────┘ 午休插入:到达午休开始时间 → D(午休开始) → [午休时长] → E(午休结束) → 恢复工作循环 ``` | 弹窗 | 触发时机 | 按钮 | 默认显示时长 | |---|---|---|---| | A 工作开始 | 工作开始时间 | 立即开始 / 今日休息 | 5 分钟(迟到 10 秒) | | B 休息提醒 | session_start + 工作时长 | 知道了 | 5 分钟 | | C 休息结束 | 休息开始 + 休息时长 | 知道了 | 5 分钟 | | D 午休开始 | 午休开始时间 | 知道了 | 5 分钟 | | E 午休结束 | 午休开始 + 午休时长 | 知道了 | 5 分钟 | **说明**:弹窗显示时长不影响排程,排程基于固定时间点(如 `session_start + 工作时长`)。 点击按钮或超时只决定弹窗何时消失,不改变下一次弹窗的预定时间。 ## 界面预览 ### 弹窗界面 - 卡片式布局,白色卡片浮于浅灰背景 - 每种弹窗带有对应 emoji 图标(☀ ☕ ▶ 🍽 🌞) - 倒计时进度条 + 剩余秒数文字 - 现代扁平按钮,hover 变色效果 - A 弹窗:蓝色「立即开始」+ 红色「今日休息」 - B/C/D/E 弹窗:蓝色「知道了」 ### 配置界面 - 顶部白色标题栏,左侧「⚙ 设置」,右侧蓝色「✓ 保存」按钮 - 时间设置卡片(⏰)+ 弹窗文案卡片(🔔),各带 emoji 图标 - 弹窗文案使用多行文本框,支持换行输入 - 输入框获焦时高亮(蓝色边框) - 支持鼠标滚轮滑动,窗口可调整大小 ## 环境要求 - Windows 11 - Python 3.10+(需包含 tkinter,官方安装包默认含) - 依赖:`pystray`、`pillow` ## 安装与运行 ```powershell # 1. 安装依赖 pip install -r requirements.txt # 2. 运行 python main.py ``` 运行后系统托盘会出现自定义图标,右键即可操作。 ## 使用说明 ### 托盘菜单 - **打开设置**:打开配置窗口(双击托盘图标亦可) - **今日休息**:立即暂停当天所有提醒 - **开机自启**:切换是否随系统启动 - **退出**:退出程序 ### 配置项 通过「打开设置」窗口图形化编辑,或直接编辑 `config.json`: ```json { "work_start": "09:00", "work_end": "18:00", "work_duration_min": 50, "break_duration_min": 10, "lunch_start": "12:00", "lunch_duration_min": 60, "popups": { "work_start": { "text": "...", "display_sec": 300, "late_display_sec": 10 }, "break_reminder": { "text": "...", "display_sec": 300 }, "break_end": { "text": "...", "display_sec": 300 }, "lunch_start": { "text": "...", "display_sec": 300 }, "lunch_end": { "text": "...", "display_sec": 300 } }, "auto_start": false } ``` | 字段 | 说明 | |---|---| | `work_start` / `work_end` | 工作开始/结束时间,格式 `HH:MM` | | `work_duration_min` | 单次连续工作时长(分钟),到时弹 B | | `break_duration_min` | 单次休息时长(分钟),到时弹 C | | `lunch_start` | 午休开始时间,到时弹 D | | `lunch_duration_min` | 午休时长(分钟),到时弹 E | | `popups.*.text` | 各弹窗的文案,支持多行文本 | | `popups.*.display_sec` | 各弹窗的显示时长(秒),到时自动消失 | | `popups.work_start.late_display_sec` | 迟到开机时 A 弹窗的显示时长(秒) | ## 项目结构 ``` BreakReminder/ ├── main.py # 入口:启动 tkinter 主循环 + 托盘子线程 + 调度器 ├── config.py # 配置加载/保存/默认值 ├── scheduler.py # 状态机 + tick() 轮询调度 ├── popups.py # 5 类弹窗(卡片式 UI + 倒计时进度条) ├── tray.py # 系统托盘图标(外部 icon.png)+ 菜单 ├── settings_gui.py # 设置窗口(卡片式 + 可滚动 + 多行文案) ├── autostart.py # 开机自启注册表操作 ├── icon.png # 托盘图标文件 ├── config.json # 用户配置(首次运行自动生成) ├── requirements.txt # 依赖 ├── test_scheduler.py # 状态机逻辑测试 ├── test_popups.py # 弹窗模块测试 ├── build_simple.bat # 打包脚本 └── BreakReminder.spec # PyInstaller 配置 ``` ## 架构设计 ### 线程模型 ``` ┌──────────────────────────────────────────┐ │ 主线程(tkinter) │ │ - root.withdraw() 隐藏主窗口 │ │ - root.after(1000, tick) 每秒检查状态机 │ │ - 创建 Toplevel 弹窗(A/B/C/D/E) │ │ - 创建设置窗口 │ │ - 通过 queue 接收托盘线程的命令 │ └──────────────────────────────────────────┘ ┌──────────────────────────────────────────┐ │ 托盘线程(pystray) │ │ - 显示托盘图标 + 菜单 │ │ - 菜单事件 → 放入 queue → 主线程处理 │ └──────────────────────────────────────────┘ ``` **为何不用 APScheduler / 独立线程调度?** tkinter 不允许在非主线程创建/操作 GUI。若调度器在子线程,弹窗需通过队列通知主线程, 复杂度增加。直接用 `root.after(1000, tick)` 在主线程轮询状态机,每秒检查一次是否到达 某个弹窗时间点,精度 1 秒足够,且天然避免了跨线程 GUI 问题。 ### 状态机 ``` IDLE 未到工作时间或已过工作时间 WAIT_A A 弹窗显示中(等待关闭) WORKING 工作中 WAIT_B B 弹窗显示中 BREAK 休息中 WAIT_C C 弹窗显示中 LUNCH_WAIT_D D 弹窗显示中 LUNCH 午休中 LUNCH_WAIT_E E 弹窗显示中 TODAY_OFF 今日休息 ``` 关键决策: 1. **弹窗显示时长不影响排程**:排程基于固定时间点(`session_start + duration`)。 2. **迟到判定**:程序启动当天且启动时间晚于 `work_start` 才算迟到,A 弹窗显示 10 秒。 3. **午休优先级**:在 WORKING/BREAK 中先判 `work_end`,再判 `lunch_start`,最后判当前周期的 break/end。 4. **跨天重置**:检测到日期变化时关闭残留弹窗、回到 IDLE、清空周期内时间点,避免昨天的排程延续到今天。 5. **异常保护**:`tick()` 先注册下一轮再执行业务逻辑,并用 try/except 包裹,确保任何异常都不会中断调度链。 6. **配置即时生效**:`reload_config()` 绕过日期守卫,强制重算所有时间点派生值(work_start、work_end、lunch_start、lunch_end),同时更新 next_break / next_break_end。 ## 测试 ```powershell # 状态机逻辑测试(含正常循环、迟到、今日休息、午休插入、跨天重置、配置重载等) python test_scheduler.py # 弹窗模块测试(超时取消、回调仅一次、安全关闭) python test_popups.py ``` ## 打包为可执行文件 使用 PyInstaller 打包成单个 Windows 可执行文件,方便分发和部署。 ### 安装打包工具 ```powershell pip install pyinstaller ``` ### 执行打包 ```powershell # 方式一:直接命令 pyinstaller --noconsole --onefile --name "BreakReminder" --clean main.py # 方式二:使用打包脚本 .\build_simple.bat ``` **参数说明**: - `--noconsole`:隐藏控制台窗口(GUI 程序) - `--onefile`:打包成单个 exe 文件 - `--name`:可执行文件名称 - `--clean`:清理临时文件 ### 打包结果 打包完成后,可执行文件位于 `dist\BreakReminder.exe`。 **使用方式**: 1. 将 `dist\BreakReminder.exe` 和 `dist\icon.png` 复制到同一目录 2. 双击运行,首次运行会在当前目录自动生成 `config.json` 3. 系统托盘会出现自定义图标,右键可打开设置 4. 如需开机自启,通过托盘菜单切换 **注意事项**: - 打包后的程序仍依赖 Windows 11 环境 - `icon.png` 必须与 exe 在同一目录,否则回退为默认蓝色圆形图标 - 配置文件在 exe 所在目录生成,移动程序需一并移动 `config.json` 和 `icon.png` - 如需修改默认配置,首次运行后编辑生成的 `config.json`,或通过设置窗口修改 - 如需自定义图标,替换 `icon.png` 文件即可,无需重新打包 ## 常见问题 **Q: 程序启动后没看到弹窗?** A: 若当前时间在工作时段外(早于 work_start 或晚于 work_end),程序处于 IDLE 状态, 会在下一个工作开始时间触发。可在设置中调整时间,或临时把 work_start 改为当前时间测试。 **Q: 点了「今日休息」后如何恢复?** A: 当天无法恢复(设计如此);次日工作开始时间会自动清除「今日休息」状态。 如需当天恢复,重启程序即可。 **Q: 修改了配置不生效?** A: 通过设置窗口修改会即时生效(时间点、时长、文案等全部立即重算); 手动编辑 `config.json` 需通过托盘菜单重新打开设置或重启程序加载。 **Q: 开机自启没生效?** A: 注册表写入 `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`,值为 `pythonw.exe main.py 的绝对路径`。若移动了项目目录,需先关闭再重新开启自启。 **Q: 托盘图标显示为默认蓝色圆形?** A: `icon.png` 未与 exe 放在同一目录。将图标文件复制到 exe 所在目录即可。 **Q: 打包后配置无法保存?** A: 配置文件保存在 exe 所在目录的 `config.json`,确保 exe 有该目录的写入权限。 不要放在 `C:\Program Files` 等受保护目录。