Skip to content

Commit 6d482bb

Browse files
committed
优化UIA置顶判断&新增主窗口置顶
1 parent 7916f39 commit 6d482bb

6 files changed

Lines changed: 243 additions & 1 deletion

File tree

CHANGELOG/v2.3.0-beta.1/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ v2.3 - Shirako (砂狼白子) beta 1
66

77
- 新增 **置顶模式**,支持UIA置顶
88
- 新增 **UIAccess提权**,自动重启生效
9+
- 新增 **主窗口置顶**,基础设置支持主窗口置顶配置
910

1011
## 💡 功能优化
1112

1213
- 优化 **UIA置顶**,统一封装DLL调用
1314
- 优化 **UIA置顶**,切换后支持提权重启
1415
- 优化 **UIA置顶**,提权后主界面显示再重启
16+
- 优化 **UIA置顶**,若已在UIA环境则无需重启
1517

1618
## 🐛 修复问题
1719

app/Language/modules/basic_settings.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,35 @@
2727
"description": "设置软件启动时是否自动显示主窗口",
2828
"switchbutton_name": {"enable": "", "disable": ""},
2929
},
30+
"main_window_topmost_mode": {
31+
"name": "主窗口置顶",
32+
"description": "选择主窗口置顶方式(UIA置顶需以管理员运行)",
33+
"combo_items": ["关闭置顶", "置顶", "UIA置顶"],
34+
},
35+
"uia_topmost_restart_dialog_title": {
36+
"name": "需要重启",
37+
"description": "UIA置顶切换后重启提示标题",
38+
},
39+
"uia_topmost_restart_dialog_content": {
40+
"name": "已切换为UIA置顶模式,需要重启生效,是否立即重启?",
41+
"description": "UIA置顶切换后重启提示内容",
42+
},
43+
"uia_topmost_disable_restart_dialog_content": {
44+
"name": "已关闭UIA置顶模式,需要完全退出软件后重新启动才会生效",
45+
"description": "关闭UIA置顶后提示内容",
46+
},
47+
"uia_topmost_disable_restart_dialog_ok_btn": {
48+
"name": "知道了",
49+
"description": "关闭UIA置顶后提示按钮文本",
50+
},
51+
"uia_topmost_restart_dialog_restart_btn": {
52+
"name": "重启",
53+
"description": "UIA置顶切换后重启按钮文本",
54+
},
55+
"uia_topmost_restart_dialog_cancel_btn": {
56+
"name": "取消",
57+
"description": "UIA置顶切换后取消按钮文本",
58+
},
3059
"background_resident": {
3160
"name": "后台驻留",
3261
"description": "关闭所有窗口后是否仍在后台常驻",
@@ -384,6 +413,35 @@
384413
"name": "Show splash screen",
385414
"description": "Set whether to show the splash screen on boot",
386415
},
416+
"main_window_topmost_mode": {
417+
"name": "Main window topmost",
418+
"description": "Select main window topmost mode (UIA requires run as administrator)",
419+
"combo_items": ["Disable topmost", "Topmost", "UIA topmost"],
420+
},
421+
"uia_topmost_restart_dialog_title": {
422+
"name": "Restart Required",
423+
"description": "Restart dialog title after switching UIA topmost",
424+
},
425+
"uia_topmost_restart_dialog_content": {
426+
"name": "UIA topmost mode has been enabled. Restart now to apply changes?",
427+
"description": "Restart dialog content after switching UIA topmost",
428+
},
429+
"uia_topmost_disable_restart_dialog_content": {
430+
"name": "UIA topmost mode has been disabled. Fully exit the app and relaunch to apply.",
431+
"description": "Hint content after disabling UIA topmost",
432+
},
433+
"uia_topmost_disable_restart_dialog_ok_btn": {
434+
"name": "OK",
435+
"description": "OK button text after disabling UIA topmost",
436+
},
437+
"uia_topmost_restart_dialog_restart_btn": {
438+
"name": "Restart",
439+
"description": "Restart button text after switching UIA topmost",
440+
},
441+
"uia_topmost_restart_dialog_cancel_btn": {
442+
"name": "Cancel",
443+
"description": "Cancel button text after switching UIA topmost",
444+
},
387445
"export_diagnostic_data": {
388446
"name": "Export diagnostic data",
389447
"description": "Export Diagnostic Information on Exit",

app/tools/settings_default_storage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"simplified_mode": {"default_value": True},
3333
"autostart": {"default_value": False},
3434
"show_startup_window": {"default_value": True},
35+
"main_window_topmost_mode": {"default_value": 0},
3536
"background_resident": {"default_value": True},
3637
"auto_save_window_size": {"default_value": True},
3738
"url_protocol": {"default_value": False},

app/view/main/window.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from loguru import logger
55
from PySide6.QtWidgets import QApplication, QWidget
66
from PySide6.QtGui import QIcon
7-
from PySide6.QtCore import QTimer, QEvent, Signal, QThreadPool, QRunnable
7+
from PySide6.QtCore import QTimer, QEvent, Signal, QThreadPool, QRunnable, Qt
88
from qfluentwidgets import FluentWindow, NavigationItemPosition
99

1010
from app.common.IPC_URL.csharp_ipc_handler import CSharpIPCHandler
@@ -146,6 +146,37 @@ def _setup_window_properties(self):
146146
QIcon(str(get_data_path("assets/icon", "secrandom-icon-paper.png")))
147147
)
148148
self._position_window()
149+
self._setup_general_settings_listener()
150+
self._apply_topmost_mode()
151+
152+
def _setup_general_settings_listener(self):
153+
"""设置通用设置监听器"""
154+
from app.tools.settings_access import get_settings_signals
155+
156+
get_settings_signals().settingChanged.connect(self._on_general_setting_changed)
157+
158+
def _on_general_setting_changed(self, first, second, value):
159+
"""处理通用设置变更"""
160+
if first == "basic_settings" and second == "main_window_topmost_mode":
161+
self._apply_topmost_mode(value)
162+
163+
def _apply_topmost_mode(self, mode=None):
164+
"""应用主窗口置顶模式"""
165+
if mode is None:
166+
mode = (
167+
readme_settings_async("basic_settings", "main_window_topmost_mode") or 0
168+
)
169+
170+
mode = int(mode)
171+
flags = self.windowFlags()
172+
if mode != 0:
173+
flags |= Qt.WindowStaysOnTopHint
174+
else:
175+
flags &= ~Qt.WindowStaysOnTopHint
176+
177+
self.setWindowFlags(flags)
178+
if self.isVisible():
179+
self.show()
149180

