@yufengtadian/freedom-cli 1.12.18 → 1.13.1

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.
Files changed (80) hide show
  1. package/README.md +11 -3
  2. package/lib/build.js +1 -1
  3. package/lib/security.js +2 -2
  4. package/package.json +5 -1
  5. package/shell/darwin-arm64/freedom-shell +0 -0
  6. package/shell/linux-x64/freedom-shell +0 -0
  7. package/shell/win-x64/freedom-shell.exe +0 -0
  8. package/templates/go/go.mod +1 -7
  9. package/templates/go/go.sum +2 -2
  10. package/templates/go/main.go +22 -8
  11. package/templates/go/pkg/freedom/anti_debug_other.go +2 -3
  12. package/templates/go/pkg/freedom/anti_debug_windows.go +10 -12
  13. package/templates/go/pkg/freedom/assets/{index.html → default.html} +1 -1
  14. package/templates/go/pkg/freedom/assets/freedom.js +200 -5
  15. package/templates/go/pkg/freedom/assets_embed.go +11 -13
  16. package/templates/go/pkg/freedom/authenticode_other.go +11 -0
  17. package/templates/go/pkg/freedom/authenticode_windows.go +87 -0
  18. package/templates/go/pkg/freedom/backend_proc.go +291 -53
  19. package/templates/go/pkg/freedom/bridge.go +13 -2
  20. package/templates/go/pkg/freedom/capability.go +77 -0
  21. package/templates/go/pkg/freedom/center_other.go +3 -0
  22. package/templates/go/pkg/freedom/center_windows.go +45 -35
  23. package/templates/go/pkg/freedom/dispatch.go +60 -0
  24. package/templates/go/pkg/freedom/events_windows.go +319 -0
  25. package/templates/go/pkg/freedom/freedom.go +193 -99
  26. package/templates/go/pkg/freedom/msgwindow_windows.go +257 -0
  27. package/templates/go/pkg/freedom/osver_other.go +15 -0
  28. package/templates/go/pkg/freedom/osver_windows.go +121 -0
  29. package/templates/go/pkg/freedom/proc_hide_windows.go +2 -2
  30. package/templates/go/pkg/freedom/{configfile.go → resources.go} +38 -35
  31. package/templates/go/pkg/freedom/security.go +19 -12
  32. package/templates/go/pkg/freedom/singleinstance_other.go +10 -0
  33. package/templates/go/pkg/freedom/singleinstance_windows.go +145 -0
  34. package/templates/go/pkg/freedom/spawn_other.go +11 -0
  35. package/templates/go/pkg/freedom/spawn_windows.go +23 -0
  36. package/templates/go/pkg/freedom/store.go +458 -0
  37. package/templates/go/pkg/freedom/syscap_linux.go +278 -0
  38. package/templates/go/pkg/freedom/syscap_other.go +15 -3
  39. package/templates/go/pkg/freedom/syscap_windows.go +228 -36
  40. package/templates/go/pkg/freedom/sysint_common.go +70 -0
  41. package/templates/go/pkg/freedom/sysint_windows.go +426 -0
  42. package/templates/go/pkg/freedom/tray_common.go +12 -0
  43. package/templates/go/pkg/freedom/tray_linux.go +302 -0
  44. package/templates/go/pkg/freedom/tray_other.go +2 -2
  45. package/templates/go/pkg/freedom/tray_windows.go +69 -39
  46. package/templates/go/pkg/freedom/updater.go +372 -0
  47. package/templates/go/pkg/freedom/window_mgr.go +385 -0
  48. package/templates/go/pkg/freedom/window_other.go +32 -66
  49. package/templates/go/pkg/freedom/window_windows.go +397 -254
  50. package/templates/go/build_all.txt +0 -0
  51. package/templates/go/build_err.txt +0 -0
  52. package/templates/go/gen_config.go +0 -22
  53. package/templates/go/pkg/freedom/center_windows_test.go +0 -32
  54. package/templates/go/pkg/freedom/security_test.go +0 -153
  55. package/templates/go/temp_vet_test/main.go +0 -40
  56. package/templates/go/test_err.txt +0 -0
  57. package/templates/go/vet_err.txt +0 -1
  58. package/templates/go/vet_err2.txt +0 -2
  59. package/templates/go/webview_go/.github/workflows/ci.yaml +0 -62
  60. package/templates/go/webview_go/CHANGELOG.md +0 -15
  61. package/templates/go/webview_go/LICENSE +0 -22
  62. package/templates/go/webview_go/README.md +0 -50
  63. package/templates/go/webview_go/examples/basic/main.go +0 -12
  64. package/templates/go/webview_go/examples/bind/main.go +0 -38
  65. package/templates/go/webview_go/glue.c +0 -36
  66. package/templates/go/webview_go/go.mod +0 -3
  67. package/templates/go/webview_go/go.sum +0 -0
  68. package/templates/go/webview_go/libs/mswebview2/LICENSE +0 -27
  69. package/templates/go/webview_go/libs/mswebview2/include/WebView2.h +0 -23568
  70. package/templates/go/webview_go/libs/mswebview2/include/vendor.go +0 -2
  71. package/templates/go/webview_go/libs/mswebview2/vendor.go +0 -2
  72. package/templates/go/webview_go/libs/mswebview2/version.txt +0 -1
  73. package/templates/go/webview_go/libs/webview/LICENSE +0 -22
  74. package/templates/go/webview_go/libs/webview/include/vendor.go +0 -2
  75. package/templates/go/webview_go/libs/webview/include/webview.h +0 -3871
  76. package/templates/go/webview_go/libs/webview/vendor.go +0 -2
  77. package/templates/go/webview_go/libs/webview/version.txt +0 -1
  78. package/templates/go/webview_go/webview.cc +0 -1
  79. package/templates/go/webview_go/webview.go +0 -481
  80. package/templates/go/webview_go/webview_test.go +0 -50
