@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
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
//go:build linux
|
|
2
|
+
|
|
3
|
+
package freedom
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"fmt"
|
|
8
|
+
"os"
|
|
9
|
+
"os/exec"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"strings"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// sysCapCall Linux 分发器(M4):对标 Tauri/Wails 的 Linux 一等公民面。
|
|
15
|
+
// - 一级实现:clipboard.*(wl-clipboard 优先、回退 xclip)、shell.open(xdg-open)、
|
|
16
|
+
// notification.show(notify-send)、autostart.*(XDG autostart desktop 条目)、
|
|
17
|
+
// app.launchArgs;数据层 path/store/os/process/update 由 sysGeneric 平台无关承接。
|
|
18
|
+
// - 未实现(明确报错,不静默):taskbar.*(Windows 概念)、dialog.*(GTK chooser
|
|
19
|
+
// 待补)、shortcut.*(X11 全局热键待补)、protocol.*、window.monitors/效果。
|
|
20
|
+
//
|
|
21
|
+
// 契约与 Windows 侧一致:外部工具都是 argv 直传(不经 shell),拒绝路径零副作用。
|
|
22
|
+
func (a *App) sysCapCall(method string, paramsJSON string) (result interface{}, err error) {
|
|
23
|
+
defer func() {
|
|
24
|
+
if r := recover(); r != nil {
|
|
25
|
+
result = nil
|
|
26
|
+
err = fmt.Errorf("freedom: sys method %q panicked: %v", method, r)
|
|
27
|
+
}
|
|
28
|
+
}()
|
|
29
|
+
|
|
30
|
+
var args map[string]json.RawMessage
|
|
31
|
+
if len(paramsJSON) > 0 && paramsJSON != "null" {
|
|
32
|
+
if err := json.Unmarshal([]byte(paramsJSON), &args); err != nil {
|
|
33
|
+
return nil, fmt.Errorf("freedom: sys method %q: invalid args: %w", method, err)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
argStr := func(k string) string {
|
|
37
|
+
if v, ok := args[k]; ok {
|
|
38
|
+
var s string
|
|
39
|
+
if json.Unmarshal(v, &s) == nil {
|
|
40
|
+
return s
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return ""
|
|
44
|
+
}
|
|
45
|
+
argBool := func(k string) (bool, bool) {
|
|
46
|
+
if v, ok := args[k]; ok {
|
|
47
|
+
var b bool
|
|
48
|
+
if json.Unmarshal(v, &b) == nil {
|
|
49
|
+
return b, true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false, false
|
|
53
|
+
}
|
|
54
|
+
// 平台无关方法(path/store/os/process/update)优先由 sysGeneric 处理。
|
|
55
|
+
if res, ok, err := a.sysGeneric(method, args); ok {
|
|
56
|
+
return res, err
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
switch method {
|
|
60
|
+
case "clipboard.read":
|
|
61
|
+
return clipboardRead()
|
|
62
|
+
case "clipboard.write":
|
|
63
|
+
return nil, clipboardWrite(argStr("text"))
|
|
64
|
+
case "shell.open":
|
|
65
|
+
return nil, linuxShellOpen(argStr("target"))
|
|
66
|
+
case "notification.show":
|
|
67
|
+
// 外部工具冷启动可能卡顿:异步派发,不占 GTK 主循环(回调运行在消息泵内)。
|
|
68
|
+
title, body := argStr("title"), argStr("body")
|
|
69
|
+
appName := a.cfg.AppID
|
|
70
|
+
if appName == "" {
|
|
71
|
+
appName = defaultAppID()
|
|
72
|
+
}
|
|
73
|
+
go func() {
|
|
74
|
+
if err := linuxNotify(appName, title, body); err != nil {
|
|
75
|
+
fmt.Fprintf(os.Stderr, "freedom: %v\n", err)
|
|
76
|
+
}
|
|
77
|
+
}()
|
|
78
|
+
return nil, nil
|
|
79
|
+
case "autostart.get":
|
|
80
|
+
name := argStr("name")
|
|
81
|
+
if name == "" {
|
|
82
|
+
name = defaultAppID()
|
|
83
|
+
}
|
|
84
|
+
return linuxAutostartGet(name)
|
|
85
|
+
case "autostart.set":
|
|
86
|
+
name := argStr("name")
|
|
87
|
+
if name == "" {
|
|
88
|
+
name = defaultAppID()
|
|
89
|
+
}
|
|
90
|
+
enabled, ok := argBool("enabled")
|
|
91
|
+
if !ok {
|
|
92
|
+
return nil, fmt.Errorf("freedom: autostart.set 缺少 enabled")
|
|
93
|
+
}
|
|
94
|
+
return nil, linuxAutostartSet(name, enabled, argStr("args"))
|
|
95
|
+
case "app.launchArgs":
|
|
96
|
+
return launchArgsJSON(), nil
|
|
97
|
+
|
|
98
|
+
default:
|
|
99
|
+
return nil, fmt.Errorf("freedom: sys method %q is not supported on linux", method)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---- 剪贴板(wl-clipboard / xclip 双通道,argv 直传不经 shell)----
|
|
104
|
+
|
|
105
|
+
// clipboardMode 依环境变量选择后端:WAYLAND_DISPLAY 在 → wl-*,否则 xclip。
|
|
106
|
+
func clipboardMode() (wayland bool) {
|
|
107
|
+
return os.Getenv("WAYLAND_DISPLAY") != ""
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
func runCapture(name string, args []string, stdin string) (string, error) {
|
|
111
|
+
cmd := exec.Command(name, args...)
|
|
112
|
+
if stdin != "" {
|
|
113
|
+
cmd.Stdin = strings.NewReader(stdin)
|
|
114
|
+
}
|
|
115
|
+
out, err := cmd.Output()
|
|
116
|
+
if err != nil {
|
|
117
|
+
return "", fmt.Errorf("freedom: %s %v: %w", name, args, err)
|
|
118
|
+
}
|
|
119
|
+
return string(out), nil
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func clipboardRead() (string, error) {
|
|
123
|
+
if clipboardMode() {
|
|
124
|
+
if s, err := runCapture("wl-paste", []string{"-n"}, ""); err == nil {
|
|
125
|
+
return s, nil
|
|
126
|
+
}
|
|
127
|
+
// wl-clipboard 缺席或失败 → 回退 xclip(XWayland 下可用)
|
|
128
|
+
}
|
|
129
|
+
return runCapture("xclip", []string{"-selection", "clipboard", "-o"}, "")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
func clipboardWrite(text string) error {
|
|
133
|
+
if clipboardMode() {
|
|
134
|
+
// wl-copy fork 后台常驻持有 clipboard selection;它继承 stdout/stderr,
|
|
135
|
+
// 用 Output() 会等常驻进程的管道 EOF 永久阻塞,故只 Run 不捕获输出。
|
|
136
|
+
wc := exec.Command("wl-copy")
|
|
137
|
+
wc.Stdin = strings.NewReader(text)
|
|
138
|
+
if err := wc.Run(); err == nil {
|
|
139
|
+
return nil
|
|
140
|
+
}
|
|
141
|
+
// 失败(如无 Wayland 会话权限)回退 xclip(XWayland 下可用)
|
|
142
|
+
}
|
|
143
|
+
// xclip 自行 fork 常驻为属主;EOF 到达即完成装载
|
|
144
|
+
cmd := exec.Command("xclip", "-selection", "clipboard", "-i")
|
|
145
|
+
cmd.Stdin = strings.NewReader(text)
|
|
146
|
+
return cmd.Run()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---- openExternal / 通知 ----
|
|
150
|
+
|
|
151
|
+
func linuxShellOpen(target string) error {
|
|
152
|
+
if target == "" {
|
|
153
|
+
return fmt.Errorf("freedom: shell.open 缺少 target")
|
|
154
|
+
}
|
|
155
|
+
if !shellTargetAllowed(target) {
|
|
156
|
+
return fmt.Errorf("freedom: 拒绝打开非白名单目标 %q(仅允许 http/https/mailto)", target)
|
|
157
|
+
}
|
|
158
|
+
// xdg-open 会阻塞到目标应用退出:Start 后即撒手,Wait 回收僵尸。
|
|
159
|
+
cmd := exec.Command("xdg-open", target)
|
|
160
|
+
if err := cmd.Start(); err != nil {
|
|
161
|
+
return fmt.Errorf("freedom: xdg-open %q: %w", target, err)
|
|
162
|
+
}
|
|
163
|
+
go cmd.Wait()
|
|
164
|
+
return nil
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// notifyArgs 构造 notify-send argv;"--" 终止选项解析,防 title/body 以 - 开头被当参数。
|
|
168
|
+
func notifyArgs(appName, title, body string) []string {
|
|
169
|
+
args := []string{"--app-name", appName, "--", title}
|
|
170
|
+
if body != "" {
|
|
171
|
+
args = append(args, body)
|
|
172
|
+
}
|
|
173
|
+
return args
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
func linuxNotify(appName, title, body string) error {
|
|
177
|
+
cmd := exec.Command("notify-send", notifyArgs(appName, title, body)...)
|
|
178
|
+
if out, err := cmd.CombinedOutput(); err != nil {
|
|
179
|
+
return fmt.Errorf("freedom: 通知发送失败: %w (%s)", err, strings.TrimSpace(string(out)))
|
|
180
|
+
}
|
|
181
|
+
return nil
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---- 开机自启(XDG autostart:~/.config/autostart/<name>.desktop)----
|
|
185
|
+
|
|
186
|
+
func autostartDir() (string, error) {
|
|
187
|
+
base := os.Getenv("XDG_CONFIG_HOME")
|
|
188
|
+
if base == "" {
|
|
189
|
+
home, err := os.UserHomeDir()
|
|
190
|
+
if err != nil {
|
|
191
|
+
return "", fmt.Errorf("freedom: 无法定位配置目录: %w", err)
|
|
192
|
+
}
|
|
193
|
+
base = filepath.Join(home, ".config")
|
|
194
|
+
}
|
|
195
|
+
return filepath.Join(base, "autostart"), nil
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
func desktopEntryPath(name string) (string, error) {
|
|
199
|
+
if name == "" {
|
|
200
|
+
return "", fmt.Errorf("freedom: 自启项名称为空或非法")
|
|
201
|
+
}
|
|
202
|
+
dir, err := autostartDir()
|
|
203
|
+
if err != nil {
|
|
204
|
+
return "", err
|
|
205
|
+
}
|
|
206
|
+
return filepath.Join(dir, sanitizeName(name)+".desktop"), nil
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// desktopExecOwned 判断 .desktop 的 Exec 行是否指向本程序(防覆盖/误删他程序条目)。
|
|
210
|
+
func desktopExecOwned(content, exePath string) bool {
|
|
211
|
+
for _, line := range strings.Split(content, "\n") {
|
|
212
|
+
v := strings.TrimSpace(line)
|
|
213
|
+
if !strings.HasPrefix(v, "Exec=") {
|
|
214
|
+
continue
|
|
215
|
+
}
|
|
216
|
+
v = strings.ToLower(strings.TrimSpace(strings.TrimPrefix(v, "Exec=")))
|
|
217
|
+
p := strings.ToLower(exePath)
|
|
218
|
+
return strings.HasPrefix(v, `"`+p+`"`) || strings.HasPrefix(v, p)
|
|
219
|
+
}
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
func linuxAutostartSet(name string, enabled bool, args string) error {
|
|
224
|
+
path, err := desktopEntryPath(name)
|
|
225
|
+
if err != nil {
|
|
226
|
+
return err
|
|
227
|
+
}
|
|
228
|
+
if !enabled {
|
|
229
|
+
old, readErr := os.ReadFile(path)
|
|
230
|
+
if readErr != nil {
|
|
231
|
+
if os.IsNotExist(readErr) {
|
|
232
|
+
return nil // 幂等:本就不存在
|
|
233
|
+
}
|
|
234
|
+
return readErr
|
|
235
|
+
}
|
|
236
|
+
exe, err := os.Executable()
|
|
237
|
+
if err != nil {
|
|
238
|
+
return fmt.Errorf("freedom: 无法定位自身路径: %w", err)
|
|
239
|
+
}
|
|
240
|
+
if !desktopExecOwned(string(old), exe) {
|
|
241
|
+
return fmt.Errorf("freedom: 自启项 %q 不属于本程序,拒绝删除", name)
|
|
242
|
+
}
|
|
243
|
+
return os.Remove(path)
|
|
244
|
+
}
|
|
245
|
+
exe, err := os.Executable()
|
|
246
|
+
if err != nil {
|
|
247
|
+
return fmt.Errorf("freedom: 无法定位自身路径: %w", err)
|
|
248
|
+
}
|
|
249
|
+
if old, readErr := os.ReadFile(path); readErr == nil && !desktopExecOwned(string(old), exe) {
|
|
250
|
+
return fmt.Errorf("freedom: 自启项 %q 已存在且不属于本程序,拒绝覆盖", name)
|
|
251
|
+
}
|
|
252
|
+
execLine := `"` + exe + `"`
|
|
253
|
+
if a := strings.TrimSpace(args); a != "" {
|
|
254
|
+
execLine += " " + a
|
|
255
|
+
}
|
|
256
|
+
// DisplayName 未透传:Name 用消毒后的条目名,足够桌面环境展示与撤回。
|
|
257
|
+
content := "[Desktop Entry]\nType=Application\nName=" + sanitizeName(name) +
|
|
258
|
+
"\nExec=" + execLine + "\nX-GNOME-Autostart-enabled=true\nTerminal=false\n"
|
|
259
|
+
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
260
|
+
return err
|
|
261
|
+
}
|
|
262
|
+
return os.WriteFile(path, []byte(content), 0o644)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
func linuxAutostartGet(name string) (bool, error) {
|
|
266
|
+
path, err := desktopEntryPath(name)
|
|
267
|
+
if err != nil {
|
|
268
|
+
return false, err
|
|
269
|
+
}
|
|
270
|
+
_, statErr := os.Stat(path)
|
|
271
|
+
if statErr == nil {
|
|
272
|
+
return true, nil
|
|
273
|
+
}
|
|
274
|
+
if os.IsNotExist(statErr) {
|
|
275
|
+
return false, nil
|
|
276
|
+
}
|
|
277
|
+
return false, statErr
|
|
278
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//go:build !windows && !linux
|
|
2
|
+
|
|
3
|
+
package freedom
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"fmt"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// sysCapCall 非 Windows/Linux 平台(macOS 等):系统能力暂不支持,但平台无关的
|
|
11
|
+
// 数据层方法(path/store/os/process,见 store.go 的 sysGeneric)在三端一致可用。
|
|
12
|
+
// Linux 实装见 syscap_linux.go(M4)。
|
|
13
|
+
func (a *App) sysCapCall(method string, paramsJSON string) (interface{}, error) {
|
|
14
|
+
var args map[string]json.RawMessage
|
|
15
|
+
if len(paramsJSON) > 0 && paramsJSON != "null" {
|
|
16
|
+
_ = json.Unmarshal([]byte(paramsJSON), &args)
|
|
17
|
+
}
|
|
18
|
+
if res, ok, err := a.sysGeneric(method, args); ok {
|
|
19
|
+
return res, err
|
|
20
|
+
}
|
|
21
|
+
return nil, fmt.Errorf("freedom: sys method %q is not supported on this platform", method)
|
|
22
|
+
}
|