-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsystem.go
More file actions
411 lines (383 loc) · 12.6 KB
/
Copy pathsystem.go
File metadata and controls
411 lines (383 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
package main
import (
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"sync"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)
// ---------------- 注册表:默认资源管理器 Hijack ----------------
// explorerCommandKeys 文件夹打开命令的注册表键(HKCU\Software\Classes 下)
// 最后一项是"此电脑"(This PC)CLSID:双击桌面"此电脑"图标时生效
// explore/opennewwindow 为"在新窗口打开"类动词(参考 Sigma File Manager 的注册键)
var explorerCommandKeys = []string{
`Directory\shell\open\command`,
`Directory\Background\shell\open\command`,
`Folder\shell\open\command`,
`Folder\shell\explore\command`,
`Folder\shell\opennewwindow\command`,
`Drive\shell\open\command`,
`CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\shell\open\command`,
}
// SetDefaultExplorer 把本程序注册为默认资源管理器(备份原值)
func (a *App) SetDefaultExplorer() error {
s, err := LoadSettings()
if err != nil {
return fmt.Errorf("读取设置失败:%v", err)
}
exe, err := os.Executable()
if err != nil {
return fmt.Errorf("获取程序路径失败:%v", err)
}
cmd := fmt.Sprintf(`"%s" "%s"`, exe, "%V")
for _, key := range explorerCommandKeys {
full := `Software\Classes\` + key
k, err := registry.OpenKey(registry.CURRENT_USER, full, registry.QUERY_VALUE|registry.SET_VALUE)
if err != nil {
if err == registry.ErrNotExist {
// CreateKey 返回的句柄需带 QUERY_VALUE 权限,否则后续读取会拒绝访问
k, _, err = registry.CreateKey(registry.CURRENT_USER, full, registry.QUERY_VALUE|registry.SET_VALUE)
if err != nil {
return fmt.Errorf("创建注册表键失败:%v", err)
}
s.ExplorerCreated[key] = true
} else {
return fmt.Errorf("打开注册表键失败:%v", err)
}
} else {
s.ExplorerCreated[key] = false
}
val, _, gerr := k.GetStringValue("")
if gerr != nil && gerr != registry.ErrNotExist {
k.Close()
return fmt.Errorf("读取注册表值失败:%v", gerr)
}
if _, backed := s.ExplorerBackup[key]; !backed {
s.ExplorerBackup[key] = val
}
// DelegateExecute(Win11 委托优先于默认命令):备份并删除
delegate, _, dgerr := k.GetStringValue("DelegateExecute")
if dgerr == nil {
if _, backed := s.ExplorerDelegate[key]; !backed {
s.ExplorerDelegate[key] = delegate
}
if err := k.DeleteValue("DelegateExecute"); err != nil {
k.Close()
return fmt.Errorf("移除 DelegateExecute 失败:%v", err)
}
} else if dgerr == registry.ErrNotExist {
if _, backed := s.ExplorerDelegate[key]; !backed {
s.ExplorerDelegate[key] = "" // 原值不存在
}
}
if err := k.SetStringValue("", cmd); err != nil {
k.Close()
return fmt.Errorf("写入注册表失败:%v", err)
}
k.Close()
}
s.DefaultExplorer = true
if err := s.Save(); err != nil {
return fmt.Errorf("保存设置失败:%v", err)
}
return nil
}
// RestoreExplorer 还原系统默认资源管理器
func (a *App) RestoreExplorer() error {
s, err := LoadSettings()
if err != nil {
return fmt.Errorf("读取设置失败:%v", err)
}
exe, _ := os.Executable()
for _, key := range explorerCommandKeys {
orig, existed := s.ExplorerBackup[key]
full := `Software\Classes\` + key
k, err := registry.OpenKey(registry.CURRENT_USER, full, registry.QUERY_VALUE|registry.SET_VALUE)
if err != nil {
continue // 键已不存在,无需还原
}
if s.ExplorerCreated[key] {
// 该键是 Hijack 时新建的,整键删除
k.Close()
if err := registry.DeleteKey(registry.CURRENT_USER, full); err != nil && err != registry.ErrNotExist {
return fmt.Errorf("删除注册表键失败:%v", err)
}
continue
}
// 当前值是否是我们写入的残留(用于备份丢失时的兜底清理)
cur, _, cerr := k.GetStringValue("")
curHijacked := cerr == nil && exe != "" && strings.Contains(strings.ToLower(cur), strings.ToLower(exe))
if existed && orig != "" {
if err := k.SetStringValue("", orig); err != nil {
k.Close()
return fmt.Errorf("还原注册表失败:%v", err)
}
} else if curHijacked {
// 无备份但当前值残留了本程序路径:清空默认值,回落到系统默认
if err := k.DeleteValue(""); err != nil && err != registry.ErrNotExist {
k.Close()
return fmt.Errorf("清理注册表残留失败:%v", err)
}
}
// 恢复 DelegateExecute
if delegate, had := s.ExplorerDelegate[key]; had {
if delegate != "" {
if err := k.SetStringValue("DelegateExecute", delegate); err != nil {
k.Close()
return fmt.Errorf("还原 DelegateExecute 失败:%v", err)
}
} else if err := k.DeleteValue("DelegateExecute"); err != nil && err != registry.ErrNotExist {
k.Close()
return fmt.Errorf("清理 DelegateExecute 失败:%v", err)
}
}
k.Close()
}
s.DefaultExplorer = false
s.ExplorerBackup = map[string]string{}
s.ExplorerDelegate = map[string]string{}
s.ExplorerCreated = map[string]bool{}
if err := s.Save(); err != nil {
return fmt.Errorf("保存设置失败:%v", err)
}
return nil
}
// isExplorerHijacked 检查 Directory\shell\open\command 是否指向本程序
func isExplorerHijacked(s *Settings) bool {
exe, err := os.Executable()
if err != nil {
return false
}
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Classes\Directory\shell\open\command`, registry.QUERY_VALUE)
if err != nil {
return false
}
defer k.Close()
val, _, err := k.GetStringValue("")
if err != nil {
return false
}
return strings.Contains(strings.ToLower(val), strings.ToLower(exe))
}
// ---------------- Win+E 全局键盘钩子 ----------------
var (
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
user32 = windows.NewLazySystemDLL("user32.dll")
procSetWindowsHookExW = user32.NewProc("SetWindowsHookExW")
procUnhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx")
procCallNextHookEx = user32.NewProc("CallNextHookEx")
procGetMessageW = user32.NewProc("GetMessageW")
procGetAsyncKeyState = user32.NewProc("GetAsyncKeyState")
procPostThreadMessageW = user32.NewProc("PostThreadMessageW")
procGetCurrentThreadId = kernel32.NewProc("GetCurrentThreadId")
procFindWindowW = user32.NewProc("FindWindowW")
procShowWindow = user32.NewProc("ShowWindow")
procSetForegroundWindow = user32.NewProc("SetForegroundWindow")
procGetForegroundWindow = user32.NewProc("GetForegroundWindow")
procGetWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId")
procAttachThreadInput = user32.NewProc("AttachThreadInput")
procBringWindowToTop = user32.NewProc("BringWindowToTop")
procSetFocus = user32.NewProc("SetFocus")
procKeybdEvent = user32.NewProc("keybd_event")
procCreateMutexW = kernel32.NewProc("CreateMutexW")
procCloseHandle = kernel32.NewProc("CloseHandle")
procGetDiskFreeSpaceExW = kernel32.NewProc("GetDiskFreeSpaceExW")
)
const (
whKeyboardLL = 13
wmQuit = 0x0012
hcAction = 0
keyDown = 0x0100
sysKeyDown = 0x0104
vkE = 0x45
vkLWin = 0x5B
vkRWin = 0x5C
vkControl = 0x11
vkMenu = 0x12
vkShift = 0x10
swRestore = 9
keyEventFKeyUp = 0x0002
)
// KBDLLHOOKSTRUCT 低级键盘钩子参数
type KBDLLHOOKSTRUCT struct {
VkCode uint32
ScanCode uint32
Flags uint32
Time uint32
DwExtraInfo uintptr
}
// MSG Windows 消息结构
type MSG struct {
Hwnd uintptr
Message uint32
WParam uintptr
LParam uintptr
Time uint32
Pt struct{ X, Y int32 }
}
var (
hookMu sync.Mutex
hookHandle uintptr
hookThread uint32
hookWakeCh chan struct{}
hookRunning bool
)
// lowLevelKeyboardProc 低级键盘钩子回调(stdcall)
var lowLevelKeyboardProc = syscall.NewCallback(func(nCode, wParam uintptr, lParam unsafe.Pointer) uintptr {
if int(nCode) == hcAction && (wParam == keyDown || wParam == sysKeyDown) {
kbd := (*KBDLLHOOKSTRUCT)(lParam)
if (kbd.VkCode == vkE && isKeyDown(vkLWin)) || (kbd.VkCode == vkE && isKeyDown(vkRWin)) {
// 仅 Win+E,排除 Ctrl/Alt/Shift 组合
if !isKeyDown(vkControl) && !isKeyDown(vkMenu) && !isKeyDown(vkShift) {
select {
case hookWakeCh <- struct{}{}:
default:
}
return 1 // 吞掉按键
}
}
}
ret, _, _ := procCallNextHookEx.Call(0, nCode, wParam, uintptr(lParam))
return ret
})
func isKeyDown(vk int) bool {
r, _, _ := procGetAsyncKeyState.Call(uintptr(vk))
return r&0x8000 != 0
}
// hookMessageLoop 钩子线程:安装钩子 + 消息循环 + 消费唤醒信号
func hookMessageLoop() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
tid, _, _ := procGetCurrentThreadId.Call()
hookThread = uint32(tid)
hook, _, _ := procSetWindowsHookExW.Call(whKeyboardLL, lowLevelKeyboardProc, 0, 0)
if hook == 0 {
hookMu.Lock()
hookHandle = 0
hookRunning = false
hookMu.Unlock()
return
}
hookMu.Lock()
hookHandle = hook
hookRunning = true
hookMu.Unlock()
// 唤醒信号消费 goroutine
done := make(chan struct{})
go func() {
for {
select {
case <-hookWakeCh:
wakeOrLaunch()
case <-done:
return
}
}
}()
// 消息循环(阻塞,直到收到 WM_QUIT)
var msg MSG
for {
r, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(&msg)), 0, 0, 0)
if r == 0 || r == ^uintptr(0) {
break
}
}
close(done)
procUnhookWindowsHookEx.Call(hook)
hookMu.Lock()
hookHandle = 0
hookRunning = false
hookMu.Unlock()
}
// applyWinEHook 启用/禁用 Win+E 接管
func applyWinEHook(enabled bool) {
hookMu.Lock()
defer hookMu.Unlock()
if enabled && !hookRunning {
hookWakeCh = make(chan struct{}, 4)
go hookMessageLoop()
} else if !enabled && hookRunning {
if hookThread != 0 {
procPostThreadMessageW.Call(uintptr(hookThread), wmQuit, 0, 0)
}
}
}
// wakeOrLaunch 唤醒已运行实例或启动新实例
func wakeOrLaunch() {
if singleInstanceExists() {
wakeExistingWindow()
} else {
if exe, err := os.Executable(); err == nil {
_ = exec.Command(exe).Start()
}
}
}
// ---------------- 单实例与窗口唤醒 ----------------
const singleInstanceMutex = "Local\\FileManager_SingleInstance"
// singleInstanceExists 检查是否已有实例在运行(检测用,不持有句柄)
func singleInstanceExists() bool {
mutexName, _ := syscall.UTF16PtrFromString(singleInstanceMutex)
handle, _, err := procCreateMutexW.Call(0, 0, uintptr(unsafe.Pointer(mutexName)))
if handle == 0 {
return false
}
procCloseHandle.Call(handle)
return err == syscall.Errno(183) // ERROR_ALREADY_EXISTS
}
var singleInstanceHandle uintptr // 主实例持有,直到进程退出
// ensureSingleInstance 主程序启动时调用:已运行则返回 false(窗口唤醒由调用方负责)
func ensureSingleInstance() bool {
mutexName, _ := syscall.UTF16PtrFromString(singleInstanceMutex)
handle, _, err := procCreateMutexW.Call(0, 0, uintptr(unsafe.Pointer(mutexName)))
if handle == 0 {
return true // 创建失败,不阻塞启动
}
if err == syscall.Errno(183) {
procCloseHandle.Call(handle)
return false
}
singleInstanceHandle = handle // 持有句柄直到进程退出,保证互斥有效
return true
}
// wakeExistingWindow 找到已运行实例窗口并强制置前显示
func wakeExistingWindow() {
if trayCtx != nil && wailsReady {
wruntime.EventsEmit(trayCtx, "show-window") // 窗口可能已隐藏且标签已关闭,通知前端恢复
}
title, _ := syscall.UTF16PtrFromString("文件管理器")
hwnd, _, _ := procFindWindowW.Call(0, uintptr(unsafe.Pointer(title)))
if hwnd == 0 {
return
}
procShowWindow.Call(hwnd, swRestore)
// 强制置前:绕过 Windows 前台锁定(后台进程直接 SetForegroundWindow 会被拒绝)
fg, _, _ := procGetForegroundWindow.Call()
fgThread, _, _ := procGetWindowThreadProcessId.Call(fg, 0)
curThread, _, _ := procGetCurrentThreadId.Call()
winThread, _, _ := procGetWindowThreadProcessId.Call(hwnd, 0)
attached := false
if fgThread != 0 && fgThread != curThread {
if r, _, _ := procAttachThreadInput.Call(curThread, fgThread, 1); r != 0 {
attached = true
}
}
if r, _, _ := procAttachThreadInput.Call(curThread, winThread, 1); r != 0 {
attached = true
}
// 模拟一次 Alt 键按下/释放,使系统放宽前台限制
procKeybdEvent.Call(vkMenu, 0, 0, 0)
procKeybdEvent.Call(vkMenu, 0, keyEventFKeyUp, 0)
procSetForegroundWindow.Call(hwnd)
procBringWindowToTop.Call(hwnd)
procSetFocus.Call(hwnd)
if attached {
procAttachThreadInput.Call(curThread, winThread, 0)
procAttachThreadInput.Call(curThread, fgThread, 0)
}
}