package/README.md CHANGED
@@ -1,9 +1,17 @@
1
1
  # freedom-cli
2
2
 
3
- Freedom 桌面壳打包工具:把你的 Web 前端一键打包成跨平台桌面应用(v1.12.17)。
3
+ Freedom 桌面壳打包工具:把你的 Web 前端一键打包成跨平台桌面应用(v1.13.1)。
4
4
 
5
5
  基于自研 Freedom WebView 壳层(对标 Wails / Tauri):前端完全自由、后端可任意语言、渲染复用系统 WebView(Windows WebView2 / macOS WKWebView / Linux WebKitGTK),产物为单个可执行文件 + resources 目录,前端页面内存加载,不占本地端口。
6
6
 
7
+ **v1.13.x 框架线回归 + 稳定性收口**(v1.13.0 功能,v1.13.1 为文档勘误发布):
8
+ - **freedom-cli 源码回归主仓库**:本 CLI 与框架源码同仓维护(`freedom-cli/` 目录,`templates/go` 为框架源码快照),npm 包与 GitHub Release 一一对应,历史"源码丢失停在 1.12.18"的断档已修复;
9
+ - **三平台通用壳经 tag CI 自动发布**:推送 `vX.Y.Z` tag 即由 `build.yml` 在 win / mac(Apple Silicon)/ linux runner 上编译通用壳并自动创建 GitHub Release,资产命名 `freedom-shell-<plat>`;`freedom shell download <plat>` 直接拉取对应版本资产(默认定位 `v<包版本>`,可用 `FREEDOM_SHELL_TAG` 覆盖),包内另自带三平台壳兜底,均随包分发;
10
+ - **运行时资源层回归**:壳从 exe 同目录 `resources/` 读取 `config.json`(窗口 / 后端配置覆盖)与前端页面,high 模式下为加密 `app.bin`(FRDM1 容器)+ `.integrity` 清单;JS 侧构建加密 → Go 壳内存解密已有跨语言黄金向量与端到端互验(改名 / 篡改即拒绝运行);
11
+ - **多窗口(M2)随 v1.13.0 壳可用**:前端 `window.freedom.window.create / close / list / focus` 开二级窗口,Go 侧 `App.NewWindow / Window.Close`;次级窗口独立消息泵、页面源支持内联 HTML / URL;
12
+ - **销毁竞态收口**:修复 webview2 在 `Destroy` 中泵出滞留 dispatch 回调导致的随机崩溃(0xc0000005,多窗口 / 快速关闭场景),回收后所有排队回调按拆除旗标自我作废,并配套红绿回归测试;
13
+ - **v1.13.1**:文档同步(本 README 更新至 v1.13.x 真实现状、壳 CI 章节勘误),无功能与壳二进制变更。
14
+
7
15
  **v1.12.17 安全模式全面落地**:三档安全模式 `freedom security <none|basic|high>` 正式随包分发——high 档把 resources 加密为 `app.bin`(AES-256-CTR + HMAC-SHA256 + PBKDF2 密钥派生),配合 `.integrity` 完整性校验、anti-debug 与进程隐藏,磁盘无明文、篡改即拒运行;三平台预编译壳经 CI 重建分发,补齐 v1.12.16 仅重编 win-x64 壳的缺口。
8
16
 
9
17
  **v1.12.16 自动更新 + 完整 CLI 模式 + 壳生命周期全面防御**:
@@ -45,7 +53,7 @@ Freedom 桌面壳打包工具:把你的 Web 前端一键打包成跨平台桌
45
53
  **v1.12.x 修复与加固**:
46
54
  - 修复 close 按钮缺陷:窗口关闭改走 `PostMessage(WM_CLOSE)`(原 `CloseWindow` 语义为最小化,导致点关闭只最小化);
47
55
  - 壳二进制平台校验:构建前校验壳文件头(PE / Mach-O / ELF),杜绝"Windows 壳冒充 mac/linux 壳"的假壳被静默分发;
48
- - `freedom shell download` 默认版本改为动态跟随包版本(`releaseTag()` 不再硬编码 v1.1.10),并新增 `.github/workflows/build-shell.yml` 三平台 CI 构建与 Release 资产上传;
56
+ - `freedom shell download` 默认版本改为动态跟随包版本(`releaseTag()` 不再硬编码 v1.1.10),并新增三平台 CI 构建与 Release 资产上传流水线(自 v1.13.0 起并入 `build.yml` 的 tag 触发,见「预编译壳的来源与 CI」);
49
57
  - 壳进程启用 DPI 感知,高 DPI 屏幕下窗口坐标 / 渲染 / 鼠标自动化定位一致,内容更清晰;
50
58
  - `freedom config set` 兼容双引号配置写法;依赖变更检测改用 `package-lock.json` 作基准。
51
59
 
@@ -127,7 +135,7 @@ mac 用户解压 `.app.zip` 即得 `.app`,拖入 `/Applications` 即可直接
127
135
 
128
136
  **壳二进制平台校验**:`freedom build` 在分发前会读取壳二进制文件头,校验其确为目标平台的真实格式(Windows PE / macOS Mach-O / Linux ELF)。若某平台壳缺失或格式不匹配(例如误用其它平台的二进制顶替),会**明确报错并给出修复指引**,绝不静默产出无法运行的假产物。
