修复依赖缺失问题

This commit is contained in:
2026-04-08 10:11:16 +08:00
parent 535b83cd56
commit cd4ffbfa90
4 changed files with 193 additions and 244 deletions
+116 -12
View File
@@ -1,7 +1,11 @@
import sys
import os
import time
from PyQt6.QtCore import Qt
import ctypes
import ctypes.wintypes
import subprocess
import importlib.util
from PyQt6.QtCore import Qt, QAbstractNativeEventFilter
from PyQt6.QtWidgets import QApplication
from PyQt6.QtGui import QIcon
from db.database import init_db
@@ -12,6 +16,111 @@ from db import database
from app_info import __VERSION__, app_title
def _install_requirements_on_startup() -> None:
"""
启动时仅在“检测到缺失依赖”时安装 requirements。
失败时不阻塞主程序启动。
"""
if getattr(sys, "frozen", False):
# 打包态通常无法通过当前 exe 直接调用 pip,直接跳过
return
req_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "requirements.txt")
if not os.path.exists(req_path):
return
module_name_map = {
"pyqt6": "PyQt6",
"pillow": "PIL",
}
def _req_name(req_line: str) -> str:
s = req_line.split(";", 1)[0].strip()
s = s.split("[", 1)[0]
for sep in ("==", ">=", "<=", "~=", "!=", ">", "<"):
if sep in s:
s = s.split(sep, 1)[0].strip()
break
return s
missing_reqs: list[str] = []
try:
with open(req_path, "r", encoding="utf-8") as f:
for raw in f:
line = raw.strip()
if not line or line.startswith("#"):
continue
pkg_name = _req_name(line)
if not pkg_name:
continue
mod_name = module_name_map.get(pkg_name.lower(), pkg_name)
if importlib.util.find_spec(mod_name) is None:
missing_reqs.append(line)
except Exception:
return
if not missing_reqs:
return
cmd = [sys.executable, "-m", "pip", "install", *missing_reqs]
kwargs = {
"stdout": subprocess.DEVNULL,
"stderr": subprocess.DEVNULL,
"check": False,
}
if sys.platform == "win32":
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
try:
subprocess.run(cmd, **kwargs)
except Exception:
pass
class GlobalHotkey(QAbstractNativeEventFilter):
"""用 Win32 RegisterHotKey 注册真正的全局快捷键 Alt+`"""
_HOTKEY_ID = 0x4E4D # 任意唯一 ID
_MOD_ALT = 0x0001
_VK_BACKTICK = 0xC0 # VK_OEM_3 = ` 键
def __init__(self, panel: "PanelWindow", ball: "FloatBall"):
super().__init__()
self._panel = panel
self._ball = ball
self._hwnd = None
def install(self):
if sys.platform != "win32":
return
# 用一个隐藏窗口的 HWND 来接收 WM_HOTKEY
self._hwnd = int(self._panel.winId())
ctypes.windll.user32.RegisterHotKey(
self._hwnd, self._HOTKEY_ID, self._MOD_ALT, self._VK_BACKTICK
)
QApplication.instance().installNativeEventFilter(self)
def uninstall(self):
if self._hwnd:
ctypes.windll.user32.UnregisterHotKey(self._hwnd, self._HOTKEY_ID)
QApplication.instance().removeNativeEventFilter(self)
def nativeEventFilter(self, event_type, message):
if event_type == b"windows_generic_MSG":
import ctypes
msg = ctypes.cast(int(message), ctypes.POINTER(ctypes.wintypes.MSG)).contents
WM_HOTKEY = 0x0312
if msg.message == WM_HOTKEY and msg.wParam == self._HOTKEY_ID:
self._toggle()
return False, 0
def _toggle(self):
if self._panel.isVisible():
self._panel.minimize_to_ball()
else:
self._panel.show_near(self._ball.pos(), BALL_SIZE)
self._panel.raise_()
self._panel.activateWindow()
# ===================== 打包兼容核心函数 =====================
def get_resource_path(relative_path):
"""
@@ -77,7 +186,9 @@ def _wake_existing_or_exit() -> bool:
def main():
if not _wake_existing_or_exit():
return
_install_requirements_on_startup()
QApplication.setAttribute(Qt.ApplicationAttribute.AA_Use96Dpi, True)
try:
QApplication.setHighDpiScaleFactorRoundingPolicy(
@@ -111,16 +222,9 @@ def main():
ball.clicked.connect(lambda: panel.show_near(ball.pos(), BALL_SIZE))
ball.right_clicked.connect(lambda pos: panel.tray.contextMenu().exec(pos))
# 全局快捷键 Alt+` 唤醒/隐藏主界面
from PyQt6.QtGui import QShortcut, QKeySequence
_shortcut = QShortcut(QKeySequence("Alt+`"), panel)
_shortcut.setContext(Qt.ShortcutContext.ApplicationShortcut)
def _toggle_panel():
if panel.isVisible():
panel.minimize_to_ball()
else:
panel.show_near(ball.pos(), BALL_SIZE)
_shortcut.activated.connect(_toggle_panel)
# 全局快捷键 Alt+` 唤醒/隐藏主界面(Win32 RegisterHotKey,真正全局)
_hotkey = GlobalHotkey(panel, ball)
_hotkey.install()
has_saved = bool(database.get_setting("panel_x", ""))
if has_saved: