@yufengtadian/freedom-cli 1.12.17 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/build.js +74 -9
- package/lib/cli.js +61 -3
- package/lib/init.js +9 -1
- package/lib/security.js +2 -2
- package/lib/utils.js +5 -2
- package/lib/verify.js +236 -0
- package/package.json +43 -39
- package/shell/darwin-arm64/freedom-shell +0 -0
- package/shell/linux-x64/freedom-shell +0 -0
- package/shell/win-x64/freedom-shell.exe +0 -0
- package/templates/go/go.mod +1 -7
- package/templates/go/go.sum +2 -2
- package/templates/go/main.go +22 -8
- package/templates/go/pkg/freedom/anti_debug_other.go +2 -3
- package/templates/go/pkg/freedom/anti_debug_windows.go +10 -12
- package/templates/go/pkg/freedom/assets/{index.html → default.html} +1 -1
- package/templates/go/pkg/freedom/assets/freedom.js +219 -21
- package/templates/go/pkg/freedom/assets_embed.go +11 -13
- package/templates/go/pkg/freedom/authenticode_other.go +11 -0
- package/templates/go/pkg/freedom/authenticode_windows.go +87 -0
- package/templates/go/pkg/freedom/backend_proc.go +295 -51
- package/templates/go/pkg/freedom/bridge.go +13 -2
- package/templates/go/pkg/freedom/capability.go +77 -0
- package/templates/go/pkg/freedom/center_other.go +3 -0
- package/templates/go/pkg/freedom/center_windows.go +64 -36
- package/templates/go/pkg/freedom/dispatch.go +60 -0
- package/templates/go/pkg/freedom/events_windows.go +319 -0
- package/templates/go/pkg/freedom/freedom.go +242 -92
- package/templates/go/pkg/freedom/msgwindow_windows.go +257 -0
- package/templates/go/pkg/freedom/osver_other.go +15 -0
- package/templates/go/pkg/freedom/osver_windows.go +121 -0
- package/templates/go/pkg/freedom/proc_hide_windows.go +2 -2
- package/templates/go/pkg/freedom/{configfile.go → resources.go} +56 -32
- package/templates/go/pkg/freedom/security.go +19 -12
- package/templates/go/pkg/freedom/singleinstance_other.go +10 -0
- package/templates/go/pkg/freedom/singleinstance_windows.go +145 -0
- package/templates/go/pkg/freedom/spawn_other.go +11 -0
- package/templates/go/pkg/freedom/spawn_windows.go +23 -0
- package/templates/go/pkg/freedom/store.go +458 -0
- package/templates/go/pkg/freedom/syscap_linux.go +278 -0
- package/templates/go/pkg/freedom/syscap_other.go +22 -0
- package/templates/go/pkg/freedom/syscap_windows.go +845 -0
- package/templates/go/pkg/freedom/sysint_common.go +70 -0
- package/templates/go/pkg/freedom/sysint_windows.go +426 -0
- package/templates/go/pkg/freedom/tray_common.go +12 -0
- package/templates/go/pkg/freedom/tray_linux.go +302 -0
- package/templates/go/pkg/freedom/tray_other.go +10 -0
- package/templates/go/pkg/freedom/tray_windows.go +536 -0
- package/templates/go/pkg/freedom/updater.go +372 -0
- package/templates/go/pkg/freedom/window_mgr.go +385 -0
- package/templates/go/pkg/freedom/window_other.go +32 -66
- package/templates/go/pkg/freedom/window_windows.go +406 -251
- package/templates/project-minimal/freedom.config.js +58 -0
- package/templates/project-minimal/index.html +41 -0
- package/templates/project-minimal/package.json +14 -0
- package/templates/project-minimal/src/main.js +19 -0
- package/templates/project-minimal/vite.config.js +18 -0
- package/templates/go/gen_config.go +0 -22
- package/templates/go/pkg/freedom/security_test.go +0 -153
- package/templates/go/webview_go/.github/workflows/ci.yaml +0 -62
- package/templates/go/webview_go/CHANGELOG.md +0 -15
- package/templates/go/webview_go/LICENSE +0 -22
- package/templates/go/webview_go/README.md +0 -50
- package/templates/go/webview_go/examples/basic/main.go +0 -12
- package/templates/go/webview_go/examples/bind/main.go +0 -38
- package/templates/go/webview_go/glue.c +0 -36
- package/templates/go/webview_go/go.mod +0 -3
- package/templates/go/webview_go/go.sum +0 -0
- package/templates/go/webview_go/libs/mswebview2/LICENSE +0 -27
- package/templates/go/webview_go/libs/mswebview2/include/WebView2.h +0 -23568
- package/templates/go/webview_go/libs/mswebview2/include/vendor.go +0 -2
- package/templates/go/webview_go/libs/mswebview2/vendor.go +0 -2
- package/templates/go/webview_go/libs/mswebview2/version.txt +0 -1
- package/templates/go/webview_go/libs/webview/LICENSE +0 -22
- package/templates/go/webview_go/libs/webview/include/vendor.go +0 -2
- package/templates/go/webview_go/libs/webview/include/webview.h +0 -3871
- package/templates/go/webview_go/libs/webview/vendor.go +0 -2
- package/templates/go/webview_go/libs/webview/version.txt +0 -1
- package/templates/go/webview_go/webview.cc +0 -1
- package/templates/go/webview_go/webview.go +0 -481
- package/templates/go/webview_go/webview_test.go +0 -50
|
@@ -3,33 +3,12 @@
|
|
|
3
3
|
package freedom
|
|
4
4
|
|
|
5
5
|
import (
|
|
6
|
-
"
|
|
7
|
-
"encoding/base64"
|
|
8
|
-
"encoding/binary"
|
|
6
|
+
"encoding/json"
|
|
9
7
|
"fmt"
|
|
10
|
-
"image"
|
|
11
|
-
"image/color"
|
|
12
|
-
"image/png"
|
|
13
8
|
"syscall"
|
|
14
|
-
|
|
15
|
-
"golang.org/x/sys/windows"
|
|
16
|
-
"golang.org/x/sys/windows/registry"
|
|
9
|
+
"unsafe"
|
|
17
10
|
)
|
|
18
11
|
|
|
19
|
-
// 进程级 DPI 感知:让窗口坐标 / WebView 渲染统一按物理像素工作。
|
|
20
|
-
// 若不设置,高 DPI 系统(如 125%/150% 缩放)下 GetWindowRect 返回逻辑坐标,
|
|
21
|
-
// 与物理像素的鼠标坐标错位(UI 自动化难定位),且 WebView 内容渲染模糊。
|
|
22
|
-
// 必须在窗口创建前生效,故放在 init()。
|
|
23
|
-
func init() {
|
|
24
|
-
shcore := syscall.NewLazyDLL("shcore.dll")
|
|
25
|
-
procSetProcessDpiAwareness := shcore.NewProc("SetProcessDpiAwareness")
|
|
26
|
-
r, _, _ := procSetProcessDpiAwareness.Call(2) // PER_MONITOR_DPI_AWARE
|
|
27
|
-
if r != 0 {
|
|
28
|
-
// 回退到 user32 的旧版 API(Win 8 以下 / 调用失败时)
|
|
29
|
-
user32win.NewProc("SetProcessDPIAware").Call()
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
12
|
// Windows 平台原生窗口操作(user32 / dwmapi)。
|
|
34
13
|
// 供标题栏策略(applyTitleBar)与前端 window.freedom.window.* 控制使用。
|
|
35
14
|
|
|
@@ -40,51 +19,127 @@ var (
|
|
|
40
19
|
procGetWindowLongPtr = user32win.NewProc("GetWindowLongPtrW")
|
|
41
20
|
procSetWindowLongPtr = user32win.NewProc("SetWindowLongPtrW")
|
|
42
21
|
procShowWindow = user32win.NewProc("ShowWindow")
|
|
22
|
+
procPostMessage = user32win.NewProc("PostMessageW")
|
|
43
23
|
procIsZoomed = user32win.NewProc("IsZoomed")
|
|
44
24
|
procSetWindowPos = user32win.NewProc("SetWindowPos")
|
|
45
25
|
procSetWindowText = user32win.NewProc("SetWindowTextW")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
procGetSystemMetrics = user32win.NewProc("GetSystemMetrics")
|
|
50
|
-
procSendMessage = user32win.NewProc("SendMessageW")
|
|
51
|
-
procReleaseCapture = user32win.NewProc("ReleaseCapture")
|
|
52
|
-
procGetWindowRect = user32win.NewProc("GetWindowRect")
|
|
26
|
+
// 以下 proc 供 center_windows.go 的 applyCenter 使用(窗口居中),
|
|
27
|
+
// 统一在此声明,避免多文件各自 NewLazyDLL/NewProc 重复加载 user32。
|
|
28
|
+
procGetSystemMetrics = user32win.NewProc("GetSystemMetrics")
|
|
53
29
|
procMonitorFromWindow = user32win.NewProc("MonitorFromWindow")
|
|
54
|
-
procGetMonitorInfo
|
|
55
|
-
procMoveWindow
|
|
56
|
-
|
|
57
|
-
|
|
30
|
+
procGetMonitorInfo = user32win.NewProc("GetMonitorInfoW")
|
|
31
|
+
procMoveWindow = user32win.NewProc("MoveWindow")
|
|
32
|
+
// 以下 proc 供 tray_windows.go / syscap_windows.go 使用(图标加载与释放),
|
|
33
|
+
// 与模板 window_windows.go 保持一致,避免多文件重复声明。
|
|
34
|
+
procGetModuleHandle = kernel32.NewProc("GetModuleHandleW")
|
|
35
|
+
procLoadImage = user32win.NewProc("LoadImageW")
|
|
36
|
+
procDestroyIcon = user32win.NewProc("DestroyIcon")
|
|
58
37
|
|
|
59
38
|
dwmapi = syscall.NewLazyDLL("dwmapi.dll")
|
|
60
39
|
procDwmExtendFrameIntoArea = dwmapi.NewProc("DwmExtendFrameIntoClientArea")
|
|
40
|
+
|
|
41
|
+
// 以下 proc 供 events_windows.go(窗口事件/全屏)与 windowControl 扩展动作使用。
|
|
42
|
+
comctl32 = syscall.NewLazyDLL("comctl32.dll")
|
|
43
|
+
|
|
44
|
+
procSetWindowSubclass = comctl32.NewProc("SetWindowSubclass")
|
|
45
|
+
procDefSubclassProc = comctl32.NewProc("DefSubclassProc")
|
|
46
|
+
|
|
47
|
+
procGetWindowRect = user32win.NewProc("GetWindowRect")
|
|
48
|
+
procGetClientRect = user32win.NewProc("GetClientRect")
|
|
49
|
+
procBringWindowToTop = user32win.NewProc("BringWindowToTop")
|
|
50
|
+
procIsIconic = user32win.NewProc("IsIconic")
|
|
51
|
+
procIsWindowVisible = user32win.NewProc("IsWindowVisible")
|
|
52
|
+
procGetForegroundWindow = user32win.NewProc("GetForegroundWindow")
|
|
53
|
+
procGetDpiForWindow = user32win.NewProc("GetDpiForWindow")
|
|
54
|
+
procGetWindowTextLength = user32win.NewProc("GetWindowTextLengthW")
|
|
55
|
+
procGetWindowText = user32win.NewProc("GetWindowTextW")
|
|
56
|
+
// 显示器枚举(sysCapCall 的 window.monitors 使用)。
|
|
57
|
+
procEnumDisplayMonitors = user32win.NewProc("EnumDisplayMonitors")
|
|
58
|
+
|
|
59
|
+
// W6 审查修复追加:PNG→HICON 绘制(gdi32)、图标合成、子类化摘除、单实例互斥量。
|
|
60
|
+
gdi32 = syscall.NewLazyDLL("gdi32.dll")
|
|
61
|
+
procCreateDIBSection = gdi32.NewProc("CreateDIBSection")
|
|
62
|
+
procDeleteObject = gdi32.NewProc("DeleteObject")
|
|
63
|
+
procCreateIconIndirect = user32win.NewProc("CreateIconIndirect")
|
|
64
|
+
procRemoveWindowSubclass = comctl32.NewProc("RemoveWindowSubclass")
|
|
65
|
+
procCreateMutexW = kernel32.NewProc("CreateMutexW")
|
|
66
|
+
|
|
67
|
+
shcore = syscall.NewLazyDLL("shcore.dll")
|
|
68
|
+
procGetDpiForMonitor = shcore.NewProc("GetDpiForMonitor")
|
|
69
|
+
|
|
70
|
+
// 以下 proc 供 W2 系统集成(msgwindow/剪贴板/通知/外壳/注册表)使用,
|
|
71
|
+
// 按约定集中在本文件声明,其他文件复用。
|
|
72
|
+
procRegisterHotKey = user32win.NewProc("RegisterHotKey")
|
|
73
|
+
procUnregisterHotKey = user32win.NewProc("UnregisterHotKey")
|
|
74
|
+
procPostQuitMessage = user32win.NewProc("PostQuitMessage")
|
|
75
|
+
procGetMessageW = user32win.NewProc("GetMessageW")
|
|
76
|
+
procTranslateMessage = user32win.NewProc("TranslateMessage")
|
|
77
|
+
procDispatchMessageW = user32win.NewProc("DispatchMessageW")
|
|
78
|
+
|
|
79
|
+
procOpenClipboard = user32win.NewProc("OpenClipboard")
|
|
80
|
+
procCloseClipboard = user32win.NewProc("CloseClipboard")
|
|
81
|
+
procGetClipboardData = user32win.NewProc("GetClipboardData")
|
|
82
|
+
procEmptyClipboard = user32win.NewProc("EmptyClipboard")
|
|
83
|
+
procSetClipboardData = user32win.NewProc("SetClipboardData")
|
|
84
|
+
procIsClipboardFormatAvai = user32win.NewProc("IsClipboardFormatAvailable")
|
|
85
|
+
|
|
86
|
+
// Global* 复用上方 kernel32 声明。
|
|
87
|
+
procGlobalAlloc = kernel32.NewProc("GlobalAlloc")
|
|
88
|
+
procGlobalLock = kernel32.NewProc("GlobalLock")
|
|
89
|
+
procGlobalUnlock = kernel32.NewProc("GlobalUnlock")
|
|
90
|
+
procGlobalSize = kernel32.NewProc("GlobalSize")
|
|
91
|
+
procGlobalFree = kernel32.NewProc("GlobalFree")
|
|
92
|
+
|
|
93
|
+
procFindWindowW = user32win.NewProc("FindWindowW")
|
|
94
|
+
procSendMessageTimeoutW = user32win.NewProc("SendMessageTimeoutW")
|
|
95
|
+
|
|
96
|
+
procShellExecuteW = shell32.NewProc("ShellExecuteW")
|
|
97
|
+
shlwapi = syscall.NewLazyDLL("shlwapi.dll")
|
|
98
|
+
procSHDeleteKeyW = shlwapi.NewProc("SHDeleteKeyW")
|
|
99
|
+
|
|
100
|
+
advapi32 = syscall.NewLazyDLL("advapi32.dll")
|
|
101
|
+
procRegOpenKeyExW = advapi32.NewProc("RegOpenKeyExW")
|
|
102
|
+
procRegCreateKeyExW = advapi32.NewProc("RegCreateKeyExW")
|
|
103
|
+
procRegSetValueExW = advapi32.NewProc("RegSetValueExW")
|
|
104
|
+
procRegQueryValueExW = advapi32.NewProc("RegQueryValueExW")
|
|
105
|
+
procRegDeleteValueW = advapi32.NewProc("RegDeleteValueW")
|
|
106
|
+
procRegCloseKey = advapi32.NewProc("RegCloseKey")
|
|
107
|
+
|
|
108
|
+
procSetProcessDpiAwarenessContext = user32win.NewProc("SetProcessDpiAwarenessContext")
|
|
61
109
|
)
|
|
62
110
|
|
|
111
|
+
// DPI Per-Monitor V2 经进程启动时 API 声明,而非 .syso 内嵌 manifest——
|
|
112
|
+
// WinLibs mingw 链接期会自动注入 default-manifest.o,与自定义 manifest 冲突。
|
|
113
|
+
// init 先于消息循环执行,确保首个窗口创建前生效(对标 Tauri manifest 声明)。
|
|
114
|
+
func init() {
|
|
115
|
+
if procSetProcessDpiAwarenessContext.Find() != nil {
|
|
116
|
+
return // Win10 1703 之前无此 API:保持系统默认感知级
|
|
117
|
+
}
|
|
118
|
+
const dpiPerMonitorV2 = ^uintptr(3) // (DPI_HANDLE)-4:DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
|
|
119
|
+
procSetProcessDpiAwarenessContext.Call(dpiPerMonitorV2)
|
|
120
|
+
}
|
|
121
|
+
|
|
63
122
|
const (
|
|
64
123
|
wsCaption = 0x00C00000 // WS_CAPTION = WS_BORDER | WS_DLGFRAME
|
|
65
124
|
wsSysMenu = 0x00080000
|
|
66
125
|
wsThickFrame = 0x00040000
|
|
67
126
|
|
|
68
|
-
wmClose = 0x0010 // WM_CLOSE:请求窗口正常关闭(触发 GoWnd 的关闭回调)
|
|
69
|
-
wmSetIcon = 0x0080 // WM_SETICON:设置窗口大/小图标(ICON_BIG=1 / ICON_SMALL=0)
|
|
70
|
-
|
|
71
|
-
rtIcon = 3 // RT_ICON 资源类型(rcedit 注入图标二进制)
|
|
72
127
|
swHide = 0
|
|
73
128
|
swShow = 5
|
|
74
129
|
swMinimize = 6
|
|
75
130
|
swRestore = 9
|
|
76
131
|
swMaximize = 3
|
|
77
132
|
|
|
78
|
-
smCXIcon = 11 // SM_CXICON
|
|
79
|
-
smCYIcon = 12 // SM_CYICON
|
|
80
|
-
smCXSmall = 13 // SM_CXSMICON
|
|
81
|
-
smCYSmall = 14 // SM_CYSMICON
|
|
82
|
-
|
|
83
133
|
swpFrameChanged = 0x0020
|
|
84
134
|
swpNoMove = 0x0002
|
|
85
135
|
swpNoSize = 0x0001
|
|
86
136
|
swpNoZOrder = 0x0004
|
|
87
137
|
swpNoActivate = 0x0010
|
|
138
|
+
|
|
139
|
+
wmClose = 0x0010 // WM_CLOSE:请求窗口正常关闭(触发 DestroyWindow 释放 WebView 资源)
|
|
140
|
+
|
|
141
|
+
smCXSmall = 13 // SM_CXSMICON
|
|
142
|
+
smCYSmall = 14 // SM_CYSMICON
|
|
88
143
|
)
|
|
89
144
|
|
|
90
145
|
// gwlStyle = GWL_STYLE(-16)。用变量声明,避免 uintptr 常量转换溢出。
|
|
@@ -110,151 +165,348 @@ func refreshFrame(hwnd uintptr) {
|
|
|
110
165
|
}
|
|
111
166
|
|
|
112
167
|
// windowControl 处理前端 window.freedom.window.* 请求(Windows 实现)。
|
|
113
|
-
|
|
114
|
-
|
|
168
|
+
// paramsJSON 为动作参数(JSON object,可空),与 sysCapCall 的 params 风格一致。
|
|
169
|
+
func windowControl(hwnd uintptr, action string, mode TitleBarMode, paramsJSON string) (interface{}, error) {
|
|
115
170
|
if hwnd == 0 {
|
|
116
171
|
return nil, fmt.Errorf("window not ready")
|
|
117
172
|
}
|
|
173
|
+
p := parseWinParams(paramsJSON)
|
|
118
174
|
switch action {
|
|
119
175
|
case "minimize":
|
|
120
176
|
procShowWindow.Call(hwnd, swMinimize)
|
|
121
177
|
return nil, nil
|
|
122
178
|
case "maximize":
|
|
123
|
-
|
|
179
|
+
procShowWindow.Call(hwnd, swMaximize)
|
|
124
180
|
return nil, nil
|
|
125
181
|
case "unmaximize", "restore":
|
|
126
|
-
|
|
182
|
+
procShowWindow.Call(hwnd, swRestore)
|
|
127
183
|
return nil, nil
|
|
128
184
|
case "toggleMaximize":
|
|
129
|
-
if
|
|
130
|
-
|
|
185
|
+
if isZoomed(hwnd) {
|
|
186
|
+
procShowWindow.Call(hwnd, swRestore)
|
|
131
187
|
} else {
|
|
132
|
-
|
|
188
|
+
procShowWindow.Call(hwnd, swMaximize)
|
|
133
189
|
}
|
|
134
190
|
return nil, nil
|
|
135
191
|
case "close":
|
|
136
|
-
//
|
|
137
|
-
|
|
192
|
+
// force=true 时先解除关闭拦截,保证前端确认后能真正关闭。
|
|
193
|
+
if force, _ := p.boolean("force"); force {
|
|
194
|
+
if rt, err := runtimeFor(hwnd); err == nil {
|
|
195
|
+
rt.interceptClose.Store(false)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// 发送 WM_CLOSE 走正常关闭流程(触发 DestroyWindow,释放 WebView 资源)。
|
|
199
|
+
// 不能使用 user32.CloseWindow——该 API 的语义是最小化窗口而非关闭。
|
|
138
200
|
procPostMessage.Call(hwnd, wmClose, 0, 0)
|
|
139
201
|
return nil, nil
|
|
140
202
|
case "isMaximized":
|
|
141
|
-
return
|
|
203
|
+
return isZoomed(hwnd), nil
|
|
142
204
|
case "isFrameless":
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
case "
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
205
|
+
// 仅 frameless 返回 true:hidden 模式保留 DWM 原生按钮,
|
|
206
|
+
// 前端若据 isFrameless 自绘按钮会与原生按钮重叠。
|
|
207
|
+
return mode == TitleBarFrameless, nil
|
|
208
|
+
|
|
209
|
+
// ---- W1 对标 Tauri:位置 / 尺寸 ----
|
|
210
|
+
case "setPosition":
|
|
211
|
+
r, err := getWindowRect(hwnd)
|
|
212
|
+
if err != nil {
|
|
213
|
+
return nil, err
|
|
214
|
+
}
|
|
215
|
+
x, okx := p.intv("x")
|
|
216
|
+
y, oky := p.intv("y")
|
|
217
|
+
if !okx || !oky {
|
|
218
|
+
return nil, fmt.Errorf("setPosition requires x,y")
|
|
219
|
+
}
|
|
220
|
+
procMoveWindow.Call(hwnd, uintptr(int32(x)), uintptr(int32(y)),
|
|
221
|
+
uintptr(r.right-r.left), uintptr(r.bottom-r.top), 1)
|
|
222
|
+
return nil, nil
|
|
223
|
+
case "setSize":
|
|
224
|
+
r, err := getWindowRect(hwnd)
|
|
225
|
+
if err != nil {
|
|
226
|
+
return nil, err
|
|
227
|
+
}
|
|
228
|
+
w, okw := p.intv("width")
|
|
229
|
+
h, okh := p.intv("height")
|
|
230
|
+
if !okw || !okh {
|
|
231
|
+
return nil, fmt.Errorf("setSize requires width,height")
|
|
232
|
+
}
|
|
233
|
+
procMoveWindow.Call(hwnd, uintptr(r.left), uintptr(r.top), uintptr(int32(w)), uintptr(int32(h)), 1)
|
|
234
|
+
return nil, nil
|
|
235
|
+
case "getPosition":
|
|
236
|
+
r, err := getWindowRect(hwnd)
|
|
237
|
+
if err != nil {
|
|
238
|
+
return nil, err
|
|
239
|
+
}
|
|
240
|
+
return map[string]int{"x": int(r.left), "y": int(r.top)}, nil
|
|
241
|
+
case "getSize":
|
|
242
|
+
r, err := getWindowRect(hwnd)
|
|
243
|
+
if err != nil {
|
|
244
|
+
return nil, err
|
|
245
|
+
}
|
|
246
|
+
return map[string]int{"width": int(r.right - r.left), "height": int(r.bottom - r.top)}, nil
|
|
247
|
+
case "innerSize":
|
|
248
|
+
r := getClientRect(hwnd)
|
|
249
|
+
return map[string]int{"width": int(r.right - r.left), "height": int(r.bottom - r.top)}, nil
|
|
250
|
+
case "center":
|
|
251
|
+
r, err := getWindowRect(hwnd)
|
|
252
|
+
if err != nil {
|
|
253
|
+
return nil, err
|
|
254
|
+
}
|
|
255
|
+
x, y := centeredForWindow(hwnd, int(r.right-r.left), int(r.bottom-r.top))
|
|
256
|
+
procMoveWindow.Call(hwnd, uintptr(int32(x)), uintptr(int32(y)),
|
|
257
|
+
uintptr(r.right-r.left), uintptr(r.bottom-r.top), 1)
|
|
258
|
+
return nil, nil
|
|
259
|
+
case "setTitle":
|
|
260
|
+
s, ok := p.strv("title")
|
|
261
|
+
if !ok {
|
|
262
|
+
return nil, fmt.Errorf("setTitle requires title")
|
|
263
|
+
}
|
|
264
|
+
ptr, err := syscall.UTF16PtrFromString(s)
|
|
265
|
+
if err != nil {
|
|
266
|
+
return nil, fmt.Errorf("setTitle: title must not contain NUL")
|
|
267
|
+
}
|
|
268
|
+
procSetWindowText.Call(hwnd, uintptr(unsafe.Pointer(ptr)))
|
|
153
269
|
return nil, nil
|
|
270
|
+
|
|
271
|
+
// ---- W1:可见性 / 层级 / 焦点 ----
|
|
272
|
+
case "show":
|
|
273
|
+
procShowWindow.Call(hwnd, swShow)
|
|
274
|
+
return nil, nil
|
|
275
|
+
case "hide":
|
|
276
|
+
procShowWindow.Call(hwnd, swHide)
|
|
277
|
+
return nil, nil
|
|
278
|
+
case "focus":
|
|
279
|
+
if isIconic(hwnd) {
|
|
280
|
+
procShowWindow.Call(hwnd, swRestore)
|
|
281
|
+
}
|
|
282
|
+
procBringWindowToTop.Call(hwnd)
|
|
283
|
+
procSetForegroundWindow.Call(hwnd)
|
|
284
|
+
return nil, nil
|
|
285
|
+
case "isVisible":
|
|
286
|
+
r, _, _ := procIsWindowVisible.Call(hwnd)
|
|
287
|
+
return r != 0, nil
|
|
288
|
+
case "isFocused":
|
|
289
|
+
fg, _, _ := procGetForegroundWindow.Call()
|
|
290
|
+
return fg == hwnd, nil
|
|
291
|
+
case "isMinimized":
|
|
292
|
+
return isIconic(hwnd), nil
|
|
293
|
+
case "setAlwaysOnTop":
|
|
294
|
+
on, ok := p.boolean("on")
|
|
295
|
+
if !ok {
|
|
296
|
+
return nil, fmt.Errorf("setAlwaysOnTop requires on")
|
|
297
|
+
}
|
|
298
|
+
top := hwndNotopmost
|
|
299
|
+
if on {
|
|
300
|
+
top = hwndTopmost
|
|
301
|
+
}
|
|
302
|
+
procSetWindowPos.Call(hwnd, top, 0, 0, 0, 0, swpNoMove|swpNoSize|swpNoActivate)
|
|
303
|
+
return nil, nil
|
|
304
|
+
case "setSkipTaskbar":
|
|
305
|
+
on, ok := p.boolean("on")
|
|
306
|
+
if !ok {
|
|
307
|
+
return nil, fmt.Errorf("setSkipTaskbar requires on")
|
|
308
|
+
}
|
|
309
|
+
ex, _, _ := procGetWindowLongPtr.Call(hwnd, uintptr(int(gwlExStyle)))
|
|
310
|
+
if on {
|
|
311
|
+
ex |= wsExToolwindow
|
|
312
|
+
} else {
|
|
313
|
+
ex &^= wsExToolwindow
|
|
314
|
+
}
|
|
315
|
+
procSetWindowLongPtr.Call(hwnd, uintptr(int(gwlExStyle)), ex)
|
|
316
|
+
// 任务栏按钮变化需 SWP_FRAMECHANGED + 显示标志才能即时生效。
|
|
317
|
+
procSetWindowPos.Call(hwnd, 0, 0, 0, 0, 0,
|
|
318
|
+
swpFrameChanged|swpNoMove|swpNoSize|swpNoZOrder|swpNoActivate|swpShowWindow)
|
|
319
|
+
return nil, nil
|
|
320
|
+
|
|
321
|
+
// ---- W1:行为开关 ----
|
|
322
|
+
case "setResizable":
|
|
323
|
+
on, ok := p.boolean("on")
|
|
324
|
+
if !ok {
|
|
325
|
+
return nil, fmt.Errorf("setResizable requires on")
|
|
326
|
+
}
|
|
327
|
+
style := getWindowStyle(hwnd)
|
|
328
|
+
if on {
|
|
329
|
+
style |= wsThickFrame
|
|
330
|
+
} else {
|
|
331
|
+
style &^= wsThickFrame
|
|
332
|
+
}
|
|
333
|
+
setWindowStyle(hwnd, style)
|
|
334
|
+
refreshFrame(hwnd)
|
|
335
|
+
return nil, nil
|
|
336
|
+
case "setMaximizable":
|
|
337
|
+
on, ok := p.boolean("on")
|
|
338
|
+
if !ok {
|
|
339
|
+
return nil, fmt.Errorf("setMaximizable requires on")
|
|
340
|
+
}
|
|
341
|
+
style := getWindowStyle(hwnd)
|
|
342
|
+
if on {
|
|
343
|
+
style |= wsMaximizeBox
|
|
344
|
+
} else {
|
|
345
|
+
style &^= wsMaximizeBox
|
|
346
|
+
}
|
|
347
|
+
setWindowStyle(hwnd, style)
|
|
348
|
+
refreshFrame(hwnd)
|
|
349
|
+
return nil, nil
|
|
350
|
+
case "setMinimizable":
|
|
351
|
+
on, ok := p.boolean("on")
|
|
352
|
+
if !ok {
|
|
353
|
+
return nil, fmt.Errorf("setMinimizable requires on")
|
|
354
|
+
}
|
|
355
|
+
style := getWindowStyle(hwnd)
|
|
356
|
+
if on {
|
|
357
|
+
style |= wsMinimizeBox
|
|
358
|
+
} else {
|
|
359
|
+
style &^= wsMinimizeBox
|
|
360
|
+
}
|
|
361
|
+
setWindowStyle(hwnd, style)
|
|
362
|
+
refreshFrame(hwnd)
|
|
363
|
+
return nil, nil
|
|
364
|
+
|
|
365
|
+
// ---- W1:全屏 / 关闭拦截 ----
|
|
366
|
+
case "setFullscreen":
|
|
367
|
+
on, ok := p.boolean("on")
|
|
368
|
+
if !ok {
|
|
369
|
+
return nil, fmt.Errorf("setFullscreen requires on")
|
|
370
|
+
}
|
|
371
|
+
return nil, setFullscreen(hwnd, on)
|
|
372
|
+
case "isFullscreen":
|
|
373
|
+
rt, err := runtimeFor(hwnd)
|
|
374
|
+
if err != nil {
|
|
375
|
+
return false, nil
|
|
376
|
+
}
|
|
377
|
+
return rt.getFullscreen(), nil
|
|
378
|
+
case "interceptClose":
|
|
379
|
+
on, ok := p.boolean("on")
|
|
380
|
+
if !ok {
|
|
381
|
+
return nil, fmt.Errorf("interceptClose requires on")
|
|
382
|
+
}
|
|
383
|
+
rt, err := runtimeFor(hwnd)
|
|
384
|
+
if err != nil {
|
|
385
|
+
return nil, err
|
|
386
|
+
}
|
|
387
|
+
rt.interceptClose.Store(on)
|
|
388
|
+
return nil, nil
|
|
389
|
+
|
|
390
|
+
// ---- W1:信息查询 ----
|
|
391
|
+
case "getInfo":
|
|
392
|
+
return getWindowInfo(hwnd, mode)
|
|
154
393
|
default:
|
|
155
394
|
return nil, fmt.Errorf("unknown window action %q", action)
|
|
156
395
|
}
|
|
157
396
|
}
|
|
158
397
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
398
|
+
// getWindowInfo 聚合窗口状态(物理像素;scaleFactor 为 DPI 缩放)。
|
|
399
|
+
func getWindowInfo(hwnd uintptr, mode TitleBarMode) (map[string]interface{}, error) {
|
|
400
|
+
r, err := getWindowRect(hwnd)
|
|
401
|
+
if err != nil {
|
|
402
|
+
return nil, err
|
|
403
|
+
}
|
|
404
|
+
cr := getClientRect(hwnd)
|
|
405
|
+
ex, _, _ := procGetWindowLongPtr.Call(hwnd, uintptr(int(gwlExStyle)))
|
|
406
|
+
vis, _, _ := procIsWindowVisible.Call(hwnd)
|
|
407
|
+
fg, _, _ := procGetForegroundWindow.Call()
|
|
408
|
+
rt, rtErr := runtimeFor(hwnd)
|
|
409
|
+
return map[string]interface{}{
|
|
410
|
+
"title": getWindowText(hwnd),
|
|
411
|
+
"outerX": int(r.left),
|
|
412
|
+
"outerY": int(r.top),
|
|
413
|
+
"outerWidth": int(r.right - r.left),
|
|
414
|
+
"outerHeight": int(r.bottom - r.top),
|
|
415
|
+
"innerWidth": int(cr.right - cr.left),
|
|
416
|
+
"innerHeight": int(cr.bottom - cr.top),
|
|
417
|
+
"scaleFactor": windowScaleFactor(hwnd),
|
|
418
|
+
"visible": vis != 0,
|
|
419
|
+
"focused": fg == hwnd,
|
|
420
|
+
"maximized": isZoomed(hwnd),
|
|
421
|
+
"minimized": isIconic(hwnd),
|
|
422
|
+
"alwaysOnTop": ex&wsExTopmost != 0,
|
|
423
|
+
"skipTaskbar": ex&wsExToolwindow != 0,
|
|
424
|
+
"frameless": mode == TitleBarFrameless,
|
|
425
|
+
"fullscreen": rtErr == nil && rt.getFullscreen(),
|
|
426
|
+
}, nil
|
|
165
427
|
}
|
|
166
428
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
429
|
+
func isIconic(hwnd uintptr) bool {
|
|
430
|
+
r, _, _ := procIsIconic.Call(hwnd)
|
|
431
|
+
return r != 0
|
|
170
432
|
}
|
|
171
433
|
|
|
172
|
-
//
|
|
173
|
-
func
|
|
174
|
-
|
|
434
|
+
// getWindowText 读取窗口标题(GetWindowTextW)。
|
|
435
|
+
func getWindowText(hwnd uintptr) string {
|
|
436
|
+
n, _, _ := procGetWindowTextLength.Call(hwnd)
|
|
437
|
+
if n == 0 {
|
|
438
|
+
return ""
|
|
439
|
+
}
|
|
440
|
+
buf := make([]uint16, n+1)
|
|
441
|
+
procGetWindowText.Call(hwnd, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
|
|
442
|
+
return syscall.UTF16ToString(buf)
|
|
175
443
|
}
|
|
176
444
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return nil
|
|
445
|
+
// winParams 是窗口动作的 JSON 参数对象。
|
|
446
|
+
type winParams map[string]json.RawMessage
|
|
447
|
+
|
|
448
|
+
func parseWinParams(paramsJSON string) winParams {
|
|
449
|
+
var p winParams
|
|
450
|
+
if len(paramsJSON) > 0 && paramsJSON != "null" {
|
|
451
|
+
_ = json.Unmarshal([]byte(paramsJSON), &p)
|
|
185
452
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
453
|
+
return p
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
func (p winParams) intv(k string) (int, bool) {
|
|
457
|
+
if v, ok := p[k]; ok {
|
|
458
|
+
var f float64
|
|
459
|
+
if json.Unmarshal(v, &f) == nil {
|
|
460
|
+
return int(f), true
|
|
461
|
+
}
|
|
190
462
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
463
|
+
return 0, false
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
func (p winParams) strv(k string) (string, bool) {
|
|
467
|
+
if v, ok := p[k]; ok {
|
|
468
|
+
var s string
|
|
469
|
+
if json.Unmarshal(v, &s) == nil {
|
|
470
|
+
return s, true
|
|
471
|
+
}
|
|
194
472
|
}
|
|
195
|
-
return
|
|
473
|
+
return "", false
|
|
196
474
|
}
|
|
197
475
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
476
|
+
func (p winParams) boolean(k string) (bool, bool) {
|
|
477
|
+
if v, ok := p[k]; ok {
|
|
478
|
+
var b bool
|
|
479
|
+
if json.Unmarshal(v, &b) == nil {
|
|
480
|
+
return b, true
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return false, false
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
func isZoomed(hwnd uintptr) bool {
|
|
487
|
+
r, _, _ := procIsZoomed.Call(hwnd)
|
|
488
|
+
return r != 0
|
|
201
489
|
}
|
|
202
490
|
|
|
203
491
|
// loadExeIcon 从 exe 内嵌的 RT_GROUP_ICON 资源加载 HICON。
|
|
204
492
|
// rcedit 注入的 group icon 资源 ID 固定为 0(LoadImageW 需按该 ID 加载;
|
|
205
493
|
// 历史按 ID=1 硬编码导致加载失败,故此处显式用 0)。
|
|
206
494
|
// 调用方用完须 procDestroyIcon 释放;加载失败返回 0。
|
|
495
|
+
// 供 tray_windows.go 托盘图标与 syscap_windows.go 窗口效果使用。
|
|
207
496
|
func loadExeIcon(cx, cy uintptr) uintptr {
|
|
208
497
|
hInst, _, _ := procGetModuleHandle.Call(0)
|
|
209
498
|
if hInst == 0 {
|
|
210
499
|
return 0
|
|
211
500
|
}
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
func exeAppIconDataURL() string {
|
|
220
|
-
data := exeIconData()
|
|
221
|
-
if len(data) == 0 {
|
|
222
|
-
return ""
|
|
223
|
-
}
|
|
224
|
-
// rcedit 注入 .ico 内嵌 PNG 时,RT_ICON 数据即 PNG 字节,直接编码
|
|
225
|
-
if isPNG(data) {
|
|
226
|
-
return "data:image/png;base64," + base64.StdEncoding.EncodeToString(data)
|
|
227
|
-
}
|
|
228
|
-
// 非 PNG(BMP/DIB):纯 Go 解析像素并编码为 PNG,无需 GDI 绘制
|
|
229
|
-
img, err := dibToPNG(data)
|
|
230
|
-
if err != nil {
|
|
231
|
-
return ""
|
|
232
|
-
}
|
|
233
|
-
return "data:image/png;base64," + base64.StdEncoding.EncodeToString(img)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// webview2Available 检测 Windows 是否已安装 WebView2 Runtime。
|
|
237
|
-
// webview_create(WEBVIEW_EDGE)失败的最常见原因是缺少 WebView2 Runtime,
|
|
238
|
-
// 检测其安装注册表键(EdgeUpdate Clients GUID)判断状态,便于给出可操作提示。
|
|
239
|
-
// 返回值仅用于决定错误文案,不参与窗口创建逻辑。
|
|
240
|
-
func webview2Available() bool {
|
|
241
|
-
guid := `{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}`
|
|
242
|
-
key := `SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\` + guid
|
|
243
|
-
if k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE|registry.READ); err == nil {
|
|
244
|
-
k.Close()
|
|
245
|
-
return true
|
|
246
|
-
}
|
|
247
|
-
key2 := `SOFTWARE\Microsoft\EdgeUpdate\Clients\` + guid
|
|
248
|
-
if k, err := registry.OpenKey(registry.LOCAL_MACHINE, key2, registry.QUERY_VALUE|registry.READ); err == nil {
|
|
249
|
-
k.Close()
|
|
250
|
-
return true
|
|
501
|
+
// 先按 rcedit 注入约定 RT_GROUP_ICON ID=0 加载;失败回退 ID=1(M3:
|
|
502
|
+
// 与 appIcon 的 RT_ICON ID 假设解耦,兼容不同注入工具的资源 ID)。
|
|
503
|
+
for _, id := range []uintptr{0, 1} {
|
|
504
|
+
// LoadImageW(hInst, MAKEINTRESOURCE(id), IMAGE_ICON, cx, cy, LR_DEFAULTCOLOR)
|
|
505
|
+
if r, _, _ := procLoadImage.Call(hInst, id, 1 /*IMAGE_ICON*/, cx, cy, 0 /*LR_DEFAULTCOLOR*/); r != 0 {
|
|
506
|
+
return r
|
|
507
|
+
}
|
|
251
508
|
}
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
func isZoomed(hwnd uintptr) bool {
|
|
256
|
-
r, _, _ := procIsZoomed.Call(hwnd)
|
|
257
|
-
return r != 0
|
|
509
|
+
return 0
|
|
258
510
|
}
|
|
259
511
|
|
|
260
512
|
// applyTitleBar 依据配置调整窗口标题栏(Windows 实现)。
|
|
@@ -265,121 +517,24 @@ func (a *App) applyTitleBar() {
|
|
|
265
517
|
}
|
|
266
518
|
switch a.cfg.TitleBar {
|
|
267
519
|
case TitleBarFrameless:
|
|
268
|
-
// 完全无边框:去掉标题栏 /
|
|
269
|
-
//
|
|
270
|
-
// (window.freedom.window.*)接管;
|
|
271
|
-
// - 去掉 WS_THICKFRAME 是最大化行为正确的关键:只要该位存在,系统在
|
|
272
|
-
// WM_GETMINMAXINFO 之后仍会按 AdjustWindowRectEx 追加标题栏 + 边框空间,
|
|
273
|
-
// 导致最大化窗口溢出工作区、盖住任务栏(F11 全屏感);
|
|
274
|
-
// - 边缘拖拽 resize 由壳层 WndProc 的 WM_NCHITTEST 手动实现(返回 HT* 边界值),
|
|
275
|
-
// 不依赖 WS_THICKFRAME。
|
|
520
|
+
// 完全无边框:去掉标题栏 / 系统菜单,客户区铺满整个窗口。
|
|
521
|
+
// 最小化 / 最大化 / 关闭按钮由前端自绘(window.freedom.window.*)。
|
|
276
522
|
style := getWindowStyle(hwnd)
|
|
277
|
-
style &^= wsCaption | wsSysMenu
|
|
523
|
+
style &^= wsCaption | wsSysMenu
|
|
278
524
|
setWindowStyle(hwnd, style)
|
|
279
525
|
refreshFrame(hwnd)
|
|
280
|
-
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// setWindowIcon 把 exe 内嵌的应用程序图标同步到窗口标题栏与任务栏:
|
|
292
|
-
// - native 模式:标题栏图标(ICON_SMALL)与任务栏 / Alt-Tab 图标(ICON_BIG)均来自 exe 图标,
|
|
293
|
-
// 保证「标题栏图标 = exe 图标」;
|
|
294
|
-
// - frameless 模式:无标题栏,但任务栏 / Alt-Tab 仍显示 exe 图标。
|
|
295
|
-
//
|
|
296
|
-
// 图标经 LoadImageW 从 PE 资源 RT_GROUP_ICON(ID=0) 加载(rcedit 注入的 group icon ID=0),
|
|
297
|
-
// 不依赖 resources 文件夹里的任何图标文件。
|
|
298
|
-
func (a *App) setWindowIcon() {
|
|
299
|
-
hwnd := a.WindowHandle()
|
|
300
|
-
if hwnd == 0 {
|
|
301
|
-
return
|
|
302
|
-
}
|
|
303
|
-
cxIcon, _, _ := procGetSystemMetrics.Call(smCXIcon)
|
|
304
|
-
cyIcon, _, _ := procGetSystemMetrics.Call(smCYIcon)
|
|
305
|
-
cxSmall, _, _ := procGetSystemMetrics.Call(smCXSmall)
|
|
306
|
-
cySmall, _, _ := procGetSystemMetrics.Call(smCYSmall)
|
|
307
|
-
big := loadExeIcon(cxIcon, cyIcon)
|
|
308
|
-
small := loadExeIcon(cxSmall, cySmall)
|
|
309
|
-
if big != 0 {
|
|
310
|
-
procSendMessage.Call(hwnd, wmSetIcon, 1 /* ICON_BIG */, big)
|
|
311
|
-
procDestroyIcon.Call(big)
|
|
312
|
-
}
|
|
313
|
-
if small != 0 {
|
|
314
|
-
procSendMessage.Call(hwnd, wmSetIcon, 0 /* ICON_SMALL */, small)
|
|
315
|
-
procDestroyIcon.Call(small)
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// dibToPNG 将 ICO 内嵌的 DIB 图像(BITMAPINFOHEADER 起始的裸 DIB,即 rcedit 注入
|
|
320
|
-
// 非 PNG .ico 时的 RT_ICON 数据)纯 Go 解析为 PNG 字节,替代原 GDI
|
|
321
|
-
// CreateDIBSection+DrawIconEx 绘制路径(消除对 gdi32/user32 绘图 API 的依赖)。
|
|
322
|
-
// 支持 32/24 bpp;透明由 AND mask 决定(对应位=1 表示透明)。
|
|
323
|
-
func dibToPNG(data []byte) ([]byte, error) {
|
|
324
|
-
if len(data) < 40 {
|
|
325
|
-
return nil, fmt.Errorf("DIB too short: %d bytes", len(data))
|
|
326
|
-
}
|
|
327
|
-
biSize := binary.LittleEndian.Uint32(data[0:4])
|
|
328
|
-
if biSize < 40 {
|
|
329
|
-
return nil, fmt.Errorf("unsupported BITMAPINFOHEADER size %d", biSize)
|
|
330
|
-
}
|
|
331
|
-
width := int(binary.LittleEndian.Uint32(data[4:8]))
|
|
332
|
-
heightRaw := int32(binary.LittleEndian.Uint32(data[8:12]))
|
|
333
|
-
bitCount := binary.LittleEndian.Uint16(data[14:16])
|
|
334
|
-
if width <= 0 || heightRaw == 0 {
|
|
335
|
-
return nil, fmt.Errorf("bad dimensions %dx%d", width, heightRaw)
|
|
336
|
-
}
|
|
337
|
-
bpp := (int(bitCount) + 7) / 8 // bytes per pixel
|
|
338
|
-
if bpp != 3 && bpp != 4 {
|
|
339
|
-
return nil, fmt.Errorf("unsupported bitcount %d", bitCount)
|
|
340
|
-
}
|
|
341
|
-
height := int(heightRaw)
|
|
342
|
-
if height < 0 {
|
|
343
|
-
height = -height
|
|
344
|
-
}
|
|
345
|
-
// ICO 的 DIB 高度可能为实际像素高度的 2 倍(上方 XOR + 下方 AND mask),
|
|
346
|
-
// 此时需折半,否则图像会被纵向拉伸。
|
|
347
|
-
pixelRowBytes := (width*bpp + 3) &^ 3 // 每行按 4 字节对齐
|
|
348
|
-
andRowBytes := ((width + 31) / 32) * 4
|
|
349
|
-
pxLen := pixelRowBytes * height
|
|
350
|
-
if pxLen > len(data)-int(biSize) && height%2 == 0 {
|
|
351
|
-
height /= 2
|
|
352
|
-
pxLen = pixelRowBytes * height
|
|
353
|
-
}
|
|
354
|
-
pxStart := int(biSize)
|
|
355
|
-
andStart := pxStart + pxLen
|
|
356
|
-
hasAndMask := heightRaw > 0 // 正高度表示 DIB 末尾带 AND mask
|
|
357
|
-
if !hasAndMask {
|
|
358
|
-
hasAndMask = andStart+andRowBytes*height <= len(data)
|
|
359
|
-
}
|
|
360
|
-
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
361
|
-
for y := 0; y < height; y++ {
|
|
362
|
-
srcY := height - 1 - y // DIB 自底向上:数据首行是图像末行
|
|
363
|
-
row := pxStart + srcY*pixelRowBytes
|
|
364
|
-
andRow := andStart + srcY*andRowBytes
|
|
365
|
-
for x := 0; x < width; x++ {
|
|
366
|
-
off := row + x*bpp
|
|
367
|
-
if off+bpp > len(data) {
|
|
368
|
-
return nil, fmt.Errorf("DIB data truncated at x=%d,y=%d", x, y)
|
|
369
|
-
}
|
|
370
|
-
b := data[off]
|
|
371
|
-
g := data[off+1]
|
|
372
|
-
r := data[off+2]
|
|
373
|
-
a := uint8(255)
|
|
374
|
-
if hasAndMask && (data[andRow+x/8]>>uint(7-x%8))&1 == 1 {
|
|
375
|
-
a = 0
|
|
376
|
-
}
|
|
377
|
-
img.SetRGBA(x, y, color.RGBA{r, g, b, a})
|
|
526
|
+
case TitleBarHidden:
|
|
527
|
+
// 隐藏标题栏视觉但保留系统原生按钮:DWM 玻璃扩展。
|
|
528
|
+
// 标题栏区域透明化并并入客户区,右上角的最小化 / 最大化 / 关闭按钮
|
|
529
|
+
// 由 DWM 继续原生绘制,标题文字置空。
|
|
530
|
+
// 标题栏区域并入客户区须扩展顶部(cyTopHeight),扩展底部不影响标题栏视觉。
|
|
531
|
+
m := margins{cxLeftWidth: 0, cxRightWidth: 0, cyTopHeight: 1, cyBottomHeight: 0}
|
|
532
|
+
procDwmExtendFrameIntoArea.Call(hwnd, uintptr(unsafe.Pointer(&m)))
|
|
533
|
+
// 标题文字一并清除,标题栏区域只保留系统按钮
|
|
534
|
+
if title, err := syscall.UTF16PtrFromString(""); err == nil {
|
|
535
|
+
procSetWindowText.Call(hwnd, uintptr(unsafe.Pointer(title)))
|
|
378
536
|
}
|
|
537
|
+
refreshFrame(hwnd)
|
|
538
|
+
default: // TitleBarNative:不处理
|
|
379
539
|
}
|
|
380
|
-
var buf bytes.Buffer
|
|
381
|
-
if err := png.Encode(&buf, img); err != nil {
|
|
382
|
-
return nil, err
|
|
383
|
-
}
|
|
384
|
-
return buf.Bytes(), nil
|
|
385
540
|
}
|