Skip to content
opentoysPublic

About

Purego webview, not webview fallback local chrome.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

151 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

purego-webview

基于 purego 的无 CGO Go 桌面 WebView 框架。

English

特性

  • 无需 CGO 或 C 工具链,支持普通 Go 交叉编译
  • 原生后端:macOS WKWebView、Windows WebView2、Linux WebKitGTK
  • 可选 Chrome/Chromium 后端,通过 Chrome DevTools Protocol 驱动
  • JavaScript 与 Go 双向桥接,Go 函数在 JavaScript 中以 Promise 形式调用
  • 原生菜单、快捷键和异步回调
  • 独立系统托盘,支持多托盘及无窗口运行
  • 自定义 URL Scheme,可直接加载内嵌前端资源,无需启动本地 HTTP 服务
  • 页面加载前注入 JavaScript
  • 隐身模式和自定义浏览器数据目录
  • <input type="file"> 系统文件选择器
  • Windows 按架构内嵌 WebView2Loader.dll(amd64、arm64、x86)

平台与依赖

系统 原生后端 运行依赖
macOS 10.13+ WKWebView + AppKit 系统框架
Windows 10+ WebView2 WebView2 Runtime
Linux(原生窗口) GTK4 + WebKitGTK 6.0 GTK4、WebKitGTK 6.0 和显示服务器
Linux(纯托盘) GIO/GDBus GLib/GObject/GIO、会话 D-Bus 和 StatusNotifier 托盘宿主

项目要求 Go 1.25+。Linux 原生窗口需安装 GTK4 / WebKitGTK 6.0 运行时:

# Debian / Ubuntu
sudo apt install libwebkitgtk-6.0-4

# Fedora
sudo dnf install webkitgtk6.0

# Arch Linux
sudo pacman -S webkitgtk-6.0

包名可参考 Ubuntu、Fedora 和 Arch Linux 的软件包目录。

Linux 纯托盘只需系统提供 libglib-2.0.so.0、libgobject-2.0.so.0、libgio-2.0.so.0,以及可访问的会话 D-Bus 和 StatusNotifier 托盘宿主,无需 GTK、WebKitGTK 或显示服务器。独立文件对话框需要 GTK4 和显示服务器;创建原生 WebView 窗口时才需要 WebKitGTK 6.0。

Chrome 后端需要本机安装 Chrome 或 Chromium。程序会自动查找,也可通过 WEBVIEW_CHROME 指定可执行文件。

安装

go get github.com/opentoys/webview

快速开始

package main

import (
	"log"

	"github.com/opentoys/webview"
)

func main() {
	w, err := webview.New(webview.Options{Debug: true})
	if err != nil {
		log.Fatal(err)
	}
	defer w.Close()

	count := 0
	if err := w.Bind("increment", func() int {
		count++
		return count
	}); err != nil {
		log.Fatal(err)
	}

	w.SetTitle("Counter")
	w.SetSize(600, 400, webview.SizeNone)
	if err := w.SetHTML(`<!doctype html>
<html>
<body style="font-family:system-ui;text-align:center;padding-top:2em">
  <p id="count" style="font-size:2em">0</p>
  <button onclick="increment().then(n => count.textContent = n)">+1</button>
</body>
</html>`); err != nil {
		log.Fatal(err)
	}

	if err := w.Run(); err != nil {
		log.Fatal(err)
	}
}

运行仓库中的示例:

CGO_ENABLED=0 go run ./example

配置与后端选择

w, err := webview.New(webview.Options{
	Debug:     true,                     // 开启开发者工具或调试输出
	Incognito: true,                     // 使用临时/隐身浏览数据
	DataDir:   "./browser-data",         // 自定义浏览器数据目录
	Backend:   webview.BackendWebview,   // 选择渲染后端
})
常量 值 选择顺序
BackendWebview "" 使用平台原生后端(默认)
BackendChrome "chrome" 仅使用 Chrome/Chromium
BackendFallbackWebview "fallback-webview" 优先 Chrome,创建失败时改用原生后端
BackendFallbackChrome "fallback-chrome" 优先原生后端,创建失败时改用 Chrome

后端探测发生在 New 阶段。部分原生动态库到 Run 阶段才加载,因此这类运行时初始化错误无法再自动切换后端。

API 概览