129
137
 
130
- **预编译壳的来源与 CI**:`webview_go` 依赖各系统自带 WebView 框架,**无法交叉编译**,三平台壳必须在对应平台本机编译。仓库已提供 `.github/workflows/build-shell.yml`:在 win / mac(Apple Silicon)/ linux runner 上分别编译真实壳并上传到 GitHub Release,供 `freedom shell download` 拉取(Intel Mac 已不支持,见 `nativePlatform()` 的明确报错)。发布新版本时:先 `npm publish`,再创建同名 GitHub Release,手动触发 `build-shell` workflow(或直接发 Release 自动触发)即自动补齐各平台壳资产。
138
+ **预编译壳的来源与 CI**:`webview_go` 依赖各系统自带 WebView 框架,**无法交叉编译**,三平台壳必须在对应平台本机编译。自 v1.13.0 起由仓库 `.github/workflows/build.yml` 统一承担:推送 `vX.Y.Z` tag → 三平台 runner 编译通用壳 → `release` job 自动创建 GitHub Release 并上传资产 `freedom-shell-<plat>`,供 `freedom shell download` 按「`v<包版本>` tag + 同名资产」拉取(Intel Mac 已不支持,见 `nativePlatform()` 的明确报错)。发版顺序:先推 GitHub tag、等 Release 资产就绪,再 `npm publish` 同版本——保证 shell 下载默认源始终可用(紧急时可设 `FREEDOM_SHELL_TAG` 指向既有 tag)。
131
139
 
132
140
  产物目录可在 `freedom.config.js` 的 `outDir` 中调整:默认 `'dist'`,设为 `'.'` 则直接输出到项目根目录(dist 的上级),设为任意相对 / 绝对路径亦可。输出到项目根目录时会自动跳过 `index.html` 副本,避免覆盖项目源文件。
133
141
 
package/lib/build.js CHANGED
@@ -434,7 +434,7 @@ function appVersionFromPkg(dir) {
434
434
  }
435
435
  }
436
436
 
