107 lines
3.1 KiB
Python
107 lines
3.1 KiB
Python
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()
|