Skip to content

Commit 4eac4b5

Browse files
committed
UIA功能!
1 parent b31ee90 commit 4eac4b5

12 files changed

Lines changed: 547 additions & 27 deletions

File tree

CHANGELOG/v2.2.6/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<img width="1800" height="766" alt="新版本" src="https://github.com/SECTL/SecRandom/blob/master/data/assets/icon/secrandom-beta.png" />
1+
<img width="1800" height="766" alt="新版本" src="https://github.com/SECTL/SecRandom/blob/master/data/assets/icon/secrandom-release.png" />
22

33
v2.0 - Koharu(小鸟游星野) release 5
44

CHANGELOG/v2.2.7/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<img width="1800" height="766" alt="新版本" src="https://github.com/SECTL/SecRandom/blob/master/data/assets/icon/secrandom-beta.png" />
1+
<img width="1800" height="766" alt="新版本" src="https://github.com/SECTL/SecRandom/blob/master/data/assets/icon/secrandom-release.png" />
22

33
v2.0 - Koharu(小鸟游星野) release 6
44

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ v2.3 - Shirako (砂狼白子) beta 1
44

55
## 🚀 主要更新
66

7-
-
7+
- 新增 **置顶模式**,支持UIA置顶
8+
- 新增 **UIAccess提权**,自动重启生效
89

910
## 💡 功能优化
1011

11-
-
12+
- 优化 **UIA置顶**,统一封装DLL调用
13+
- 优化 **UIA置顶**,切换后询问重启
1214

1315
## 🐛 修复问题
1416