150181
def _setup_url_handler(self):
151182
"""设置URL处理器"""

app/view/settings/basic_settings.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
# ==================================================
44

55
from loguru import logger
6+
import os
7+
import ctypes
68
from PySide6.QtWidgets import QWidget, QVBoxLayout, QApplication
79
from PySide6.QtGui import QFontDatabase
10+
from PySide6.QtCore import QSignalBlocker
811
from qfluentwidgets import (
912
GroupHeaderCardWidget,
1013
SwitchButton,
@@ -15,8 +18,11 @@
1518
Theme,
1619
setTheme,
1720
setThemeColor,
21+
MessageBox,
1822
)
1923

24+
from app.tools.variable import EXIT_CODE_RESTART
25+
2026
from app.tools.personalised import get_theme_icon
2127
from app.tools.settings_access import readme_settings_async, update_settings
2228
from app.tools.settings_visibility_manager import is_setting_visible
@@ -41,6 +47,7 @@
4147
NotificationConfig,
4248
)
4349
from app.common.IPC_URL import URLIPCHandler
50+
from app.common.windows.uiaccess import is_uiaccess_process
4451
from app.page_building.another_window import (
4552
create_log_viewer_window,
4653
create_backup_manager_window,
@@ -150,6 +157,19 @@ def __init__(self, parent=None):
150157
self.__on_auto_save_window_size_changed
151158
)
152159

160+
# 主窗口置顶设置
161+
self.main_window_topmost_mode_combo_box = ComboBox()
162+
self.main_window_topmost_mode_combo_box.addItems(
163+
get_content_combo_name_async("basic_settings", "main_window_topmost_mode")
164+
)
165+
topmost_mode = readme_settings_async(
166+
"basic_settings", "main_window_topmost_mode"
167+
)
168+
self.main_window_topmost_mode_combo_box.setCurrentIndex(int(topmost_mode or 0))
169+
self.main_window_topmost_mode_combo_box.currentIndexChanged.connect(
170+
self.__on_main_window_topmost_mode_changed
171+
)
172+
153173
# 后台驻留设置
154174
self.resident_switch = SwitchButton()
155175
self.resident_switch.setOffText(
@@ -215,6 +235,15 @@ def __init__(self, parent=None):
215235
),
216236
self.auto_save_window_size_switch,
217237
)
238+
if is_setting_visible("basic_settings", "main_window_topmost_mode"):
239+
self.addGroup(
240+
get_theme_icon("ic_fluent_pin_20_filled"),
241+
get_content_name_async("basic_settings", "main_window_topmost_mode"),
242+
get_content_description_async(
243+
"basic_settings", "main_window_topmost_mode"
244+
),
245+
self.main_window_topmost_mode_combo_box,
246+
)
218247
if is_setting_visible("basic_settings", "background_resident"):
219248
self.addGroup(
220249
get_theme_icon("ic_fluent_resize_20_filled"),
@@ -388,6 +417,107 @@ def __on_auto_save_window_size_changed(self, checked):
388417
parent=self.window(),
389418
)
390419