方法 说明
webview.Run() error 启动进程事件循环,直到显式调用 Quit;w.Run() 为兼容入口
webview.Quit() error 清理所有窗口和托盘并停止事件循环,不调用 os.Exit
Show() error / Hidden() error 显示并激活 / 隐藏窗口,保留页面状态
OnClose(func()) error 拦截原生关闭请求;nil 恢复销毁行为
Close() error 销毁当前窗口及 WebView;已关闭后其他操作返回 ErrClosed
SetTitle(title) error 设置标题
SetSize(width, height, hint) error 设置窗口尺寸约束
Navigate(url) error 导航到 URL
SetHTML(html) error 加载 HTML 字符串
Eval(js) error 在当前页面执行 JavaScript
Init(js) error 注册每次页面加载前执行的 JavaScript
Bind(name, value) error 暴露 Go 函数、对象方法及只读属性
Unbind(name) error 删除整个绑定
SetMenu(menus...) error 替换原生菜单栏
MainThread(fn) 在 UI 线程执行函数并等待完成
InterceptResource(scheme, handler) 注册自定义 URL Scheme 处理器;应在 Run 前调用

SetTitle、SetSize、Navigate、SetHTML 和 Init 均可在 Run 前调用。

Init 与 Eval 的执行时机

Init(js) 注册在后续文档开始加载时执行的脚本,可在 Run 前调用。运行中新增 Init 不会立即在当前页面执行,需要下一次导航或加载新文档才生效。此时 DOM 可能尚未构建,访问 document.body 等节点应放在 DOMContentLoaded 回调中。

Eval(js) 只在当前页面执行一次,不会保存到后续页面。应在窗口运行、页面就绪后调用;在 Run 前调用会返回错误,请改用 Init。SetHTML 和 Navigate 发起的是异步加载,紧接着调用 Eval 并不代表目标页面已经加载完毕。Eval 返回 nil 也不代表脚本的所有异步操作已经完成。

例如,通过页面就绪回调执行 Eval,避免使用固定时间的 Sleep 猜测加载时机:

if err := w.Bind("pageReady", func() {
    if err := w.Eval(`document.body.style.backgroundColor = '#eef6ff'`); err != nil {
        fmt.Println("Eval:", err)
    }
}); err != nil {
    panic(err)
}
if err := w.Init(`
    document.addEventListener('DOMContentLoaded', () => {
        console.log('Init registered at document start');
        pageReady();
    }, {once: true});
`); err != nil {
    panic(err)
}
if err := w.SetHTML(`<!doctype html><html><body>Init / Eval demo</body></html>`); err != nil {
    panic(err)
}
if err := w.Run(); err != nil {
    panic(err)
}

文件与目录选择

文件对话框现在是包级函数,原来的 w.ChooseDir()、w.ChooseFile() 和 w.SaveFile() 已移除:

函数 行为
webview.ChooseDir() 选择一个目录
webview.ChooseFile(filters ...webview.FileFilter) 选择一个文件,可按扩展名和 MIME 类型过滤
webview.SaveFile(opts webview.FileDialogOptions) 选择保存路径,支持默认目录、建议文件名和原生覆盖确认

三个函数同步等待选择并返回绝对路径,无需创建 WebView 或托盘,可在 Run 前及运行期间使用,Chrome 后端也可使用。对话框不绑定业务窗口,关闭全部窗口后仍可使用。SaveFile 不创建文件,也不会自动运行或退出应用。

macOS 在 Run 前必须从 main 主 goroutine 调用,其他线程返回 ErrDialogMainThread;运行期间可从普通 goroutine、绑定函数或托盘回调调用。请求按入队顺序串行显示,并复制参数;等待期间调用方应避免修改正在传入的切片。UI 线程在已有请求时调用返回 ErrDialogReentrant。

取消返回 ErrDialogCancelled,缺少原生能力返回 ErrDialogUnsupported。Quit 会取消活动和排队请求,这些请求及退出后的新请求返回 ErrApplicationStopped。错误均可用 errors.Is 判断。

独立示例见 example/dialogs:先在 Run 前选文件,再从纯托盘回调打开三个对话框。

if err := w.Bind("chooseFile", func() (string, error) {
	path, err := webview.ChooseFile()
	if errors.Is(err, webview.ErrDialogCancelled) {
		return "", nil
	}
	return path, err
}); err != nil {
	log.Fatal(err)
}