437
- // 渲染 resources/config.json(与壳 configfile.go 的 runtimeConfigFile 字段对齐)
437
+ // 渲染 resources/config.json(与壳 resources.go 的 runtimeConfigFile 字段对齐)
438
438
  function renderConfigJSON(cfg, name) {
439
439
  const obj = {
440
440
  name,
package/lib/security.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // 安全加固模块(v1.13.10)
3
+ // 安全加固模块(v1.13.0)
4
4
  //
5
5
  // 为 freedom build 提供两种安全模式:
6
6
  // - basic(简单模式):资源压缩 + 壳剥离符号建议,阻止"随手打开 resources/
@@ -16,7 +16,7 @@
16
16
  // 提取主密钥并复现派生算法"。真正"每应用唯一密钥"需要每应用独立编译壳
17
17
  // (破坏通用壳 + 零工具链架构),不在本模式范围内。
18
18
  //
19
- // ⚠️ 跨语言同步点:本文件与 templates/go/pkg/freedom/security.go 必须保持
19
+ // ⚠️ 跨语言同步点:本文件与 templates/go/pkg/freedom/security.go(= 仓库根 security.go)必须保持
20
20
  // MASTER_KEY / DERIVE_SALT / PBKDF2_ITER / KEY_LEN / APP_BIN_MAGIC
21
21
  // 以及加密算法(aes-256-ctr + hmac-sha256)完全一致。
22
22
  // 修改任一侧必须同步另一侧,否则 build 出的加密资源壳无法解密。
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@yufengtadian/freedom-cli",
3
- "version": "1.12.18",
3
+ "version": "1.13.1",
4
4
  "description": "Freedom WebView desktop shell packaging tool - no Go toolchain required, one command packs three-platform desktop apps",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/YUfeng12TA/freedom.git"
8
+ },
5
9
  "keywords": [
6
10
  "desktop",
7
11
  "webview",
Binary file
Binary file
Binary file
@@ -1,11 +1,5 @@
1
1
  module freedom-cli-shell
2
2
 
3
- go 1.22
3
+ go 1.26.5
4
4
 
5
5
  require github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6
6
-
7
- require golang.org/x/sys v0.28.0
8
-
9
- // Freedom fork: 使用随包分发的本地 webview_go(标题栏图标与 exe 图标一致、
10
- // 无边框窗口最大化限定工作区、彻底清理非客户区等定制),避免拉取上游被覆盖。
11
- replace github.com/webview/webview_go => ./webview_go
@@ -1,2 +1,2 @@
1
- golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
2
- golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1
+ github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
2
+ github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
@@ -1,13 +1,27 @@
1
+ // Freedom 通用壳源码模板(templates/go)。
2
+ //
3
+ // pkg/freedom 为框架源码快照(与 npm 包版本同步发布);main 是预编译通用壳入口:
4
+ // 应用内容全部来自 exe 同目录 resources/(CLI build 写入 index.html / config.json /
5
+ // app.bin),运行时由框架 resources.go 覆盖到实际配置。
6
+ //
7
+ // 用途:`freedom shell build <plat>` 在本目录编译对应平台壳二进制(需 Go 工具链,
8
+ // webview_go 依赖系统 WebView 框架,仅能编本机平台);日常打包走
9
+ // 包内 shell/<plat>/ 或 GitHub Releases 下载,无需本模板。
1
10
  package main
2
11
 
3
- import "freedom-cli-shell/pkg/freedom"
12
+ import (
13
+ "flag"
14
+ "fmt"
15
+
16
+ "freedom-cli-shell/pkg/freedom"
17
+ )
4
18
 
5
- // main 是 Freedom 壳层应用入口。
6
- // 窗口与标题栏配置来自 build 阶段生成的 gen_config.go(appConfig),
7
- // 前端页面运行时从 exe 同目录 resources/index.html 加载(build 阶段由 CLI
8
- // 把打包后的 index.html 与 config.json 写入该目录,壳 SetHtml 内存加载、不占端口)。
9
19
  func main() {
10
- cfg := appConfig()
11
- app := freedom.New(cfg)
12
- app.Run()
20
+ showVersion := flag.Bool("version", false, "打印壳版本号并退出")
21
+ flag.Parse()
22
+ if *showVersion {
23
+ fmt.Println(freedom.Version)
24
+ return
25
+ }
26
+ freedom.New(freedom.Config{}).Run()
13
27
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  package freedom
4
4
 
5
- // 反调试(非 Windows 平台空实现)。
6
- // macOS/Linux 的调试器检测(ptrace / task_for_pid 等)依赖平台特定 API,
7
- // 本版本不做;如需加强可在对应平台文件扩展。
5
+ // 反调试(非 Windows 平台空实现)。macOS/Linux 的调试器检测依赖
6
+ // ptrace / task_for_pid 等平台特定 API,本版本不做;如需加强在对应平台文件扩展。
8
7
  func antiDebugCheck() {}
@@ -2,9 +2,9 @@
2
2
 
3
3
  package freedom
4
4
 
5
- // 反调试(Windows 实现):high 安全模式下调用。
6
- // 检测常见用户态调试器,发现时静默退出,防止攻击者在调试器下
7
- // 单步追踪资源解密 / 密钥派生逻辑。
5
+ // 反调试(Windows 实现):high 安全模式下由 Run() 调用。
6
+ // 检测常见调试器,命中时静默退出,防止攻击者在调试器下单步追踪
7
+ // 资源解密 / 密钥派生逻辑。
8
8
 
9
9
  import (
10
10
  "os"
@@ -13,27 +13,25 @@ import (
13
13
  )
14
14
 
15
15
  var (
16
- kernel32dbg = syscall.NewLazyDLL("kernel32.dll")
17
- procIsDebuggerPresent = kernel32dbg.NewProc("IsDebuggerPresent")
18
- procCheckRemoteDebuggerPresent = kernel32dbg.NewProc("CheckRemoteDebuggerPresent")
16
+ kernel32AntiDebug = syscall.NewLazyDLL("kernel32.dll")
17
+ procIsDebuggerPresent = kernel32AntiDebug.NewProc("IsDebuggerPresent")
18
+ procCheckRemoteDebuggerPresent = kernel32AntiDebug.NewProc("CheckRemoteDebuggerPresent")
19
19
  )
20
20
 
21
21
  // antiDebugCheck 检测调试器;发现则立即退出进程(退出码 77,无提示)。
22
- // 仅在 high 安全模式启用时由 freedom.go Run() 调用。
23
22
  func antiDebugCheck() {
24
23
  if isBeingDebugged() {
25
24
  os.Exit(77)
26
25
  }
27
26
  }
28
27
 
29
- // isBeingDebugged 同时检测本地调试器(IsDebuggerPresent)与远程/内核调试器
30
- // (CheckRemoteDebuggerPresent),任一命中即认为正在被调试。
28
+ // isBeingDebugged 同时检测用户态调试器(IsDebuggerPresent)与远程/内核调试器
29
+ // (CheckRemoteDebuggerPresent)。hProcess 用 GetCurrentProcess 伪句柄(-1)。
31
30
  func isBeingDebugged() bool {
32
- r, _, _ := procIsDebuggerPresent.Call()
33
- if r != 0 {
31
+ if r, _, _ := procIsDebuggerPresent.Call(); r != 0 {
34
32
  return true
35
33
  }
36
34
  var present int32
37
- procCheckRemoteDebuggerPresent.Call(uintptr(0), uintptr(unsafe.Pointer(&present)))
35
+ procCheckRemoteDebuggerPresent.Call(^uintptr(0), uintptr(unsafe.Pointer(&present)))
38
36
  return present != 0
39
37
  }
@@ -45,7 +45,7 @@
45
45
  font-size: 13px;
46
46
  }
47
47
 
48
- /* 无边框模式:前端自绘标题栏(frameless 时显示) */
48
+ /* 无边框模式:前端自绘标题栏(hidden / frameless 时显示) */
49
49
  .titlebar {
50
50
  display: none;
51
51
  height: 36px;
@@ -11,13 +11,27 @@
11
11
 
12
12
  var listeners = {};
13
13
 
14
+ // M1 异步桥接协议:__freedom_bridge(id, method, paramsJson) 只做投递(ack),
15
+ // Go 侧完成后经 freedom.__resolve(id, {ok,result|error}) 回写本表登记的 Promise。
16
+ var callSeq = 0;
17
+ var pendingCalls = {};
18
+
14
19
  function call(method) {
15
20
  var params = Array.prototype.slice.call(arguments, 1);
16
21
  var bridge = getBridge();
17
22
  if (!bridge) {
18
23
  return Promise.reject(new Error('[freedom] 未检测到原生桥接(__freedom_bridge),当前不在桌面壳内运行。'));
19
24
  }
20
- return bridge(method, JSON.stringify(params));
25
+ var id = ++callSeq;
26
+ return new Promise(function (resolve, reject) {
27
+ pendingCalls[id] = { resolve: resolve, reject: reject };
28
+ Promise.resolve(bridge(id, method, JSON.stringify(params))).catch(function (e) {
29
+ if (pendingCalls[id]) {
30
+ delete pendingCalls[id];
31
+ reject(e);
32
+ }
33
+ });
34
+ });
21
35
  }
22
36
 
23
37
  function on(event, cb) {
@@ -32,6 +46,18 @@
32
46
  if (i >= 0) l.splice(i, 1);
33
47
  }
34
48
 
49
+ // 一次性订阅:触发一次后自动退订(对标 Tauri listen+unlisten)。
50
+ // 注意:必须用"注册进数组的那个 wrapper"退订——on() 返回的是 unlisten
51
+ // 闭包而非回调本身,拿它去 off() 删不掉监听器(旧 bug,tests/sdk-surface 锁定)。
52
+ function once(event, cb) {
53
+ if (typeof cb !== 'function') return function () {};
54
+ var fn = function (data) {
55
+ off(event, fn);
56
+ cb(data);
57
+ };
58
+ return on(event, fn);
59
+ }
60
+
35
61
  // 后端通过 app.Emit(event, data) 触发(Eval 调用本函数)。
36
62
  function emit(event, data) {
37
63
  var l = listeners[event] || [];
@@ -40,12 +66,24 @@
40
66
  }
41
67
  }
42
68
 
69
+ // Go 侧异步回写入口(dispatch.go 的 resolveJS 经 Eval 调用)。
70
+ // 拒绝值保持旧契约:webview_go 时代 catch 到的是错误消息字符串。
71
+ function resolveCall(id, env) {
72
+ var p = pendingCalls[id];
73
+ if (!p) return; // 页面已重载/回写迟到:静默丢弃
74
+ delete pendingCalls[id];
75
+ if (env && env.ok) p.resolve(env.result);
76
+ else p.reject((env && env.error) || '[freedom] call failed');
77
+ }
78
+
43
79
  var freedom = {
44
80
  call: call,
45
81
  invoke: call,
46
82
  on: on,
47
83
  off: off,
84
+ once: once,
48
85
  emit: emit,
86
+ __resolve: resolveCall,
49
87
  window: {
50
88
  // 窗口控制(无边框/隐藏标题栏模式下前端自绘按钮使用)。
51
89
  // 全部经桥接调用原生实现,返回 Promise。
@@ -53,12 +91,168 @@
53
91
  maximize: function () { return windowAction('maximize'); },
54
92
  unmaximize: function () { return windowAction('unmaximize'); },
55
93
  toggleMaximize: function () { return windowAction('toggleMaximize'); },
56
- close: function () { return windowAction('close'); },
94
+ // close(force):force=true 时绕过 interceptClose 拦截直接关闭。
95
+ close: function (force) { return windowAction('close', { force: !!force }); },
57
96
  isMaximized: function () { return windowAction('isMaximized'); },
58
97
  isFrameless: function () { return windowAction('isFrameless'); },
98
+ // —— W1 对标 Tauri:位置 / 尺寸(物理像素)——
99
+ setPosition: function (x, y) { return windowAction('setPosition', { x: x, y: y }); },
100
+ setSize: function (width, height) { return windowAction('setSize', { width: width, height: height }); },
101
+ getPosition: function () { return windowAction('getPosition'); },
102
+ getSize: function () { return windowAction('getSize'); },
103
+ innerSize: function () { return windowAction('innerSize'); },
104
+ center: function () { return windowAction('center'); },
105
+ setTitle: function (title) { return windowAction('setTitle', { title: String(title) }); },
106
+ // —— 可见性 / 层级 / 焦点 ——
107
+ show: function () { return windowAction('show'); },
108
+ hide: function () { return windowAction('hide'); },
109
+ focus: function () { return windowAction('focus'); },
110
+ isVisible: function () { return windowAction('isVisible'); },
111
+ isFocused: function () { return windowAction('isFocused'); },
112
+ isMinimized: function () { return windowAction('isMinimized'); },
113
+ setAlwaysOnTop: function (on) { return windowAction('setAlwaysOnTop', { on: !!on }); },
114
+ setSkipTaskbar: function (on) { return windowAction('setSkipTaskbar', { on: !!on }); },
115
+ // —— 行为开关 ——
116
+ setResizable: function (on) { return windowAction('setResizable', { on: !!on }); },
117
+ setMaximizable: function (on) { return windowAction('setMaximizable', { on: !!on }); },
118
+ setMinimizable: function (on) { return windowAction('setMinimizable', { on: !!on }); },
119
+ // —— 全屏 / 关闭拦截 ——
120
+ setFullscreen: function (on) { return windowAction('setFullscreen', { on: !!on }); },
121
+ enterFullscreen: function () { return windowAction('setFullscreen', { on: true }); },
122
+ exitFullscreen: function () { return windowAction('setFullscreen', { on: false }); },
123
+ isFullscreen: function () { return windowAction('isFullscreen'); },
124
+ // 拦截系统/按钮关闭:开启后关闭动作变为 window.closeRequested 事件,
125
+ // 前端处理后以 close(true) 放行或不做操作保持窗口存活。
126
+ interceptClose: function (on) { return windowAction('interceptClose', { on: !!on }); },
127
+ // 聚合信息:title/outerX/outerY/outerWidth/outerHeight/innerWidth/innerHeight/
128
+ // scaleFactor/visible/focused/maximized/minimized/alwaysOnTop/skipTaskbar/frameless/fullscreen。
129
+ getInfo: function () { return windowAction('getInfo'); },
130
+ // 显示器列表(经 sys 桥接):[{x,y,width,height,workX,workY,workWidth,workHeight,scaleFactor,isPrimary}]
131
+ monitors: function () { return sysCall('window.monitors', {}); },
132
+ // 窗口视觉效果(Win11):backdrop: auto|none|solid|mica|acrylic;corner: round|roundSmall|square|default;borderColor: 0x00BBGGRR
133
+ setBackdrop: function (mode) { return sysCall('window.backdrop', { mode: mode }); },
134
+ setCorner: function (mode) { return sysCall('window.corner', { mode: mode }); },
135
+ setBorderColor: function (color) { return sysCall('window.borderColor', { color: color }); },
136
+ // —— M2 多窗口 ——
137
+ // id():本窗口标识('main' 或 'w1'…);create({title,width,height,center,url,html}):
138
+ // 异步拉起次级窗口返回 {id};list() 全部存活窗口;closeWindow/focusWindow(id)。
139
+ // create 的页面来源三选一:url(外链)> html(内联字符串)> 复制主页面——
140
+ // 主页面含开窗脚本时务必给 url 或 html,避免级联。
141
+ // 注意:次级窗口无 sys/tray 桥,托盘/热键/状态记忆等平台单例只挂主窗口;
142
+ // 次级窗口里 window.close() 关闭的是它自己。
143
+ id: function () { return windowAction('id'); },
144
+ list: function () { return windowAction('list'); },
145
+ create: function (opts) { return windowAction('create', opts || {}); },
146
+ closeWindow: function (id) { return windowAction('closeWindow', { id: id }); },
147
+ focusWindow: function (id) { return windowAction('focusWindow', { id: id }); },
148
+ },
149
+ // ---- W2 系统集成命名空间(均经 __freedom_sys / __freedom_tray 桥接)----
150
+ sys: sysCall,
151
+ clipboard: {
152
+ readText: function () { return sysCall('clipboard.read', {}); },
153
+ writeText: function (text) { return sysCall('clipboard.write', { text: String(text) }); },
154
+ },
155
+ shell: {
156
+ // 用系统默认程序打开 URL/文件(openExternal)。
157
+ open: function (target) { return sysCall('shell.open', { target: String(target) }); },
158
+ },
159
+ notification: {
160
+ show: function (title, body) { return sysCall('notification.show', { title: String(title), body: String(body || '') }); },
161
+ },
162
+ shortcut: {
163
+ // combo 形如 "ctrl+alt+k";触发时收到事件 freedom.on('shortcut.triggered', ({id}) => ...)。
164
+ register: function (id, combo) { return sysCall('shortcut.register', { id: id, combo: combo }); },
165
+ unregister: function (id) { return sysCall('shortcut.unregister', { id: id }); },
166
+ list: function () { return sysCall('shortcut.list', {}); },
167
+ },
168
+ autostart: {
169
+ isEnabled: function (name) { return sysCall('autostart.get', { name: name || '' }); },
170
+ set: function (enabled, opts) { return sysCall('autostart.set', { name: (opts && opts.name) || '', enabled: !!enabled, args: (opts && opts.args) || '' }); },
171
+ enable: function (opts) { return this.set(true, opts); },
172
+ disable: function (opts) { return this.set(false, opts); },
173
+ },
174
+ protocol: {
175
+ // URL Scheme 注册(deep link);拉起参数经 'app.secondInstance' 事件或 app.launchArgs() 获取。
176
+ register: function (scheme, displayName) { return sysCall('protocol.register', { scheme: scheme, name: displayName || '' }); },
177
+ unregister: function (scheme) { return sysCall('protocol.unregister', { scheme: scheme }); },
178
+ },
179
+ app: {
180
+ launchArgs: function () { return sysCall('app.launchArgs', {}); },
181
+ },
182
+ // ---- W3 数据层(对标 Tauri path / store / os / process)----
183
+ // kind ∈ config|data|cache|temp|home|exe;name 省略时用 AppID(目录名已消毒)。
184
+ path: function (kind, name) { return sysCall('path.get', { kind: kind, name: name || '' }); },
185
+ store: {
186
+ // 命名 JSON KV,落盘 <dataDir>/<name>.store.json,set 即持久化。
187
+ load: function (name) { return sysCall('store.load', { store: name || '' }); },
188
+ get: function (key, name) { return sysCall('store.get', { store: name || '', key: key }); },
189
+ set: function (key, value, name) { return sysCall('store.set', { store: name || '', key: key, value: value }); },
190
+ delete: function (key, name) { return sysCall('store.delete', { store: name || '', key: key }); },
191
+ keys: function (name) { return sysCall('store.keys', { store: name || '' }); },
192
+ },
193
+ os: {
194
+ info: function () { return sysCall('os.info', {}); }, // {platform,arch,hostname,osVersion,goVersion,numCPU}
195
+ },
196
+ process: {
197
+ id: function () { return sysCall('process.id', {}); },
198
+ exit: function (code) { return sysCall('process.exit', { code: code || 0 }); },
199
+ restart: function () { return sysCall('process.restart', {}); }, // 拉起新实例后退出的安全重启
200
+ },
201
+ // ---- G4 自动更新(对标 Tauri updater)----
202
+ // 检查/安装是异步长任务:调用只发起,结果经事件回推——
203
+ // 'update.available'({version,url,sha256,notes}) / 'update.upToDate'({current})
204
+ // / 'update.installed'({version,restartRequired}) / 'update.error'({message})
205
+ // install 只接受 check 时已通过 ed25519 验签的缓存条目;换装后需 process.restart 生效。
206
+ update: {
207
+ check: function () { return sysCall('update.check', {}); },
208
+ install: function () { return sysCall('update.install', {}); },
209
+ pending: function () { return sysCall('update.pending', {}); },
210
+ onAvailable: function (cb) { return on('update.available', cb); },
211
+ onUpToDate: function (cb) { return on('update.upToDate', cb); },
212
+ onInstalled: function (cb) { return on('update.installed', cb); },
213
+ onError: function (cb) { return on('update.error', cb); },
214
+ },
215
+ taskbar: {
216
+ setProgress: function (value) { return sysCall('taskbar.progress', { value: value }); },
217
+ setState: function (state) { return sysCall('taskbar.state', { state: state }); }, // normal|paused|error|indeterminate
218
+ clear: function () { return sysCall('taskbar.clear', {}); },
219
+ setOverlay: function (iconDataURL) { return sysCall('taskbar.overlay', { icon: iconDataURL }); },
220
+ clearOverlay: function () { return sysCall('taskbar.clearOverlay', {}); },
221
+ },
222
+ dialog: {
223
+ message: function (opts) { return sysCall('dialog.message', opts || {}); },
224
+ open: function (opts) { return sysCall('dialog.open', opts || {}); },
225
+ save: function (opts) { return sysCall('dialog.save', opts || {}); },
226
+ },
227
+ tray: {
228
+ // create({icon: dataURL, tooltip});事件:tray:click / tray:double-click / tray:menu({id})。
229
+ create: function (opts) { return trayCall('tray.create', opts || {}); },
230
+ destroy: function () { return trayCall('tray.destroy', {}); },
231
+ setTooltip: function (text) { return trayCall('tray.tooltip', { tooltip: text }); },
232
+ setMenu: function (items) { return trayCall('tray.menu', { items: items }); },
233
+ },
234
+ menu: {
235
+ // 原生菜单栏(挂主窗口):items 同 tray.setMenu 结构;点击走 'tray:menu' 事件。
236
+ set: function (items) { return trayCall('menu.set', { items: items }); },
59
237
  },
60
238
  };
61
239
 
240
+ function trayCall(method, args) {
241
+ if (typeof window.__freedom_tray !== 'function') {
242
+ return Promise.reject(new Error('[freedom] 未检测到托盘桥接。'));
243
+ }
244
+ return window.__freedom_tray(method, JSON.stringify(args || {}));
245
+ }
246
+
247
+ // sys 桥接:原生系统能力(taskbar.* / window.* / dialog.*)。
248
+ // W5 会扩展为 freedom.sys 命名空间,先行提供内部包装。
249
+ function sysCall(method, args) {
250
+ if (typeof window.__freedom_sys !== 'function') {
251
+ return Promise.reject(new Error('[freedom] 未检测到原生系统能力桥接。'));
252
+ }
253
+ return window.__freedom_sys(method, JSON.stringify(args || {}));
254
+ }
255
+
62
256
  // 前端自绘按钮的便捷绑定:把按钮 DOM 接到窗口控制。
63
257
  // freedom.window.bindButtons({ min: '#minBtn', max: '#maxBtn', close: '#closeBtn' })
64
258
  freedom.window.bindButtons = function (sel) {
@@ -75,12 +269,13 @@
75
269
  if (close) close.addEventListener('click', function () { self.close().catch(ignoreErr); });
76
270
  };
77
271
 
78
- function windowAction(action) {
79
- // __freedom_window 是 webview_go Bind 注入的异步全局函数(返回 Promise)
272
+ function windowAction(action, args) {
273
+ // __freedom_window 是 webview_go Bind 注入的异步全局函数(返回 Promise)。
274
+ // Go 侧签名为 (action, paramsJSON),paramsJSON 恒为 JSON object 字符串。
80
275
  if (typeof window.__freedom_window !== 'function') {
81
276
  return Promise.reject(new Error('[freedom] 未检测到原生窗口控制桥接。'));
82
277
  }
83
- return window.__freedom_window(action);
278
+ return window.__freedom_window(action, JSON.stringify(args || {}));
84
279
  }
85
280
 
86
281
  Object.defineProperty(freedom, 'isDesktop', {
@@ -2,20 +2,18 @@ package freedom
2
2
 
3
3
  import _ "embed"
4
4
 
5
- // jsSDK 是注入到每个前端页面的 Freedom 前端 SDK,暴露 window.freedom 全局对象。
6
- // 用法见 assets/freedom.js 顶部注释。
5
+ // jsSDK 是注入到每个前端页面的 Freedom 前端 SDK。
6
+ // 它暴露 window.freedom 全局对象:
7
7
  //
8
+ // window.freedom.call(method, ...args) -> Promise // 调用后端方法
9
+ // window.freedom.invoke(...) // call 的别名
10
+ // window.freedom.on(event, cb) -> unsubscribe // 订阅后端事件
11
+ // window.freedom.off(event, cb) // 取消订阅
12
+ // window.freedom.emit(event, data) // 应用内事件广播(后端 Eval 亦使用)
13
+ //
14
+ // bridge 由 go-webview2 的 Bind 机制注册,返回 Promise,Go 侧自动 JSON 编解码。
8
15
  //go:embed assets/freedom.js
9
16
  var jsSDK string
10
17
 
11
- // indexHTML 是内置的默认占位页。
12
- //
13
- // 预编译通用壳不嵌入应用专属页面:应用 HTML 来自 exe 同目录 resources/index.html
14
- //(freedom CLI build 时写入,见 resolveHTML 的加载优先级)。仅当外部页面缺失时
15
- // 回退到本占位页,保证"直接运行壳"也有可见 UI。
16
- //
17
- //go:embed assets/index.html
18
- var indexHTML string
19
-
20
- // defaultHTML 兼容框架默认占位页语义:无自定义前端时回退到 indexHTML。
21
- var defaultHTML = indexHTML
18
+ //go:embed assets/default.html
19
+ var defaultHTML string
@@ -0,0 +1,11 @@
1
+ //go:build !windows
2
+
3
+ package freedom
4
+
5
+ import "fmt"
6
+
7
+ // authenticodeCheck 非 Windows 平台不提供 Authenticode 校验能力。
8
+ // RequireSignature 开启时更新流会因此显式失败——静默跳过等于谎报已验签。
9
+ func authenticodeCheck(path string) error {
10
+ return fmt.Errorf("freedom: Authenticode 校验仅支持 Windows(当前平台无法完成 RequireSignature 验证)")
11
+ }
@@ -0,0 +1,87 @@
1
+ //go:build windows
2
+
3
+ package freedom
4
+
5
+ import (
6
+ "fmt"
7
+ "syscall"
8
+ "unsafe"
9
+ )
10
+
11
+ // M6 Authenticode 复核(wintrust!WinVerifyTrust):sha256 之外的可选发布者身份
12
+ // 二级信号——主信任锚仍是 manifest 的 ed25519 验签,此项属纵深防御
13
+ // (持哈希而不知签名私钥的被攻陷源会被拒)。
14
+ // 布局依 wintrust.h:WINTRUST_DATA / WINTRUST_FILE_INFO 字段序与自然对齐。
15
+
16
+ type wintrustFileInfo struct {
17
+ cbStruct uint32
18
+ pcwszFilePath *uint16
19
+ hFile uintptr
20
+ pgKnownSubject *syscall.GUID
21
+ }
22
+
23
+ type wintrustData struct {
24
+ cbStruct uint32
25
+ pPolicyCallbackData uintptr
26
+ pSIPClientData uintptr
27
+ dwUIChoice uint32
28
+ fdwRevocationChecks uint32
29
+ dwUnionChoice uint32
30
+ pFile *wintrustFileInfo
31
+ dwStateAction uint32
32
+ hWVTStateData uintptr
33
+ pwszURLReference *uint16
34
+ dwProvFlags uint32
35
+ dwUIContext uint32
36
+ }
37
+
38
+ const (
39
+ wtdUINone = 2 // WTD_UI_NONE:全程不弹 UI
40
+ wtdRevokeNone = 0x00000000 // 不检索吊销信息
41
+ wtdChoiceFile = 1 // WTD_CHOICE_FILE
42
+ wtdRevCheckNone = 0x00000010 // WTD_REVOCATION_CHECK_NONE(provFlags)
43
+ wtdStateClose = 0x00000002 // WTD_STATEACTION_CLOSE
44
+ wtdUIContextExec = 0 // WTD_UICONTEXT_EXECUTE
45
+ )
46
+
47
+ var (
48
+ wintrustDLL = syscall.NewLazyDLL("wintrust.dll")
49
+ procWinVerifyTrust = wintrustDLL.NewProc("WinVerifyTrust")
50
+ // WINTRUST_ACTION_GENERIC_VERIFY_V2 {00AAC56B-CD44-11d0-8CC2-006C9B2F61C7}
51
+ wtdActionGenericVerify = syscall.GUID{
52
+ Data1: 0x00AAC56B, Data2: 0xCD44, Data3: 0x11D0,
53
+ Data4: [8]byte{0x8C, 0xC2, 0x00, 0x6C, 0x9B, 0x2F, 0x61, 0xC7},
54
+ }
55
+ )
56
+
57
+ // authenticodeCheck 校验 PE 数字签名链。离线确定性由 WTD_REVOKE_NONE +
58
+ // WTD_REVOCATION_CHECK_NONE 保证(吊销检查需外网,且主信任已由 ed25519 覆盖)。
59
+ func authenticodeCheck(path string) error {
60
+ p, err := syscall.UTF16PtrFromString(path)
61
+ if err != nil {
62
+ return fmt.Errorf("freedom: 签名校验路径非法: %w", err)
63
+ }
64
+ fi := wintrustFileInfo{cbStruct: uint32(unsafe.Sizeof(wintrustFileInfo{})), pcwszFilePath: p}
65
+ wd := wintrustData{
66
+ cbStruct: uint32(unsafe.Sizeof(wintrustData{})),
67
+ dwUIChoice: wtdUINone,
68
+ fdwRevocationChecks: wtdRevokeNone,
69
+ dwUnionChoice: wtdChoiceFile,
70
+ pFile: &fi,
71
+ dwProvFlags: wtdRevCheckNone,
72
+ dwUIContext: wtdUIContextExec,
73
+ }
74
+ r, _, e := procWinVerifyTrust.Call(0,
75
+ uintptr(unsafe.Pointer(&wtdActionGenericVerify)),
76
+ uintptr(unsafe.Pointer(&wd)))
77
+ if wd.hWVTStateData != 0 { // 释放 verify 缓存的 provider 数据
78
+ wd.dwStateAction = wtdStateClose
79
+ procWinVerifyTrust.Call(0,
80
+ uintptr(unsafe.Pointer(&wtdActionGenericVerify)),
81
+ uintptr(unsafe.Pointer(&wd)))
82
+ }
83
+ if r != 0 {
84
+ return fmt.Errorf("freedom: Authenticode 校验未通过 (wintrust=0x%08X): %w", uint32(r), e)
85
+ }
86
+ return nil
87
+ }