app/Language/modules/sidebar_tray_management.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,27 @@
1717
"name": "浮窗透明度",
1818
"description": "调整浮窗透明度",
1919
},
20+
"floating_window_topmost_mode": {
21+
"name": "置顶模式",
22+
"description": "选择浮窗置顶方式(UIA置顶需以管理员运行)",
23+
"combo_items": ["关闭置顶", "置顶", "UIA置顶"],
24+
},
25+
"uia_topmost_restart_dialog_title": {
26+
"name": "需要重启",
27+
"description": "UIA置顶切换后重启提示标题",
28+
},
29+
"uia_topmost_restart_dialog_content": {
30+
"name": "已切换为UIA置顶模式,需要重启生效,是否立即重启?",
31+
"description": "UIA置顶切换后重启提示内容",
32+
},
33+
"uia_topmost_restart_dialog_restart_btn": {
34+
"name": "重启",
35+
"description": "UIA置顶切换后重启按钮文本",
36+
},
37+
"uia_topmost_restart_dialog_cancel_btn": {
38+
"name": "稍后",
39+
"description": "UIA置顶切换后取消按钮文本",
40+
},
2041
"reset_floating_window_position_button": {
2142
"name": "重置浮窗位置",
2243
"description": "将浮窗位置重置为默认位置",
@@ -108,6 +129,27 @@
108129
"name": "Floating window transparency",
109130
"description": "Adjust floating window transparency",
110131
},
132+
"floating_window_topmost_mode": {
133+
"name": "Topmost mode",
134+
"description": "Select floating window topmost mode (UIA requires run as administrator)",
135+
"combo_items": ["Disable topmost", "Topmost", "UIA topmost"],
136+
},
137+
"uia_topmost_restart_dialog_title": {
138+
"name": "Restart Required",
139+
"description": "Restart dialog title after switching UIA topmost",
140+
},
141+
"uia_topmost_restart_dialog_content": {
142+
"name": "UIA topmost mode has been enabled. Restart now to apply changes?",
143+
"description": "Restart dialog content after switching UIA topmost",
144+
},
145+
"uia_topmost_restart_dialog_restart_btn": {
146+
"name": "Restart",
147+
"description": "Restart button text after switching UIA topmost",
148+
},
149+
"uia_topmost_restart_dialog_cancel_btn": {
150+
"name": "Later",
151+
"description": "Cancel button text after switching UIA topmost",
152+
},
111153
"reset_floating_window_position_button": {
112154
"name": "Reset floating window position",
113155
"description": "Reset floating window to default position",

app/common/windows/uiaccess.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import os
2+
import sys
3+
import ctypes
4+
from ctypes import wintypes
5+
from subprocess import list2cmdline
6+
from app.tools.path_utils import get_data_path
7+
8+
from loguru import logger
9+
10+
_uiaccess_dll = None
11+
_user32 = None
12+
_kernel32 = None
13+
14+
UIACCESS_RESTART_ENV = "SECRANDOM_RESTART_UIACCESS"
15+
16+
17+
def _is_windows() -> bool:
18+
return os.name == "nt"
19+
20+
21+
def _get_kernel32():
22+
global _kernel32
23+
if _kernel32 is not None:
24+
return _kernel32
25+
_kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
26+
_kernel32.ProcessIdToSessionId.argtypes = [
27+
wintypes.DWORD,
28+
ctypes.POINTER(wintypes.DWORD),
29+
]
30+
_kernel32.ProcessIdToSessionId.restype = wintypes.BOOL
31+
return _kernel32
32+
33+
34+
def _get_user32():
35+
global _user32
36+
if _user32 is not None:
37+
return _user32
38+
_user32 = ctypes.WinDLL("user32", use_last_error=True)
39+
try:
40+
fn = _user32.SetWindowBand
41+
fn.argtypes = [wintypes.HWND, wintypes.HWND, wintypes.DWORD]
42+
fn.restype = wintypes.BOOL
43+
except Exception:
44+
pass
45+
_user32.SetWindowPos.argtypes = [
46+
wintypes.HWND,
47+
wintypes.HWND,
48+
ctypes.c_int,
49+
ctypes.c_int,
50+
ctypes.c_int,
51+
ctypes.c_int,
52+
wintypes.UINT,
53+
]
54+
_user32.SetWindowPos.restype = wintypes.BOOL
55+
return _user32
56+
57+
58+
def _get_uiaccess_dll():
59+
global _uiaccess_dll
60+
if _uiaccess_dll is not None:
61+
return _uiaccess_dll
62+
dll_path = get_data_path("dlls", "uiaccess.dll")
63+
_uiaccess_dll = ctypes.WinDLL(str(dll_path), use_last_error=True)
64+
65+
_uiaccess_dll.IsUIAccess.argtypes = []
66+
_uiaccess_dll.IsUIAccess.restype = wintypes.BOOL
67+
68+
_uiaccess_dll.StartUIAccessProcess.argtypes = [
69+
wintypes.LPCWSTR,
70+
wintypes.LPCWSTR,
71+
wintypes.DWORD,
72+
ctypes.POINTER(wintypes.DWORD),
73+
wintypes.DWORD,
74+
]
75+
_uiaccess_dll.StartUIAccessProcess.restype = wintypes.BOOL
76+
77+
return _uiaccess_dll
78+
79+
80+
def is_uiaccess_process() -> bool:
81+
if not _is_windows():
82+
return False
83+
try:
84+
dll = _get_uiaccess_dll()
85+
return bool(dll.IsUIAccess())
86+
except Exception:
87+
return False
88+
89+
90+
def start_uiaccess_process(cmd_list: list[str]) -> int:
91+
if not _is_windows():
92+
return 0
93+
if not cmd_list:
94+
return 0
95+
96+
try:
97+
kernel32 = _get_kernel32()
98+
pid = wintypes.DWORD(os.getpid())
99+
session = wintypes.DWORD(0)
100+
if not kernel32.ProcessIdToSessionId(pid, ctypes.byref(session)):
101+
err = ctypes.get_last_error()
102+
logger.debug("获取 SessionId 失败: {}", err)
103+
return 0
104+
105+
cmd_line = list2cmdline(list(cmd_list))
106+
107+
dll = _get_uiaccess_dll()
108+
out_pid = wintypes.DWORD(0)
109+
ctypes.set_last_error(0)
110+
ok = bool(
111+
dll.StartUIAccessProcess(
112+
None,
113+
cmd_line,
114+
0,
115+
ctypes.byref(out_pid),
116+
session.value,
117+
)
118+
)
119+
if not ok:
120+
err = ctypes.get_last_error()
121+
logger.debug("启动 UIAccess 进程失败: {}", err)
122+
return 0
123+
124+
logger.debug("已启动 UIAccess 进程: pid={}", int(out_pid.value))
125+
return int(out_pid.value)
126+
except Exception as e:
127+
logger.debug("启动 UIAccess 进程异常: {}", e)
128+
return 0
129+
130+
131+
def ensure_uiaccess_for_current_process() -> bool:
132+
if not _is_windows():
133+
return False
134+
if is_uiaccess_process():
135+
return False
136+
137+
try:
138+
kernel32 = _get_kernel32()
139+
pid = wintypes.DWORD(os.getpid())
140+
session = wintypes.DWORD(0)
141+
if not kernel32.ProcessIdToSessionId(pid, ctypes.byref(session)):
142+
err = ctypes.get_last_error()
143+
logger.debug("获取 SessionId 失败: {}", err)
144+
return False
145+
146+
if getattr(sys, "frozen", False):
147+
cmd_list = [sys.executable] + (sys.argv[1:] if sys.argv else [])
148+
else:
149+
argv = list(sys.argv) if sys.argv else []
150+
if argv:
151+
cmd_list = [sys.executable] + argv
152+
else:
153+
cmd_list = [sys.executable]
154+
155+
return bool(start_uiaccess_process(cmd_list))
156+
except Exception as e:
157+
logger.debug("启动 UIAccess 进程异常: {}", e)
158+
return False
159+
160+
161+
def set_window_band_uiaccess(hwnd: int) -> bool:
162+
if not _is_windows():
163+
return False
164+
if not hwnd:
165+
return False
166+
if not is_uiaccess_process():
167+
return False
168+
169+
try:
170+
user32 = _get_user32()
171+
h = wintypes.HWND(hwnd)
172+
hwnd_topmost = wintypes.HWND(-1)
173+
174+
SWP_NOMOVE = 0x0002
175+
SWP_NOSIZE = 0x0001
176+
SWP_NOACTIVATE = 0x0010
177+
SWP_SHOWWINDOW = 0x0040
178+
flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW
179+
return bool(user32.SetWindowPos(h, hwnd_topmost, 0, 0, 0, 0, flags))
180+
except Exception:
181+
return False

app/tools/settings_default_storage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@
336336
"edge_settings": {"default_value": None},
337337
"startup_display_floating_window": {"default_value": True},
338338
"floating_window_opacity": {"default_value": 0.8},
339+
"floating_window_topmost_mode": {"default_value": 1},
339340
"reset_floating_window_position_button": {"default_value": None},
340341
"floating_window_button_control": {"default_value": 3},
341342
"floating_window_placement": {"default_value": 1},

app/tools/settings_visibility_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"floating_window_stick_to_edge_display_style": False,
4242
"floating_window_draggable": False,
4343
"floating_window_size": False,
44+
"floating_window_topmost_mode": False,
4445
"do_not_steal_focus": False,
4546
},
4647
}

app/tools/variable.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
# -------------------- 软件基本信息 --------------------
1111
APPLY_NAME = "SecRandom" # 软件名称
1212
VERSION = "v0.0.0" # 软件当前版本
13-
NEXT_VERSION = "v2.2.7" # 软件下一个版本
14-
CODENAME = "Koharu" # 软件代号
13+
NEXT_VERSION = "v2.3.0-beta.1" # 软件下一个版本
14+
CODENAME = "Shirako" # 软件代号
1515
SPECIAL_VERSION = VERSION if VERSION != "v0.0.0" else NEXT_VERSION
1616

1717

0 commit comments

Comments
 (0)