可使用扩展名或 MIME 类型过滤文件:

path, err := webview.ChooseFile(webview.FileFilter{
	Name: "Images", Extensions: []string{"png", "jpg"},
	MIMETypes: []string{"image/png", "image/jpeg"},
})

SaveFile 只弹出保存面板并返回绝对路径,由调用方负责实际写文件:

path, err := webview.SaveFile(webview.FileDialogOptions{
	Title: "另存为", Filename: "report.pdf",
	Filters: []webview.FileFilter{{Name: "PDF", Extensions: []string{"pdf"}, MIMETypes: []string{"application/pdf"}}},
})

窗口尺寸

常量 含义
SizeNone 普通窗口尺寸
SizeMin 设置最小尺寸
SizeMax 设置最大尺寸
SizeFixed 固定尺寸

JavaScript 桥接

Bind 也接受 struct 或非 nil 的 *struct,统一推荐传指针,以包含指针接收者方法。struct 值仅暴露值方法集,不自动取地址。所有导出方法(包括提升方法)都会按 Go 原有名称及大小写暴露;导出字段暴露为只读 JSON 快照,未导出成员不会暴露。

type App struct{}

func (*App) Greet(name string) string { return "你好," + name }

// 在初始化函数中:
if err := w.Bind("greet", (&App{}).Greet); err != nil { panic(err) }
if err := w.Bind("app", &App{}); err != nil { panic(err) }
const greeting = await window.greet("张三");
const objectGreeting = await window.app.Greet("张三");
// 后续解绑整个对象:
if err := w.Unbind("app"); err != nil { panic(err) }

同名绑定整体替换,也支持函数和对象互换;无效输入保留原绑定。名称按字面处理:Bind("app.Greet", fn) 对应 window["app.Greet"],与 window.app.Greet 独立。运行中的变更会更新当前页面和后续导航。Unbind 删除框架生成的整个页面入口;旧代理引用按当前 Go 注册表调用,目标不存在时 Promise 会 reject。已开始执行的调用允许完成。

调用异步执行且可能并发,共享可变状态需自行同步。参数与返回值沿用函数绑定的 JSON / Promise 规则;字段和返回对象不递归绑定。nil、nil 函数、nil 指针、nil map、不支持的类型、没有导出字段或方法的对象,以及无法 JSON 编码的属性均返回错误。

Bind 也接受非 nil 的 map[string]any:顶层函数值暴露为异步方法,其他值暴露为只读属性,允许空 map。struct 属性使用 Go 字段名,包含提升的导出字段;外层字段的 json 标签不会重命名或隐藏属性。字段与方法同名时返回错误。

if err := w.Bind("config", map[string]any{
    "version": "1.0",
    "features": []string{"search", "export"},
    "greet": func(name string) string { return "你好," + name },
}); err != nil { panic(err) }
console.log(window.config.version); // "1.0",无需 await
const greeting = await window.config.greet("张三");
// config.version = "2.0" 无法改变属性值,严格模式下抛出 TypeError。

属性在 Bind 时通过 JSON 生成快照,页面中的嵌套对象和数组也递归冻结。Go 后续修改不会自动反映到页面,需要重新 Bind 更新;Bind 读取 Go 数据期间也需自行同步。嵌套值是普通 JSON 数据,不是实时 Go 对象,不支持嵌套函数。Unbind 删除页面入口,但旧引用仍可读取其不可变快照。

Bind 接受以下返回形式:

  • func(args...)
  • func(args...) T
  • func(args...) error
  • func(args...) (T, error)

参数和返回值通过 JSON 转换。绑定后的函数是全局 JavaScript 函数,并始终返回 Promise:

if err := w.Bind("add", func(a, b int) int {
	return a + b
}); err != nil {
	log.Fatal(err)
}

if err := w.Bind("readFile", func(path string) (string, error) {
	data, err := os.ReadFile(path)
	return string(data), err
}); err != nil {
	log.Fatal(err)
}
const sum = await add(1, 2);

try {
  const content = await readFile("notes.txt");
} catch (error) {
  console.error(error.message);
}

自定义 URL Scheme

资源拦截适合从 embed.FS、zip 或其他数据源加载前端文件。协议名不包含 ://,处理器必须调用一次 respond;传入 nil 表示未找到资源。

