修复依赖缺失问题
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user