420+
def __on_main_window_topmost_mode_changed(self, index):
421+
previous_index = int(
422+
readme_settings_async("basic_settings", "main_window_topmost_mode") or 0
423+
)
424+
425+
# 如果当前已经是 UIA 进程,切换到 UIA 模式不需要重启
426+
if index == 2 and is_uiaccess_process():
427+
update_settings("basic_settings", "main_window_topmost_mode", index)
428+
return
429+
430+
# 如果当前是 UIA 进程,且浮窗也是 UIA 模式,切换回非 UIA 模式不需要重启(因为进程必须保持 UIA)
431+
if previous_index == 2 and index != 2 and is_uiaccess_process():
432+
floating_mode = int(
433+
readme_settings_async(
434+
"floating_window_management", "floating_window_topmost_mode"
435+
)
436+
or 0
437+
)
438+
if floating_mode == 2:
439+
update_settings("basic_settings", "main_window_topmost_mode", index)
440+
return
441+
442+
if previous_index == 2 and index != 2:
443+
dialog = MessageBox(
444+
get_content_name_async(
445+
"basic_settings", "uia_topmost_restart_dialog_title"
446+
),
447+
get_content_name_async(
448+
"basic_settings",
449+
"uia_topmost_disable_restart_dialog_content",
450+
),
451+
self.window(),
452+
)
453+
dialog.yesButton.setText(
454+
get_content_name_async(
455+
"basic_settings",
456+
"uia_topmost_disable_restart_dialog_ok_btn",
457+
)
458+
)
459+
dialog.cancelButton.setText(
460+
get_content_name_async(
461+
"basic_settings",
462+
"uia_topmost_restart_dialog_cancel_btn",
463+
)
464+
)
465+
if dialog.exec():
466+
update_settings("basic_settings", "main_window_topmost_mode", index)
467+
else:
468+
blocker = QSignalBlocker(self.main_window_topmost_mode_combo_box)
469+
self.main_window_topmost_mode_combo_box.setCurrentIndex(previous_index)
470+
del blocker
471+
return
472+
473+
if index == 2 and previous_index != 2:
474+
dialog = MessageBox(
475+
get_content_name_async(
476+
"basic_settings", "uia_topmost_restart_dialog_title"
477+
),
478+
get_content_name_async(
479+
"basic_settings", "uia_topmost_restart_dialog_content"
480+
),
481+
self.window(),
482+
)
483+
dialog.yesButton.setText(
484+
get_content_name_async(
485+
"basic_settings",
486+
"uia_topmost_restart_dialog_restart_btn",
487+
)
488+
)
489+
dialog.cancelButton.setText(
490+
get_content_name_async(
491+
"basic_settings",
492+
"uia_topmost_restart_dialog_cancel_btn",
493+
)
494+
)
495+
if dialog.exec():
496+
update_settings("basic_settings", "main_window_topmost_mode", index)
497+
try:
498+
from app.common.windows.uiaccess import (
499+
ELEVATE_RESTART_ENV,
500+
UIACCESS_RESTART_ENV,
501+
)
502+
503+
os.environ[str(UIACCESS_RESTART_ENV)] = "1"
504+
try:
505+
is_admin = bool(ctypes.windll.shell32.IsUserAnAdmin())
506+
except Exception:
507+
is_admin = False
508+
if not is_admin:
509+
os.environ[str(ELEVATE_RESTART_ENV)] = "1"
510+
except Exception:
511+
pass
512+
QApplication.exit(EXIT_CODE_RESTART)
513+
else:
514+
blocker = QSignalBlocker(self.main_window_topmost_mode_combo_box)
515+
self.main_window_topmost_mode_combo_box.setCurrentIndex(previous_index)
516+
del blocker
517+
return
518+
519+
update_settings("basic_settings", "main_window_topmost_mode", index)
520+
391521
def __on_url_protocol_changed(self, checked):
392522
"""URL协议开关变化处理"""
393523
# 临时断开信号连接,避免递归

app/view/settings/floating_window_management.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from app.tools.settings_access import *
1919
from app.Language.obtain_language import *
2020
from app.tools.settings_visibility_manager import is_setting_visible
21+
from app.common.windows.uiaccess import is_uiaccess_process
2122

2223

2324
# ==================================================
@@ -296,6 +297,25 @@ def floating_window_topmost_mode_combo_box_changed(self, index):
296297
"floating_window_management", "floating_window_topmost_mode"
297298
)
298299
)
300+
301+
# 如果当前已经是 UIA 进程,切换到 UIA 模式不需要重启
302+
if index == 2 and is_uiaccess_process():
303+
update_settings(
304+
"floating_window_management", "floating_window_topmost_mode", index
305+
)
306+
return
307+
308+
# 如果当前是 UIA 进程,且主窗口也是 UIA 模式,切换回非 UIA 模式不需要重启(因为进程必须保持 UIA)
309+
if previous_index == 2 and index != 2 and is_uiaccess_process():
310+
main_window_mode = int(
311+
readme_settings_async("basic_settings", "main_window_topmost_mode") or 0
312+
)
313+
if main_window_mode == 2:
314+
update_settings(
315+
"floating_window_management", "floating_window_topmost_mode", index
316+
)
317+
return
318+
299319
if previous_index == 2 and index != 2:
300320
dialog = MessageBox(
301321
get_content_name_async(

0 commit comments

Comments
 (0)