Skip to content

Commit 29ddb01

Browse files
committed
新增URL&IPC功能(基础)
1 parent 6dbfae5 commit 29ddb01

13 files changed

Lines changed: 2499 additions & 37 deletions

File tree

app/common/IPC_URL/__init__.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
URL和IPC混合体模块 - 跨平台通用实现
3+
支持Windows和Linux系统的URL协议注册和IPC通信
4+
5+
主要功能:
6+
1. URL协议注册管理(跨平台)
7+
2. IPC进程间通信
8+
3. 命令行参数处理
9+
4. 多实例检测和管理
10+
11+
快速开始:
12+
from app.common.IPC_URL import URLIPCHandler
13+
14+
# 创建处理器
15+
handler = URLIPCHandler("MyApp", "myapp")
16+
17+
# 注册协议
18+
handler.register_url_protocol()
19+
20+
# 启动IPC服务器
21+
handler.start_ipc_server()
22+
23+
# 注册消息处理器
24+
def handle_url(payload):
25+
print(f"收到URL: {payload.get('url', '')}")
26+
return {"status": "success"}
27+
28+
handler.register_message_handler('url', handle_url)
29+
30+
详细文档请参考:
31+
- README.md: 完整的使用文档
32+
- QUICK_START.md: 快速开始指南
33+
- example_usage.py: 使用示例
34+
"""
35+
36+
from .url_ipc_handler import URLIPCHandler
37+
from .protocol_manager import ProtocolManager
38+
from .url_command_handler import URLCommandHandler
39+
from .security_verifier import SecurityVerifier, SimplePasswordVerifier, DynamicPasswordVerifier, CompositeVerifier
40+
41+
__version__ = "1.0.0"
42+
__author__ = "SecRandom Team"
43+
__description__ = "跨平台URL协议注册和IPC通信处理器"
44+
45+
__all__ = ['URLIPCHandler', 'ProtocolManager', 'URLCommandHandler',
46+
'SecurityVerifier', 'SimplePasswordVerifier', 'DynamicPasswordVerifier', 'CompositeVerifier',
47+
'__version__', '__author__', '__description__']
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
"""
2+
协议管理器 - 跨平台URL协议注册管理
3+
支持Windows和Linux系统的自定义协议注册
4+
"""
5+
import os
6+
import sys
7+
import winreg
8+
import subprocess
9+
from pathlib import Path
10+
from typing import Optional
11+
from loguru import logger
12+
13+
14+
15+
class ProtocolManager:
16+
"""协议管理器 - 处理跨平台的URL协议注册"""
17+
18+
def __init__(self, app_name: str, protocol_name: str):
19+
"""
20+
初始化协议管理器
21+
22+
Args:
23+
app_name: 应用程序名称
24+
protocol_name: 自定义协议名称(不含://)
25+
"""
26+
self.app_name = app_name
27+
self.protocol_name = protocol_name
28+
self.is_windows = sys.platform.startswith('win')
29+
self.is_linux = sys.platform.startswith('linux')
30+
31+
def register_protocol(self) -> bool:
32+
"""
33+
注册自定义协议
34+
35+
Returns:
36+
注册成功返回True,失败返回False
37+
"""
38+
if self.is_windows:
39+
return self._register_windows_protocol()
40+
elif self.is_linux:
41+
return self._register_linux_protocol()
42+
else:
43+
logger.error(f"不支持的操作系统: {sys.platform}")
44+
return False
45+
46+
def unregister_protocol(self) -> bool:
47+
"""
48+
注销自定义协议
49+
50+
Returns:
51+
注销成功返回True,失败返回False
52+
"""
53+
if self.is_windows:
54+
return self._unregister_windows_protocol()
55+
elif self.is_linux:
56+
return self._unregister_linux_protocol()
57+
else:
58+
logger.error(f"不支持的操作系统: {sys.platform}")
59+
return False
60+
61+
def is_protocol_registered(self) -> bool:
62+
"""
63+
检查协议是否已注册
64+
65+
Returns:
66+
已注册返回True,未注册返回False
67+
"""
68+
if self.is_windows:
69+
return self._is_windows_protocol_registered()
70+
elif self.is_linux:
71+
return self._is_linux_protocol_registered()
72+
else:
73+
return False
74+
75+
def _register_windows_protocol(self) -> bool:
76+
"""Windows系统注册协议"""
77+
try:
78+
# 获取当前可执行文件路径
79+
exe_path = self._get_executable_path()
80+
81+
# 首先尝试注册到HKEY_CLASSES_ROOT
82+
try:
83+
# 注册协议到HKEY_CLASSES_ROOT
84+
with winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, self.protocol_name) as key:
85+
winreg.SetValueEx(key, "", 0, winreg.REG_SZ, f"URL:{self.app_name} Protocol")
86+
winreg.SetValueEx(key, "URL Protocol", 0, winreg.REG_SZ, "")
87+
88+
# 注册命令
89+
command_key_path = f"{self.protocol_name}\\shell\\open\\command"
90+
with winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, command_key_path) as key:
91+
winreg.SetValueEx(key, "", 0, winreg.REG_SZ, f'"{exe_path}" --url "%1"')
92+
93+
return True
94+
95+
except (OSError, PermissionError) as e:
96+
# 如果HKEY_CLASSES_ROOT失败,尝试注册到HKEY_CURRENT_USER
97+
if e.errno == 5 or "Access is denied" in str(e): # WinError 5
98+
logger.error(f"管理员权限不足,尝试注册到当前用户: {e}")
99+
return self._register_windows_protocol_current_user(exe_path)
100+
else:
101+
raise
102+
103+
except Exception as e:
104+
logger.error(f"Windows协议注册失败: {e}")
105+
return False
106+
107+
def _register_windows_protocol_current_user(self, exe_path: str) -> bool:
108+
"""注册到当前用户的Windows协议(无需管理员权限)"""
109+
try:
110+
# 注册协议到HKEY_CURRENT_USER\Software\Classes
111+
key_path = f"Software\\Classes\\{self.protocol_name}"
112+
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_path) as key:
113+
winreg.SetValueEx(key, "", 0, winreg.REG_SZ, f"URL:{self.app_name} Protocol")
114+
winreg.SetValueEx(key, "URL Protocol", 0, winreg.REG_SZ, "")
115+
116+
# 注册命令
117+
command_key_path = f"Software\\Classes\\{self.protocol_name}\\shell\\open\\command"
118+
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, command_key_path) as key:
119+
winreg.SetValueEx(key, "", 0, winreg.REG_SZ, f'"{exe_path}" --url "%1"')
120+
121+
return True
122+
123+
except Exception as e:
124+
logger.error(f"Windows当前用户协议注册失败: {e}")
125+
return False
126+
127+
def _unregister_windows_protocol(self) -> bool:
128+
"""Windows系统注销协议"""
129+
try:
130+
# 首先尝试删除系统级注册(HKEY_CLASSES_ROOT)
131+
try:
132+
winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"{self.protocol_name}\\shell\\open\\command")
133+
winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"{self.protocol_name}\\shell\\open")
134+
winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, f"{self.protocol_name}\\shell")
135+
winreg.DeleteKey(winreg.HKEY_CLASSES_ROOT, self.protocol_name)
136+
return True
137+
except (OSError, PermissionError) as e:
138+
if e.errno == 5 or "Access is denied" in str(e): # WinError 5
139+
# 如果系统级删除失败,尝试删除当前用户注册
140+
return self._unregister_windows_protocol_current_user()
141+
else:
142+
raise
143+
144+
except Exception as e:
145+
logger.error(f"Windows协议注销失败: {e}")
146+
return False
147+
148+
def _unregister_windows_protocol_current_user(self) -> bool:
149+
"""注销当前用户的Windows协议"""
150+
try:
151+
# 删除当前用户注册表项
152+
key_path = f"Software\\Classes\\{self.protocol_name}\\shell\\open\\command"
153+
winreg.DeleteKey(winreg.HKEY_CURRENT_USER, key_path)
154+
winreg.DeleteKey(winreg.HKEY_CURRENT_USER, f"Software\\Classes\\{self.protocol_name}\\shell\\open")
155+
winreg.DeleteKey(winreg.HKEY_CURRENT_USER, f"Software\\Classes\\{self.protocol_name}\\shell")
156+
winreg.DeleteKey(winreg.HKEY_CURRENT_USER, f"Software\\Classes\\{self.protocol_name}")
157+
return True
158+
159+
except Exception as e:
160+
logger.error(f"Windows当前用户协议注销失败: {e}")
161+
return False
162+
163+
def _is_windows_protocol_registered(self) -> bool:
164+
"""检查Windows协议是否已注册"""
165+
try:
166+
# 首先检查HKEY_CLASSES_ROOT(系统级)
167+
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, self.protocol_name) as key:
168+
return True
169+
except (OSError, FileNotFoundError):
170+
try:
171+
# 如果没找到,检查HKEY_CURRENT_USER(用户级)
172+
key_path = f"Software\\Classes\\{self.protocol_name}"
173+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path) as key:
174+
return True
175+
except (OSError, FileNotFoundError):
176+
return False
177+
178+
def _register_linux_protocol(self) -> bool:
179+
"""Linux系统注册协议"""
180+
try:
181+
# 获取当前可执行文件路径
182+
exe_path = self._get_executable_path()
183+
184+
# 创建.desktop文件
185+
desktop_file_path = self._get_linux_desktop_file_path()
186+
desktop_content = self._generate_linux_desktop_file(exe_path)
187+
188+
# 写入.desktop文件
189+
with open(desktop_file_path, 'w', encoding='utf-8') as f:
190+
f.write(desktop_content)
191+
192+
# 设置可执行权限
193+
os.chmod(desktop_file_path, 0o755)
194+
195+
# 注册协议处理程序
196+
self._register_linux_mime_handler()
197+
198+
# 更新桌面数据库
199+
self._update_linux_desktop_database()
200+
201+
return True
202+
203+
except Exception as e:
204+
logger.error(f"Linux协议注册失败: {e}")
205+
return False
206+
207+
def _unregister_linux_protocol(self) -> bool:
208+
"""Linux系统注销协议"""
209+
try:
210+
desktop_file_path = self._get_linux_desktop_file_path()
211+
212+
if os.path.exists(desktop_file_path):
213+
os.remove(desktop_file_path)
214+
215+
# 更新桌面数据库
216+
self._update_linux_desktop_database()
217+
218+
return True
219+
220+
except Exception as e:
221+
logger.error(f"Linux协议注销失败: {e}")
222+
return False
223+
224+
def _is_linux_protocol_registered(self) -> bool:
225+
"""检查Linux协议是否已注册"""
226+
desktop_file_path = self._get_linux_desktop_file_path()
227+
return os.path.exists(desktop_file_path)
228+
229+
def _get_linux_desktop_file_path(self) -> str:
230+
"""获取Linux桌面文件路径"""
231+
applications_dir = Path.home() / '.local' / 'share' / 'applications'
232+
applications_dir.mkdir(parents=True, exist_ok=True)
233+
return str(applications_dir / f"{self.app_name.lower()}-url-handler.desktop")
234+
235+
def _generate_linux_desktop_file(self, exe_path: str) -> str:
236+
"""生成Linux桌面文件内容"""
237+
return f"""[Desktop Entry]
238+
Name={self.app_name} URL Handler
239+
Comment=Handle {self.protocol_name}:// URLs for {self.app_name}
240+
Exec={exe_path} --url %u
241+
Icon={self.app_name.lower()}
242+
Terminal=false
243+
Type=Application
244+
Categories=Utility;
245+
MimeType=x-scheme-handler/{self.protocol_name};
246+
"""
247+
248+
def _register_linux_mime_handler(self):
249+
"""注册Linux MIME处理程序"""
250+
try:
251+
# 使用xdg-mime注册协议处理程序
252+
subprocess.run([
253+
'xdg-mime', 'default',
254+
f'{self.app_name.lower()}-url-handler.desktop',
255+
f'x-scheme-handler/{self.protocol_name}'
256+
], check=True, capture_output=True)
257+
except (subprocess.CalledProcessError, FileNotFoundError):
258+
# 如果xdg-mime不可用,尝试直接创建mimeapps.list
259+
self._create_linux_mimeapps_list()
260+
261+
def _create_linux_mimeapps_list(self):
262+
"""创建Linux mimeapps.list文件"""
263+
config_dir = Path.home() / '.config'
264+
config_dir.mkdir(parents=True, exist_ok=True)
265+
266+
mimeapps_file = config_dir / 'mimeapps.list'
267+
content = f"""[Default Applications]
268+
x-scheme-handler/{self.protocol_name}={self.app_name.lower()}-url-handler.desktop
269+
270+
[Added Associations]
271+
x-scheme-handler/{self.protocol_name}={self.app_name.lower()}-url-handler.desktop
272+
"""
273+
274+
with open(mimeapps_file, 'a', encoding='utf-8') as f:
275+
f.write(content)
276+
277+
def _update_linux_desktop_database(self):
278+
"""更新Linux桌面数据库"""
279+
try:
280+
applications_dir = Path.home() / '.local' / 'share' / 'applications'
281+
subprocess.run([
282+
'update-desktop-database', str(applications_dir)
283+
], check=True, capture_output=True)
284+
except (subprocess.CalledProcessError, FileNotFoundError):
285+
# 如果update-desktop-database不可用,忽略错误
286+
pass
287+
288+
def _get_executable_path(self) -> str:
289+
"""获取当前可执行文件路径"""
290+
if getattr(sys, 'frozen', False):
291+
# PyInstaller打包后的可执行文件
292+
exe_path = sys.executable
293+
else:
294+
# 普通Python脚本
295+
exe_path = sys.argv[0]
296+
297+
return os.path.abspath(exe_path)

0 commit comments

Comments
 (0)