w.InterceptResource("app", func(
	req webview.ResourceRequest,
	respond func(*webview.ResourceResponse),
) {
	if req.URL != "app://host/index.html" {
		respond(nil)
		return
	}

	respond(&webview.ResourceResponse{
		StatusCode: 200,
		Headers: http.Header{
			"Content-Type": {"text/html; charset=utf-8"},
		},
		Body: []byte("<h1>Hello</h1>"),
	})
})

if err := w.Navigate("app://host/index.html"); err != nil {
	log.Fatal(err)
}

相关类型:

type ResourceRequest struct {
	URL     string
	Method  string
	Headers http.Header
	Body    []byte
}

type ResourceResponse struct {
	StatusCode int
	Headers    http.Header
	Body       []byte
}

自定义协议在各后端中会注册为安全上下文,可使用 localStorage、crypto.subtle 等仅限安全上下文的 Web API,且不需要监听本地端口。

原生菜单

menus := webview.DefaultMenus(w)
menus = append(menus, webview.Menu{
	Label: "File",
	Items: []webview.MenuItem{
		{
			Label:    "Open",
			Shortcut: webview.CmdOrCtrl + "+O",
			Action:   func() { /* ... */ },
		},
		{Separator: true},
		{
			Label:    "Quit",
			Shortcut: webview.CmdOrCtrl + "+Q",
			Action:   func() { _ = webview.Quit() },
		},
	},
})
w.SetMenu(menus...)

CmdOrCtrl 在 macOS 为 Cmd,在其他平台为 Ctrl。DefaultMenus 在 macOS 返回应用菜单和 Edit 菜单,在 Linux/Windows 返回空切片。Chrome 后端目前忽略 SetMenu。

UI 线程

MainThread 用于必须在平台 UI 线程中执行的窗口或系统 API:

w.MainThread(func() {
	// macOS: AppKit 线程
	// Windows: Win32 消息循环线程
	// Linux: GTK 主线程
})

原生文件拖放

OnFilesDropped 接收拖入窗口内容区域的本地文件和目录(路径为绝对、清理后的路径):

if err := w.OnFilesDropped(func(paths []string) {
	for _, path := range paths {
		fmt.Println(path)
	}
}); err != nil {
	log.Fatal(err)
}

可在 Run 前或运行期间注册/替换。启用时原生层独占本地文件拖放,网页的 dragover/drop 不会收到该操作;回调异步且严格串行,不会阻塞 UI 线程。paths 保留拖入顺序和重复项,忽略 URL、文本、虚拟文件及没有本地路径的项目。使用 w.OnFilesDropped(nil) 注销并恢复浏览器默认行为。Chrome/Chromium 后端注册非空处理器会返回 webview.ErrDropUnsupported。

通用应用外壳 cmd/app

cmd/app 从当前目录的 app.data zip 文件加载应用;找不到 zip 时回退到 data/ 目录。两种数据源使用相同结构:

app.data(zip)或 data/
├── config.json
└── dist/
    ├── index.html
    └── ...

config.json 示例:

{
  "title": "My App",
  "width": 1024,
  "height": 720,
  "debug": false,
  "incognito": false,
  "dir": "./browser-data",
  "version": "1.0.0",
  "entry": "index.html",
  "dist": "dist",
  "scheme": "app"
}
CGO_ENABLED=0 go run ./cmd/app

外壳向页面绑定了 app.version()、app.close() 和 app.debug(message)。完整实现见 cmd/app。

系统托盘与原生右键菜单

托盘无需 WebView,可创建多个。窗口、托盘和进程事件循环分别管理生命周期:关闭最后一个窗口或托盘仍会保持运行,只有 webview.Quit() 才清理全部对象并让 Run() 返回。w.Run() 是包级 Run() 的兼容入口。

w, err := webview.New(webview.Options{})
if err != nil { panic(err) }
if err := w.SetHTML(`<input value="隐藏后仍保留">`); err != nil { panic(err) }
if err := w.OnClose(func() {
    if err := w.Hidden(); err != nil { log.Print(err) }
}); err != nil { panic(err) }
tray, err := webview.NewSystray(webview.SystrayOptions{
    Icon: pngBytes,
    Title: "运行中",
    Tooltip: "点击显示窗口",
    OnClick: func() { if err := w.Show(); err != nil { log.Print(err) } },
    Items: []webview.MenuItem{
        {ID: "show", Label: "显示窗口", Action: func() {
            if err := w.Show(); err != nil { log.Print(err) }
        }},
        {ID: "quit", Label: "退出", Action: func() {
            if err := webview.Quit(); err != nil { log.Print(err) }
        }},
    },
})
if err != nil { panic(err) }
_ = tray // tray.Update(opts) 整体更新;tray.Close() 幂等移除。
if err := webview.Run(); err != nil { panic(err) }

