@yufengtadian/freedom-cli 1.12.18 → 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 +1 -1
- package/lib/security.js +1 -1
- package/package.json +5 -1
- 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 +200 -5
- 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 +291 -53
- 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 +45 -35
- 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 +193 -99
- 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} +38 -35
- 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 +15 -3
- package/templates/go/pkg/freedom/syscap_windows.go +228 -36
- 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 +2 -2
- package/templates/go/pkg/freedom/tray_windows.go +69 -39
- 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 +397 -254
- package/templates/go/build_all.txt +0 -0
- package/templates/go/build_err.txt +0 -0
- package/templates/go/gen_config.go +0 -22
- package/templates/go/pkg/freedom/center_windows_test.go +0 -32
- package/templates/go/pkg/freedom/security_test.go +0 -153
- package/templates/go/temp_vet_test/main.go +0 -40
- package/templates/go/test_err.txt +0 -0
- package/templates/go/vet_err.txt +0 -1
- package/templates/go/vet_err2.txt +0 -2
- 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
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
// 编译三平台。
|
|
8
8
|
// - backend:后端抽象。任意时刻绑定一个后端,前端 window.freedom.call 全部路由过去。
|
|
9
9
|
// 内置两种实现:
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
10
|
+
// - EmbedBackend:Go 方法直接注册在壳进程内(app.Bind,兼容 v1 用法)。
|
|
11
|
+
// - ProcBackend:后端是任意语言实现的独立进程(Go/Rust/Python/Node/Java…),
|
|
12
|
+
// 通过 stdin/stdout 上的 NDJSON/JSON-RPC 与壳通信。协议语言无关,
|
|
13
|
+
// 换一种后端语言无需改壳与前端。
|
|
14
14
|
// - ipc:双向桥接。前端 window.freedom.call(method, ...args) -> Promise;
|
|
15
15
|
// 后端 app.Emit(event, data) 向所有前端监听器推送事件。
|
|
16
16
|
// - assets:前端编译产物(Vite 单文件 HTML)通过 go:embed 嵌入二进制,运行时
|
|
@@ -21,22 +21,29 @@ import (
|
|
|
21
21
|
"encoding/json"
|
|
22
22
|
"errors"
|
|
23
23
|
"fmt"
|
|
24
|
+
"os"
|
|
24
25
|
"sync"
|
|
25
|
-
"
|
|
26
|
+
"sync/atomic"
|
|
26
27
|
|
|
27
28
|
webview "github.com/webview/webview_go"
|
|
28
29
|
)
|
|
29
30
|
|
|
31
|
+
// Version 是应用版本号,构建期经 -ldflags "-X freedom.Version=x.y.z" 注入
|
|
32
|
+
// (见 build.ps1/build.sh 的 -Version/VERSION)。前端经 os.info 的 appVersion 读取。
|
|
33
|
+
var Version = "dev"
|
|
34
|
+
|
|
30
35
|
// TitleBarMode 描述窗口标题栏策略。
|
|
31
36
|
type TitleBarMode string
|
|
32
37
|
|
|
33
38
|
const (
|
|
34
|
-
// TitleBarNative
|
|
39
|
+
// TitleBarNative 保留系统原生标题栏(非默认;默认见 TitleBarFrameless)。
|
|
35
40
|
TitleBarNative TitleBarMode = "native"
|
|
41
|
+
// TitleBarHidden 隐藏标题栏视觉,但保留系统原生最小化 / 最大化 / 关闭按钮。
|
|
42
|
+
// 当前仅 Windows 生效(基于 DWM 扩展实现);macOS / Linux 上回退为原生标题栏。
|
|
43
|
+
TitleBarHidden TitleBarMode = "hidden"
|
|
36
44
|
// TitleBarFrameless 完全无边框,客户区铺满整个窗口。
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// 需由前端自绘(通过 window.freedom.window.* 控制),三平台行为一致。
|
|
45
|
+
// 最小化 / 最大化 / 关闭按钮需由前端自绘(通过 window.freedom.window.* 控制),
|
|
46
|
+
// 三平台行为一致。
|
|
40
47
|
TitleBarFrameless TitleBarMode = "frameless"
|
|
41
48
|
)
|
|
42
49
|
|
|
@@ -44,14 +51,15 @@ const (
|
|
|
44
51
|
type Config struct {
|
|
45
52
|
// Title 是窗口标题。
|
|
46
53
|
Title string
|
|
47
|
-
// TitleBar 指定标题栏策略(默认 TitleBarFrameless
|
|
54
|
+
// TitleBar 指定标题栏策略(默认 TitleBarFrameless)。可通过 freedom CLI 一键切换。
|
|
48
55
|
TitleBar TitleBarMode
|
|
49
56
|
// Width / Height 是窗口初始尺寸(像素)。
|
|
50
57
|
Width int
|
|
51
58
|
Height int
|
|
52
59
|
// Center 为 true 时窗口在屏幕居中(Windows 生效;macOS/Linux 由窗口管理器决定)。
|
|
53
60
|
Center bool
|
|
54
|
-
// MinWidth / MinHeight
|
|
61
|
+
// MinWidth / MinHeight 为窗口最小尺寸。两个字段均 >0 时作为一组约束生效
|
|
62
|
+
//(webview 的 HintMin 按整组应用);任一 <=0 表示不启用最小尺寸限制。
|
|
55
63
|
MinWidth int
|
|
56
64
|
MinHeight int
|
|
57
65
|
// Debug 为 true 时开启 WebView 开发者工具(目标平台支持时)。
|
|
@@ -61,17 +69,86 @@ type Config struct {
|
|
|
61
69
|
// HTML 返回要加载到窗口的前端页面内容(内存加载,无本地端口)。
|
|
62
70
|
// 为 nil 时使用框架内置的默认占位页。
|
|
63
71
|
HTML func() (string, error)
|
|
72
|
+
// AppID 是应用标识,决定数据/配置目录名与单实例窗口类标题。
|
|
73
|
+
// 为空时回退可执行文件名(去扩展名)。一经发布不要更改,否则用户数据"丢失"。
|
|
74
|
+
AppID string
|
|
75
|
+
// RememberWindowState 为 true 时窗口位置/尺寸/最大化状态跨启动记忆
|
|
76
|
+
// (存于 <dataDir>/window-state.json;Windows 生效,其他平台暂不持久化)。
|
|
77
|
+
RememberWindowState bool
|
|
78
|
+
// Update 配置自动更新(ed25519 验签 manifest + sha256 校验产物)。
|
|
79
|
+
// 为 nil 时更新能力关闭。见 updater.go。
|
|
80
|
+
Update *UpdateConfig
|
|
81
|
+
// Capabilities 收口前端 sys/tray/window 三桥的可调用面(M3)。
|
|
82
|
+
// nil(默认)全开;语义见 capability.go。os.info 回显生效配置。
|
|
83
|
+
Capabilities *Capabilities
|
|
64
84
|
}
|
|
65
85
|
|
|
66
86
|
// App 是 Freedom 应用实例。
|
|
67
87
|
type App struct {
|
|
68
|
-
cfg
|
|
69
|
-
|
|
70
|
-
view
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
onReady
|
|
74
|
-
|
|
88
|
+
cfg Config
|
|
89
|
+
view webview.WebView
|
|
90
|
+
viewMu sync.RWMutex // 保护 view:Emit/Quit/WindowHandle 承诺可从任意 goroutine 调用
|
|
91
|
+
wsMu sync.Mutex // 串行化 window-state 落盘(拖拽异步保存可与销毁保存交叠)
|
|
92
|
+
backend Backend
|
|
93
|
+
onReady func(a *App)
|
|
94
|
+
|
|
95
|
+
// 运行时资源覆盖(resources.go):secure = high 模式(app.bin 解密成功),
|
|
96
|
+
// backendExplicit = 用户经 Bind 显式使用内嵌后端(config.json 的 backend 不得再替换)。
|
|
97
|
+
secure bool
|
|
98
|
+
backendExplicit bool
|
|
99
|
+
|
|
100
|
+
// upMu 保护 upPending:CheckUpdate 验签通过的更新条目,install 桥接只认它
|
|
101
|
+
//(前端无法注入未验签的 URL/哈希)。见 updater.go。
|
|
102
|
+
upMu sync.Mutex
|
|
103
|
+
upPending *UpdateInfo
|
|
104
|
+
upInstalling bool // 单飞:并发 update.install 只放行一个换装
|
|
105
|
+
|
|
106
|
+
// M2 多窗口注册表:windows 含 "main",次级窗口关闭后自行摘除。
|
|
107
|
+
// winMu 同时保护 map、winSeq 与 running 标志(runningMu 并入,减少锁面)。
|
|
108
|
+
winMu sync.Mutex
|
|
109
|
+
windows map[string]*Window
|
|
110
|
+
winSeq int
|
|
111
|
+
running bool
|
|
112
|
+
|
|
113
|
+
// viewTearing 在 Run 的 Destroy 前置位(语义同 Window.tearing):
|
|
114
|
+
// 主窗口已入队的 dispatch 回调会被销毁路径泵出执行,闭包凭此原子量自我作废。
|
|
115
|
+
viewTearing atomic.Bool
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// appForEvents 指向当前运行的 App,供平台层回调(单实例/热键等)向 Emit 事件。
|
|
119
|
+
// 契约:一个进程只跑一个 App——托盘/消息窗口/热键等平台层状态均为进程级单例,
|
|
120
|
+
// 并发创建多个 App 会让事件串门。
|
|
121
|
+
var appForEvents atomic.Pointer[App]
|
|
122
|
+
|
|
123
|
+
func currentApp() *App { return appForEvents.Load() }
|
|
124
|
+
|
|
125
|
+
// setView 设置主窗口 webview 引用。
|
|
126
|
+
func (a *App) setView(w webview.WebView) {
|
|
127
|
+
a.viewMu.Lock()
|
|
128
|
+
a.view = w
|
|
129
|
+
a.viewMu.Unlock()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// withView 在 viewMu 读锁内对主窗口 webview 执行 f;未创建/已摘除时静默跳过。
|
|
133
|
+
// 与 Run 的拆除序列(写锁内摘引用 + Destroy)互斥:调用方拿到引用并 Dispatch 的
|
|
134
|
+
// 窗口期内实例不可能被销毁,杜绝 check-then-destroy 竞态下的 use-after-free。
|
|
135
|
+
func (a *App) withView(f func(view webview.WebView)) {
|
|
136
|
+
a.viewMu.RLock()
|
|
137
|
+
defer a.viewMu.RUnlock()
|
|
138
|
+
if a.view != nil {
|
|
139
|
+
f(a.view)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// teardownView 是 Run 的回收 defer 与测试的同构入口:写锁内立拆除旗标、摘引用、
|
|
144
|
+
// Destroy。Destroy 会泵出队列里的 dispatch 回调(闭包读 viewTearing 自我作废,
|
|
145
|
+
// 不得再取 viewMu——回调就跑在本临界区内)。
|
|
146
|
+
func (a *App) teardownView(view webview.WebView) {
|
|
147
|
+
a.viewMu.Lock()
|
|
148
|
+
defer a.viewMu.Unlock()
|
|
149
|
+
a.viewTearing.Store(true)
|
|
150
|
+
a.view = nil
|
|
151
|
+
view.Destroy()
|
|
75
152
|
}
|
|
76
153
|
|
|
77
154
|
// New 创建并初始化一个 Freedom 应用。调用 Run() 之前不会显示窗口。
|
|
@@ -79,9 +156,9 @@ func New(cfg Config) *App {
|
|
|
79
156
|
if cfg.Title == "" {
|
|
80
157
|
cfg.Title = "Freedom App"
|
|
81
158
|
}
|
|
82
|
-
// M1
|
|
83
|
-
//
|
|
84
|
-
//
|
|
159
|
+
// M1:默认无边框(frameless),与 freedom-cli 模板默认一致。
|
|
160
|
+
// 此前默认 native 而 CLI 默认 frameless,resources 缺失/配置不完整时
|
|
161
|
+
// 会出现"系统原生标题栏 + 页面自绘标题栏"双标题栏错位。
|
|
85
162
|
if cfg.TitleBar == "" {
|
|
86
163
|
cfg.TitleBar = TitleBarFrameless
|
|
87
164
|
}
|
|
@@ -91,11 +168,10 @@ func New(cfg Config) *App {
|
|
|
91
168
|
if cfg.Height <= 0 {
|
|
92
169
|
cfg.Height = 768
|
|
93
170
|
}
|
|
94
|
-
|
|
95
|
-
if !explicit {
|
|
171
|
+
if cfg.Backend == nil {
|
|
96
172
|
cfg.Backend = NewEmbedBackend()
|
|
97
173
|
}
|
|
98
|
-
return &App{cfg: cfg, backend: cfg.Backend,
|
|
174
|
+
return &App{cfg: cfg, backend: cfg.Backend, windows: map[string]*Window{}}
|
|
99
175
|
}
|
|
100
176
|
|
|
101
177
|
// OnReady 注册一个回调,在窗口与桥接层就绪、页面加载前执行。
|
|
@@ -111,6 +187,9 @@ func (a *App) OnReady(fn func(a *App)) {
|
|
|
111
187
|
// - 参数与返回值通过 JSON 编解码,前端用 window.freedom.call(name, ...args) 调用,
|
|
112
188
|
// 返回 Promise(resolve 为返回值,reject 为 error 的字符串表示)。
|
|
113
189
|
// - 返回值约定:可返回 (T, error) 或仅 (T) 或仅 error 或空。
|
|
190
|
+
// - 执行线程(M1 起):绑定函数在 worker goroutine 中执行,UI 线程不被
|
|
191
|
+
// 长任务阻塞;多次调用可并发、完成顺序不保证。handler 内调用 Emit 等
|
|
192
|
+
// 线程安全 API 即可回推结果。
|
|
114
193
|
func (a *App) Bind(name string, fn interface{}) error {
|
|
115
194
|
eb, ok := a.backend.(*EmbedBackend)
|
|
116
195
|
if !ok {
|
|
@@ -119,9 +198,8 @@ func (a *App) Bind(name string, fn interface{}) error {
|
|
|
119
198
|
if err := eb.Bind(name, fn); err != nil {
|
|
120
199
|
return err
|
|
121
200
|
}
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// 避免"用户 Bind 的方法被 config.json 的进程后端静默替换而全部失效"。
|
|
201
|
+
// 用户显式绑定方法 = 显式指定内嵌后端:resources/config.json 的 backend
|
|
202
|
+
// 配置不再覆盖,避免内嵌方法被进程后端静默替换而全部失效。
|
|
125
203
|
a.backendExplicit = true
|
|
126
204
|
return nil
|
|
127
205
|
}
|
|
@@ -133,29 +211,13 @@ func (a *App) Unbind(name string) {
|
|
|
133
211
|
}
|
|
134
212
|
}
|
|
135
213
|
|
|
136
|
-
// getView / setView 以互斥锁保护 view 字段:Run 在主 goroutine 写,
|
|
137
|
-
// Emit/Quit/WindowHandle 可能被任意 goroutine(如后端事件推送)读,
|
|
138
|
-
// 无保护时 race detector 报数据竞争。
|
|
139
|
-
func (a *App) getView() webview.WebView {
|
|
140
|
-
a.viewMu.Lock()
|
|
141
|
-
defer a.viewMu.Unlock()
|
|
142
|
-
return a.view
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
func (a *App) setView(v webview.WebView) {
|
|
146
|
-
a.viewMu.Lock()
|
|
147
|
-
a.view = v
|
|
148
|
-
a.viewMu.Unlock()
|
|
149
|
-
}
|
|
150
|
-
|
|
151
214
|
// Run 启动窗口并进入主事件循环,阻塞直到窗口被关闭。
|
|
152
215
|
func (a *App) Run() {
|
|
153
|
-
// 通用壳:先加载 exe 同目录 resources/
|
|
154
|
-
|
|
155
|
-
// 配置存在但非法时打印告警(不中断启动),避免用户手改配置出错时静默无感。
|
|
216
|
+
// 通用壳:先加载 exe 同目录 resources/ 覆盖窗口与后端配置(CLI build 写入;
|
|
217
|
+
// 缺失则使用编译期/默认配置)。配置存在但非法时打印告警不中断启动。
|
|
156
218
|
if err := a.loadRuntimeConfig(); err != nil {
|
|
157
|
-
// H2:high
|
|
158
|
-
//
|
|
219
|
+
// H2:high 模式资源解密/完整性校验失败必须拒绝运行——不显示窗口、
|
|
220
|
+
// 不回退占位页,防止资源被篡改/替换后静默降级运行。
|
|
159
221
|
var se *secureFatalError
|
|
160
222
|
if errors.As(err, &se) {
|
|
161
223
|
fmt.Printf("freedom: 安全模式资源校验失败,拒绝运行:%v\n", err)
|
|
@@ -163,12 +225,28 @@ func (a *App) Run() {
|
|
|
163
225
|
}
|
|
164
226
|
fmt.Printf("freedom: warning: %v\n", err)
|
|
165
227
|
}
|
|
166
|
-
|
|
167
|
-
// high 安全模式:反调试检测(调试器下静默退出,防止逆向解密逻辑)。
|
|
228
|
+
// high 模式:反调试检测(调试器下静默退出,防止逆向解密逻辑)。
|
|
168
229
|
if a.secure {
|
|
169
230
|
antiDebugCheck()
|
|
170
231
|
}
|
|
171
|
-
|
|
232
|
+
// 平台层回调(单实例转发/热键)经 currentApp 找到本实例推送事件。
|
|
233
|
+
appForEvents.Store(a)
|
|
234
|
+
defer appForEvents.Store(nil)
|
|
235
|
+
// M2:标记运行中(NewWindow 的前置条件)并登记主窗口;退出前回收次级窗口。
|
|
236
|
+
a.winMu.Lock()
|
|
237
|
+
a.running = true
|
|
238
|
+
a.windows[mainWindowID] = &Window{id: mainWindowID, app: a, doneCh: make(chan struct{})}
|
|
239
|
+
a.winMu.Unlock()
|
|
240
|
+
defer func() {
|
|
241
|
+
a.CloseWindows()
|
|
242
|
+
a.winMu.Lock()
|
|
243
|
+
if mw := a.windows[mainWindowID]; mw != nil {
|
|
244
|
+
close(mw.doneCh) // 主窗口随 Run 退出回收:Window("main").WaitClosed() 可等待
|
|
245
|
+
}
|
|
246
|
+
delete(a.windows, mainWindowID)
|
|
247
|
+
a.running = false
|
|
248
|
+
a.winMu.Unlock()
|
|
249
|
+
}()
|
|
172
250
|
html, err := a.resolveHTML()
|
|
173
251
|
if err != nil {
|
|
174
252
|
fmt.Printf("freedom: failed to resolve HTML: %v\n", err)
|
|
@@ -177,21 +255,18 @@ func (a *App) Run() {
|
|
|
177
255
|
|
|
178
256
|
w := webview.New(a.cfg.Debug)
|
|
179
257
|
if w == nil {
|
|
180
|
-
|
|
181
|
-
fmt.Println("freedom: failed to create webview: 未检测到 WebView2 Runtime,请安装 Microsoft Edge WebView2 Runtime(https://go.microsoft.com/fwlink/?linkid=2124703)后重试")
|
|
182
|
-
} else {
|
|
183
|
-
fmt.Println("freedom: failed to create webview")
|
|
184
|
-
}
|
|
258
|
+
fmt.Println("freedom: failed to create webview")
|
|
185
259
|
return
|
|
186
260
|
}
|
|
187
261
|
a.setView(w)
|
|
188
262
|
defer func() {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
263
|
+
if err := a.backend.Close(); err != nil {
|
|
264
|
+
fmt.Fprintf(os.Stderr, "freedom: backend close: %v\n", err)
|
|
265
|
+
}
|
|
266
|
+
a.uninstallWindowEvents()
|
|
267
|
+
// 摘引用与 Destroy 必须同处一个写锁临界区:读锁侧(withView)判空后
|
|
268
|
+
// Dispatch 的窗口期内实例不会被销毁(否则 Emit/Quit/pushResolve UAF)。
|
|
269
|
+
a.teardownView(w)
|
|
195
270
|
}()
|
|
196
271
|
|
|
197
272
|
// 进程后端:启动后端进程并把其推送的事件转发到前端。
|
|
@@ -207,21 +282,24 @@ func (a *App) Run() {
|
|
|
207
282
|
|
|
208
283
|
w.SetTitle(a.cfg.Title)
|
|
209
284
|
w.SetSize(a.cfg.Width, a.cfg.Height, webview.HintNone)
|
|
210
|
-
// 最小尺寸:任一项 >0 即生效;未设置的一项传 0
|
|
211
|
-
//(GTK gtk_widget_set_size_request / Cocoa minSize 对 0 均视为"未指定")。
|
|
285
|
+
// 最小尺寸:任一项 >0 即生效;未设置的一项传 0 表示该维度不限制。
|
|
212
286
|
if a.cfg.MinWidth > 0 || a.cfg.MinHeight > 0 {
|
|
213
287
|
w.SetSize(a.cfg.MinWidth, a.cfg.MinHeight, webview.HintMin)
|
|
214
288
|
}
|
|
215
289
|
a.applyCenter()
|
|
216
290
|
a.applyTitleBar()
|
|
217
|
-
|
|
291
|
+
// 窗口事件与关闭拦截(Windows 经 WndProc 子类化;其他平台 no-op)。
|
|
292
|
+
a.installWindowEvents()
|
|
293
|
+
// 恢复上次退出的窗口几何(仅 Config.RememberWindowState 时生效)。
|
|
294
|
+
a.restoreWindowState()
|
|
218
295
|
|
|
219
296
|
// 注入前端 SDK:window.freedom 全局对象。
|
|
220
297
|
w.Init(jsSDK)
|
|
221
298
|
|
|
222
|
-
// 绑定 IPC 桥接入口:前端 window.__freedom_bridge(method, paramsJson)。
|
|
223
|
-
//
|
|
224
|
-
|
|
299
|
+
// 绑定 IPC 桥接入口:前端 window.__freedom_bridge(id, method, paramsJson)。
|
|
300
|
+
// M1 异步化:调用立即返回,handler 在 worker goroutine 执行,结果经
|
|
301
|
+
// __freedom__resolve 回写前端 Promise(见 dispatch.go)。
|
|
302
|
+
if err := w.Bind("__freedom_bridge", a.bridgeAsync); err != nil {
|
|
225
303
|
fmt.Printf("freedom: failed to bind bridge: %v\n", err)
|
|
226
304
|
return
|
|
227
305
|
}
|
|
@@ -230,8 +308,13 @@ func (a *App) Run() {
|
|
|
230
308
|
fmt.Printf("freedom: failed to bind ping: %v\n", err)
|
|
231
309
|
return
|
|
232
310
|
}
|
|
233
|
-
//
|
|
234
|
-
// 支持动作:minimize / maximize / unmaximize / toggleMaximize / close / isMaximized /
|
|
311
|
+
// 框架内置方法:窗口控制(无边框模式下前端自绘按钮使用 + 运行时窗口能力)。
|
|
312
|
+
// 支持动作:minimize / maximize / unmaximize / toggleMaximize / close / isMaximized /
|
|
313
|
+
// isFrameless / setPosition / setSize / getPosition / getSize / innerSize / center /
|
|
314
|
+
// setTitle / show / hide / focus / isVisible / isFocused / isMinimized /
|
|
315
|
+
// setAlwaysOnTop / setSkipTaskbar / setResizable / setMaximizable / setMinimizable /
|
|
316
|
+
// setFullscreen / isFullscreen / interceptClose / getInfo。
|
|
317
|
+
// 第二参数为 JSON object 字符串(SDK 始终传入,可解析为空对象)。
|
|
235
318
|
if err := w.Bind("__freedom_window", a.windowControl); err != nil {
|
|
236
319
|
fmt.Printf("freedom: failed to bind window control: %v\n", err)
|
|
237
320
|
return
|
|
@@ -239,14 +322,15 @@ func (a *App) Run() {
|
|
|
239
322
|
// 框架内置方法:原生系统能力(wails v3 对标层)。
|
|
240
323
|
// 支持方法:taskbar.*(进度/状态/角标)、window.backdrop/corner/borderColor、
|
|
241
324
|
// dialog.message/open/save。平台实现分文件:syscap_windows.go / syscap_other.go。
|
|
242
|
-
|
|
325
|
+
// 绑定入口带 M3 能力闸(见 capability.go)。
|
|
326
|
+
if err := w.Bind("__freedom_sys", a.sysCapGated); err != nil {
|
|
243
327
|
fmt.Printf("freedom: failed to bind sys capability: %v\n", err)
|
|
244
328
|
return
|
|
245
329
|
}
|
|
246
330
|
// 框架内置方法:系统托盘与原生菜单栏。
|
|
247
331
|
// 支持方法:tray.create/destroy/tooltip/menu、menu.set。
|
|
248
|
-
// 平台实现分文件:tray_windows.go / tray_other.go
|
|
249
|
-
if err := w.Bind("__freedom_tray", a.
|
|
332
|
+
// 平台实现分文件:tray_windows.go / tray_other.go。绑定入口带 M3 能力闸。
|
|
333
|
+
if err := w.Bind("__freedom_tray", a.trayGated); err != nil {
|
|
250
334
|
fmt.Printf("freedom: failed to bind tray: %v\n", err)
|
|
251
335
|
return
|
|
252
336
|
}
|
|
@@ -284,6 +368,12 @@ func (a *App) bridge(method string, paramsJSON string) (result json.RawMessage,
|
|
|
284
368
|
}
|
|
285
369
|
}()
|
|
286
370
|
|
|
371
|
+
// 框架内置健康检查:__freedom__ping 同时以 JS 直调全局函数与桥接路由两种方式
|
|
372
|
+
// 暴露(占位页/文档示例走桥接路由),此处兜底保证两条链路行为一致。
|
|
373
|
+
if method == "__freedom__ping" {
|
|
374
|
+
return json.RawMessage(`"pong"`), nil
|
|
375
|
+
}
|
|
376
|
+
|
|
287
377
|
var params []json.RawMessage
|
|
288
378
|
if len(paramsJSON) > 0 && paramsJSON != "null" {
|
|
289
379
|
if err := json.Unmarshal([]byte(paramsJSON), ¶ms); err != nil {
|
|
@@ -291,13 +381,6 @@ func (a *App) bridge(method string, paramsJSON string) (result json.RawMessage,
|
|
|
291
381
|
}
|
|
292
382
|
}
|
|
293
383
|
|
|
294
|
-
// 框架内置方法特判:__freedom__ping 由 webview Bind 注册为全局函数
|
|
295
|
-
// window.__freedom__ping(),同时兼容经 freedom.call()/__freedom_bridge 路由的旧写法,
|
|
296
|
-
// 避免模板"测试桥接"自检报"method not bound"。
|
|
297
|
-
if method == "__freedom__ping" {
|
|
298
|
-
return json.RawMessage(`"pong"`), nil
|
|
299
|
-
}
|
|
300
|
-
|
|
301
384
|
raw, err := a.backend.Handle(method, params)
|
|
302
385
|
if err != nil {
|
|
303
386
|
return nil, err
|
|
@@ -312,41 +395,45 @@ func (a *App) bridge(method string, paramsJSON string) (result json.RawMessage,
|
|
|
312
395
|
// Emit 把事件推送到前端。前端通过 window.freedom.on(event, cb) 订阅。
|
|
313
396
|
// 线程安全:可从任意 goroutine 调用(进程后端推送的事件亦经由本函数)。
|
|
314
397
|
func (a *App) Emit(event string, data interface{}) {
|
|
315
|
-
v := a.getView()
|
|
316
|
-
if v == nil {
|
|
317
|
-
return
|
|
318
|
-
}
|
|
319
398
|
eb, _ := json.Marshal(event)
|
|
320
399
|
db, err := json.Marshal(data)
|
|
321
400
|
if err != nil {
|
|
322
401
|
db = []byte("null")
|
|
323
402
|
}
|
|
324
403
|
js := "window.freedom && window.freedom.emit(" + string(eb) + "," + string(db) + ");"
|
|
325
|
-
|
|
326
|
-
|
|
404
|
+
a.withView(func(view webview.WebView) {
|
|
405
|
+
view.Dispatch(func() {
|
|
406
|
+
if !a.viewTearing.Load() {
|
|
407
|
+
view.Eval(js)
|
|
408
|
+
}
|
|
409
|
+
})
|
|
327
410
|
})
|
|
411
|
+
a.emitSecondary(js) // M2:事件广播到全部次级窗口
|
|
328
412
|
}
|
|
329
413
|
|
|
330
414
|
// Quit 关闭窗口并退出应用。可从任意 goroutine 调用。
|
|
331
415
|
func (a *App) Quit() {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
416
|
+
a.withView(func(view webview.WebView) {
|
|
417
|
+
view.Dispatch(func() {
|
|
418
|
+
if !a.viewTearing.Load() {
|
|
419
|
+
view.Terminate()
|
|
420
|
+
}
|
|
335
421
|
})
|
|
336
|
-
}
|
|
422
|
+
})
|
|
337
423
|
}
|
|
338
424
|
|
|
339
425
|
// WindowHandle 返回底层原生窗口句柄(Windows 上为 HWND)。
|
|
340
426
|
func (a *App) WindowHandle() uintptr {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
427
|
+
var h uintptr
|
|
428
|
+
a.withView(func(view webview.WebView) {
|
|
429
|
+
h = uintptr(view.Window())
|
|
430
|
+
})
|
|
431
|
+
return h
|
|
345
432
|
}
|
|
346
433
|
|
|
347
434
|
// windowControl 处理前端 window.freedom.window.* 的窗口控制请求。
|
|
348
435
|
// 具体实现按平台分文件:window_windows.go(Windows)/ window_other.go(macOS、Linux)。
|
|
349
|
-
func (a *App) windowControl(action string) (result interface{}, err error) {
|
|
436
|
+
func (a *App) windowControl(action string, paramsJSON string) (result interface{}, err error) {
|
|
350
437
|
// H1:windowControl 直接经 w.Bind 暴露给前端,不经 bridge 的 recover 兜底;
|
|
351
438
|
// 平台实现(如 appIcon → dibToPNG 解析越界)一旦 panic 会直接崩掉整个壳进程。
|
|
352
439
|
// 这里统一加 recover,把 panic 转成错误回传前端(页面 catch 后提示,进程不崩)。
|
|
@@ -356,19 +443,26 @@ func (a *App) windowControl(action string) (result interface{}, err error) {
|
|
|
356
443
|
err = fmt.Errorf("freedom: window action %q panicked: %v", action, r)
|
|
357
444
|
}
|
|
358
445
|
}()
|
|
359
|
-
|
|
446
|
+
// M3 能力闸先于一切动作处理(拒绝路径不触平台层,无副作用)。
|
|
447
|
+
if err := a.capCheckWindow(action); err != nil {
|
|
448
|
+
return nil, err
|
|
449
|
+
}
|
|
450
|
+
if res, ok, merr := a.windowManage(action, paramsJSON); ok {
|
|
451
|
+
return res, merr
|
|
452
|
+
}
|
|
453
|
+
return windowControl(a.WindowHandle(), action, a.cfg.TitleBar, paramsJSON)
|
|
360
454
|
}
|
|
361
455
|
|
|
362
456
|
// resolveHTML 依据配置返回页面内容。
|
|
363
|
-
// 优先级:exe 同目录 resources
|
|
457
|
+
// 优先级:exe 同目录 resources/(high 模式 app.bin 或 index.html,预编译通用壳)
|
|
458
|
+
// > cfg.HTML(go:embed 源码构建)> 内置占位页。
|
|
364
459
|
func (a *App) resolveHTML() (string, error) {
|
|
365
460
|
html, err := loadRuntimeHTML()
|
|
366
461
|
if err == nil && html != "" {
|
|
367
462
|
return html, nil
|
|
368
463
|
}
|
|
369
|
-
// H2 双保险:high
|
|
370
|
-
// 正常路径下 Run
|
|
371
|
-
// 此处防御未来调用顺序调整导致的静默降级。
|
|
464
|
+
// H2 双保险:high 模式校验失败时禁止回退 cfg.HTML/占位页(拒绝运行)。
|
|
465
|
+
// 正常路径下 Run 已捕获 secureFatalError 提前 return,此处防御调用顺序调整。
|
|
372
466
|
var se *secureFatalError
|
|
373
467
|
if errors.As(err, &se) {
|
|
374
468
|
return "", err
|