import sys import time from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QApplication from db.database import init_db from ui.dock import PanelWindow, _set_autostart from ui.ball import FloatBall, BALL_SIZE import ui.theme as theme from db import database def _wake_existing_or_exit() -> bool: """ Windows 单实例: - 若已有进程在运行:唤醒已有窗口并返回 False(当前进程退出) - 若无已有进程:返回 True(继续正常启动) """ if sys.platform != "win32": return True import ctypes import ctypes.wintypes mutex_name = r"Global\CleanDesktopOrganizerSingleton" title = "牛马软件柜" kernel32 = ctypes.windll.kernel32 user32 = ctypes.windll.user32 ERROR_ALREADY_EXISTS = 183 h_mutex = kernel32.CreateMutexW(None, False, mutex_name) if not h_mutex: return True last_err = kernel32.GetLastError() if last_err == ERROR_ALREADY_EXISTS: # 轮询一小段时间,避免首次窗口尚未创建 hwnd = None for _ in range(8): # ~2.4s hwnd = user32.FindWindowW(None, title) if hwnd: break time.sleep(0.3) if hwnd: # SW_SHOW = 5 user32.ShowWindow(hwnd, 5) user32.SetForegroundWindow(hwnd) return False return True def main(): # 必须在创建 QApplication 之前:不按各显示器缩放,逻辑像素固定(跨分辨率/跨屏拖动宽高保持一致) if not _wake_existing_or_exit(): return QApplication.setAttribute(Qt.ApplicationAttribute.AA_Use96Dpi, True) # 这个策略同样要求在创建 QApplication 前设置 try: QApplication.setHighDpiScaleFactorRoundingPolicy( Qt.HighDpiScaleFactorRoundingPolicy.PassThrough ) except Exception: pass app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) init_db() theme.load() # 从数据库读取上次主题 _set_autostart(True) panel = PanelWindow() ball = FloatBall() panel._ball_ref = ball ball.clicked.connect(lambda: panel.show_near(ball.pos(), BALL_SIZE)) ball.right_clicked.connect(lambda pos: panel.tray.contextMenu().exec(pos)) # 判断是否有保存的位置:有则直接在原位显示,没有则用悬浮球旁边 has_saved = bool(database.get_setting("panel_x", "")) if has_saved: # _restore_geometry 已在 PanelWindow.__init__ 里执行,直接 show panel.setWindowOpacity(0) panel.show() panel.raise_() if hasattr(panel, "_ball_ref"): panel._ball_ref.hide() # 淡入 from PyQt6.QtCore import QPropertyAnimation anim = QPropertyAnimation(panel, b"windowOpacity") anim.setDuration(180) anim.setStartValue(0.0) anim.setEndValue(1.0) anim.start() panel._anim = anim else: ball._place_default() panel.show_near(ball.pos(), BALL_SIZE) sys.exit(app.exec()) if __name__ == "__main__": main()