初始
This commit is contained in:
commit
f682fafc93
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.13
|
48
clipboard_history.txt
Normal file
48
clipboard_history.txt
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
[2025-07-30 16:02:24]
|
||||
apt-get install -y ca-certificates curl software-properties-common
|
||||
|
||||
==================================================
|
||||
|
||||
[2025-07-30 16:03:37]
|
||||
|
||||
==================================================
|
||||
|
||||
[2025-07-30 16:07:25]
|
||||
《攻坚》共5集,分别为《军令如山》、《淬锋砺刃》、《合力制胜》、《勇闯新域》、《务期必成》。该片围绕“铸牢政治忠诚、打好攻坚之战”,讲述全军部队全力以赴打好实现建军一百年奋斗目标攻坚战的生动故事,进行式反映部队官兵迎难而上、攻坚克难的新气象新作为,将进一步教育激励广大官兵深刻领悟“两个确立”的决定性意义,增强“四个意识”、坚定“四个自信”、做到“两个维护”,贯彻军委主席负责制,坚决听从党中央、中央军委和习主席指挥,忠实履行党和人民赋予的使命任务,为如期实现建军一百年奋斗目标、加快把人民军队建成世界一流军队团结奋斗。
|
||||
==================================================
|
||||
|
||||
[2025-07-30 16:08:25]
|
||||
当被问及他与爱泼斯坦的关系为何破裂时,特朗普起初回应称,“那都是陈年旧事,解释起来也很简单,但我不想浪费你们的时间。”不过他随后还是作出解释,称自己是在爱泼斯坦“做了一些不合适的事”之后中断了与他的来往。
|
||||
==================================================
|
||||
|
||||
[2025-07-30 16:13:14]
|
||||
[图片文件: clipboard_media\20250730_161314_970d8838.png]
|
||||
==================================================
|
||||
|
||||
[2025-07-30 16:15:36]
|
||||
[图片文件: clipboard_media\20250730_161536_7842fbf8.png]
|
||||
==================================================
|
||||
|
||||
[2025-07-30 16:15:57]
|
||||
为携手缩小早期预警能力差距、共同应对全球气候挑战,中国气象局发布“妈祖(MAZU)”,与全球特别是发展中国家分享中国经验和技术成果,提供早期预警技术、联合开展能力建设、共建风险识别与评估体系、创新合作机制与模式等。在经验方面,我国分享递进式气象服务、高级别预警“叫应”等早期预警实践;在技术方面,与各国共建城市工具箱、风云地球工具箱、海外设备等气象早期预警业务平台。
|
||||
==================================================
|
||||
|
||||
[2025-07-30 21:58:05]
|
||||
import time
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
import uuid
|
||||
import io
|
||||
import pyperclip
|
||||
from pystray import Icon, Menu, MenuItem
|
||||
from PIL import Image, ImageDraw, ImageGrab
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
import logging
|
||||
import win32clipboard
|
||||
from io import BytesIO
|
||||
|
||||
==================================================
|
BIN
clipboard_media/20250730_161314_970d8838.png
Normal file
BIN
clipboard_media/20250730_161314_970d8838.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
BIN
clipboard_media/20250730_161536_7842fbf8.png
Normal file
BIN
clipboard_media/20250730_161536_7842fbf8.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 229 KiB |
35
clipboard_monitor.log
Normal file
35
clipboard_monitor.log
Normal file
@ -0,0 +1,35 @@
|
||||
2025-07-30 16:01:41,483 - INFO - 剪贴板监控程序已启动
|
||||
2025-07-30 16:02:24,061 - INFO - 检测到剪贴板内容变化
|
||||
2025-07-30 16:03:37,649 - INFO - 检测到剪贴板内容变化
|
||||
2025-07-30 16:07:25,852 - INFO - 检测到剪贴板内容变化
|
||||
2025-07-30 16:08:25,415 - INFO - 检测到剪贴板内容变化
|
||||
2025-07-30 16:08:56,512 - ERROR - An error occurred when calling message handler
|
||||
Traceback (most recent call last):
|
||||
File "D:\miniconda3\envs\dev-3.13\Lib\site-packages\pystray\_win32.py", line 412, in _dispatcher
|
||||
return int(icon._message_handlers.get(
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
uMsg, lambda w, l: 0)(wParam, lParam) or 0)
|
||||
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
|
||||
File "D:\miniconda3\envs\dev-3.13\Lib\site-packages\pystray\_win32.py", line 224, in _on_notify
|
||||
descriptors[index - 1](self)
|
||||
~~~~~~~~~~~~~~~~~~~~~~^^^^^^
|
||||
File "D:\miniconda3\envs\dev-3.13\Lib\site-packages\pystray\_base.py", line 328, in inner
|
||||
callback(self)
|
||||
~~~~~~~~^^^^^^
|
||||
File "D:\miniconda3\envs\dev-3.13\Lib\site-packages\pystray\_base.py", line 453, in __call__
|
||||
return self._action(icon, self)
|
||||
~~~~~~~~~~~~^^^^^^^^^^^^
|
||||
File "D:\miniconda3\envs\dev-3.13\Lib\site-packages\pystray\_base.py", line 548, in wrapper0
|
||||
return action()
|
||||
File "E:\Worker\Python\Tools\playwright\clipboard_monitor.py", line 130, in exit_app
|
||||
sys.exit(0)
|
||||
~~~~~~~~^^^
|
||||
SystemExit: 0
|
||||
2025-07-30 16:13:03,690 - INFO - 剪贴板监控程序已启动
|
||||
2025-07-30 16:13:14,273 - INFO - 检测到剪贴板图片变化
|
||||
2025-07-30 16:13:14,300 - INFO - 图片已保存到: clipboard_media\20250730_161314_970d8838.png
|
||||
2025-07-30 16:14:34,142 - INFO - 剪贴板监控程序已启动
|
||||
2025-07-30 16:15:24,678 - INFO - 剪贴板监控程序已启动
|
||||
2025-07-30 16:15:36,353 - INFO - 检测到剪贴板图片变化
|
||||
2025-07-30 16:15:36,385 - INFO - 图片已保存到: clipboard_media\20250730_161536_7842fbf8.png
|
||||
2025-07-30 16:15:57,592 - INFO - 检测到剪贴板文本变化
|
304
clipboard_monitor.py
Normal file
304
clipboard_monitor.py
Normal file
@ -0,0 +1,304 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
剪贴板监控程序
|
||||
|
||||
该程序在后台运行,实时监控Windows系统剪贴板的变化,
|
||||
当检测到剪贴板内容变化时,记录内容并输出提示。
|
||||
支持文本和图片媒体的记录。
|
||||
"""
|
||||
|
||||
import time
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
import uuid
|
||||
import io
|
||||
import pyperclip
|
||||
from pystray import Icon, Menu, MenuItem
|
||||
from PIL import Image, ImageDraw, ImageGrab
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
import logging
|
||||
import win32clipboard
|
||||
from io import BytesIO
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler("clipboard_monitor.log", encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
class ClipboardMonitor:
|
||||
def __init__(self):
|
||||
self.previous_clipboard = ""
|
||||
self.previous_image_hash = None
|
||||
self.monitoring = True
|
||||
self.paused = False
|
||||
self.log_file = "clipboard_history.txt"
|
||||
self.media_folder = "clipboard_media"
|
||||
|
||||
# 确保媒体文件夹存在
|
||||
if not os.path.exists(self.media_folder):
|
||||
os.makedirs(self.media_folder)
|
||||
|
||||
self.setup_gui()
|
||||
self.create_tray_icon()
|
||||
|
||||
def setup_gui(self):
|
||||
"""设置GUI窗口,但默认不显示"""
|
||||
self.root = tk.Tk()
|
||||
self.root.title("剪贴板监控")
|
||||
self.root.geometry("400x300")
|
||||
self.root.protocol("WM_DELETE_WINDOW", self.hide_window)
|
||||
|
||||
# 创建文本区域显示剪贴板历史
|
||||
self.text_area = tk.Text(self.root, wrap=tk.WORD)
|
||||
self.text_area.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)
|
||||
|
||||
# 创建按钮框架
|
||||
button_frame = tk.Frame(self.root)
|
||||
button_frame.pack(fill=tk.X, padx=10, pady=5)
|
||||
|
||||
# 添加按钮
|
||||
tk.Button(button_frame, text="暂停监控", command=self.toggle_pause).pack(side=tk.LEFT, padx=5)
|
||||
tk.Button(button_frame, text="清空历史", command=self.clear_history).pack(side=tk.LEFT, padx=5)
|
||||
tk.Button(button_frame, text="退出程序", command=self.exit_app).pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# 默认隐藏窗口
|
||||
self.root.withdraw()
|
||||
|
||||
def create_tray_icon(self):
|
||||
"""创建系统托盘图标"""
|
||||
# 创建图标
|
||||
image = self.create_icon_image()
|
||||
|
||||
# 创建菜单
|
||||
menu = Menu(
|
||||
MenuItem('显示主窗口', self.show_window),
|
||||
MenuItem('暂停/继续监控', self.toggle_pause),
|
||||
MenuItem('清空历史记录', self.clear_history),
|
||||
MenuItem('退出', self.exit_app)
|
||||
)
|
||||
|
||||
# 创建托盘图标
|
||||
self.icon = Icon("clipboard_monitor", image, "剪贴板监控", menu)
|
||||
|
||||
# 在单独的线程中运行图标
|
||||
threading.Thread(target=self.icon.run, daemon=True).start()
|
||||
|
||||
def create_icon_image(self):
|
||||
"""创建托盘图标图像"""
|
||||
# 创建一个32x32的图像,白色背景
|
||||
image = Image.new('RGB', (32, 32), color=(255, 255, 255))
|
||||
d = ImageDraw.Draw(image)
|
||||
|
||||
# 绘制剪贴板图标
|
||||
d.rectangle([(8, 8), (24, 24)], outline=(0, 0, 0), width=2)
|
||||
d.rectangle([(12, 4), (20, 8)], fill=(0, 0, 0))
|
||||
|
||||
return image
|
||||
|
||||
def show_window(self):
|
||||
"""显示主窗口"""
|
||||
self.root.deiconify()
|
||||
self.root.lift()
|
||||
self.update_text_area()
|
||||
|
||||
def hide_window(self):
|
||||
"""隐藏主窗口"""
|
||||
self.root.withdraw()
|
||||
|
||||
def toggle_pause(self):
|
||||
"""切换暂停/继续监控状态"""
|
||||
self.paused = not self.paused
|
||||
status = "暂停" if self.paused else "继续"
|
||||
logging.info(f"监控已{status}")
|
||||
self.show_notification(f"剪贴板监控已{status}")
|
||||
|
||||
def clear_history(self):
|
||||
"""清空历史记录"""
|
||||
try:
|
||||
with open(self.log_file, 'w', encoding='utf-8') as f:
|
||||
f.write("")
|
||||
self.update_text_area()
|
||||
logging.info("历史记录已清空")
|
||||
self.show_notification("历史记录已清空")
|
||||
except Exception as e:
|
||||
logging.error(f"清空历史记录失败: {e}")
|
||||
|
||||
def exit_app(self):
|
||||
"""退出应用程序"""
|
||||
self.monitoring = False
|
||||
if hasattr(self, 'icon'):
|
||||
self.icon.stop()
|
||||
self.root.quit()
|
||||
# 不使用sys.exit(),避免引发SystemExit异常
|
||||
# 让程序自然退出
|
||||
|
||||
def show_notification(self, message):
|
||||
"""显示通知"""
|
||||
if hasattr(self, 'icon'):
|
||||
self.icon.notify(message)
|
||||
|
||||
def update_text_area(self):
|
||||
"""更新文本区域内容"""
|
||||
self.text_area.delete(1.0, tk.END)
|
||||
try:
|
||||
if os.path.exists(self.log_file):
|
||||
with open(self.log_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
self.text_area.insert(tk.END, content)
|
||||
except Exception as e:
|
||||
logging.error(f"读取历史记录失败: {e}")
|
||||
|
||||
def save_clipboard_content(self, content, media_path=None):
|
||||
"""保存剪贴板内容到文件"""
|
||||
try:
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
with open(self.log_file, 'a', encoding='utf-8') as f:
|
||||
f.write(f"\n[{timestamp}]\n")
|
||||
|
||||
# 如果有媒体文件,添加引用
|
||||
if media_path:
|
||||
f.write(f"[图片文件: {media_path}]\n")
|
||||
else:
|
||||
f.write(f"{content}\n")
|
||||
|
||||
f.write(f"{'='*50}\n")
|
||||
|
||||
# 如果窗口可见,更新文本区域
|
||||
if self.root.winfo_viewable():
|
||||
self.update_text_area()
|
||||
except Exception as e:
|
||||
logging.error(f"保存剪贴板内容失败: {e}")
|
||||
|
||||
def get_clipboard_image(self):
|
||||
"""尝试从剪贴板获取图片"""
|
||||
try:
|
||||
# 尝试使用PIL的ImageGrab获取剪贴板图片
|
||||
image = ImageGrab.grabclipboard()
|
||||
if isinstance(image, Image.Image):
|
||||
return image
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error(f"获取剪贴板图片失败: {e}")
|
||||
return None
|
||||
|
||||
def save_image_to_file(self, image):
|
||||
"""保存图片到文件"""
|
||||
try:
|
||||
# 生成唯一文件名
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"{timestamp}_{uuid.uuid4().hex[:8]}.png"
|
||||
filepath = os.path.join(self.media_folder, filename)
|
||||
|
||||
# 保存图片
|
||||
image.save(filepath)
|
||||
logging.info(f"图片已保存到: {filepath}")
|
||||
return filepath
|
||||
except Exception as e:
|
||||
logging.error(f"保存图片失败: {e}")
|
||||
return None
|
||||
|
||||
def get_image_hash(self, image):
|
||||
"""获取图片的简单哈希值用于比较"""
|
||||
if not image:
|
||||
return None
|
||||
try:
|
||||
# 缩小图片以加快哈希计算
|
||||
small_image = image.resize((32, 32), Image.LANCZOS)
|
||||
# 转换为灰度
|
||||
gray_image = small_image.convert("L")
|
||||
# 获取像素数据
|
||||
pixels = list(gray_image.getdata())
|
||||
# 简单哈希:将像素值二值化并转为字符串
|
||||
binary_pixels = ['1' if p > 128 else '0' for p in pixels]
|
||||
return ''.join(binary_pixels)
|
||||
except Exception as e:
|
||||
logging.error(f"计算图片哈希失败: {e}")
|
||||
return None
|
||||
|
||||
def monitor_clipboard(self):
|
||||
"""监控剪贴板变化"""
|
||||
try:
|
||||
# 获取初始剪贴板内容
|
||||
self.previous_clipboard = pyperclip.paste()
|
||||
# 获取初始图片(如果有)
|
||||
initial_image = self.get_clipboard_image()
|
||||
if initial_image:
|
||||
self.previous_image_hash = self.get_image_hash(initial_image)
|
||||
except Exception as e:
|
||||
self.previous_clipboard = ""
|
||||
self.previous_image_hash = None
|
||||
logging.error(f"获取初始剪贴板内容失败: {e}")
|
||||
|
||||
while self.monitoring:
|
||||
try:
|
||||
if not self.paused:
|
||||
# 检查是否有图片
|
||||
current_image = self.get_clipboard_image()
|
||||
if current_image:
|
||||
current_image_hash = self.get_image_hash(current_image)
|
||||
|
||||
# 如果图片变化了
|
||||
if current_image_hash != self.previous_image_hash:
|
||||
logging.info("检测到剪贴板图片变化")
|
||||
self.show_notification("检测到新的剪贴板图片")
|
||||
|
||||
# 保存图片到文件
|
||||
image_path = self.save_image_to_file(current_image)
|
||||
if image_path:
|
||||
# 保存引用到日志
|
||||
self.save_clipboard_content("", image_path)
|
||||
|
||||
# 更新上一次的图片哈希
|
||||
self.previous_image_hash = current_image_hash
|
||||
else:
|
||||
# 获取当前文本内容
|
||||
current_clipboard = pyperclip.paste()
|
||||
|
||||
# 如果文本内容变化了
|
||||
if current_clipboard != self.previous_clipboard:
|
||||
logging.info("检测到剪贴板文本变化")
|
||||
self.show_notification("检测到新的剪贴板内容")
|
||||
|
||||
# 保存新内容
|
||||
self.save_clipboard_content(current_clipboard)
|
||||
|
||||
# 更新上一次的内容
|
||||
self.previous_clipboard = current_clipboard
|
||||
except Exception as e:
|
||||
logging.error(f"监控剪贴板时出错: {e}")
|
||||
|
||||
# 休眠一段时间再检查
|
||||
time.sleep(0.5)
|
||||
|
||||
def run(self):
|
||||
"""运行监控程序"""
|
||||
# 启动监控线程
|
||||
monitor_thread = threading.Thread(target=self.monitor_clipboard, daemon=True)
|
||||
monitor_thread.start()
|
||||
|
||||
# 显示启动通知
|
||||
self.show_notification("剪贴板监控已启动")
|
||||
logging.info("剪贴板监控程序已启动")
|
||||
|
||||
# 运行主循环
|
||||
self.root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
monitor = ClipboardMonitor()
|
||||
monitor.run()
|
||||
except Exception as e:
|
||||
logging.critical(f"程序启动失败: {e}")
|
||||
messagebox.showerror("错误", f"程序启动失败: {e}")
|
||||
sys.exit(1)
|
83
clipboard_monitor_readme.md
Normal file
83
clipboard_monitor_readme.md
Normal file
@ -0,0 +1,83 @@
|
||||
# 剪贴板监控程序
|
||||
|
||||
## 简介
|
||||
|
||||
这是一个基于Windows 64位系统的剪贴板实时监控程序,使用Python 3.11及以上版本开发。程序在后台运行,当检测到剪贴板内容变化时,会记录剪贴板内容并显示通知提示。支持文本和图片媒体的记录。
|
||||
|
||||
## 功能特点
|
||||
|
||||
- 实时监控剪贴板变化
|
||||
- 自动记录剪贴板历史内容
|
||||
- 支持图片媒体的记录和保存
|
||||
- 系统托盘图标,最小化运行
|
||||
- 可暂停/继续监控
|
||||
- 查看历史记录
|
||||
- 支持开机自启动
|
||||
|
||||
## 系统要求
|
||||
|
||||
- Windows 64位操作系统
|
||||
- Python 3.11或更高版本
|
||||
- 必要的Python库:pyperclip, pystray, pillow (PIL), pywin32
|
||||
|
||||
## 安装步骤
|
||||
|
||||
1. 确保已安装Python 3.11或更高版本
|
||||
2. 双击运行`start_clipboard_monitor.bat`,脚本会自动检查并安装必要的库
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 启动程序
|
||||
|
||||
双击运行`start_clipboard_monitor.bat`文件即可启动剪贴板监控程序。程序启动后会在系统托盘显示图标。
|
||||
|
||||
### 设置开机自启动
|
||||
|
||||
如果希望程序在Windows启动时自动运行,请双击运行`setup_autostart.bat`文件,它会在Windows的启动文件夹中创建必要的快捷方式。
|
||||
|
||||
### 使用界面
|
||||
|
||||
程序启动后会在系统托盘区域显示一个图标,右键点击图标可以看到以下选项:
|
||||
|
||||
- **显示主窗口**:打开主界面,查看剪贴板历史记录
|
||||
- **暂停/继续监控**:临时暂停或继续监控剪贴板
|
||||
- **清空历史记录**:删除所有已记录的剪贴板历史
|
||||
- **退出**:完全退出程序
|
||||
|
||||
### 主窗口
|
||||
|
||||
在主窗口中,您可以:
|
||||
|
||||
- 查看所有剪贴板历史记录
|
||||
- 使用按钮暂停/继续监控
|
||||
- 清空历史记录
|
||||
- 退出程序
|
||||
|
||||
## 文件说明
|
||||
|
||||
- `clipboard_monitor.py`:主程序文件
|
||||
- `start_clipboard_monitor.bat`:启动脚本
|
||||
- `setup_autostart.bat`:设置开机自启动脚本
|
||||
- `clipboard_monitor.log`:程序运行日志
|
||||
- `clipboard_history.txt`:剪贴板历史记录
|
||||
- `clipboard_media/`:保存剪贴板图片的文件夹
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 程序会在后台持续运行,如需完全退出,请通过系统托盘图标的"退出"选项或主窗口的"退出程序"按钮退出
|
||||
2. 剪贴板历史记录保存在程序所在目录的`clipboard_history.txt`文件中
|
||||
3. 剪贴板中的图片会保存在`clipboard_media`文件夹中,文本记录中会包含图片文件的引用
|
||||
4. 如果遇到问题,可以查看`clipboard_monitor.log`日志文件了解详情
|
||||
|
||||
## 故障排除
|
||||
|
||||
如果程序无法正常启动或运行,请检查:
|
||||
|
||||
1. 确认Python版本是否为3.11或更高
|
||||
2. 确认已安装所需的库:pyperclip, pystray, pillow, pywin32
|
||||
3. 查看`clipboard_monitor.log`日志文件,了解错误详情
|
||||
4. 如果提示缺少库,可以手动安装:`pip install pyperclip pystray pillow pywin32`
|
||||
|
||||
## 隐私说明
|
||||
|
||||
本程序仅在本地运行,不会将任何数据发送到互联网。所有剪贴板内容仅保存在本地文件中。
|
304
main.py
Normal file
304
main.py
Normal file
@ -0,0 +1,304 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
剪贴板监控程序
|
||||
|
||||
该程序在后台运行,实时监控Windows系统剪贴板的变化,
|
||||
当检测到剪贴板内容变化时,记录内容并输出提示。
|
||||
支持文本和图片媒体的记录。
|
||||
"""
|
||||
|
||||
import time
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
import uuid
|
||||
import io
|
||||
import pyperclip
|
||||
from pystray import Icon, Menu, MenuItem
|
||||
from PIL import Image, ImageDraw, ImageGrab
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
import logging
|
||||
import win32clipboard
|
||||
from io import BytesIO
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler("clipboard_monitor.log", encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
class ClipboardMonitor:
|
||||
def __init__(self):
|
||||
self.previous_clipboard = ""
|
||||
self.previous_image_hash = None
|
||||
self.monitoring = True
|
||||
self.paused = False
|
||||
self.log_file = "clipboard_history.txt"
|
||||
self.media_folder = "clipboard_media"
|
||||
|
||||
# 确保媒体文件夹存在
|
||||
if not os.path.exists(self.media_folder):
|
||||
os.makedirs(self.media_folder)
|
||||
|
||||
self.setup_gui()
|
||||
self.create_tray_icon()
|
||||
|
||||
def setup_gui(self):
|
||||
"""设置GUI窗口,但默认不显示"""
|
||||
self.root = tk.Tk()
|
||||
self.root.title("剪贴板监控")
|
||||
self.root.geometry("400x300")
|
||||
self.root.protocol("WM_DELETE_WINDOW", self.hide_window)
|
||||
|
||||
# 创建文本区域显示剪贴板历史
|
||||
self.text_area = tk.Text(self.root, wrap=tk.WORD)
|
||||
self.text_area.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)
|
||||
|
||||
# 创建按钮框架
|
||||
button_frame = tk.Frame(self.root)
|
||||
button_frame.pack(fill=tk.X, padx=10, pady=5)
|
||||
|
||||
# 添加按钮
|
||||
tk.Button(button_frame, text="暂停监控", command=self.toggle_pause).pack(side=tk.LEFT, padx=5)
|
||||
tk.Button(button_frame, text="清空历史", command=self.clear_history).pack(side=tk.LEFT, padx=5)
|
||||
tk.Button(button_frame, text="退出程序", command=self.exit_app).pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# 默认隐藏窗口
|
||||
self.root.withdraw()
|
||||
|
||||
def create_tray_icon(self):
|
||||
"""创建系统托盘图标"""
|
||||
# 创建图标
|
||||
image = self.create_icon_image()
|
||||
|
||||
# 创建菜单
|
||||
menu = Menu(
|
||||
MenuItem('显示主窗口', self.show_window),
|
||||
MenuItem('暂停/继续监控', self.toggle_pause),
|
||||
MenuItem('清空历史记录', self.clear_history),
|
||||
MenuItem('退出', self.exit_app)
|
||||
)
|
||||
|
||||
# 创建托盘图标
|
||||
self.icon = Icon("clipboard_monitor", image, "剪贴板监控", menu)
|
||||
|
||||
# 在单独的线程中运行图标
|
||||
threading.Thread(target=self.icon.run, daemon=True).start()
|
||||
|
||||
def create_icon_image(self):
|
||||
"""创建托盘图标图像"""
|
||||
# 创建一个32x32的图像,白色背景
|
||||
image = Image.new('RGB', (32, 32), color=(255, 255, 255))
|
||||
d = ImageDraw.Draw(image)
|
||||
|
||||
# 绘制剪贴板图标
|
||||
d.rectangle([(8, 8), (24, 24)], outline=(0, 0, 0), width=2)
|
||||
d.rectangle([(12, 4), (20, 8)], fill=(0, 0, 0))
|
||||
|
||||
return image
|
||||
|
||||
def show_window(self):
|
||||
"""显示主窗口"""
|
||||
self.root.deiconify()
|
||||
self.root.lift()
|
||||
self.update_text_area()
|
||||
|
||||
def hide_window(self):
|
||||
"""隐藏主窗口"""
|
||||
self.root.withdraw()
|
||||
|
||||
def toggle_pause(self):
|
||||
"""切换暂停/继续监控状态"""
|
||||
self.paused = not self.paused
|
||||
status = "暂停" if self.paused else "继续"
|
||||
logging.info(f"监控已{status}")
|
||||
self.show_notification(f"剪贴板监控已{status}")
|
||||
|
||||
def clear_history(self):
|
||||
"""清空历史记录"""
|
||||
try:
|
||||
with open(self.log_file, 'w', encoding='utf-8') as f:
|
||||
f.write("")
|
||||
self.update_text_area()
|
||||
logging.info("历史记录已清空")
|
||||
self.show_notification("历史记录已清空")
|
||||
except Exception as e:
|
||||
logging.error(f"清空历史记录失败: {e}")
|
||||
|
||||
def exit_app(self):
|
||||
"""退出应用程序"""
|
||||
self.monitoring = False
|
||||
if hasattr(self, 'icon'):
|
||||
self.icon.stop()
|
||||
self.root.quit()
|
||||
# 不使用sys.exit(),避免引发SystemExit异常
|
||||
# 让程序自然退出
|
||||
|
||||
def show_notification(self, message):
|
||||
"""显示通知"""
|
||||
if hasattr(self, 'icon'):
|
||||
self.icon.notify(message)
|
||||
|
||||
def update_text_area(self):
|
||||
"""更新文本区域内容"""
|
||||
self.text_area.delete(1.0, tk.END)
|
||||
try:
|
||||
if os.path.exists(self.log_file):
|
||||
with open(self.log_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
self.text_area.insert(tk.END, content)
|
||||
except Exception as e:
|
||||
logging.error(f"读取历史记录失败: {e}")
|
||||
|
||||
def save_clipboard_content(self, content, media_path=None):
|
||||
"""保存剪贴板内容到文件"""
|
||||
try:
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
with open(self.log_file, 'a', encoding='utf-8') as f:
|
||||
f.write(f"\n[{timestamp}]\n")
|
||||
|
||||
# 如果有媒体文件,添加引用
|
||||
if media_path:
|
||||
f.write(f"[图片文件: {media_path}]\n")
|
||||
else:
|
||||
f.write(f"{content}\n")
|
||||
|
||||
f.write(f"{'='*50}\n")
|
||||
|
||||
# 如果窗口可见,更新文本区域
|
||||
if self.root.winfo_viewable():
|
||||
self.update_text_area()
|
||||
except Exception as e:
|
||||
logging.error(f"保存剪贴板内容失败: {e}")
|
||||
|
||||
def get_clipboard_image(self):
|
||||
"""尝试从剪贴板获取图片"""
|
||||
try:
|
||||
# 尝试使用PIL的ImageGrab获取剪贴板图片
|
||||
image = ImageGrab.grabclipboard()
|
||||
if isinstance(image, Image.Image):
|
||||
return image
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error(f"获取剪贴板图片失败: {e}")
|
||||
return None
|
||||
|
||||
def save_image_to_file(self, image):
|
||||
"""保存图片到文件"""
|
||||
try:
|
||||
# 生成唯一文件名
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"{timestamp}_{uuid.uuid4().hex[:8]}.png"
|
||||
filepath = os.path.join(self.media_folder, filename)
|
||||
|
||||
# 保存图片
|
||||
image.save(filepath)
|
||||
logging.info(f"图片已保存到: {filepath}")
|
||||
return filepath
|
||||
except Exception as e:
|
||||
logging.error(f"保存图片失败: {e}")
|
||||
return None
|
||||
|
||||
def get_image_hash(self, image):
|
||||
"""获取图片的简单哈希值用于比较"""
|
||||
if not image:
|
||||
return None
|
||||
try:
|
||||
# 缩小图片以加快哈希计算
|
||||
small_image = image.resize((32, 32), Image.LANCZOS)
|
||||
# 转换为灰度
|
||||
gray_image = small_image.convert("L")
|
||||
# 获取像素数据
|
||||
pixels = list(gray_image.getdata())
|
||||
# 简单哈希:将像素值二值化并转为字符串
|
||||
binary_pixels = ['1' if p > 128 else '0' for p in pixels]
|
||||
return ''.join(binary_pixels)
|
||||
except Exception as e:
|
||||
logging.error(f"计算图片哈希失败: {e}")
|
||||
return None
|
||||
|
||||
def monitor_clipboard(self):
|
||||
"""监控剪贴板变化"""
|
||||
try:
|
||||
# 获取初始剪贴板内容
|
||||
self.previous_clipboard = pyperclip.paste()
|
||||
# 获取初始图片(如果有)
|
||||
initial_image = self.get_clipboard_image()
|
||||
if initial_image:
|
||||
self.previous_image_hash = self.get_image_hash(initial_image)
|
||||
except Exception as e:
|
||||
self.previous_clipboard = ""
|
||||
self.previous_image_hash = None
|
||||
logging.error(f"获取初始剪贴板内容失败: {e}")
|
||||
|
||||
while self.monitoring:
|
||||
try:
|
||||
if not self.paused:
|
||||
# 检查是否有图片
|
||||
current_image = self.get_clipboard_image()
|
||||
if current_image:
|
||||
current_image_hash = self.get_image_hash(current_image)
|
||||
|
||||
# 如果图片变化了
|
||||
if current_image_hash != self.previous_image_hash:
|
||||
logging.info("检测到剪贴板图片变化")
|
||||
self.show_notification("检测到新的剪贴板图片")
|
||||
|
||||
# 保存图片到文件
|
||||
image_path = self.save_image_to_file(current_image)
|
||||
if image_path:
|
||||
# 保存引用到日志
|
||||
self.save_clipboard_content("", image_path)
|
||||
|
||||
# 更新上一次的图片哈希
|
||||
self.previous_image_hash = current_image_hash
|
||||
else:
|
||||
# 获取当前文本内容
|
||||
current_clipboard = pyperclip.paste()
|
||||
|
||||
# 如果文本内容变化了
|
||||
if current_clipboard != self.previous_clipboard:
|
||||
logging.info("检测到剪贴板文本变化")
|
||||
self.show_notification("检测到新的剪贴板内容")
|
||||
|
||||
# 保存新内容
|
||||
self.save_clipboard_content(current_clipboard)
|
||||
|
||||
# 更新上一次的内容
|
||||
self.previous_clipboard = current_clipboard
|
||||
except Exception as e:
|
||||
logging.error(f"监控剪贴板时出错: {e}")
|
||||
|
||||
# 休眠一段时间再检查
|
||||
time.sleep(0.5)
|
||||
|
||||
def run(self):
|
||||
"""运行监控程序"""
|
||||
# 启动监控线程
|
||||
monitor_thread = threading.Thread(target=self.monitor_clipboard, daemon=True)
|
||||
monitor_thread.start()
|
||||
|
||||
# 显示启动通知
|
||||
self.show_notification("剪贴板监控已启动")
|
||||
logging.info("剪贴板监控程序已启动")
|
||||
|
||||
# 运行主循环
|
||||
self.root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
monitor = ClipboardMonitor()
|
||||
monitor.run()
|
||||
except Exception as e:
|
||||
logging.critical(f"程序启动失败: {e}")
|
||||
messagebox.showerror("错误", f"程序启动失败: {e}")
|
||||
sys.exit(1)
|
12
pyproject.toml
Normal file
12
pyproject.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "clipboard"
|
||||
version = "0.9.1"
|
||||
description = "系统剪贴板监视利用"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"pillow>=11.3.0",
|
||||
"pyperclip>=1.9.0",
|
||||
"pystray>=0.19.5",
|
||||
"pywin32>=311",
|
||||
]
|
39
setup_autostart.bat
Normal file
39
setup_autostart.bat
Normal file
@ -0,0 +1,39 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
echo 剪贴板监控程序 - 开机自启动设置
|
||||
echo ====================================
|
||||
echo.
|
||||
|
||||
:: 获取当前脚本所在目录的完整路径
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
set "MONITOR_SCRIPT=%SCRIPT_DIR%start_clipboard_monitor.bat"
|
||||
|
||||
:: 检查文件是否存在
|
||||
if not exist "%MONITOR_SCRIPT%" (
|
||||
echo 错误:未找到启动脚本 %MONITOR_SCRIPT%
|
||||
echo 请确保 start_clipboard_monitor.bat 文件存在
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 创建开机启动快捷方式
|
||||
set "STARTUP_FOLDER=%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup"
|
||||
set "SHORTCUT_PATH=%STARTUP_FOLDER%\剪贴板监控.lnk"
|
||||
|
||||
echo 正在创建开机启动快捷方式...
|
||||
|
||||
:: 使用PowerShell创建快捷方式
|
||||
powershell -Command "$WshShell = New-Object -ComObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut('%SHORTCUT_PATH%'); $Shortcut.TargetPath = '%MONITOR_SCRIPT%'; $Shortcut.WorkingDirectory = '%SCRIPT_DIR%'; $Shortcut.Description = '剪贴板监控程序'; $Shortcut.Save()"
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo 创建快捷方式失败!
|
||||
echo 请尝试手动将 %MONITOR_SCRIPT% 添加到开机启动项
|
||||
) else (
|
||||
echo 成功创建开机启动快捷方式!
|
||||
echo 剪贴板监控程序将在系统启动时自动运行
|
||||
)
|
||||
|
||||
echo.
|
||||
echo 按任意键退出...
|
||||
pause > nul
|
35
start_clipboard_monitor.bat
Normal file
35
start_clipboard_monitor.bat
Normal file
@ -0,0 +1,35 @@
|
||||
@echo off
|
||||
echo 正在启动剪贴板监控程序...
|
||||
|
||||
:: 设置Python路径,如果Python已在PATH中,可以直接使用python命令
|
||||
set PYTHON_CMD=python
|
||||
|
||||
:: 检查Python是否可用
|
||||
%PYTHON_CMD% --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo Python未找到,请确保已安装Python 3.11或更高版本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 检查必要的库是否已安装
|
||||
echo 检查必要的库...
|
||||
%PYTHON_CMD% -c "import pyperclip, pystray, PIL, win32clipboard" >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo 正在安装必要的库...
|
||||
%PYTHON_CMD% -m pip install pyperclip pystray pillow pywin32
|
||||
if %errorlevel% neq 0 (
|
||||
echo 安装库失败,请手动安装:pip install pyperclip pystray pillow pywin32
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
:: 启动剪贴板监控程序
|
||||
start "剪贴板监控" /B %PYTHON_CMD% "%~dp0clipboard_monitor.py"
|
||||
|
||||
echo 剪贴板监控程序已在后台启动
|
||||
echo 可以在系统托盘中找到程序图标
|
||||
|
||||
timeout /t 5
|
||||
exit
|
174
uv.lock
Normal file
174
uv.lock
Normal file
@ -0,0 +1,174 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard"
|
||||
version = "0.9.1"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "pillow" },
|
||||
{ name = "pyperclip" },
|
||||
{ name = "pystray" },
|
||||
{ name = "pywin32" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "pillow", specifier = ">=11.3.0" },
|
||||
{ name = "pyperclip", specifier = ">=1.9.0" },
|
||||
{ name = "pystray", specifier = ">=0.19.5" },
|
||||
{ name = "pywin32", specifier = ">=311" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "11.3.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-core"
|
||||
version = "11.1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/e8/e9/0b85c81e2b441267bca707b5d89f56c2f02578ef8f3eafddf0e0c0b8848c/pyobjc_core-11.1.tar.gz", hash = "sha256:b63d4d90c5df7e762f34739b39cc55bc63dbcf9fb2fb3f2671e528488c7a87fe" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c5/24/12e4e2dae5f85fd0c0b696404ed3374ea6ca398e7db886d4f1322eb30799/pyobjc_core-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:18986f83998fbd5d3f56d8a8428b2f3e0754fd15cef3ef786ca0d29619024f2c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f7/79/031492497624de4c728f1857181b06ce8c56444db4d49418fa459cba217c/pyobjc_core-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8849e78cfe6595c4911fbba29683decfb0bf57a350aed8a43316976ba6f659d2" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ed/7d/6169f16a0c7ec15b9381f8bf33872baf912de2ef68d96c798ca4c6ee641f/pyobjc_core-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8cb9ed17a8d84a312a6e8b665dd22393d48336ea1d8277e7ad20c19a38edf731" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/49/0f/f5ab2b0e57430a3bec9a62b6153c0e79c05a30d77b564efdb9f9446eeac5/pyobjc_core-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:f2455683e807f8541f0d83fbba0f5d9a46128ab0d5cc83ea208f0bec759b7f96" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-cocoa"
|
||||
version = "11.1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4e/0b/a01477cde2a040f97e226f3e15e5ffd1268fcb6d1d664885a95ba592eca9/pyobjc_framework_cocoa-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:54e93e1d9b0fc41c032582a6f0834befe1d418d73893968f3f450281b11603da" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bc/e6/64cf2661f6ab7c124d0486ec6d1d01a9bb2838a0d2a46006457d8c5e6845/pyobjc_framework_cocoa-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fd5245ee1997d93e78b72703be1289d75d88ff6490af94462b564892e9266350" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/33/87/01e35c5a3c5bbdc93d5925366421e10835fcd7b23347b6c267df1b16d0b3/pyobjc_framework_cocoa-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:aede53a1afc5433e1e7d66568cc52acceeb171b0a6005407a42e8e82580b4fc0" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c1/7c/54afe9ffee547c41e1161691e72067a37ed27466ac71c089bfdcd07ca70d/pyobjc_framework_cocoa-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:1b5de4e1757bb65689d6dc1f8d8717de9ec8587eb0c4831c134f13aba29f9b71" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-quartz"
|
||||
version = "11.1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
{ name = "pyobjc-framework-cocoa" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bd/27/4f4fc0e6a0652318c2844608dd7c41e49ba6006ee5fb60c7ae417c338357/pyobjc_framework_quartz-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43a1138280571bbf44df27a7eef519184b5c4183a588598ebaaeb887b9e73e76" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b8/8a/1d15e42496bef31246f7401aad1ebf0f9e11566ce0de41c18431715aafbc/pyobjc_framework_quartz-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b23d81c30c564adf6336e00b357f355b35aad10075dd7e837cfd52a9912863e5" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/32/a8/a3f84d06e567efc12c104799c7fd015f9bea272a75f799eda8b79e8163c6/pyobjc_framework_quartz-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:07cbda78b4a8fcf3a2d96e047a2ff01f44e3e1820f46f0f4b3b6d77ff6ece07c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/76/ef/8c08d4f255bb3efe8806609d1f0b1ddd29684ab0f9ffb5e26d3ad7957b29/pyobjc_framework_quartz-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:39d02a3df4b5e3eee1e0da0fb150259476910d2a9aa638ab94153c24317a9561" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyperclip"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/30/23/2f0a3efc4d6a32f3b63cdff36cd398d9701d26cda58e3ab97ac79fb5e60d/pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310" }
|
||||
|
||||
[[package]]
|
||||
name = "pystray"
|
||||
version = "0.19.5"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "pillow" },
|
||||
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "python-xlib", marker = "sys_platform == 'linux'" },
|
||||
{ name = "six" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5c/64/927a4b9024196a4799eba0180e0ca31568426f258a4a5c90f87a97f51d28/pystray-0.19.5-py2.py3-none-any.whl", hash = "sha256:a0c2229d02cf87207297c22d86ffc57c86c227517b038c0d3c59df79295ac617" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-xlib"
|
||||
version = "0.33"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "311"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274" },
|
||||
]
|
Loading…
Reference in New Issue
Block a user