306 lines
9.8 KiB
Python
306 lines
9.8 KiB
Python
"""
|
|
通知工具 - 支持桌面通知、声音提示等
|
|
"""
|
|
|
|
import platform
|
|
import subprocess
|
|
import logging
|
|
from typing import Optional
|
|
|
|
|
|
def send_notification(title: str, message: str, urgency: str = "normal") -> bool:
|
|
"""
|
|
发送桌面通知
|
|
|
|
Args:
|
|
title: 通知标题
|
|
message: 通知内容
|
|
urgency: 通知紧急程度 (low, normal, high)
|
|
|
|
Returns:
|
|
是否成功发送
|
|
"""
|
|
system = platform.system()
|
|
|
|
try:
|
|
if system == "Windows":
|
|
# Windows 使用 PowerShell 发送通知
|
|
ps_script = f"""
|
|
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType=WindowsRuntime] > $null
|
|
$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
|
|
$template.SelectSingleNode("//text[@id='1']").AppendChild($template.CreateTextNode('{title}')) > $null
|
|
$template.SelectSingleNode("//text[@id='2']").AppendChild($template.CreateTextNode('{message}')) > $null
|
|
$toast = [Windows.UI.Notifications.ToastNotification]::new($template)
|
|
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Screen2Feishu").Show($toast)
|
|
"""
|
|
|
|
# 使用 PowerShell 执行
|
|
subprocess.run(
|
|
["powershell", "-Command", ps_script],
|
|
capture_output=True,
|
|
timeout=5
|
|
)
|
|
return True
|
|
|
|
elif system == "Darwin": # macOS
|
|
# macOS 使用 osascript 发送通知
|
|
script = f'display notification "{message}" with title "{title}"'
|
|
subprocess.run(
|
|
["osascript", "-e", script],
|
|
capture_output=True,
|
|
timeout=5
|
|
)
|
|
return True
|
|
|
|
elif system == "Linux":
|
|
# Linux 使用 notify-send
|
|
try:
|
|
subprocess.run(
|
|
["notify-send", "-u", urgency, title, message],
|
|
capture_output=True,
|
|
timeout=5
|
|
)
|
|
return True
|
|
except FileNotFoundError:
|
|
# 如果 notify-send 不可用,尝试使用 zenity
|
|
try:
|
|
subprocess.run(
|
|
["zenity", "--info", "--title", title, "--text", message],
|
|
capture_output=True,
|
|
timeout=5
|
|
)
|
|
return True
|
|
except FileNotFoundError:
|
|
return False
|
|
else:
|
|
return False
|
|
|
|
except Exception as e:
|
|
logging.error(f"发送通知失败: {str(e)}")
|
|
return False
|
|
|
|
|
|
def send_notification_with_icon(title: str, message: str, icon_path: Optional[str] = None) -> bool:
|
|
"""
|
|
发送带图标的桌面通知
|
|
|
|
Args:
|
|
title: 通知标题
|
|
message: 通知内容
|
|
icon_path: 图标文件路径
|
|
|
|
Returns:
|
|
是否成功发送
|
|
"""
|
|
system = platform.system()
|
|
|
|
try:
|
|
if system == "Linux" and icon_path:
|
|
# Linux 支持图标
|
|
subprocess.run(
|
|
["notify-send", "-u", "normal", "-i", icon_path, title, message],
|
|
capture_output=True,
|
|
timeout=5
|
|
)
|
|
return True
|
|
else:
|
|
# 其他系统使用普通通知
|
|
return send_notification(title, message)
|
|
|
|
except Exception as e:
|
|
logging.error(f"发送带图标通知失败: {str(e)}")
|
|
return False
|
|
|
|
|
|
def play_sound(sound_type: str = "success") -> bool:
|
|
"""
|
|
播放系统声音
|
|
|
|
Args:
|
|
sound_type: 声音类型 (success, error, warning, info)
|
|
|
|
Returns:
|
|
是否成功播放
|
|
"""
|
|
system = platform.system()
|
|
|
|
try:
|
|
if system == "Windows":
|
|
# Windows 使用 PowerShell 播放系统声音
|
|
sound_map = {
|
|
"success": "SystemAsterisk",
|
|
"error": "SystemHand",
|
|
"warning": "SystemExclamation",
|
|
"info": "SystemDefault"
|
|
}
|
|
|
|
sound = sound_map.get(sound_type, "SystemDefault")
|
|
ps_script = f'[System.Media.SystemSounds]::{sound}.Play()'
|
|
|
|
subprocess.run(
|
|
["powershell", "-Command", ps_script],
|
|
capture_output=True,
|
|
timeout=5
|
|
)
|
|
return True
|
|
|
|
elif system == "Darwin": # macOS
|
|
# macOS 使用 afplay 播放系统声音
|
|
sound_map = {
|
|
"success": "/System/Library/Sounds/Purr.aiff",
|
|
"error": "/System/Library/Sounds/Basso.aiff",
|
|
"warning": "/System/Library/Sounds/Funk.aiff",
|
|
"info": "/System/Library/Sounds/Glass.aiff"
|
|
}
|
|
|
|
sound_file = sound_map.get(sound_type, "/System/Library/Sounds/Glass.aiff")
|
|
|
|
# 异步播放声音
|
|
subprocess.Popen(
|
|
["afplay", sound_file],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL
|
|
)
|
|
return True
|
|
|
|
elif system == "Linux":
|
|
# Linux 使用 paplay 或 aplay 播放声音
|
|
# 这里使用简单的 beep 声音
|
|
try:
|
|
# 尝试使用 beep
|
|
subprocess.run(
|
|
["beep"],
|
|
capture_output=True,
|
|
timeout=5
|
|
)
|
|
return True
|
|
except FileNotFoundError:
|
|
# 如果 beep 不可用,尝试使用 paplay
|
|
try:
|
|
# 创建临时的声音文件
|
|
import tempfile
|
|
import wave
|
|
import struct
|
|
|
|
# 创建简单的正弦波声音
|
|
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as f:
|
|
# 写入 WAV 文件头
|
|
sample_rate = 44100
|
|
duration = 0.2
|
|
num_samples = int(sample_rate * duration)
|
|
|
|
wav_file = wave.open(f.name, 'w')
|
|
wav_file.setnchannels(1)
|
|
wav_file.setsampwidth(2)
|
|
wav_file.setframerate(sample_rate)
|
|
|
|
# 生成正弦波
|
|
for i in range(num_samples):
|
|
value = int(32767 * 0.3 * (1 if i < num_samples // 2 else 0))
|
|
wav_file.writeframes(struct.pack('<h', value))
|
|
|
|
wav_file.close()
|
|
|
|
# 播放声音
|
|
subprocess.run(
|
|
["paplay", f.name],
|
|
capture_output=True,
|
|
timeout=5
|
|
)
|
|
|
|
# 删除临时文件
|
|
import os
|
|
os.unlink(f.name)
|
|
|
|
return True
|
|
except Exception:
|
|
return False
|
|
else:
|
|
return False
|
|
|
|
except Exception as e:
|
|
logging.error(f"播放声音失败: {str(e)}")
|
|
return False
|
|
|
|
|
|
def notify_task_processed(success: bool, file_name: str, count: int = 1) -> bool:
|
|
"""
|
|
通知任务处理结果
|
|
|
|
Args:
|
|
success: 是否成功
|
|
file_name: 文件名
|
|
count: 处理数量
|
|
|
|
Returns:
|
|
是否成功发送通知
|
|
"""
|
|
if success:
|
|
title = " 任务处理成功"
|
|
message = f"已成功处理 {count} 个文件: {file_name}"
|
|
sound_type = "success"
|
|
else:
|
|
title = " 任务处理失败"
|
|
message = f"处理文件失败: {file_name}"
|
|
sound_type = "error"
|
|
|
|
# 发送桌面通知
|
|
notification_sent = send_notification(title, message)
|
|
|
|
# 播放声音提示
|
|
sound_played = play_sound(sound_type)
|
|
|
|
return notification_sent or sound_played
|
|
|
|
|
|
def notify_batch_result(results: dict) -> bool:
|
|
"""
|
|
通知批量处理结果
|
|
|
|
Args:
|
|
results: 处理结果字典 {文件名: 是否成功}
|
|
|
|
Returns:
|
|
是否成功发送通知
|
|
"""
|
|
success_count = sum(1 for v in results.values() if v)
|
|
total_count = len(results)
|
|
|
|
if success_count == total_count:
|
|
title = " 批量处理完成"
|
|
message = f"所有 {total_count} 个文件处理成功"
|
|
sound_type = "success"
|
|
elif success_count == 0:
|
|
title = " 批量处理失败"
|
|
message = f"所有 {total_count} 个文件处理失败"
|
|
sound_type = "error"
|
|
else:
|
|
title = "⚠️ 批量处理部分成功"
|
|
message = f"{success_count}/{total_count} 个文件处理成功"
|
|
sound_type = "warning"
|
|
|
|
# 发送桌面通知
|
|
notification_sent = send_notification(title, message)
|
|
|
|
# 播放声音提示
|
|
sound_played = play_sound(sound_type)
|
|
|
|
return notification_sent or sound_played
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# 测试通知功能
|
|
import logging
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
print("测试桌面通知...")
|
|
success = send_notification("测试通知", "这是一个测试通知消息")
|
|
print(f"通知发送: {'成功' if success else '失败'}")
|
|
|
|
print("测试声音播放...")
|
|
success = play_sound("success")
|
|
print(f"声音播放: {'成功' if success else '失败'}")
|
|
|
|
print("测试任务处理通知...")
|
|
success = notify_task_processed(True, "test_image.png")
|
|
print(f"任务通知: {'成功' if success else '失败'}") |