Python桌面端应用消息提醒功能开发实践

最近这两天,在优化「TodoList」应用的消息提醒功能。PS:TodoList应用如果有同学感兴趣,可以查看这篇文章进行安装:为了不付费,我硬生生用AI开发了一个跨平台待办应用

桌面端应用消息提醒实现起来真是大有学问,看着简单的弹窗消息点击和消息驻留,开发起来挺费劲的。

*说明:消息的图标还没弄好,所以相对看着比较模糊~

前言提要

之前的桌面消息提醒是直接采用 python 调用指令的方式,让 powershell 运行脚本来实现弹窗,具体可以看这期的文章:才知道python还可以这样发消息提醒的

不过这种方式可定制性太差了,单纯使用 powershell 很难实现:

  1. 消息可以驻留在通知中,
  2. 消息点击后可以跳转到应用
  3. 支持跨平台
  4. 消息含应用图标

所以,最终还是要采用第三方组件来实现上述功能。

AI 辅助

我先尝试 AI 辅助,操作下来,感觉 AI 的差异还是蛮大的。

首先是 AI 智能体实现,试了多个应用,几轮对话下来,都达不到预期。

咨询 AI 相关组件,结果发现 AI 间的回答有偏差,甚至可以说是误导。

问了四个 AI 应用,发现推荐的第三方依赖包的功能描述都不太准确。比如:

  • 豆包推荐 notify-py,但是这个依赖包做不了点击跳转功能;
  • Deepseek 推荐 desktop-notifier 最终采用其提供的代码没有效果;
  • 千问说 desktop-notifier 消息点击是完全 "伪回调" ,而其他的 AI 应用诸如 Deepseekgemini 是反驳的......

最终根据 Deepseekgemini 的推荐,我采用的是 desktop-notifier

代码核心实现

这里我以 gemini 的参考代码进行介绍。

初始化

这里需要初始化 asyncio 环境,同时启动两个线程,一个维护 asyncio 环境,一个维护消息队列。

python 复制代码
def __init__(self):
	self.running = True
	self.notification_queue = Queue()
	self.loop = asyncio.new_event_loop()

	# 1. 先启动一个纯粹运行事件循环的后台线程
	self.loop_thread = threading.Thread(target=self._run_event_loop, daemon=True)
	self.loop_thread.start()

	# 2. 启动你的队列监听线程
	self.process_thread = threading.Thread(target=self._process_notifications, daemon=True)
	self.process_thread.start()

asyncio 线程维护

这里根据 gemini 的建议:通过专门的 loop_thread 维持一个活跃的 loop 是最稳健的做法。

python 复制代码
def _run_event_loop(self):
	"""专门负责运行 asyncio 循环的线程"""
	asyncio.set_event_loop(self.loop)
	self.loop.run_forever()

消息处理和发送

这里是消息发送,省略了消息回调,在下个章节会专门介绍。

python 复制代码
def _process_notifications(self):
	"""处理队列,将任务提交给已经运行的 loop"""
	while self.running:
		try:
			# 获取队列中的数据(同步阻塞)
			notification = self.notification_queue.get(timeout=1)

			# 关键:将协程任务提交给正在运行的 self.loop
			asyncio.run_coroutine_threadsafe(
				self._show_notification(notification),
				self.loop
			)
		except Exception: # 队列为空超时
			continue

async def _show_notification(self, data):
	"""真正的异步发送逻辑"""
	# 这里调用 desktop_notifier.send(...)
	print(f"正在发送通知: {data}")

消息点击回调

这个专门起一个章节是因为这个功能是最影响组件选型的,也是费了我最多时间的。

这是我最终消息发送的实现:

python 复制代码
from desktop_notifier.common import Button
# 尝试使用desktop-notifier显示通知
await self.notifier.send(
    title=title,
    message=message,
    urgency=Urgency.Critical if priority == 'high' else Urgency.Normal,
    on_clicked=lambda: on_click(),
    timeout=0  # 0表示通知常驻
)

on_click() 方法的回调效果是在应用隐藏时,唤醒应用,因为我使用的是 pywebview 框架,所以用的是 show等函数,具体代码实现是这样的:

python 复制代码
def on_click():
    print("用户点击了通知,准备唤醒窗口...")
    # 关键判断:确保窗口实例存在且未被销毁
    if backend.globals.window:
        try:
            # 1. 从最小化恢复
            backend.globals.window.restore
            # 2. 提到最前显示(如果之前是 hide 状态)
            backend.globals.window.show
            # 3. 某些平台下可能需要额外聚焦
            backend.globals.window.focus
            # Windows部分系统需要强制处理下
            hwnd = win32gui.FindWindow(None, "TodoList")
            if hwnd:
                # 恢复窗口(如果最小化)
                win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
                # 强制推至前台
                win32gui.SetForegroundWindow(hwnd)
        except Exception as e:
            print(f"唤醒失败,窗口可能已关闭: {e}")
    else:
        print("错误:找不到窗口实例!")

不过很遗憾,调试了一下午,最终我实现的仅仅是弹窗消息点击能触发回调,而通知中心的依然触发不了。

根据 AI 的分析,是 Python 进程与 Windows 通知系统的 RPC 链接(远程过程调用) 在某种环境下断开了。

尝试过注册 AUMID、创建开始菜单快捷方式、使用 URL Protocol 等方式改进,结果都不奏效......

补充:组件测验效果

组件名称 组件大小 组件说明
desktop_notifier 30KB 基本能达到预期效果,除了通知中心的消息点击依然触发不了回调
plyer 5MB 体积较大,且 Deepseek 说无法点击消息跳转
win10toast-click 39.5KB Windows 专用,无法跨平台,而且版本过老,实测有报错,需要改源码
notify-py 0.3 MB 很轻量化,实测支持消息驻留,但是无法实现点击跳转
py-notifier 0.4 MB 实测过的给忘了特性啦,千问说不支持消息驻留和点击跳转
windows-toasts 36KB win10toast-click的 2.0 版本吧,是后面问 gemini 才知道有这个版本的,不过也是通知中心点击跳转不生效

好了,以上就是今天分享的内容,感谢阅读,我是唐叔,欢迎三连。

相关推荐
IT_陈寒32 分钟前
SpringBoot自动配置的坑,我的API突然就404了
前端·人工智能·后端
ServBay1 小时前
为什么说 MCP 是 2026 年开发者必须掌握的黄金协议?
后端·mcp
程序员夏洛1 小时前
Spring Boot 多模块项目中 IDEA 提示 Cannot resolve symbol 的一次排查记录
后端
子兮曰1 小时前
OpenMontage 深度解剖:你的 AI 编程助手,其实是个视频工作室
前端·后端·ai编程
子兮曰2 小时前
前端工具链的「Rust 化」:一场没有赢家的军备竞赛?
前端·后端·rust
爱勇宝3 小时前
从 Ctrl+CV 到 Enter:程序员正在失去什么
前端·后端·程序员
码事漫谈3 小时前
EdgeOne Makers + WorkBuddy:零基础也能快速搭建可上线的 AI 智能体(附图文教程)
后端
像我这样帅的人丶你还3 小时前
Java 后端详解(四):分页与搜索
java·javascript·后端
她的男孩4 小时前
数据权限为什么不能只靠注解?Forge 的 Mapper 层 SQL 改写源码拆解
java·后端·架构
烤代码的吐司君4 小时前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端