旧 w.SetSystray / w.RemoveSystray 已移除,迁移为 NewSystray、tray.Update 和 tray.Close。菜单中的“退出应用”应调用 webview.Quit()。Title 仅在 macOS 显示,可用纯文字代替 Icon;Windows、Linux 需要 PNG 图标。Tooltip 是独立的悬停提示。

Show 显示并激活窗口;Hidden 保留 DOM、输入、页面及 JS 状态,不重复执行 Init。隐藏不代表操作系统挂起,后台 JS 节流仍遵循引擎和 Offscreen 配置。Close 真正销毁当前窗口与 WebView,不能用 Show 重建,必须重新 New。关闭窗口不会影响其他窗口或托盘。

OnClose 接管关闭按钮等原生请求,回调自行选择 Hidden、Close 或不处理。同一窗口回调运行时的重复请求会合并,传 nil 恢复默认销毁。主动 Close 和 Quit 绕过 OnClose。回调异步执行,Quit 不等待正在执行的用户回调;关闭对象会使其尚未执行的回调失效。

Run 前创建的对象只保存配置,Hidden 可设置初始隐藏;运行中 New / NewSystray 会等待原生创建结果。可在 Run 前 Close 待创建对象。启动环境错误由 Run 返回并清理资源。并发 Run 返回 ErrAlreadyRunning;Quit 后禁止创建对象或再次运行,返回 ErrApplicationStopped。macOS 的 Run 应在 main 函数的主 goroutine 调用,以满足 AppKit 主线程要求。同一进程只能启动一次应用事件循环。无窗口和托盘时 Run 也会等待 Quit。

Chrome 的 Close 只结束对应浏览器进程,Show / Hidden / OnClose 返回 ErrWindowUnsupported;独立原生托盘不受窗口后端选择影响。

完整示例:example/systray 演示关闭按钮隐藏、点击托盘恢复;example/tray-only 演示纯托盘启动,以及窗口关闭后继续创建新窗口;example/native-menus 演示托盘更新与右键菜单。

OnClick 自定义左键行为,未设置时使用菜单;右键仍打开菜单。Linux 依赖宿主通过 StatusNotifierItem.Activate 通知点击:部分桌面始终显示菜单或忽略 Activate,具体行为取决于宿主。每个 Linux 托盘拥有独立的 D-Bus 连接,关闭一个托盘不影响其他托盘。纯托盘可直接创建 NewSystray 后调用 Run,无需创建 WebView;以后创建窗口或打开文件对话框时,才初始化对应的图形环境。

Linux 托盘通过 purego 调用系统 GIO/GDBus。点击回调在 D-Bus 应答发送后交给 Go goroutine 执行,可调用 tray.Update、tray.Close、webview.Quit 或文件对话框;使用对话框时仍需满足上面的 GTK4 与显示环境要求。缺少必要库、符号、会话总线或可用托盘宿主时,返回可用 errors.Is(err, webview.ErrMenuUnsupported) 判断的错误。Run 前配置的托盘在 Run 时检查这些条件。当前不自动恢复 watcher 重启后的注册。

右键菜单接口

方法 行为
SetContextMenu([]webview.MenuItem) 完全替换默认右键菜单和之前的自定义项,仅显示传入的菜单项;同时清除之前按 role 移除的配置
AddContextMenuItem(webview.MenuItem) 追加一个自定义项;未调用 SetContextMenu 时保留浏览器默认项,调用后则追加到替换后的菜单
RemoveContextMenuItem(id string) 按 ID 移除自定义项;不存在的 ID 无副作用
RemoveContextMenuItemRole(...webview.MenuItemRole) 按标准编辑角色隐藏默认项,并移除具有相同 Role 的自定义项;支持一次传入多个 role

