Skip to content

原创声明:本文为个人从零打造 Linux 桌面番茄时钟应用的完整开发记录。转载请注明出处。

从零打造一个 Linux 桌面番茄时钟应用

作为一个效率工具爱好者,我一直想要一个简洁的番茄时钟应用。市面上的番茄钟要么功能过于复杂,要么不支持 Linux。于是决定自己动手,用 Python 从零打造一个。

本文记录了完整的开发过程,包括遇到的各种坑和解决方案。


项目背景

番茄工作法是一种时间管理方法:每工作 25 分钟,休息 5 分钟,循环进行。核心需求很简单:

  • 25 分钟倒计时,可视化显示
  • 完成后声音 + 语音提醒
  • 记录每天的番茄数
  • 系统托盘运行,不打扰工作

技术选型

需求技术方案
GUI 界面tkinter(Python 内置,轻量)
系统托盘pystray + Pillow
声音播放pygame.mixer
语音合成科大讯飞 TTS WebSocket API
单实例控制Socket 端口监听

整个应用仅依赖 pystrayPillowpygame 三个外部库,其余全部使用 Python 标准库。


开发过程中的坑

坑一:Linux 托盘菜单不弹出

使用 pystray 创建系统托盘图标后,发现点击图标时菜单完全不弹出。

原因分析:Linux 桌面环境(如 Cinnamon、GNOME)使用 AppIndicator 后端,右键菜单行为与 Windows/macOS 不同,点击事件可能无法触发。

解决方案:放弃依赖托盘菜单,改为创建独立的控制窗口:

python
# 创建一个小的控制窗口,提供所有操作按钮
self._control_window = tk.Tk()
self._control_window.title("番茄时钟")
self._control_window.geometry("300x220")

# Start、Stop、Stats、Logs、Exit 按钮
tk.Button(btn_frame, text="开始", command=self._start_tomato)
tk.Button(btn_frame, text="停止", command=self._stop_tomato)

托盘图标仅用于显示倒计时状态,控制窗口提供完整功能。


坑二:中文语音播报变成英文

最初使用 espeak 播报中文:

python
subprocess.run(["espeak", "-v", "zh", "休息时间到了"])

结果听到的是一串英文乱读,中文支持极差。

解决方案:调用已有的科大讯飞 TTS 配置:

python
def play_tts(self, text: str):
    xunfei_tts = "/home/lei/.local/bin/xunfei-tts"
    if os.path.exists(xunfei_tts):
        subprocess.run([xunfei_tts, text])

科大讯飞的中文语音自然流畅,效果完全不同。


坑三:窗口按钮显示不全

控制窗口最初设置为 220x200,退出按钮只显示一半。

解决方案:调整为 300x220,三个按钮完整显示:

python
tk.Button(bottom_frame, text="统计", width=8)
tk.Button(bottom_frame, text="日志", width=8)
tk.Button(bottom_frame, text="退出", width=8)

坑四:托盘图标方形不够美观

默认生成的图标是方形,视觉上比较生硬。

解决方案:用 Pillow 绘制圆角图标:

python
def _create_icon_image(self, text: str, color: str):
    size = 64
    radius = 12  # 圆角半径

    # 创建圆角蒙版
    mask = Image.new('L', (size, size), 0)
    mask_draw = ImageDraw.Draw(mask)
    mask_draw.rounded_rectangle([(0, 0), (size-1, size-1)],
                                radius=radius, fill=255)

    # 应用蒙版
    image = Image.new('RGB', (size, size), color=color)
    result = Image.new('RGB', (size, size), (0, 0, 0))
    result.paste(image, mask=mask)
    return result

坑五:应用可以多开

双击桌面图标可以启动多个实例,导致托盘出现多个番茄图标。

错误方案:使用文件锁。问题是窗口关闭后锁文件仍存在,无法重新启动。

正确方案:Socket 端口监听,实现「单实例 + 窗口唤醒」:

python
SOCKET_PORT = 19527

def check_single_instance():
    # 尝试连接已有实例
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect(('127.0.0.1', SOCKET_PORT))
        sock.sendall(b'SHOW_WINDOW')  # 发送唤醒命令
        return False  # 已有实例,退出
    except socket.error:
        return True  # 没有已有实例,正常启动

def start_instance_listener(tray):
    # 监听其他实例的唤醒请求
    def listener():
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind(('127.0.0.1', SOCKET_PORT))
        sock.listen(1)
        while True:
            conn, addr = sock.accept()
            data = conn.recv(1024)
            if data == b'SHOW_WINDOW':
                tray.show_window()  # 弹出窗口
            conn.close()
    threading.Thread(target=listener, daemon=True).start()

效果:关闭窗口后,再次双击图标会唤醒已运行实例的窗口,而不是报错或启动新实例。


坑六:pystray 标题不支持中文

python
self.icon = pystray.Icon("tomato", image, "番茄时钟")  # 报错!

报错:UnicodeEncodeError: 'latin-1' codec can't encode...

解决方案:托盘标题用英文,窗口界面用中文:

python
self.icon = pystray.Icon("tomato", image, "Tomato Timer")  # OK
self._control_window.title("番茄时钟")  # OK

最终效果

  • 控制窗口:显示倒计时、开始/停止/统计/日志/退出按钮
  • 系统托盘:圆角图标,动态显示剩余分钟数
  • 语音提醒:完成时播报「番茄钟完成,休息时间到了」
  • 单实例:无法多开,窗口关闭后可唤醒

桌面快捷方式:红色圆角番茄图标,双击启动。


项目结构

tomato-timer/
├── main.py          # 主入口、单实例管理
├── timer.py         # 计时逻辑(25分钟倒计时)
├── logger.py        # 日志记录(JSON格式)
├── alert.py         # 提醒(声音 + 科大讯飞语音)
├── tray.py          # 托盘图标 + 控制窗口
├── sounds/
│   └── alarm.mp3    # 提醒音效
├── tomato.png       # 桌面图标
└── venv/            # Python 虚拟环境

安装与运行

bash
# 创建虚拟环境
python3 -m venv venv
source venv/bin/activate

# 安装依赖
pip install pystray pillow pygame

# 运行
python main.py

总结

这次开发让我深刻体会到 Linux 桌面应用开发的一些特殊挑战:

  1. AppIndicator 与传统托盘行为不同,不能简单移植 Windows 的经验
  2. 中文支持是Linux弱项,espeak 中文效果很差,科大讯飞是更好选择
  3. 单实例需要考虑唤醒场景,文件锁不够,Socket 更灵活
  4. 虚拟环境必不可少,避免与系统包管理冲突

最终成品虽然简单,但功能完整、体验流畅。有时候,自己动手打造工具,比使用现成的复杂软件更高效。


源码地址:~/tomato-timer/

Released under the MIT License.