SetContextMenu(nil) 或空切片表示使用空菜单,禁用右键菜单,不会恢复默认菜单。删除替换菜单中的最后一项后仍为空菜单,也不会自动恢复默认项。自定义项(包括分隔符)必须有非空且唯一的 ID;重复追加 ID 会返回错误。菜单会自动清理首尾及连续分隔符。

完全替换默认右键菜单,然后追加、移除自定义项:

if err := w.SetContextMenu([]webview.MenuItem{
    {
        ID: "hello",
        Label: "自定义动作",
        Action: func() { w.Eval(`alert('Hello from Go')`) },
    },
}); err != nil {
    panic(err)
}
if err := w.AddContextMenuItem(webview.MenuItem{
    ID: "extra",
    Label: "追加项",
    Action: func() { w.Eval(`alert('Extra')`) },
}); err != nil {
    panic(err)
}
if err := w.RemoveContextMenuItem("extra"); err != nil {
    panic(err)
}

按 role 移除菜单项

预定义 role 如下。匹配使用原生动作标识,不依赖菜单显示语言;当前菜单中不存在的动作会忽略,其他默认项保持原生行为。

预定义 role 动作
webview.MenuItemRoleUndo 撤销
webview.MenuItemRoleRedo 重做
webview.MenuItemRoleCut 剪切
webview.MenuItemRoleCopy 复制
webview.MenuItemRolePaste 粘贴
webview.MenuItemRoleSelectAll 全选
webview.MenuItemRoleNone 无标准动作(MenuItem.Role 的零值),不能传给 RemoveContextMenuItemRole,否则返回错误

例如,保留浏览器默认菜单,隐藏“复制”和“剪切”,并追加 Go 回调项。此场景直接使用 Add,不要先调用 SetContextMenu:

if err := w.RemoveContextMenuItemRole(
    webview.MenuItemRoleCopy,
    webview.MenuItemRoleCut,
); err != nil {
    panic(err)
}
if err := w.AddContextMenuItem(webview.MenuItem{
    ID: "hello",
    Label: "自定义动作",
    Action: func() { w.Eval(`alert('Hello from Go')`) },
}); err != nil {
    panic(err)
}

自定义项可通过 Role: webview.MenuItemRoleCopy 等标记编辑角色;RemoveContextMenuItemRole 会连同这些匹配的自定义项一起移除,未标记 Role 的自定义项继续保留。重复移除同一 role 无额外副作用,传入未知 role 返回错误。

可在 Run 前配置,也可在运行时更新。回调异步执行,可调用菜单更新、Eval 和 Close;多个回调可能并发,应用共享状态应自行同步。菜单栏、托盘、右键菜单独立管理回调。窗口关闭时只释放所属窗口资源,托盘及进程事件循环继续运行。

macOS 使用 NSStatusItem 和原生菜单打开钩子;Windows 使用通知图标及 WebView2 ContextMenuRequested,需要支持该接口的运行时,支持 Explorer 重启后恢复图标;Linux 使用 StatusNotifierItem 与 DBusMenu,运行依赖见上文。Chrome 的窗口右键菜单或缺失原生能力时返回可用 errors.Is(err, webview.ErrMenuUnsupported) 判断的错误。Run 前无法确定的运行时或桌面初始化失败由 Run 返回。

测试

运行当前平台的全量测试:

go test ./...

Linux 的 GDBus 测试需要 GLib/GObject/GIO 和 dbus-run-session。以下命令创建隔离会话总线,由测试进程提供模拟 watcher,并在没有 DISPLAY/Wayland 的情况下验证注册、属性、菜单、信号、多托盘隔离与清理:

env -u DISPLAY -u WAYLAND_DISPLAY G_DEBUG=fatal-warnings \
  dbus-run-session -- go test ./internal/linux

未设置 DBUS_SESSION_BUS_ADDRESS 时,会话总线集成测试会跳过。Race 检测需要 Go 支持的 C 编译器及启用 CGO;应用本身仍可使用 CGO_ENABLED=0 构建:

env -u DISPLAY -u WAYLAND_DISPLAY G_DEBUG=fatal-warnings CGO_ENABLED=1 \
  dbus-run-session -- go test -race ./...

模拟 watcher 测试不验证真实桌面的图标显示、鼠标交互或文件对话框。这些交互需在有图形环境及托盘宿主的 Linux 桌面另行验证。

About

Purego webview, not webview fallback local chrome.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages