├── .gitignore
├── LICENSE
├── desktop.go
├── desktop.ico
├── example
└── basic
│ ├── dog.ico
│ └── main.go
├── go-webview2
├── LICENSE
├── README.md
├── common.go
├── internal
│ └── w32
│ │ └── w32.go
├── pkg
│ └── edge
│ │ ├── COREWEBVIEW2_COLOR.go
│ │ ├── COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND.go
│ │ ├── COREWEBVIEW2_KEY_EVENT_KIND.go
│ │ ├── COREWEBVIEW2_MOVE_FOCUS_REASON.go
│ │ ├── COREWEBVIEW2_PHYSICAL_KEY_STATUS.go
│ │ ├── COREWEBVIEW2_WEB_RESOURCE_CONTEXT.go
│ │ ├── ICoreWebView2AcceleratorKeyPressedEventArgs.go
│ │ ├── ICoreWebView2AcceleratorKeyPressedEventHandler.go
│ │ ├── ICoreWebView2Controller.go
│ │ ├── ICoreWebView2Controller2.go
│ │ ├── ICoreWebView2CreateCoreWebView2ControllerCompletedHandler.go
│ │ ├── ICoreWebView2NavigationCompletedEventArgs.go
│ │ ├── ICoreWebView2NavigationCompletedEventHandler.go
│ │ ├── ICoreWebView2Settings.go
│ │ ├── ICoreWebView2WebResourceRequest.go
│ │ ├── ICoreWebView2WebResourceRequestedEventArgs.go
│ │ ├── ICoreWebView2WebResourceRequestedEventHandler.go
│ │ ├── ICoreWebView2WebResourceResponse.go
│ │ ├── ICoreWebView2_2.go
│ │ ├── ICoreWebView2_3.go
│ │ ├── ICoreWebViewSettings.go
│ │ ├── chromium.go
│ │ ├── chromium_386.go
│ │ ├── chromium_amd64.go
│ │ ├── chromium_arm64.go
│ │ ├── comproc.go
│ │ ├── comproc_go17.go
│ │ ├── comproc_go18.go
│ │ ├── corewebview2.go
│ │ └── guid.go
├── webview.go
├── webviewloader
│ ├── README.md
│ ├── install.go
│ ├── module.go
│ ├── module_386.go
│ ├── module_amd64.go
│ ├── module_arm64.go
│ └── sdk
│ │ ├── LICENSE.txt
│ │ ├── arm64
│ │ └── WebView2Loader.dll
│ │ ├── x64
│ │ └── WebView2Loader.dll
│ │ └── x86
│ │ └── WebView2Loader.dll
└── window.go
├── go.mod
├── go.sum
├── readme.md
├── tray
├── systray
│ ├── systray.go
│ └── systray_windows.go
├── tray_other.go
└── tray_windows.go
├── types.go
└── util.go
/.gitignore:
--------------------------------------------------------------------------------
1 | __debug_bin*
2 | *.exe
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Yuesong Liu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/desktop.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package desktop
5 |
6 | import (
7 | "github.com/eyasliu/desktop/tray"
8 |
9 | "github.com/eyasliu/desktop/go-webview2"
10 | )
11 |
12 | // New 新建一个 webview 窗口
13 | func New(opt *Options) WebView {
14 | // 托盘图标
15 | iconpath := opt.GetIcon()
16 | if opt.Tray != nil {
17 | if opt.Tray.IconPath == "" {
18 | opt.Tray.IconPath = opt.IconPath
19 | }
20 | if len(opt.Tray.IconBytes) == 0 {
21 | opt.Tray.IconBytes = opt.IconBytes
22 | }
23 | if opt.Tray.IconPath == "" && len(opt.Tray.IconBytes) == 0 {
24 | opt.Tray.IconPath = iconpath
25 | }
26 | }
27 |
28 | wvOpts := webview2.WebViewOptions{
29 | Debug: opt.Debug,
30 | StartURL: opt.StartURL,
31 | FallbackPage: opt.FallbackPage,
32 | DataPath: opt.DataPath,
33 | AutoFocus: opt.AutoFocus,
34 | HideWindowOnClose: opt.HideWindowOnClose,
35 | Logger: opt.Logger,
36 | WindowOptions: webview2.WindowOptions{
37 | Icon: iconpath,
38 | Frameless: opt.Frameless,
39 | Title: opt.Title,
40 | Center: opt.Center,
41 | Width: uint(opt.Width),
42 | Height: uint(opt.Height),
43 | },
44 | }
45 | if wvOpts.Logger == nil {
46 | wvOpts.Logger = &defaultLogger{}
47 | }
48 |
49 | if IsSupportTray() && opt.Tray != nil {
50 | go tray.Run(opt.Tray)
51 | }
52 | w := webview2.NewWin(wvOpts, opt.Tray)
53 | return w
54 | }
55 |
--------------------------------------------------------------------------------
/desktop.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyasliu/desktop/330d29a8acece96646a2ce5236ec6fed77c46c82/desktop.ico
--------------------------------------------------------------------------------
/example/basic/dog.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyasliu/desktop/330d29a8acece96646a2ce5236ec6fed77c46c82/example/basic/dog.ico
--------------------------------------------------------------------------------
/example/basic/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "embed"
5 | "fmt"
6 | "runtime"
7 | "time"
8 |
9 | "github.com/eyasliu/desktop"
10 | "github.com/eyasliu/desktop/tray"
11 | )
12 |
13 | //go:embed dog.ico
14 | var dogIco []byte
15 | var FallbackPage string = `
出错了
`
16 |
17 | func main() {
18 | var app desktop.WebView
19 | var appTray *tray.Tray
20 | checkedMenu := &tray.TrayItem{
21 | Title: "有勾选状态菜单项",
22 | Checkbox: true,
23 | Checked: true,
24 | }
25 | checkedMenu.OnClick = func() {
26 | checkedMenu.Checked = !checkedMenu.Checked
27 | checkedMenu.Title = "未勾选"
28 | if checkedMenu.Checked {
29 | checkedMenu.Title = "已勾选"
30 | }
31 | checkedMenu.Update()
32 | }
33 | appTray = &tray.Tray{
34 | Title: "托盘演示",
35 | Tooltip: "提示文字,点击激活显示窗口",
36 | OnClick: func() {
37 | app.Show() // 显示窗口
38 | },
39 | Items: []*tray.TrayItem{
40 | checkedMenu,
41 | {
42 | Title: "修改托盘图标和文字",
43 | OnClick: func() {
44 | appTray.SetIconBytes(dogIco)
45 | appTray.SetTooltip("这是设置过后的托盘提示文字")
46 | },
47 | },
48 | {
49 | Title: "跳转到腾讯文档",
50 | OnClick: func() { app.Navigate("https://docs.qq.com") },
51 | },
52 | {
53 | Title: "触发错误页",
54 | OnClick: func() { app.Navigate("https://abcd.efgh.ijkl") },
55 | },
56 | {
57 | Title: "打开本地页面",
58 | OnClick: func() {
59 | app.SetHtml(`这是个本地页面
60 | 设置css: -webkit-app-region: drag 可移动窗口
`)
61 | },
62 | },
63 | {
64 | Title: "JS 交互",
65 | Items: []*tray.TrayItem{
66 | {
67 | Title: "执行 alert('hello')",
68 | OnClick: func() { app.Eval("alert('hello')") },
69 | },
70 | {
71 | Title: "每次进入页面执行alert",
72 | OnClick: func() {
73 | app.Init("alert('每次进入页面都会执行一次')")
74 | },
75 | },
76 | {
77 | Title: "调用Go函数",
78 | OnClick: func() {
79 |
80 | app.Eval(`golangFn('tom').then(s => alert(s))`)
81 | },
82 | },
83 | },
84 | },
85 | {
86 | Title: "窗口操作",
87 | Items: []*tray.TrayItem{
88 | {
89 | Title: "无边框打开新窗口 im.qq.com",
90 | OnClick: func() {
91 | go func() {
92 | wpsai := desktop.New(&desktop.Options{
93 | StartURL: "https://im.qq.com",
94 | Center: true,
95 | Frameless: true, // 去掉边框
96 | })
97 | wpsai.Run()
98 | }()
99 | },
100 | },
101 | {
102 | Title: "显示窗口",
103 | OnClick: func() {
104 | app.Show()
105 | },
106 | },
107 | {
108 | Title: "隐藏窗口",
109 | OnClick: func() {
110 | app.Hide()
111 | },
112 | },
113 | {
114 | Title: "设置窗口标题",
115 | OnClick: func() {
116 | app.SetTitle("这是新的标题")
117 | },
118 | },
119 | },
120 | },
121 | {
122 | Title: "退出程序",
123 | OnClick: func() {
124 | app.Destroy()
125 | },
126 | },
127 | },
128 | }
129 | app = desktop.New(&desktop.Options{
130 | Debug: true,
131 | AutoFocus: true,
132 | Width: 1280,
133 | Height: 768,
134 | HideWindowOnClose: true,
135 | Center: true,
136 | Title: "basic 演示",
137 | StartURL: "https://www.wps.cn",
138 | Tray: appTray,
139 | DataPath: "C:\\Users\\Kingsoft\\.envokv2\\webview2",
140 | FallbackPage: FallbackPage,
141 | })
142 |
143 | app.Bind("golangFn", func(name string) string {
144 | return fmt.Sprintf(`Hello %s, GOOS=%s`, name, runtime.GOOS)
145 | })
146 |
147 | // 为了验证没准备好就调用方法
148 | go func() {
149 | <-time.After(100 * time.Millisecond)
150 | app.Show()
151 | app.Navigate("https://www.wps.cn")
152 | }()
153 |
154 | app.Run()
155 | }
156 |
--------------------------------------------------------------------------------
/go-webview2/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 John Chadwick
4 | Some portions Copyright (c) 2017 Serge Zaitsev
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/go-webview2/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/jchv/go-webview2/actions/workflows/go.yml) [](https://goreportcard.com/report/github.com/jchv/go-webview2) [](https://pkg.go.dev/github.com/jchv/go-webview2)
2 |
3 | # go-webview2
4 | This package provides an interface for using the Microsoft Edge WebView2 component with Go. It is based on [webview/webview](https://github.com/webview/webview) and provides a compatible API.
5 |
6 | Please note that this package only supports Windows, since it provides functionality specific to WebView2. If you wish to use this library for Windows, but use webview/webview for all other operating systems, you could use the [go-webview-selector](https://github.com/jchv/go-webview-selector) package instead. However, you will not be able to use WebView2-specific functionality.
7 |
8 | If you wish to build desktop applications in Go using web technologies, please consider [Wails](https://wails.io/). It uses go-webview2 internally on Windows.
9 |
10 | ## Demo
11 | If you are using Windows 10+, the WebView2 runtime should already be installed. If you don't have it installed, you can download and install a copy from Microsoft's website:
12 |
13 | [WebView2 runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
14 |
15 | After that, you should be able to run go-webview2 directly:
16 |
17 | ```
18 | go run ./cmd/demo
19 | ```
20 |
21 | This will use go-winloader to load an embedded copy of WebView2Loader.dll. If you want, you can also provide a newer version of WebView2Loader.dll in the DLL search path and it should be picked up instead. It can be acquired from the WebView2 SDK (which is permissively licensed.)
22 |
--------------------------------------------------------------------------------
/go-webview2/common.go:
--------------------------------------------------------------------------------
1 | package webview2
2 |
3 | import "unsafe"
4 |
5 | // This is copied from webview/webview.
6 | // The documentation is included for convenience.
7 |
8 | // Hint is used to configure window sizing and resizing behavior.
9 | type Hint int
10 |
11 | const (
12 | // HintNone specifies that width and height are default size
13 | HintNone Hint = iota
14 |
15 | // HintFixed specifies that window size can not be changed by a user
16 | HintFixed
17 |
18 | // HintMin specifies that width and height are minimum bounds
19 | HintMin
20 |
21 | // HintMax specifies that width and height are maximum bounds
22 | HintMax
23 | )
24 |
25 | // WebView is the interface for the webview.
26 | type WebView interface {
27 |
28 | // Run runs the main loop until it's terminated. After this function exits -
29 | // you must destroy the webview.
30 | Run()
31 |
32 | // Terminate stops the main loop. It is safe to call this function from
33 | // a background thread.
34 | Terminate()
35 |
36 | // Dispatch posts a function to be executed on the main thread. You normally
37 | // do not need to call this function, unless you want to tweak the native
38 | // window.
39 | Dispatch(f func())
40 |
41 | // Destroy destroys a webview and closes the native window.
42 | Destroy()
43 |
44 | // Window returns a native window handle pointer. When using GTK backend the
45 | // pointer is GtkWindow pointer, when using Cocoa backend the pointer is
46 | // NSWindow pointer, when using Win32 backend the pointer is HWND pointer.
47 | Window() unsafe.Pointer
48 |
49 | // SetTitle updates the title of the native window. Must be called from the UI
50 | // thread.
51 | SetTitle(title string)
52 |
53 | // SetSize updates native window size. See Hint constants.
54 | SetSize(w int, h int, hint Hint)
55 |
56 | // Navigate navigates webview to the given URL. URL may be a data URI, i.e.
57 | // "data:text/text,...". It is often ok not to url-encode it
58 | // properly, webview will re-encode it for you.
59 | Navigate(url string)
60 |
61 | // SetHtml sets the webview HTML directly.
62 | // The origin of the page is `about:blank`.
63 | SetHtml(html string)
64 |
65 | // Init injects JavaScript code at the initialization of the new page. Every
66 | // time the webview will open a the new page - this initialization code will
67 | // be executed. It is guaranteed that code is executed before window.onload.
68 | Init(js string)
69 |
70 | // Eval evaluates arbitrary JavaScript code. Evaluation happens asynchronously,
71 | // also the result of the expression is ignored. Use RPC bindings if you want
72 | // to receive notifications about the results of the evaluation.
73 | Eval(js string)
74 |
75 | // Bind binds a callback function so that it will appear under the given name
76 | // as a global JavaScript function. Internally it uses webview_init().
77 | // Callback receives a request string and a user-provided argument pointer.
78 | // Request string is a JSON array of all the arguments passed to the
79 | // JavaScript function.
80 | //
81 | // f must be a function
82 | // f must return either value and error or just error
83 | Bind(name string, f interface{}) error
84 |
85 | Hide()
86 | Show()
87 | }
88 |
--------------------------------------------------------------------------------
/go-webview2/internal/w32/w32.go:
--------------------------------------------------------------------------------
1 | package w32
2 |
3 | import (
4 | "syscall"
5 | "unicode/utf16"
6 | "unsafe"
7 |
8 | "golang.org/x/sys/windows"
9 | )
10 |
11 | var (
12 | ole32 = windows.NewLazySystemDLL("ole32")
13 | Ole32CoInitializeEx = ole32.NewProc("CoInitializeEx")
14 |
15 | kernel32 = windows.NewLazySystemDLL("kernel32")
16 | Kernel32GetCurrentThreadID = kernel32.NewProc("GetCurrentThreadId")
17 |
18 | shcore = windows.NewLazySystemDLL("shcore")
19 | ShcoreSetProcessDpiAwareness = shcore.NewProc("SetProcessDpiAwareness")
20 |
21 | gdi32 = windows.NewLazySystemDLL("gdi32")
22 | Gdi32GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
23 |
24 | shlwapi = windows.NewLazySystemDLL("shlwapi")
25 | shlwapiSHCreateMemStream = shlwapi.NewProc("SHCreateMemStream")
26 |
27 | user32 = windows.NewLazySystemDLL("user32")
28 | User32LoadImageW = user32.NewProc("LoadImageW")
29 | User32GetSystemMetrics = user32.NewProc("GetSystemMetrics")
30 | User32RegisterClassExW = user32.NewProc("RegisterClassExW")
31 | User32CreateWindowExW = user32.NewProc("CreateWindowExW")
32 | User32DestroyWindow = user32.NewProc("DestroyWindow")
33 | User32ShowWindow = user32.NewProc("ShowWindow")
34 | User32UpdateWindow = user32.NewProc("UpdateWindow")
35 | User32SwitchToThisWindow = user32.NewProc("SwitchToThisWindow")
36 | User32SetFocus = user32.NewProc("SetFocus")
37 | User32GetMessageW = user32.NewProc("GetMessageW")
38 | User32PeekMessageW = user32.NewProc("PeekMessageW")
39 | User32TranslateMessage = user32.NewProc("TranslateMessage")
40 | User32DispatchMessageW = user32.NewProc("DispatchMessageW")
41 | User32DefWindowProcW = user32.NewProc("DefWindowProcW")
42 | User32GetClientRect = user32.NewProc("GetClientRect")
43 | User32GetWindowRect = user32.NewProc("GetWindowRect")
44 | User32PostQuitMessage = user32.NewProc("PostQuitMessage")
45 | User32PostMessageW = user32.NewProc("PostMessageW")
46 | User32SetWindowTextW = user32.NewProc("SetWindowTextW")
47 | User32PostThreadMessageW = user32.NewProc("PostThreadMessageW")
48 | User32GetWindowLongPtrW = user32.NewProc("GetWindowLongPtrW")
49 | User32SetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW")
50 | User32AdjustWindowRect = user32.NewProc("AdjustWindowRect")
51 | User32SetWindowPos = user32.NewProc("SetWindowPos")
52 | User32IsDialogMessage = user32.NewProc("IsDialogMessage")
53 | User32GetAncestor = user32.NewProc("GetAncestor")
54 | User32ReleaseCapture = user32.NewProc("ReleaseCapture")
55 | User32SendMessage = user32.NewProc("SendMessageW")
56 | User32GetDpiForWindow = user32.NewProc("GetDpiForWindow")
57 | User32GetDC = user32.NewProc("GetDC")
58 | User32ReleaseDC = user32.NewProc("ReleaseDC")
59 | )
60 |
61 | const (
62 | SM_CXSCREEN = 0
63 | SM_CYSCREEN = 1
64 | )
65 |
66 | const (
67 | CW_USEDEFAULT = 0x80000000
68 | )
69 |
70 | const (
71 | LR_DEFAULTCOLOR = 0x0000
72 | LR_MONOCHROME = 0x0001
73 | LR_LOADFROMFILE = 0x0010
74 | LR_LOADTRANSPARENT = 0x0020
75 | LR_DEFAULTSIZE = 0x0040
76 | LR_VGACOLOR = 0x0080
77 | LR_LOADMAP3DCOLORS = 0x1000
78 | LR_CREATEDIBSECTION = 0x2000
79 | LR_SHARED = 0x8000
80 | )
81 |
82 | const (
83 | SystemMetricsCxIcon = 11
84 | SystemMetricsCyIcon = 12
85 | )
86 |
87 | // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
88 | const (
89 | SWHide = 0
90 | SWMaximize = 2
91 | SWShow = 5
92 | SWMinimize = 6
93 | )
94 |
95 | const (
96 | SWPNoZOrder = 0x0004
97 | SWPNoActivate = 0x0010
98 | SWPNoMove = 0x0002
99 | SWPFrameChanged = 0x0020
100 | )
101 |
102 | const (
103 | WMCreate = 0x0001
104 | WMDestroy = 0x0002
105 | WMMove = 0x0003
106 | WMSize = 0x0005
107 | WMActivate = 0x0006
108 | WMClose = 0x0010
109 | WMQuit = 0x0012
110 | WMGetMinMaxInfo = 0x0024
111 | WMNCLButtonDown = 0x00A1
112 | WMMoving = 0x0216
113 | WMApp = 0x8000
114 | WMMouseMove = 0x0200
115 | WMMouseLeave = 0x02A3
116 | WMLButtonDown = 0x0201
117 | WMParentNotify = 0x0210
118 | WMDpiChanged = 0x02E0
119 | )
120 |
121 | const (
122 | GAParent = 1
123 | GARoot = 2
124 | GARootOwner = 3
125 | )
126 |
127 | const (
128 | GWLStyle = -16
129 | )
130 |
131 | // https://learn.microsoft.com/en-us/windows/win32/winmsg/window-styles
132 | const (
133 | WSBorder = 0x00800000
134 | WSOverlapped = 0x00000000
135 | WSMaximizeBox = 0x00010000
136 | WSThickFrame = 0x00040000
137 | WSCaption = 0x00C00000
138 | WSSysMenu = 0x00080000
139 | WSMinimizeBox = 0x00020000
140 | WSPopup = 0x80000000
141 | WSSizeBox = 0x00040000
142 | WSOverlappedWindow = (WSOverlapped | WSCaption | WSSysMenu | WSThickFrame | WSMinimizeBox | WSMaximizeBox)
143 | WSPopupWindow = (WSPopup | WSBorder | WSSysMenu)
144 | )
145 |
146 | const (
147 | WAInactive = 0
148 | WAActive = 1
149 | WAActiveClick = 2
150 | )
151 |
152 | type WndClassExW struct {
153 | CbSize uint32
154 | Style uint32
155 | LpfnWndProc uintptr
156 | CnClsExtra int32
157 | CbWndExtra int32
158 | HInstance windows.Handle
159 | HIcon windows.Handle
160 | HCursor windows.Handle
161 | HbrBackground windows.Handle
162 | LpszMenuName *uint16
163 | LpszClassName *uint16
164 | HIconSm windows.Handle
165 | }
166 |
167 | type Rect struct {
168 | Left int32
169 | Top int32
170 | Right int32
171 | Bottom int32
172 | }
173 |
174 | type MinMaxInfo struct {
175 | PtReserved Point
176 | PtMaxSize Point
177 | PtMaxPosition Point
178 | PtMinTrackSize Point
179 | PtMaxTrackSize Point
180 | }
181 |
182 | type Point struct {
183 | X, Y int32
184 | }
185 |
186 | type Msg struct {
187 | Hwnd syscall.Handle
188 | Message uint32
189 | WParam uintptr
190 | LParam uintptr
191 | Time uint32
192 | Pt Point
193 | LPrivate uint32
194 | }
195 |
196 | func Utf16PtrToString(p *uint16) string {
197 | if p == nil {
198 | return ""
199 | }
200 | // Find NUL terminator.
201 | end := unsafe.Pointer(p)
202 | n := 0
203 | for *(*uint16)(end) != 0 {
204 | end = unsafe.Pointer(uintptr(end) + unsafe.Sizeof(*p))
205 | n++
206 | }
207 | s := (*[(1 << 30) - 1]uint16)(unsafe.Pointer(p))[:n:n]
208 | return string(utf16.Decode(s))
209 | }
210 |
211 | func SHCreateMemStream(data []byte) (uintptr, error) {
212 | ret, _, err := shlwapiSHCreateMemStream.Call(
213 | uintptr(unsafe.Pointer(&data[0])),
214 | uintptr(len(data)),
215 | )
216 | if ret == 0 {
217 | return 0, err
218 | }
219 |
220 | return ret, nil
221 | }
222 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/COREWEBVIEW2_COLOR.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type COREWEBVIEW2_COLOR struct {
4 | A uint8
5 | R uint8
6 | G uint8
7 | B uint8
8 | }
9 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND uint32
4 |
5 | const (
6 | COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY = iota
7 | COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_ALLOW
8 | COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS
9 | )
10 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/COREWEBVIEW2_KEY_EVENT_KIND.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type COREWEBVIEW2_KEY_EVENT_KIND uint32
4 |
5 | const (
6 | COREWEBVIEW2_KEY_EVENT_KIND_KEY_DOWN = 0
7 | COREWEBVIEW2_KEY_EVENT_KIND_KEY_UP = 1
8 | COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_DOWN = 2
9 | COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_UP = 3
10 | )
11 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/COREWEBVIEW2_MOVE_FOCUS_REASON.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type COREWEBVIEW2_MOVE_FOCUS_REASON uint32
4 |
5 | const (
6 | COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC = 0
7 | COREWEBVIEW2_MOVE_FOCUS_REASON_NEXT = 1
8 | COREWEBVIEW2_MOVE_FOCUS_REASON_PREVIOUS = 2
9 | )
10 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/COREWEBVIEW2_PHYSICAL_KEY_STATUS.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type COREWEBVIEW2_PHYSICAL_KEY_STATUS struct {
4 | RepeatCount uint32
5 | ScanCode uint32
6 | IsExtendedKey bool
7 | IsMenuKeyDown bool
8 | WasKeyDown bool
9 | IsKeyReleased bool
10 | }
11 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/COREWEBVIEW2_WEB_RESOURCE_CONTEXT.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type COREWEBVIEW2_WEB_RESOURCE_CONTEXT uint32
4 |
5 | const (
6 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL = 0
7 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT = 1
8 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_STYLESHEET = 2
9 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_IMAGE = 3
10 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_MEDIA = 4
11 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_FONT = 5
12 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_SCRIPT = 6
13 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_XML_HTTP_REQUEST = 7
14 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_FETCH = 8
15 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_TEXT_TRACK = 9
16 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_EVENT_SOURCE = 10
17 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_WEBSOCKET = 11
18 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_MANIFEST = 12
19 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_SIGNED_EXCHANGE = 13
20 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_PING = 14
21 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_CSP_VIOLATION_REPORT = 15
22 | COREWEBVIEW2_WEB_RESOURCE_CONTEXT_OTHER = 16
23 | )
24 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2AcceleratorKeyPressedEventArgs.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "unsafe"
5 |
6 | "golang.org/x/sys/windows"
7 | )
8 |
9 | type _ICoreWebView2AcceleratorKeyPressedEventArgsVtbl struct {
10 | _IUnknownVtbl
11 | GetKeyEventKind ComProc
12 | GetVirtualKey ComProc
13 | GetKeyEventLParam ComProc
14 | GetPhysicalKeyStatus ComProc
15 | GetHandled ComProc
16 | PutHandled ComProc
17 | }
18 |
19 | type ICoreWebView2AcceleratorKeyPressedEventArgs struct {
20 | vtbl *_ICoreWebView2AcceleratorKeyPressedEventArgsVtbl
21 | }
22 |
23 | func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) AddRef() uintptr {
24 | r, _, _ := i.vtbl.AddRef.Call()
25 | return r
26 | }
27 |
28 | func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) GetKeyEventKind() (COREWEBVIEW2_KEY_EVENT_KIND, error) {
29 | var err error
30 | var keyEventKind COREWEBVIEW2_KEY_EVENT_KIND
31 | _, _, err = i.vtbl.GetKeyEventKind.Call(
32 | uintptr(unsafe.Pointer(i)),
33 | uintptr(unsafe.Pointer(&keyEventKind)),
34 | )
35 | if err != windows.ERROR_SUCCESS {
36 | return 0, err
37 | }
38 | return keyEventKind, nil
39 | }
40 |
41 | func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) GetVirtualKey() (uint, error) {
42 | var err error
43 | var virtualKey uint
44 | _, _, err = i.vtbl.GetVirtualKey.Call(
45 | uintptr(unsafe.Pointer(i)),
46 | uintptr(unsafe.Pointer(&virtualKey)),
47 | )
48 | if err != windows.ERROR_SUCCESS {
49 | return 0, err
50 | }
51 | return virtualKey, nil
52 | }
53 |
54 | func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) GetPhysicalKeyStatus() (COREWEBVIEW2_PHYSICAL_KEY_STATUS, error) {
55 | var err error
56 | var physicalKeyStatus COREWEBVIEW2_PHYSICAL_KEY_STATUS
57 | _, _, err = i.vtbl.GetPhysicalKeyStatus.Call(
58 | uintptr(unsafe.Pointer(i)),
59 | uintptr(unsafe.Pointer(&physicalKeyStatus)),
60 | )
61 | if err != windows.ERROR_SUCCESS {
62 | return COREWEBVIEW2_PHYSICAL_KEY_STATUS{}, err
63 | }
64 | return physicalKeyStatus, nil
65 | }
66 |
67 | func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) PutHandled(handled bool) error {
68 | var err error
69 |
70 | _, _, err = i.vtbl.PutHandled.Call(
71 | uintptr(unsafe.Pointer(i)),
72 | uintptr(boolToInt(handled)),
73 | )
74 | if err != windows.ERROR_SUCCESS {
75 | return err
76 | }
77 | return nil
78 | }
79 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2AcceleratorKeyPressedEventHandler.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type _ICoreWebView2AcceleratorKeyPressedEventHandlerVtbl struct {
4 | _IUnknownVtbl
5 | Invoke ComProc
6 | }
7 |
8 | type ICoreWebView2AcceleratorKeyPressedEventHandler struct {
9 | vtbl *_ICoreWebView2AcceleratorKeyPressedEventHandlerVtbl
10 | impl _ICoreWebView2AcceleratorKeyPressedEventHandlerImpl
11 | }
12 |
13 | func (i *ICoreWebView2AcceleratorKeyPressedEventHandler) AddRef() uintptr {
14 | r, _, _ := i.vtbl.AddRef.Call()
15 | return r
16 | }
17 | func _ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownQueryInterface(this *ICoreWebView2AcceleratorKeyPressedEventHandler, refiid, object uintptr) uintptr {
18 | return this.impl.QueryInterface(refiid, object)
19 | }
20 |
21 | func _ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownAddRef(this *ICoreWebView2AcceleratorKeyPressedEventHandler) uintptr {
22 | return this.impl.AddRef()
23 | }
24 |
25 | func _ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownRelease(this *ICoreWebView2AcceleratorKeyPressedEventHandler) uintptr {
26 | return this.impl.Release()
27 | }
28 |
29 | func _ICoreWebView2AcceleratorKeyPressedEventHandlerInvoke(this *ICoreWebView2AcceleratorKeyPressedEventHandler, sender *ICoreWebView2Controller, args *ICoreWebView2AcceleratorKeyPressedEventArgs) uintptr {
30 | return this.impl.AcceleratorKeyPressed(sender, args)
31 | }
32 |
33 | type _ICoreWebView2AcceleratorKeyPressedEventHandlerImpl interface {
34 | _IUnknownImpl
35 | AcceleratorKeyPressed(sender *ICoreWebView2Controller, args *ICoreWebView2AcceleratorKeyPressedEventArgs) uintptr
36 | }
37 |
38 | var _ICoreWebView2AcceleratorKeyPressedEventHandlerFn = _ICoreWebView2AcceleratorKeyPressedEventHandlerVtbl{
39 | _IUnknownVtbl{
40 | NewComProc(_ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownQueryInterface),
41 | NewComProc(_ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownAddRef),
42 | NewComProc(_ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownRelease),
43 | },
44 | NewComProc(_ICoreWebView2AcceleratorKeyPressedEventHandlerInvoke),
45 | }
46 |
47 | func newICoreWebView2AcceleratorKeyPressedEventHandler(impl _ICoreWebView2AcceleratorKeyPressedEventHandlerImpl) *ICoreWebView2AcceleratorKeyPressedEventHandler {
48 | return &ICoreWebView2AcceleratorKeyPressedEventHandler{
49 | vtbl: &_ICoreWebView2AcceleratorKeyPressedEventHandlerFn,
50 | impl: impl,
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2Controller.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "unsafe"
5 |
6 | "github.com/eyasliu/desktop/go-webview2/internal/w32"
7 | "golang.org/x/sys/windows"
8 | )
9 |
10 | type _ICoreWebView2ControllerVtbl struct {
11 | _IUnknownVtbl
12 | GetIsVisible ComProc
13 | PutIsVisible ComProc
14 | GetBounds ComProc
15 | PutBounds ComProc
16 | GetZoomFactor ComProc
17 | PutZoomFactor ComProc
18 | AddZoomFactorChanged ComProc
19 | RemoveZoomFactorChanged ComProc
20 | SetBoundsAndZoomFactor ComProc
21 | MoveFocus ComProc
22 | AddMoveFocusRequested ComProc
23 | RemoveMoveFocusRequested ComProc
24 | AddGotFocus ComProc
25 | RemoveGotFocus ComProc
26 | AddLostFocus ComProc
27 | RemoveLostFocus ComProc
28 | AddAcceleratorKeyPressed ComProc
29 | RemoveAcceleratorKeyPressed ComProc
30 | GetParentWindow ComProc
31 | PutParentWindow ComProc
32 | NotifyParentWindowPositionChanged ComProc
33 | Close ComProc
34 | GetCoreWebView2 ComProc
35 | }
36 |
37 | type ICoreWebView2Controller struct {
38 | vtbl *_ICoreWebView2ControllerVtbl
39 | }
40 |
41 | func (i *ICoreWebView2Controller) AddRef() uintptr {
42 | r, _, _ := i.vtbl.AddRef.Call()
43 | return r
44 | }
45 |
46 | func (i *ICoreWebView2Controller) GetBounds() (*w32.Rect, error) {
47 | var err error
48 | var bounds w32.Rect
49 | _, _, err = i.vtbl.GetBounds.Call(
50 | uintptr(unsafe.Pointer(i)),
51 | uintptr(unsafe.Pointer(&bounds)),
52 | )
53 | if err != windows.ERROR_SUCCESS {
54 | return nil, err
55 | }
56 | return &bounds, nil
57 | }
58 |
59 | func (i *ICoreWebView2Controller) PutBounds(bounds w32.Rect) error {
60 | var err error
61 |
62 | _, _, err = i.vtbl.PutBounds.Call(
63 | uintptr(unsafe.Pointer(i)),
64 | uintptr(unsafe.Pointer(&bounds)),
65 | )
66 | if err != windows.ERROR_SUCCESS {
67 | return err
68 | }
69 | return nil
70 | }
71 |
72 | func (i *ICoreWebView2Controller) AddAcceleratorKeyPressed(eventHandler *ICoreWebView2AcceleratorKeyPressedEventHandler, token *_EventRegistrationToken) error {
73 | var err error
74 | _, _, err = i.vtbl.AddAcceleratorKeyPressed.Call(
75 | uintptr(unsafe.Pointer(i)),
76 | uintptr(unsafe.Pointer(eventHandler)),
77 | uintptr(unsafe.Pointer(&token)),
78 | )
79 | if err != windows.ERROR_SUCCESS {
80 | return err
81 | }
82 | return nil
83 | }
84 |
85 | func (i *ICoreWebView2Controller) PutIsVisible(isVisible bool) error {
86 | var err error
87 |
88 | _, _, err = i.vtbl.PutIsVisible.Call(
89 | uintptr(unsafe.Pointer(i)),
90 | uintptr(boolToInt(isVisible)),
91 | )
92 | if err != windows.ERROR_SUCCESS {
93 | return err
94 | }
95 | return nil
96 | }
97 |
98 | func (i *ICoreWebView2Controller) GetICoreWebView2Controller2() *ICoreWebView2Controller2 {
99 |
100 | var result *ICoreWebView2Controller2
101 |
102 | iidICoreWebView2Controller2 := NewGUID("{c979903e-d4ca-4228-92eb-47ee3fa96eab}")
103 | _, _, _ = i.vtbl.QueryInterface.Call(
104 | uintptr(unsafe.Pointer(i)),
105 | uintptr(unsafe.Pointer(iidICoreWebView2Controller2)),
106 | uintptr(unsafe.Pointer(&result)))
107 |
108 | return result
109 | }
110 |
111 | func (i *ICoreWebView2Controller) NotifyParentWindowPositionChanged() error {
112 | var err error
113 | _, _, err = i.vtbl.NotifyParentWindowPositionChanged.Call(
114 | uintptr(unsafe.Pointer(i)),
115 | )
116 | if err != windows.ERROR_SUCCESS {
117 | return err
118 | }
119 | return nil
120 | }
121 |
122 | func (i *ICoreWebView2Controller) MoveFocus(reason uintptr) error {
123 | var err error
124 | _, _, err = i.vtbl.MoveFocus.Call(
125 | uintptr(unsafe.Pointer(i)),
126 | uintptr(reason),
127 | )
128 | if err != windows.ERROR_SUCCESS {
129 | return err
130 | }
131 | return nil
132 | }
133 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2Controller2.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "unsafe"
5 |
6 | "golang.org/x/sys/windows"
7 | )
8 |
9 | type _ICoreWebView2Controller2Vtbl struct {
10 | _IUnknownVtbl
11 | GetIsVisible ComProc
12 | PutIsVisible ComProc
13 | GetBounds ComProc
14 | PutBounds ComProc
15 | GetZoomFactor ComProc
16 | PutZoomFactor ComProc
17 | AddZoomFactorChanged ComProc
18 | RemoveZoomFactorChanged ComProc
19 | SetBoundsAndZoomFactor ComProc
20 | MoveFocus ComProc
21 | AddMoveFocusRequested ComProc
22 | RemoveMoveFocusRequested ComProc
23 | AddGotFocus ComProc
24 | RemoveGotFocus ComProc
25 | AddLostFocus ComProc
26 | RemoveLostFocus ComProc
27 | AddAcceleratorKeyPressed ComProc
28 | RemoveAcceleratorKeyPressed ComProc
29 | GetParentWindow ComProc
30 | PutParentWindow ComProc
31 | NotifyParentWindowPositionChanged ComProc
32 | Close ComProc
33 | GetCoreWebView2 ComProc
34 | GetDefaultBackgroundColor ComProc
35 | PutDefaultBackgroundColor ComProc
36 | }
37 |
38 | type ICoreWebView2Controller2 struct {
39 | vtbl *_ICoreWebView2Controller2Vtbl
40 | }
41 |
42 | func (i *ICoreWebView2Controller2) AddRef() uintptr {
43 | r, _, _ := i.vtbl.AddRef.Call()
44 | return r
45 | }
46 |
47 | func (i *ICoreWebView2Controller2) GetDefaultBackgroundColor() (*COREWEBVIEW2_COLOR, error) {
48 | var err error
49 | var backgroundColor *COREWEBVIEW2_COLOR
50 | _, _, err = i.vtbl.GetDefaultBackgroundColor.Call(
51 | uintptr(unsafe.Pointer(i)),
52 | uintptr(unsafe.Pointer(&backgroundColor)),
53 | )
54 | if err != windows.ERROR_SUCCESS {
55 | return nil, err
56 | }
57 | return backgroundColor, nil
58 | }
59 |
60 | func (i *ICoreWebView2Controller2) PutDefaultBackgroundColor(backgroundColor COREWEBVIEW2_COLOR) error {
61 | var err error
62 |
63 | // Cast to a uint32 as that's what the call is expecting
64 | col := *(*uint32)(unsafe.Pointer(&backgroundColor))
65 |
66 | _, _, err = i.vtbl.PutDefaultBackgroundColor.Call(
67 | uintptr(unsafe.Pointer(i)),
68 | uintptr(col),
69 | )
70 | if err != windows.ERROR_SUCCESS {
71 | return err
72 | }
73 | return nil
74 | }
75 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2CreateCoreWebView2ControllerCompletedHandler.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl struct {
4 | _IUnknownVtbl
5 | Invoke ComProc
6 | }
7 |
8 | type iCoreWebView2CreateCoreWebView2ControllerCompletedHandler struct {
9 | vtbl *_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl
10 | impl _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerImpl
11 | }
12 |
13 | func _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownQueryInterface(this *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler, refiid, object uintptr) uintptr {
14 | return this.impl.QueryInterface(refiid, object)
15 | }
16 |
17 | func _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownAddRef(this *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler) uintptr {
18 | return this.impl.AddRef()
19 | }
20 |
21 | func _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownRelease(this *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler) uintptr {
22 | return this.impl.Release()
23 | }
24 |
25 | func _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerInvoke(this *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler, errorCode uintptr, createdController *ICoreWebView2Controller) uintptr {
26 | return this.impl.CreateCoreWebView2ControllerCompleted(errorCode, createdController)
27 | }
28 |
29 | type _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerImpl interface {
30 | _IUnknownImpl
31 | CreateCoreWebView2ControllerCompleted(errorCode uintptr, createdController *ICoreWebView2Controller) uintptr
32 | }
33 |
34 | var _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerFn = _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl{
35 | _IUnknownVtbl{
36 | NewComProc(_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownQueryInterface),
37 | NewComProc(_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownAddRef),
38 | NewComProc(_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownRelease),
39 | },
40 | NewComProc(_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerInvoke),
41 | }
42 |
43 | func newICoreWebView2CreateCoreWebView2ControllerCompletedHandler(impl _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerImpl) *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler {
44 | return &iCoreWebView2CreateCoreWebView2ControllerCompletedHandler{
45 | vtbl: &_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerFn,
46 | impl: impl,
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2NavigationCompletedEventArgs.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "unsafe"
5 |
6 | "golang.org/x/sys/windows"
7 | )
8 |
9 | type _ICoreWebView2NavigationCompletedEventArgsVtbl struct {
10 | _IUnknownVtbl
11 | GetIsSuccess ComProc
12 | GetWebErrorStatus ComProc
13 | GetNavigationId ComProc
14 | }
15 |
16 | type ICoreWebView2NavigationCompletedEventArgs struct {
17 | vtbl *_ICoreWebView2NavigationCompletedEventArgsVtbl
18 | }
19 |
20 | func (i *ICoreWebView2NavigationCompletedEventArgs) AddRef() uintptr {
21 | r, _, _ := i.vtbl.AddRef.Call()
22 | return r
23 | }
24 |
25 | func (i *ICoreWebView2NavigationCompletedEventArgs) IsSuccess() bool {
26 | var err error
27 | var enabled bool
28 | _, _, err = i.vtbl.GetIsSuccess.Call(
29 | uintptr(unsafe.Pointer(i)),
30 | uintptr(unsafe.Pointer(&enabled)),
31 | )
32 | if err != windows.ERROR_SUCCESS {
33 | return false
34 | }
35 | return enabled
36 | }
37 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2NavigationCompletedEventHandler.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type _ICoreWebView2NavigationCompletedEventHandlerVtbl struct {
4 | _IUnknownVtbl
5 | Invoke ComProc
6 | }
7 |
8 | type ICoreWebView2NavigationCompletedEventHandler struct {
9 | vtbl *_ICoreWebView2NavigationCompletedEventHandlerVtbl
10 | impl _ICoreWebView2NavigationCompletedEventHandlerImpl
11 | }
12 |
13 | func _ICoreWebView2NavigationCompletedEventHandlerIUnknownQueryInterface(this *ICoreWebView2NavigationCompletedEventHandler, refiid, object uintptr) uintptr {
14 | return this.impl.QueryInterface(refiid, object)
15 | }
16 |
17 | func _ICoreWebView2NavigationCompletedEventHandlerIUnknownAddRef(this *ICoreWebView2NavigationCompletedEventHandler) uintptr {
18 | return this.impl.AddRef()
19 | }
20 |
21 | func _ICoreWebView2NavigationCompletedEventHandlerIUnknownRelease(this *ICoreWebView2NavigationCompletedEventHandler) uintptr {
22 | return this.impl.Release()
23 | }
24 |
25 | func _ICoreWebView2NavigationCompletedEventHandlerInvoke(this *ICoreWebView2NavigationCompletedEventHandler, sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) uintptr {
26 | return this.impl.NavigationCompleted(sender, args)
27 | }
28 |
29 | type _ICoreWebView2NavigationCompletedEventHandlerImpl interface {
30 | _IUnknownImpl
31 | NavigationCompleted(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) uintptr
32 | }
33 |
34 | var _ICoreWebView2NavigationCompletedEventHandlerFn = _ICoreWebView2NavigationCompletedEventHandlerVtbl{
35 | _IUnknownVtbl{
36 | NewComProc(_ICoreWebView2NavigationCompletedEventHandlerIUnknownQueryInterface),
37 | NewComProc(_ICoreWebView2NavigationCompletedEventHandlerIUnknownAddRef),
38 | NewComProc(_ICoreWebView2NavigationCompletedEventHandlerIUnknownRelease),
39 | },
40 | NewComProc(_ICoreWebView2NavigationCompletedEventHandlerInvoke),
41 | }
42 |
43 | func newICoreWebView2NavigationCompletedEventHandler(impl _ICoreWebView2NavigationCompletedEventHandlerImpl) *ICoreWebView2NavigationCompletedEventHandler {
44 | return &ICoreWebView2NavigationCompletedEventHandler{
45 | vtbl: &_ICoreWebView2NavigationCompletedEventHandlerFn,
46 | impl: impl,
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2Settings.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "unsafe"
5 |
6 | "golang.org/x/sys/windows"
7 | )
8 |
9 | type _ICoreWebView2SettingsVtbl struct {
10 | _IUnknownVtbl
11 | GetIsScriptEnabled ComProc
12 | PutIsScriptEnabled ComProc
13 | GetIsWebMessageEnabled ComProc
14 | PutIsWebMessageEnabled ComProc
15 | GetAreDefaultScriptDialogsEnabled ComProc
16 | PutAreDefaultScriptDialogsEnabled ComProc
17 | GetIsStatusBarEnabled ComProc
18 | PutIsStatusBarEnabled ComProc
19 | GetAreDevToolsEnabled ComProc
20 | PutAreDevToolsEnabled ComProc
21 | GetAreDefaultContextMenusEnabled ComProc
22 | PutAreDefaultContextMenusEnabled ComProc
23 | GetAreHostObjectsAllowed ComProc
24 | PutAreHostObjectsAllowed ComProc
25 | GetIsZoomControlEnabled ComProc
26 | PutIsZoomControlEnabled ComProc
27 | GetIsBuiltInErrorPageEnabled ComProc
28 | PutIsBuiltInErrorPageEnabled ComProc
29 | }
30 |
31 | type ICoreWebView2Settings struct {
32 | vtbl *_ICoreWebView2SettingsVtbl
33 | }
34 |
35 | func (i *ICoreWebView2Settings) AddRef() uintptr {
36 | r, _, _ := i.vtbl.AddRef.Call()
37 | return r
38 | }
39 |
40 | func (i *ICoreWebView2Settings) GetIsScriptEnabled() (bool, error) {
41 | var err error
42 | var isScriptEnabled bool
43 | _, _, err = i.vtbl.GetIsScriptEnabled.Call(
44 | uintptr(unsafe.Pointer(i)),
45 | uintptr(unsafe.Pointer(&isScriptEnabled)),
46 | )
47 | if err != windows.ERROR_SUCCESS {
48 | return false, err
49 | }
50 | return isScriptEnabled, nil
51 | }
52 |
53 | func (i *ICoreWebView2Settings) PutIsScriptEnabled(isScriptEnabled bool) error {
54 | var err error
55 |
56 | _, _, err = i.vtbl.PutIsScriptEnabled.Call(
57 | uintptr(unsafe.Pointer(i)),
58 | uintptr(boolToInt(isScriptEnabled)),
59 | )
60 | if err != windows.ERROR_SUCCESS {
61 | return err
62 | }
63 | return nil
64 | }
65 |
66 | func (i *ICoreWebView2Settings) GetIsWebMessageEnabled() (bool, error) {
67 | var err error
68 | var isWebMessageEnabled bool
69 | _, _, err = i.vtbl.GetIsWebMessageEnabled.Call(
70 | uintptr(unsafe.Pointer(i)),
71 | uintptr(unsafe.Pointer(&isWebMessageEnabled)),
72 | )
73 | if err != windows.ERROR_SUCCESS {
74 | return false, err
75 | }
76 | return isWebMessageEnabled, nil
77 | }
78 |
79 | func (i *ICoreWebView2Settings) PutIsWebMessageEnabled(isWebMessageEnabled bool) error {
80 | var err error
81 |
82 | _, _, err = i.vtbl.PutIsWebMessageEnabled.Call(
83 | uintptr(unsafe.Pointer(i)),
84 | uintptr(boolToInt(isWebMessageEnabled)),
85 | )
86 | if err != windows.ERROR_SUCCESS {
87 | return err
88 | }
89 | return nil
90 | }
91 |
92 | func (i *ICoreWebView2Settings) GetAreDefaultScriptDialogsEnabled() (bool, error) {
93 | var err error
94 | var areDefaultScriptDialogsEnabled bool
95 | _, _, err = i.vtbl.GetAreDefaultScriptDialogsEnabled.Call(
96 | uintptr(unsafe.Pointer(i)),
97 | uintptr(unsafe.Pointer(&areDefaultScriptDialogsEnabled)),
98 | )
99 | if err != windows.ERROR_SUCCESS {
100 | return false, err
101 | }
102 | return areDefaultScriptDialogsEnabled, nil
103 | }
104 |
105 | func (i *ICoreWebView2Settings) PutAreDefaultScriptDialogsEnabled(areDefaultScriptDialogsEnabled bool) error {
106 | var err error
107 |
108 | _, _, err = i.vtbl.PutAreDefaultScriptDialogsEnabled.Call(
109 | uintptr(unsafe.Pointer(i)),
110 | uintptr(boolToInt(areDefaultScriptDialogsEnabled)),
111 | )
112 | if err != windows.ERROR_SUCCESS {
113 | return err
114 | }
115 | return nil
116 | }
117 |
118 | func (i *ICoreWebView2Settings) GetIsStatusBarEnabled() (bool, error) {
119 | var err error
120 | var isStatusBarEnabled bool
121 | _, _, err = i.vtbl.GetIsStatusBarEnabled.Call(
122 | uintptr(unsafe.Pointer(i)),
123 | uintptr(unsafe.Pointer(&isStatusBarEnabled)),
124 | )
125 | if err != windows.ERROR_SUCCESS {
126 | return false, err
127 | }
128 | return isStatusBarEnabled, nil
129 | }
130 |
131 | func (i *ICoreWebView2Settings) PutIsStatusBarEnabled(isStatusBarEnabled bool) error {
132 | var err error
133 |
134 | _, _, err = i.vtbl.PutIsStatusBarEnabled.Call(
135 | uintptr(unsafe.Pointer(i)),
136 | uintptr(boolToInt(isStatusBarEnabled)),
137 | )
138 | if err != windows.ERROR_SUCCESS {
139 | return err
140 | }
141 | return nil
142 | }
143 |
144 | func (i *ICoreWebView2Settings) GetAreDevToolsEnabled() (bool, error) {
145 | var err error
146 | var areDevToolsEnabled bool
147 | _, _, err = i.vtbl.GetAreDevToolsEnabled.Call(
148 | uintptr(unsafe.Pointer(i)),
149 | uintptr(unsafe.Pointer(&areDevToolsEnabled)),
150 | )
151 | if err != windows.ERROR_SUCCESS {
152 | return false, err
153 | }
154 | return areDevToolsEnabled, nil
155 | }
156 |
157 | func (i *ICoreWebView2Settings) PutAreDevToolsEnabled(areDevToolsEnabled bool) error {
158 | var err error
159 | _, _, err = i.vtbl.PutAreDevToolsEnabled.Call(
160 | uintptr(unsafe.Pointer(i)),
161 | uintptr(boolToInt(areDevToolsEnabled)),
162 | )
163 | if err != windows.ERROR_SUCCESS {
164 | return err
165 | }
166 | return nil
167 | }
168 |
169 | func (i *ICoreWebView2Settings) GetAreDefaultContextMenusEnabled() (bool, error) {
170 | var err error
171 | var enabled bool
172 | _, _, err = i.vtbl.GetAreDefaultContextMenusEnabled.Call(
173 | uintptr(unsafe.Pointer(i)),
174 | uintptr(unsafe.Pointer(&enabled)),
175 | )
176 | if err != windows.ERROR_SUCCESS {
177 | return false, err
178 | }
179 | return enabled, nil
180 | }
181 |
182 | func (i *ICoreWebView2Settings) PutAreDefaultContextMenusEnabled(enabled bool) error {
183 | var err error
184 | _, _, err = i.vtbl.PutAreDefaultContextMenusEnabled.Call(
185 | uintptr(unsafe.Pointer(i)),
186 | uintptr(boolToInt(enabled)),
187 | )
188 | if err != windows.ERROR_SUCCESS {
189 | return err
190 | }
191 | return nil
192 | }
193 |
194 | func (i *ICoreWebView2Settings) GetAreHostObjectsAllowed() (bool, error) {
195 | var err error
196 | var allowed bool
197 | _, _, err = i.vtbl.GetAreHostObjectsAllowed.Call(
198 | uintptr(unsafe.Pointer(i)),
199 | uintptr(unsafe.Pointer(&allowed)),
200 | )
201 | if err != windows.ERROR_SUCCESS {
202 | return false, err
203 | }
204 | return allowed, nil
205 | }
206 |
207 | func (i *ICoreWebView2Settings) PutAreHostObjectsAllowed(allowed bool) error {
208 | var err error
209 |
210 | _, _, err = i.vtbl.PutAreHostObjectsAllowed.Call(
211 | uintptr(unsafe.Pointer(i)),
212 | uintptr(boolToInt(allowed)),
213 | )
214 | if err != windows.ERROR_SUCCESS {
215 | return err
216 | }
217 | return nil
218 | }
219 |
220 | func (i *ICoreWebView2Settings) GetIsZoomControlEnabled() (bool, error) {
221 | var err error
222 | var enabled bool
223 | _, _, err = i.vtbl.GetIsZoomControlEnabled.Call(
224 | uintptr(unsafe.Pointer(i)),
225 | uintptr(unsafe.Pointer(&enabled)),
226 | )
227 | if err != windows.ERROR_SUCCESS {
228 | return false, err
229 | }
230 | return enabled, nil
231 | }
232 |
233 | func (i *ICoreWebView2Settings) PutIsZoomControlEnabled(enabled bool) error {
234 | var err error
235 |
236 | _, _, err = i.vtbl.PutIsZoomControlEnabled.Call(
237 | uintptr(unsafe.Pointer(i)),
238 | uintptr(boolToInt(enabled)),
239 | )
240 | if err != windows.ERROR_SUCCESS {
241 | return err
242 | }
243 | return nil
244 | }
245 |
246 | func (i *ICoreWebView2Settings) GetIsBuiltInErrorPageEnabled() (bool, error) {
247 | var err error
248 | var enabled bool
249 | _, _, err = i.vtbl.GetIsBuiltInErrorPageEnabled.Call(
250 | uintptr(unsafe.Pointer(i)),
251 | uintptr(unsafe.Pointer(&enabled)),
252 | )
253 | if err != windows.ERROR_SUCCESS {
254 | return false, err
255 | }
256 | return enabled, nil
257 | }
258 |
259 | func (i *ICoreWebView2Settings) PutIsBuiltInErrorPageEnabled(enabled bool) error {
260 | var err error
261 |
262 | _, _, err = i.vtbl.PutIsBuiltInErrorPageEnabled.Call(
263 | uintptr(unsafe.Pointer(i)),
264 | uintptr(boolToInt(enabled)),
265 | )
266 | if err != windows.ERROR_SUCCESS {
267 | return err
268 | }
269 | return nil
270 | }
271 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2WebResourceRequest.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "unsafe"
5 |
6 | "golang.org/x/sys/windows"
7 | )
8 |
9 | type _ICoreWebView2WebResourceRequestVtbl struct {
10 | _IUnknownVtbl
11 | GetUri ComProc
12 | PutUri ComProc
13 | GetMethod ComProc
14 | PutMethod ComProc
15 | GetContent ComProc
16 | PutContent ComProc
17 | GetHeaders ComProc
18 | }
19 |
20 | type ICoreWebView2WebResourceRequest struct {
21 | vtbl *_ICoreWebView2WebResourceRequestVtbl
22 | }
23 |
24 | func (i *ICoreWebView2WebResourceRequest) AddRef() uintptr {
25 | r, _, _ := i.vtbl.AddRef.Call()
26 | return r
27 | }
28 |
29 | func (i *ICoreWebView2WebResourceRequest) GetUri() (string, error) {
30 | var err error
31 | // Create *uint16 to hold result
32 | var _uri *uint16
33 | _, _, err = i.vtbl.GetUri.Call(
34 | uintptr(unsafe.Pointer(i)),
35 | uintptr(unsafe.Pointer(&_uri)),
36 | )
37 | if err != windows.ERROR_SUCCESS {
38 | return "", err
39 | } // Get result and cleanup
40 | uri := windows.UTF16PtrToString(_uri)
41 | windows.CoTaskMemFree(unsafe.Pointer(_uri))
42 | return uri, nil
43 | }
44 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2WebResourceRequestedEventArgs.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "unsafe"
5 |
6 | "golang.org/x/sys/windows"
7 | )
8 |
9 | type _ICoreWebView2WebResourceRequestedEventArgsVtbl struct {
10 | _IUnknownVtbl
11 | GetRequest ComProc
12 | GetResponse ComProc
13 | PutResponse ComProc
14 | GetDeferral ComProc
15 | GetResourceContext ComProc
16 | }
17 |
18 | type ICoreWebView2WebResourceRequestedEventArgs struct {
19 | vtbl *_ICoreWebView2WebResourceRequestedEventArgsVtbl
20 | }
21 |
22 | func (i *ICoreWebView2WebResourceRequestedEventArgs) AddRef() uintptr {
23 | r, _, _ := i.vtbl.AddRef.Call()
24 | return r
25 | }
26 |
27 | func (i *ICoreWebView2WebResourceRequestedEventArgs) PutResponse(response *ICoreWebView2WebResourceResponse) error {
28 | var err error
29 |
30 | _, _, err = i.vtbl.PutResponse.Call(
31 | uintptr(unsafe.Pointer(i)),
32 | uintptr(unsafe.Pointer(response)),
33 | )
34 | if err != windows.ERROR_SUCCESS {
35 | return err
36 | }
37 | return nil
38 | }
39 |
40 | func (i *ICoreWebView2WebResourceRequestedEventArgs) GetRequest() (*ICoreWebView2WebResourceRequest, error) {
41 | var err error
42 | var request *ICoreWebView2WebResourceRequest
43 | _, _, err = i.vtbl.GetRequest.Call(
44 | uintptr(unsafe.Pointer(i)),
45 | uintptr(unsafe.Pointer(&request)),
46 | )
47 | if err != windows.ERROR_SUCCESS {
48 | return nil, err
49 | }
50 | return request, nil
51 | }
52 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2WebResourceRequestedEventHandler.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type _ICoreWebView2WebResourceRequestedEventHandlerVtbl struct {
4 | _IUnknownVtbl
5 | Invoke ComProc
6 | }
7 |
8 | type iCoreWebView2WebResourceRequestedEventHandler struct {
9 | vtbl *_ICoreWebView2WebResourceRequestedEventHandlerVtbl
10 | impl _ICoreWebView2WebResourceRequestedEventHandlerImpl
11 | }
12 |
13 | func _ICoreWebView2WebResourceRequestedEventHandlerIUnknownQueryInterface(this *iCoreWebView2WebResourceRequestedEventHandler, refiid, object uintptr) uintptr {
14 | return this.impl.QueryInterface(refiid, object)
15 | }
16 |
17 | func _ICoreWebView2WebResourceRequestedEventHandlerIUnknownAddRef(this *iCoreWebView2WebResourceRequestedEventHandler) uintptr {
18 | return this.impl.AddRef()
19 | }
20 |
21 | func _ICoreWebView2WebResourceRequestedEventHandlerIUnknownRelease(this *iCoreWebView2WebResourceRequestedEventHandler) uintptr {
22 | return this.impl.Release()
23 | }
24 |
25 | func _ICoreWebView2WebResourceRequestedEventHandlerInvoke(this *iCoreWebView2WebResourceRequestedEventHandler, sender *ICoreWebView2, args *ICoreWebView2WebResourceRequestedEventArgs) uintptr {
26 | return this.impl.WebResourceRequested(sender, args)
27 | }
28 |
29 | type _ICoreWebView2WebResourceRequestedEventHandlerImpl interface {
30 | _IUnknownImpl
31 | WebResourceRequested(sender *ICoreWebView2, args *ICoreWebView2WebResourceRequestedEventArgs) uintptr
32 | }
33 |
34 | var _ICoreWebView2WebResourceRequestedEventHandlerFn = _ICoreWebView2WebResourceRequestedEventHandlerVtbl{
35 | _IUnknownVtbl{
36 | NewComProc(_ICoreWebView2WebResourceRequestedEventHandlerIUnknownQueryInterface),
37 | NewComProc(_ICoreWebView2WebResourceRequestedEventHandlerIUnknownAddRef),
38 | NewComProc(_ICoreWebView2WebResourceRequestedEventHandlerIUnknownRelease),
39 | },
40 | NewComProc(_ICoreWebView2WebResourceRequestedEventHandlerInvoke),
41 | }
42 |
43 | func newICoreWebView2WebResourceRequestedEventHandler(impl _ICoreWebView2WebResourceRequestedEventHandlerImpl) *iCoreWebView2WebResourceRequestedEventHandler {
44 | return &iCoreWebView2WebResourceRequestedEventHandler{
45 | vtbl: &_ICoreWebView2WebResourceRequestedEventHandlerFn,
46 | impl: impl,
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2WebResourceResponse.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type _ICoreWebView2WebResourceResponseVtbl struct {
4 | _IUnknownVtbl
5 | GetContent ComProc
6 | PutContent ComProc
7 | GetHeaders ComProc
8 | GetStatusCode ComProc
9 | PutStatusCode ComProc
10 | GetReasonPhrase ComProc
11 | PutReasonPhrase ComProc
12 | }
13 |
14 | type ICoreWebView2WebResourceResponse struct {
15 | vtbl *_ICoreWebView2WebResourceResponseVtbl
16 | }
17 |
18 | func (i *ICoreWebView2WebResourceResponse) AddRef() uintptr {
19 | r, _, _ := i.vtbl.AddRef.Call()
20 | return r
21 | }
22 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2_2.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | type iCoreWebView2_2Vtbl struct {
4 | iCoreWebView2Vtbl
5 | AddWebResourceResponseReceived ComProc
6 | RemoveWebResourceResponseReceived ComProc
7 | NavigateWithWebResourceRequest ComProc
8 | AddDomContentLoaded ComProc
9 | RemoveDomContentLoaded ComProc
10 | GetCookieManager ComProc
11 | GetEnvironment ComProc
12 | }
13 |
14 | type ICoreWebView2_2 struct {
15 | vtbl *iCoreWebView2_2Vtbl
16 | }
17 |
18 | func (i *ICoreWebView2_2) AddRef() uintptr {
19 | r, _, _ := i.vtbl.AddRef.Call()
20 | return r
21 | }
22 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebView2_3.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "unsafe"
5 |
6 | "golang.org/x/sys/windows"
7 | )
8 |
9 | type iCoreWebView2_3Vtbl struct {
10 | iCoreWebView2_2Vtbl
11 | TrySuspend ComProc
12 | Resume ComProc
13 | GetIsSuspended ComProc
14 | SetVirtualHostNameToFolderMapping ComProc
15 | ClearVirtualHostNameToFolderMapping ComProc
16 | }
17 |
18 | type ICoreWebView2_3 struct {
19 | vtbl *iCoreWebView2_3Vtbl
20 | }
21 |
22 | func (i *ICoreWebView2_3) SetVirtualHostNameToFolderMapping(hostName, folderPath string, accessKind COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND) error {
23 | _hostName, err := windows.UTF16PtrFromString(hostName)
24 | if err != nil {
25 | return err
26 | }
27 |
28 | _folderPath, err := windows.UTF16PtrFromString(folderPath)
29 | if err != nil {
30 | return err
31 | }
32 |
33 | _, _, err = i.vtbl.SetVirtualHostNameToFolderMapping.Call(
34 | uintptr(unsafe.Pointer(i)),
35 | uintptr(unsafe.Pointer(_hostName)),
36 | uintptr(unsafe.Pointer(_folderPath)),
37 | uintptr(accessKind),
38 | )
39 | if err != windows.ERROR_SUCCESS {
40 | return err
41 | }
42 |
43 | return nil
44 | }
45 |
46 | func (i *ICoreWebView2) GetICoreWebView2_3() *ICoreWebView2_3 {
47 | var result *ICoreWebView2_3
48 |
49 | iidICoreWebView2_3 := NewGUID("{A0D6DF20-3B92-416D-AA0C-437A9C727857}")
50 | _, _, _ = i.vtbl.QueryInterface.Call(
51 | uintptr(unsafe.Pointer(i)),
52 | uintptr(unsafe.Pointer(iidICoreWebView2_3)),
53 | uintptr(unsafe.Pointer(&result)))
54 |
55 | return result
56 | }
57 |
58 | func (e *Chromium) GetICoreWebView2_3() *ICoreWebView2_3 {
59 | return e.webview.GetICoreWebView2_3()
60 | }
61 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/ICoreWebViewSettings.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "unsafe"
5 |
6 | "golang.org/x/sys/windows"
7 | )
8 |
9 | // ICoreWebviewSettings is the merged settings class
10 |
11 | type _ICoreWebViewSettingsVtbl struct {
12 | _IUnknownVtbl
13 | GetIsScriptEnabled ComProc
14 | PutIsScriptEnabled ComProc
15 | GetIsWebMessageEnabled ComProc
16 | PutIsWebMessageEnabled ComProc
17 | GetAreDefaultScriptDialogsEnabled ComProc
18 | PutAreDefaultScriptDialogsEnabled ComProc
19 | GetIsStatusBarEnabled ComProc
20 | PutIsStatusBarEnabled ComProc
21 | GetAreDevToolsEnabled ComProc
22 | PutAreDevToolsEnabled ComProc
23 | GetAreDefaultContextMenusEnabled ComProc
24 | PutAreDefaultContextMenusEnabled ComProc
25 | GetAreHostObjectsAllowed ComProc
26 | PutAreHostObjectsAllowed ComProc
27 | GetIsZoomControlEnabled ComProc
28 | PutIsZoomControlEnabled ComProc
29 | GetIsBuiltInErrorPageEnabled ComProc
30 | PutIsBuiltInErrorPageEnabled ComProc
31 | GetUserAgent ComProc
32 | PutUserAgent ComProc
33 | GetAreBrowserAcceleratorKeysEnabled ComProc
34 | PutAreBrowserAcceleratorKeysEnabled ComProc
35 | GetIsPasswordAutosaveEnabled ComProc
36 | PutIsPasswordAutosaveEnabled ComProc
37 | GetIsGeneralAutofillEnabled ComProc
38 | PutIsGeneralAutofillEnabled ComProc
39 | GetIsPinchZoomEnabled ComProc
40 | PutIsPinchZoomEnabled ComProc
41 | GetIsSwipeNavigationEnabled ComProc
42 | PutIsSwipeNavigationEnabled ComProc
43 | }
44 |
45 | type ICoreWebViewSettings struct {
46 | vtbl *_ICoreWebViewSettingsVtbl
47 | }
48 |
49 | func (i *ICoreWebViewSettings) AddRef() uintptr {
50 | r, _, _ := i.vtbl.AddRef.Call()
51 | return r
52 | }
53 |
54 | func (i *ICoreWebViewSettings) GetIsScriptEnabled() (bool, error) {
55 | var err error
56 | var isScriptEnabled bool
57 | _, _, err = i.vtbl.GetIsScriptEnabled.Call(
58 | uintptr(unsafe.Pointer(i)),
59 | uintptr(unsafe.Pointer(&isScriptEnabled)),
60 | )
61 | if err != windows.ERROR_SUCCESS {
62 | return false, err
63 | }
64 | return isScriptEnabled, nil
65 | }
66 |
67 | func (i *ICoreWebViewSettings) PutIsScriptEnabled(isScriptEnabled bool) error {
68 | var err error
69 |
70 | _, _, err = i.vtbl.PutIsScriptEnabled.Call(
71 | uintptr(unsafe.Pointer(i)),
72 | uintptr(boolToInt(isScriptEnabled)),
73 | )
74 | if err != windows.ERROR_SUCCESS {
75 | return err
76 | }
77 | return nil
78 | }
79 |
80 | func (i *ICoreWebViewSettings) GetIsWebMessageEnabled() (bool, error) {
81 | var err error
82 | var isWebMessageEnabled bool
83 | _, _, err = i.vtbl.GetIsWebMessageEnabled.Call(
84 | uintptr(unsafe.Pointer(i)),
85 | uintptr(unsafe.Pointer(&isWebMessageEnabled)),
86 | )
87 | if err != windows.ERROR_SUCCESS {
88 | return false, err
89 | }
90 | return isWebMessageEnabled, nil
91 | }
92 |
93 | func (i *ICoreWebViewSettings) PutIsWebMessageEnabled(isWebMessageEnabled bool) error {
94 | var err error
95 |
96 | _, _, err = i.vtbl.PutIsWebMessageEnabled.Call(
97 | uintptr(unsafe.Pointer(i)),
98 | uintptr(boolToInt(isWebMessageEnabled)),
99 | )
100 | if err != windows.ERROR_SUCCESS {
101 | return err
102 | }
103 | return nil
104 | }
105 |
106 | func (i *ICoreWebViewSettings) GetAreDefaultScriptDialogsEnabled() (bool, error) {
107 | var err error
108 | var areDefaultScriptDialogsEnabled bool
109 | _, _, err = i.vtbl.GetAreDefaultScriptDialogsEnabled.Call(
110 | uintptr(unsafe.Pointer(i)),
111 | uintptr(unsafe.Pointer(&areDefaultScriptDialogsEnabled)),
112 | )
113 | if err != windows.ERROR_SUCCESS {
114 | return false, err
115 | }
116 | return areDefaultScriptDialogsEnabled, nil
117 | }
118 |
119 | func (i *ICoreWebViewSettings) PutAreDefaultScriptDialogsEnabled(areDefaultScriptDialogsEnabled bool) error {
120 | var err error
121 |
122 | _, _, err = i.vtbl.PutAreDefaultScriptDialogsEnabled.Call(
123 | uintptr(unsafe.Pointer(i)),
124 | uintptr(boolToInt(areDefaultScriptDialogsEnabled)),
125 | )
126 | if err != windows.ERROR_SUCCESS {
127 | return err
128 | }
129 | return nil
130 | }
131 |
132 | func (i *ICoreWebViewSettings) GetIsStatusBarEnabled() (bool, error) {
133 | var err error
134 | var isStatusBarEnabled bool
135 | _, _, err = i.vtbl.GetIsStatusBarEnabled.Call(
136 | uintptr(unsafe.Pointer(i)),
137 | uintptr(unsafe.Pointer(&isStatusBarEnabled)),
138 | )
139 | if err != windows.ERROR_SUCCESS {
140 | return false, err
141 | }
142 | return isStatusBarEnabled, nil
143 | }
144 |
145 | func (i *ICoreWebViewSettings) PutIsStatusBarEnabled(isStatusBarEnabled bool) error {
146 | var err error
147 |
148 | _, _, err = i.vtbl.PutIsStatusBarEnabled.Call(
149 | uintptr(unsafe.Pointer(i)),
150 | uintptr(boolToInt(isStatusBarEnabled)),
151 | )
152 | if err != windows.ERROR_SUCCESS {
153 | return err
154 | }
155 | return nil
156 | }
157 |
158 | func (i *ICoreWebViewSettings) GetAreDevToolsEnabled() (bool, error) {
159 | var err error
160 | var areDevToolsEnabled bool
161 | _, _, err = i.vtbl.GetAreDevToolsEnabled.Call(
162 | uintptr(unsafe.Pointer(i)),
163 | uintptr(unsafe.Pointer(&areDevToolsEnabled)),
164 | )
165 | if err != windows.ERROR_SUCCESS {
166 | return false, err
167 | }
168 | return areDevToolsEnabled, nil
169 | }
170 |
171 | func (i *ICoreWebViewSettings) PutAreDevToolsEnabled(areDevToolsEnabled bool) error {
172 | var err error
173 | _, _, err = i.vtbl.PutAreDevToolsEnabled.Call(
174 | uintptr(unsafe.Pointer(i)),
175 | uintptr(boolToInt(areDevToolsEnabled)),
176 | )
177 | if err != windows.ERROR_SUCCESS {
178 | return err
179 | }
180 | return nil
181 | }
182 |
183 | func (i *ICoreWebViewSettings) GetAreDefaultContextMenusEnabled() (bool, error) {
184 | var err error
185 | var enabled bool
186 | _, _, err = i.vtbl.GetAreDefaultContextMenusEnabled.Call(
187 | uintptr(unsafe.Pointer(i)),
188 | uintptr(unsafe.Pointer(&enabled)),
189 | )
190 | if err != windows.ERROR_SUCCESS {
191 | return false, err
192 | }
193 | return enabled, nil
194 | }
195 |
196 | func (i *ICoreWebViewSettings) PutAreDefaultContextMenusEnabled(enabled bool) error {
197 | var err error
198 | _, _, err = i.vtbl.PutAreDefaultContextMenusEnabled.Call(
199 | uintptr(unsafe.Pointer(i)),
200 | uintptr(boolToInt(enabled)),
201 | )
202 | if err != windows.ERROR_SUCCESS {
203 | return err
204 | }
205 | return nil
206 | }
207 |
208 | func (i *ICoreWebViewSettings) GetAreHostObjectsAllowed() (bool, error) {
209 | var err error
210 | var allowed bool
211 | _, _, err = i.vtbl.GetAreHostObjectsAllowed.Call(
212 | uintptr(unsafe.Pointer(i)),
213 | uintptr(unsafe.Pointer(&allowed)),
214 | )
215 | if err != windows.ERROR_SUCCESS {
216 | return false, err
217 | }
218 | return allowed, nil
219 | }
220 |
221 | func (i *ICoreWebViewSettings) PutAreHostObjectsAllowed(allowed bool) error {
222 | var err error
223 |
224 | _, _, err = i.vtbl.PutAreHostObjectsAllowed.Call(
225 | uintptr(unsafe.Pointer(i)),
226 | uintptr(boolToInt(allowed)),
227 | )
228 | if err != windows.ERROR_SUCCESS {
229 | return err
230 | }
231 | return nil
232 | }
233 |
234 | func (i *ICoreWebViewSettings) GetIsZoomControlEnabled() (bool, error) {
235 | var err error
236 | var enabled bool
237 | _, _, err = i.vtbl.GetIsZoomControlEnabled.Call(
238 | uintptr(unsafe.Pointer(i)),
239 | uintptr(unsafe.Pointer(&enabled)),
240 | )
241 | if err != windows.ERROR_SUCCESS {
242 | return false, err
243 | }
244 | return enabled, nil
245 | }
246 |
247 | func (i *ICoreWebViewSettings) PutIsZoomControlEnabled(enabled bool) error {
248 | var err error
249 |
250 | _, _, err = i.vtbl.PutIsZoomControlEnabled.Call(
251 | uintptr(unsafe.Pointer(i)),
252 | uintptr(boolToInt(enabled)),
253 | )
254 | if err != windows.ERROR_SUCCESS {
255 | return err
256 | }
257 | return nil
258 | }
259 |
260 | func (i *ICoreWebViewSettings) GetIsBuiltInErrorPageEnabled() (bool, error) {
261 | var err error
262 | var enabled bool
263 | _, _, err = i.vtbl.GetIsBuiltInErrorPageEnabled.Call(
264 | uintptr(unsafe.Pointer(i)),
265 | uintptr(unsafe.Pointer(&enabled)),
266 | )
267 | if err != windows.ERROR_SUCCESS {
268 | return false, err
269 | }
270 | return enabled, nil
271 | }
272 |
273 | func (i *ICoreWebViewSettings) PutIsBuiltInErrorPageEnabled(enabled bool) error {
274 | var err error
275 |
276 | _, _, err = i.vtbl.PutIsBuiltInErrorPageEnabled.Call(
277 | uintptr(unsafe.Pointer(i)),
278 | uintptr(boolToInt(enabled)),
279 | )
280 | if err != windows.ERROR_SUCCESS {
281 | return err
282 | }
283 | return nil
284 | }
285 |
286 | func (i *ICoreWebViewSettings) GetUserAgent() (string, error) {
287 | var err error
288 | // Create *uint16 to hold result
289 | var _userAgent *uint16
290 | _, _, err = i.vtbl.GetUserAgent.Call(
291 | uintptr(unsafe.Pointer(i)),
292 | uintptr(unsafe.Pointer(_userAgent)),
293 | )
294 | if err != windows.ERROR_SUCCESS {
295 | return "", err
296 | } // Get result and cleanup
297 | userAgent := windows.UTF16PtrToString(_userAgent)
298 | windows.CoTaskMemFree(unsafe.Pointer(_userAgent))
299 | return userAgent, nil
300 | }
301 |
302 | func (i *ICoreWebViewSettings) PutUserAgent(userAgent string) error {
303 | var err error
304 | // Convert string 'userAgent' to *uint16
305 | _userAgent, err := windows.UTF16PtrFromString(userAgent)
306 | if err != nil {
307 | return err
308 | }
309 |
310 | _, _, err = i.vtbl.PutUserAgent.Call(
311 | uintptr(unsafe.Pointer(i)),
312 | uintptr(unsafe.Pointer(_userAgent)),
313 | )
314 | if err != windows.ERROR_SUCCESS {
315 | return err
316 | }
317 | return nil
318 | }
319 |
320 | func (i *ICoreWebViewSettings) GetAreBrowserAcceleratorKeysEnabled() (bool, error) {
321 | var err error
322 | var enabled bool
323 | _, _, err = i.vtbl.GetAreBrowserAcceleratorKeysEnabled.Call(
324 | uintptr(unsafe.Pointer(i)),
325 | uintptr(unsafe.Pointer(&enabled)),
326 | )
327 | if err != windows.ERROR_SUCCESS {
328 | return false, err
329 | }
330 | return enabled, nil
331 | }
332 |
333 | func (i *ICoreWebViewSettings) PutAreBrowserAcceleratorKeysEnabled(enabled bool) error {
334 | var err error
335 |
336 | _, _, err = i.vtbl.PutAreBrowserAcceleratorKeysEnabled.Call(
337 | uintptr(unsafe.Pointer(i)),
338 | uintptr(boolToInt(enabled)),
339 | )
340 | if err != windows.ERROR_SUCCESS {
341 | return err
342 | }
343 | return nil
344 | }
345 |
346 | func (i *ICoreWebViewSettings) GetIsPinchZoomEnabled() (bool, error) {
347 | var err error
348 | var enabled bool
349 | _, _, err = i.vtbl.GetIsPinchZoomEnabled.Call(
350 | uintptr(unsafe.Pointer(i)),
351 | uintptr(unsafe.Pointer(&enabled)),
352 | )
353 | if err != windows.ERROR_SUCCESS {
354 | return false, err
355 | }
356 | return enabled, nil
357 | }
358 |
359 | func (i *ICoreWebViewSettings) PutIsPinchZoomEnabled(enabled bool) error {
360 | var err error
361 |
362 | _, _, err = i.vtbl.PutIsPinchZoomEnabled.Call(
363 | uintptr(unsafe.Pointer(i)),
364 | uintptr(boolToInt(enabled)),
365 | )
366 | if err != windows.ERROR_SUCCESS {
367 | return err
368 | }
369 | return nil
370 | }
371 |
372 | func (i *ICoreWebViewSettings) GetIsSwipeNavigationEnabled() (bool, error) {
373 | var err error
374 | var enabled bool
375 | _, _, err = i.vtbl.GetIsSwipeNavigationEnabled.Call(
376 | uintptr(unsafe.Pointer(i)),
377 | uintptr(unsafe.Pointer(&enabled)),
378 | )
379 | if err != windows.ERROR_SUCCESS {
380 | return false, err
381 | }
382 | return enabled, nil
383 | }
384 |
385 | func (i *ICoreWebViewSettings) PutIsSwipeNavigationEnabled(enabled bool) error {
386 | var err error
387 |
388 | _, _, err = i.vtbl.PutIsSwipeNavigationEnabled.Call(
389 | uintptr(unsafe.Pointer(i)),
390 | uintptr(boolToInt(enabled)),
391 | )
392 | if err != windows.ERROR_SUCCESS {
393 | return err
394 | }
395 | return nil
396 | }
397 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/chromium.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package edge
5 |
6 | import (
7 | "log"
8 | "os"
9 | "path/filepath"
10 | "sync/atomic"
11 | "unsafe"
12 |
13 | "github.com/eyasliu/desktop/go-webview2/internal/w32"
14 | "github.com/eyasliu/desktop/go-webview2/webviewloader"
15 | "golang.org/x/sys/windows"
16 | )
17 |
18 | type Chromium struct {
19 | hwnd uintptr
20 | focusOnInit bool
21 | controller *ICoreWebView2Controller
22 | webview *ICoreWebView2
23 | inited uintptr
24 | envCompleted *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
25 | controllerCompleted *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler
26 | webMessageReceived *iCoreWebView2WebMessageReceivedEventHandler
27 | permissionRequested *iCoreWebView2PermissionRequestedEventHandler
28 | webResourceRequested *iCoreWebView2WebResourceRequestedEventHandler
29 | acceleratorKeyPressed *ICoreWebView2AcceleratorKeyPressedEventHandler
30 | navigationCompleted *ICoreWebView2NavigationCompletedEventHandler
31 |
32 | environment *ICoreWebView2Environment
33 |
34 | // Settings
35 | DataPath string
36 |
37 | // permissions
38 | permissions map[CoreWebView2PermissionKind]CoreWebView2PermissionState
39 | globalPermission *CoreWebView2PermissionState
40 |
41 | // Callbacks
42 | MessageCallback func(string)
43 | WebResourceRequestedCallback func(request *ICoreWebView2WebResourceRequest, args *ICoreWebView2WebResourceRequestedEventArgs)
44 | NavigationCompletedCallback []func(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs)
45 | AcceleratorKeyCallback func(uint) bool
46 |
47 | wv2Installed bool
48 | }
49 |
50 | func NewChromium() *Chromium {
51 | e := &Chromium{}
52 | /*
53 | All these handlers are passed to native code through syscalls with 'uintptr(unsafe.Pointer(handler))' and we know
54 | that a pointer to those will be kept in the native code. Furthermore these handlers als contain pointer to other Go
55 | structs like the vtable.
56 | This violates the unsafe.Pointer rule '(4) Conversion of a Pointer to a uintptr when calling syscall.Syscall.' because
57 | theres no guarantee that Go doesn't move these objects.
58 | AFAIK currently the Go runtime doesn't move HEAP objects, so we should be safe with these handlers. But they don't
59 | guarantee it, because in the future Go might use a compacting GC.
60 | There's a proposal to add a runtime.Pin function, to prevent moving pinned objects, which would allow to easily fix
61 | this issue by just pinning the handlers. The https://go-review.googlesource.com/c/go/+/367296/ should land in Go 1.19.
62 | */
63 | e.envCompleted = newICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler(e)
64 | e.controllerCompleted = newICoreWebView2CreateCoreWebView2ControllerCompletedHandler(e)
65 | e.webMessageReceived = newICoreWebView2WebMessageReceivedEventHandler(e)
66 | e.permissionRequested = newICoreWebView2PermissionRequestedEventHandler(e)
67 | e.webResourceRequested = newICoreWebView2WebResourceRequestedEventHandler(e)
68 | e.acceleratorKeyPressed = newICoreWebView2AcceleratorKeyPressedEventHandler(e)
69 | e.navigationCompleted = newICoreWebView2NavigationCompletedEventHandler(e)
70 | e.permissions = make(map[CoreWebView2PermissionKind]CoreWebView2PermissionState)
71 |
72 | return e
73 | }
74 |
75 | // CheckOrInstallWv2 检查是否安装 webview2 runtime,如果没装的话自动安装
76 | // 如果安装了或者自动安装成功了会返回 true
77 | func (e *Chromium) CheckOrInstallWv2() bool {
78 | if !e.wv2Installed {
79 | ver, err := webviewloader.GetInstalledVersion()
80 | if err != nil || ver == "" {
81 | if ok, err := webviewloader.InstallUsingBootstrapper(); !ok || err != nil {
82 | return false
83 | }
84 | }
85 | e.wv2Installed = true
86 | }
87 | return true
88 | }
89 |
90 | func (e *Chromium) Embed(hwnd uintptr) bool {
91 | e.hwnd = hwnd
92 |
93 | dataPath := e.DataPath
94 | if dataPath == "" {
95 | currentExePath := make([]uint16, windows.MAX_PATH)
96 | _, err := windows.GetModuleFileName(windows.Handle(0), ¤tExePath[0], windows.MAX_PATH)
97 | if err != nil {
98 | // What to do here?
99 | return false
100 | }
101 | currentExeName := filepath.Base(windows.UTF16ToString(currentExePath))
102 | dataPath = filepath.Join(os.Getenv("AppData"), currentExeName)
103 | }
104 |
105 | res, err := createCoreWebView2EnvironmentWithOptions(nil, windows.StringToUTF16Ptr(dataPath), 0, e.envCompleted)
106 | if err != nil {
107 | log.Printf("Error calling Webview2Loader: %v", err)
108 | return false
109 | } else if res != 0 {
110 | log.Printf("Result: %08x", res)
111 | return false
112 | }
113 | var msg w32.Msg
114 | for {
115 | if atomic.LoadUintptr(&e.inited) != 0 {
116 | break
117 | }
118 | r, _, _ := w32.User32GetMessageW.Call(
119 | uintptr(unsafe.Pointer(&msg)),
120 | 0,
121 | 0,
122 | 0,
123 | )
124 | if r == 0 {
125 | break
126 | }
127 | _, _, _ = w32.User32TranslateMessage.Call(uintptr(unsafe.Pointer(&msg)))
128 | _, _, _ = w32.User32DispatchMessageW.Call(uintptr(unsafe.Pointer(&msg)))
129 | }
130 | e.Init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}")
131 | return true
132 | }
133 |
134 | func (e *Chromium) Navigate(url string) error {
135 | _, _, err := e.webview.vtbl.Navigate.Call(
136 | uintptr(unsafe.Pointer(e.webview)),
137 | uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(url))),
138 | )
139 | return err
140 | }
141 |
142 | func (e *Chromium) NavigateToString(htmlContent string) {
143 | _, _, _ = e.webview.vtbl.NavigateToString.Call(
144 | uintptr(unsafe.Pointer(e.webview)),
145 | uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(htmlContent))),
146 | )
147 | }
148 |
149 | func (e *Chromium) Init(script string) {
150 | _, _, _ = e.webview.vtbl.AddScriptToExecuteOnDocumentCreated.Call(
151 | uintptr(unsafe.Pointer(e.webview)),
152 | uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(script))),
153 | 0,
154 | )
155 | }
156 |
157 | func (e *Chromium) Eval(script string) {
158 | _script, err := windows.UTF16PtrFromString(script)
159 | if err != nil {
160 | log.Fatal(err)
161 | }
162 | if e.webview == nil {
163 | return
164 | }
165 |
166 | _, _, _ = e.webview.vtbl.ExecuteScript.Call(
167 | uintptr(unsafe.Pointer(e.webview)),
168 | uintptr(unsafe.Pointer(_script)),
169 | 0,
170 | )
171 | }
172 |
173 | func (e *Chromium) Show() error {
174 | return e.controller.PutIsVisible(true)
175 | }
176 |
177 | func (e *Chromium) Hide() error {
178 | return e.controller.PutIsVisible(false)
179 | }
180 |
181 | func (e *Chromium) QueryInterface(_, _ uintptr) uintptr {
182 | return 0
183 | }
184 |
185 | func (e *Chromium) AddRef() uintptr {
186 | return 1
187 | }
188 |
189 | func (e *Chromium) Release() uintptr {
190 | return 1
191 | }
192 |
193 | func (e *Chromium) EnvironmentCompleted(res uintptr, env *ICoreWebView2Environment) uintptr {
194 | if int64(res) < 0 {
195 | log.Fatalf("Creating environment failed with %08x", res)
196 | }
197 | _, _, _ = env.vtbl.AddRef.Call(uintptr(unsafe.Pointer(env)))
198 | e.environment = env
199 |
200 | _, _, _ = env.vtbl.CreateCoreWebView2Controller.Call(
201 | uintptr(unsafe.Pointer(env)),
202 | e.hwnd,
203 | uintptr(unsafe.Pointer(e.controllerCompleted)),
204 | )
205 | return 0
206 | }
207 |
208 | func (e *Chromium) CreateCoreWebView2ControllerCompleted(res uintptr, controller *ICoreWebView2Controller) uintptr {
209 | if int64(res) < 0 {
210 | log.Fatalf("Creating controller failed with %08x", res)
211 | }
212 | _, _, _ = controller.vtbl.AddRef.Call(uintptr(unsafe.Pointer(controller)))
213 | e.controller = controller
214 |
215 | var token _EventRegistrationToken
216 | _, _, _ = controller.vtbl.GetCoreWebView2.Call(
217 | uintptr(unsafe.Pointer(controller)),
218 | uintptr(unsafe.Pointer(&e.webview)),
219 | )
220 | _, _, _ = e.webview.vtbl.AddRef.Call(
221 | uintptr(unsafe.Pointer(e.webview)),
222 | )
223 | _, _, _ = e.webview.vtbl.AddWebMessageReceived.Call(
224 | uintptr(unsafe.Pointer(e.webview)),
225 | uintptr(unsafe.Pointer(e.webMessageReceived)),
226 | uintptr(unsafe.Pointer(&token)),
227 | )
228 | _, _, _ = e.webview.vtbl.AddPermissionRequested.Call(
229 | uintptr(unsafe.Pointer(e.webview)),
230 | uintptr(unsafe.Pointer(e.permissionRequested)),
231 | uintptr(unsafe.Pointer(&token)),
232 | )
233 | _, _, _ = e.webview.vtbl.AddWebResourceRequested.Call(
234 | uintptr(unsafe.Pointer(e.webview)),
235 | uintptr(unsafe.Pointer(e.webResourceRequested)),
236 | uintptr(unsafe.Pointer(&token)),
237 | )
238 | _, _, _ = e.webview.vtbl.AddNavigationCompleted.Call(
239 | uintptr(unsafe.Pointer(e.webview)),
240 | uintptr(unsafe.Pointer(e.navigationCompleted)),
241 | uintptr(unsafe.Pointer(&token)),
242 | )
243 |
244 | _ = e.controller.AddAcceleratorKeyPressed(e.acceleratorKeyPressed, &token)
245 |
246 | atomic.StoreUintptr(&e.inited, 1)
247 |
248 | if e.focusOnInit {
249 | e.Focus()
250 | }
251 |
252 | return 0
253 | }
254 |
255 | func (e *Chromium) MessageReceived(sender *ICoreWebView2, args *iCoreWebView2WebMessageReceivedEventArgs) uintptr {
256 | var message *uint16
257 | _, _, _ = args.vtbl.TryGetWebMessageAsString.Call(
258 | uintptr(unsafe.Pointer(args)),
259 | uintptr(unsafe.Pointer(&message)),
260 | )
261 | if e.MessageCallback != nil {
262 | e.MessageCallback(w32.Utf16PtrToString(message))
263 | }
264 | _, _, _ = sender.vtbl.PostWebMessageAsString.Call(
265 | uintptr(unsafe.Pointer(sender)),
266 | uintptr(unsafe.Pointer(message)),
267 | )
268 | windows.CoTaskMemFree(unsafe.Pointer(message))
269 | return 0
270 | }
271 |
272 | func (e *Chromium) SetPermission(kind CoreWebView2PermissionKind, state CoreWebView2PermissionState) {
273 | e.permissions[kind] = state
274 | }
275 |
276 | func (e *Chromium) SetGlobalPermission(state CoreWebView2PermissionState) {
277 | e.globalPermission = &state
278 | }
279 |
280 | func (e *Chromium) PermissionRequested(_ *ICoreWebView2, args *iCoreWebView2PermissionRequestedEventArgs) uintptr {
281 | var kind CoreWebView2PermissionKind
282 | _, _, _ = args.vtbl.GetPermissionKind.Call(
283 | uintptr(unsafe.Pointer(args)),
284 | uintptr(kind),
285 | )
286 | var result CoreWebView2PermissionState
287 | if e.globalPermission != nil {
288 | result = *e.globalPermission
289 | } else {
290 | var ok bool
291 | result, ok = e.permissions[kind]
292 | if !ok {
293 | result = CoreWebView2PermissionStateDefault
294 | }
295 | }
296 | _, _, _ = args.vtbl.PutState.Call(
297 | uintptr(unsafe.Pointer(args)),
298 | uintptr(result),
299 | )
300 | return 0
301 | }
302 |
303 | func (e *Chromium) WebResourceRequested(sender *ICoreWebView2, args *ICoreWebView2WebResourceRequestedEventArgs) uintptr {
304 | req, err := args.GetRequest()
305 | if err != nil {
306 | log.Fatal(err)
307 | }
308 | if e.WebResourceRequestedCallback != nil {
309 | e.WebResourceRequestedCallback(req, args)
310 | }
311 | return 0
312 | }
313 |
314 | func (e *Chromium) AddWebResourceRequestedFilter(filter string, ctx COREWEBVIEW2_WEB_RESOURCE_CONTEXT) {
315 | err := e.webview.AddWebResourceRequestedFilter(filter, ctx)
316 | if err != nil {
317 | log.Fatal(err)
318 | }
319 | }
320 |
321 | func (e *Chromium) Environment() *ICoreWebView2Environment {
322 | return e.environment
323 | }
324 |
325 | // AcceleratorKeyPressed is called when an accelerator key is pressed.
326 | // If the AcceleratorKeyCallback method has been set, it will defer handling of the keypress
327 | // to the callback. That callback returns a bool indicating if the event was handled.
328 | func (e *Chromium) AcceleratorKeyPressed(sender *ICoreWebView2Controller, args *ICoreWebView2AcceleratorKeyPressedEventArgs) uintptr {
329 | if e.AcceleratorKeyCallback == nil {
330 | return 0
331 | }
332 | eventKind, _ := args.GetKeyEventKind()
333 | if eventKind == COREWEBVIEW2_KEY_EVENT_KIND_KEY_DOWN ||
334 | eventKind == COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_DOWN {
335 | virtualKey, _ := args.GetVirtualKey()
336 | status, _ := args.GetPhysicalKeyStatus()
337 | if !status.WasKeyDown {
338 | _ = args.PutHandled(e.AcceleratorKeyCallback(virtualKey))
339 | return 0
340 | }
341 | }
342 | _ = args.PutHandled(false)
343 | return 0
344 | }
345 |
346 | func (e *Chromium) GetSettings() (*ICoreWebViewSettings, error) {
347 | return e.webview.GetSettings()
348 | }
349 |
350 | func (e *Chromium) GetController() *ICoreWebView2Controller {
351 | return e.controller
352 | }
353 |
354 | func boolToInt(input bool) int {
355 | if input {
356 | return 1
357 | }
358 | return 0
359 | }
360 |
361 | func (e *Chromium) NavigationCompleted(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) uintptr {
362 | for _, h := range e.NavigationCompletedCallback {
363 | h(sender, args)
364 | }
365 | return 0
366 | }
367 |
368 | func (e *Chromium) NotifyParentWindowPositionChanged() error {
369 | //It looks like the wndproc function is called before the controller initialization is complete.
370 | //Because of this the controller is nil
371 | if e.controller == nil {
372 | return nil
373 | }
374 | return e.controller.NotifyParentWindowPositionChanged()
375 | }
376 |
377 | func (e *Chromium) Focus() {
378 | if e.controller == nil {
379 | e.focusOnInit = true
380 | return
381 | }
382 | _ = e.controller.MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC)
383 | }
384 |
385 | func (e *Chromium) PutIsBuiltInErrorPageEnabled(enable bool) error {
386 | wvSetting, err := e.GetSettings()
387 | if err != nil {
388 | return err
389 | }
390 | return wvSetting.PutIsBuiltInErrorPageEnabled(enable)
391 | }
392 |
393 | func (e *Chromium) OnNavigationCompleted(h func(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs)) {
394 | e.NavigationCompletedCallback = append(e.NavigationCompletedCallback, h)
395 | }
396 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/chromium_386.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package edge
5 |
6 | import (
7 | "github.com/eyasliu/desktop/go-webview2/internal/w32"
8 | "unsafe"
9 | )
10 |
11 | func (e *Chromium) Resize() {
12 | if e.controller == nil {
13 | return
14 | }
15 | var bounds w32.Rect
16 | w32.User32GetClientRect.Call(e.hwnd, uintptr(unsafe.Pointer(&bounds)))
17 | e.controller.vtbl.PutBounds.Call(
18 | uintptr(unsafe.Pointer(e.controller)),
19 | uintptr(bounds.Left),
20 | uintptr(bounds.Top),
21 | uintptr(bounds.Right),
22 | uintptr(bounds.Bottom),
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/chromium_amd64.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package edge
5 |
6 | import (
7 | "unsafe"
8 |
9 | "github.com/eyasliu/desktop/go-webview2/internal/w32"
10 | )
11 |
12 | func (e *Chromium) Resize() {
13 | if e.controller == nil {
14 | return
15 | }
16 | var bounds w32.Rect
17 | _, _, _ = w32.User32GetClientRect.Call(e.hwnd, uintptr(unsafe.Pointer(&bounds)))
18 | _, _, _ = e.controller.vtbl.PutBounds.Call(
19 | uintptr(unsafe.Pointer(e.controller)),
20 | uintptr(unsafe.Pointer(&bounds)),
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/chromium_arm64.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package edge
5 |
6 | import (
7 | "unsafe"
8 |
9 | "github.com/eyasliu/desktop/go-webview2/internal/w32"
10 | )
11 |
12 | func (e *Chromium) Resize() {
13 | if e.controller == nil {
14 | return
15 | }
16 |
17 | var bounds w32.Rect
18 | w32.User32GetClientRect.Call(e.hwnd, uintptr(unsafe.Pointer(&bounds)))
19 |
20 | words := (*[2]uintptr)(unsafe.Pointer(&bounds))
21 | e.controller.vtbl.PutBounds.Call(
22 | uintptr(unsafe.Pointer(e.controller)),
23 | words[0],
24 | words[1],
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/comproc.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | import (
4 | "golang.org/x/sys/windows"
5 | )
6 |
7 | // ComProc stores a COM procedure.
8 | type ComProc uintptr
9 |
10 | // NewComProc creates a new COM proc from a Go function.
11 | func NewComProc(fn interface{}) ComProc {
12 | return ComProc(windows.NewCallback(fn))
13 | }
14 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/comproc_go17.go:
--------------------------------------------------------------------------------
1 | //go:build !go1.18
2 | // +build !go1.18
3 |
4 | package edge
5 |
6 | import "syscall"
7 |
8 | //go:uintptrescapes
9 | // Call calls a COM procedure.
10 | func (p ComProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
11 | // The magic uintptrescapes comment is needed to prevent moving uintptr(unsafe.Pointer(p)) so calls to .Call() also
12 | // satisfy the unsafe.Pointer rule "(4) Conversion of a Pointer to a uintptr when calling syscall.Syscall."
13 | // Otherwise it might be that pointers get moved, especially pointer onto the Go stack which might grow dynamically.
14 | // See https://pkg.go.dev/unsafe#Pointer and https://github.com/golang/go/issues/34474
15 | switch len(a) {
16 | case 0:
17 | return syscall.Syscall(uintptr(p), 0, 0, 0, 0)
18 | case 1:
19 | return syscall.Syscall(uintptr(p), 1, a[0], 0, 0)
20 | case 2:
21 | return syscall.Syscall(uintptr(p), 2, a[0], a[1], 0)
22 | case 3:
23 | return syscall.Syscall(uintptr(p), 3, a[0], a[1], a[2])
24 | case 4:
25 | return syscall.Syscall6(uintptr(p), 4, a[0], a[1], a[2], a[3], 0, 0)
26 | case 5:
27 | return syscall.Syscall6(uintptr(p), 5, a[0], a[1], a[2], a[3], a[4], 0)
28 | case 6:
29 | return syscall.Syscall6(uintptr(p), 6, a[0], a[1], a[2], a[3], a[4], a[5])
30 | case 7:
31 | return syscall.Syscall9(uintptr(p), 7, a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0)
32 | case 8:
33 | return syscall.Syscall9(uintptr(p), 8, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0)
34 | case 9:
35 | return syscall.Syscall9(uintptr(p), 9, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8])
36 | case 10:
37 | return syscall.Syscall12(uintptr(p), 10, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0)
38 | case 11:
39 | return syscall.Syscall12(uintptr(p), 11, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0)
40 | case 12:
41 | return syscall.Syscall12(uintptr(p), 12, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])
42 | case 13:
43 | return syscall.Syscall15(uintptr(p), 13, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0)
44 | case 14:
45 | return syscall.Syscall15(uintptr(p), 14, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0)
46 | case 15:
47 | return syscall.Syscall15(uintptr(p), 15, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])
48 | default:
49 | panic("too many arguments")
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/comproc_go18.go:
--------------------------------------------------------------------------------
1 | //go:build go1.18
2 | // +build go1.18
3 |
4 | package edge
5 |
6 | import "syscall"
7 |
8 | //go:uintptrescapes
9 | // Call calls a COM procedure.
10 | func (p ComProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
11 | // The magic uintptrescapes comment is needed to prevent moving uintptr(unsafe.Pointer(p)) so calls to .Call() also
12 | // satisfy the unsafe.Pointer rule "(4) Conversion of a Pointer to a uintptr when calling syscall.Syscall."
13 | // Otherwise it might be that pointers get moved, especially pointer onto the Go stack which might grow dynamically.
14 | // See https://pkg.go.dev/unsafe#Pointer and https://github.com/golang/go/issues/34474
15 | return syscall.SyscallN(uintptr(p), a...)
16 | }
17 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/corewebview2.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package edge
5 |
6 | import (
7 | "log"
8 | "runtime"
9 | "unsafe"
10 |
11 | "github.com/eyasliu/desktop/go-webview2/internal/w32"
12 |
13 | "github.com/eyasliu/desktop/go-webview2/webviewloader"
14 | "golang.org/x/sys/windows"
15 | )
16 |
17 | func init() {
18 | runtime.LockOSThread()
19 |
20 | r, _, _ := w32.Ole32CoInitializeEx.Call(0, 2)
21 | if int(r) < 0 {
22 | log.Printf("Warning: CoInitializeEx call failed: E=%08x", r)
23 | }
24 | }
25 |
26 | type _EventRegistrationToken struct {
27 | Value int64
28 | }
29 |
30 | type CoreWebView2PermissionKind uint32
31 |
32 | const (
33 | CoreWebView2PermissionKindUnknownPermission CoreWebView2PermissionKind = iota
34 | CoreWebView2PermissionKindMicrophone
35 | CoreWebView2PermissionKindCamera
36 | CoreWebView2PermissionKindGeolocation
37 | CoreWebView2PermissionKindNotifications
38 | CoreWebView2PermissionKindOtherSensors
39 | CoreWebView2PermissionKindClipboardRead
40 | )
41 |
42 | type CoreWebView2PermissionState uint32
43 |
44 | const (
45 | CoreWebView2PermissionStateDefault CoreWebView2PermissionState = iota
46 | CoreWebView2PermissionStateAllow
47 | CoreWebView2PermissionStateDeny
48 | )
49 |
50 | func createCoreWebView2EnvironmentWithOptions(browserExecutableFolder, userDataFolder *uint16, environmentOptions uintptr, environmentCompletedHandle *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) (uintptr, error) {
51 | return webviewloader.CreateCoreWebView2EnvironmentWithOptions(
52 | browserExecutableFolder,
53 | userDataFolder,
54 | environmentOptions,
55 | uintptr(unsafe.Pointer(environmentCompletedHandle)),
56 | )
57 | }
58 |
59 | // IUnknown
60 |
61 | type _IUnknownVtbl struct {
62 | QueryInterface ComProc
63 | AddRef ComProc
64 | Release ComProc
65 | }
66 |
67 | func (i *_IUnknownVtbl) CallRelease(this unsafe.Pointer) error {
68 | _, _, err := i.Release.Call(
69 | uintptr(this),
70 | )
71 | if err != windows.ERROR_SUCCESS {
72 | return err
73 | }
74 | return nil
75 | }
76 |
77 | type _IUnknownImpl interface {
78 | QueryInterface(refiid, object uintptr) uintptr
79 | AddRef() uintptr
80 | Release() uintptr
81 | }
82 |
83 | // ICoreWebView2
84 |
85 | type iCoreWebView2Vtbl struct {
86 | _IUnknownVtbl
87 | GetSettings ComProc
88 | GetSource ComProc
89 | Navigate ComProc
90 | NavigateToString ComProc
91 | AddNavigationStarting ComProc
92 | RemoveNavigationStarting ComProc
93 | AddContentLoading ComProc
94 | RemoveContentLoading ComProc
95 | AddSourceChanged ComProc
96 | RemoveSourceChanged ComProc
97 | AddHistoryChanged ComProc
98 | RemoveHistoryChanged ComProc
99 | AddNavigationCompleted ComProc
100 | RemoveNavigationCompleted ComProc
101 | AddFrameNavigationStarting ComProc
102 | RemoveFrameNavigationStarting ComProc
103 | AddFrameNavigationCompleted ComProc
104 | RemoveFrameNavigationCompleted ComProc
105 | AddScriptDialogOpening ComProc
106 | RemoveScriptDialogOpening ComProc
107 | AddPermissionRequested ComProc
108 | RemovePermissionRequested ComProc
109 | AddProcessFailed ComProc
110 | RemoveProcessFailed ComProc
111 | AddScriptToExecuteOnDocumentCreated ComProc
112 | RemoveScriptToExecuteOnDocumentCreated ComProc
113 | ExecuteScript ComProc
114 | CapturePreview ComProc
115 | Reload ComProc
116 | PostWebMessageAsJSON ComProc
117 | PostWebMessageAsString ComProc
118 | AddWebMessageReceived ComProc
119 | RemoveWebMessageReceived ComProc
120 | CallDevToolsProtocolMethod ComProc
121 | GetBrowserProcessID ComProc
122 | GetCanGoBack ComProc
123 | GetCanGoForward ComProc
124 | GoBack ComProc
125 | GoForward ComProc
126 | GetDevToolsProtocolEventReceiver ComProc
127 | Stop ComProc
128 | AddNewWindowRequested ComProc
129 | RemoveNewWindowRequested ComProc
130 | AddDocumentTitleChanged ComProc
131 | RemoveDocumentTitleChanged ComProc
132 | GetDocumentTitle ComProc
133 | AddHostObjectToScript ComProc
134 | RemoveHostObjectFromScript ComProc
135 | OpenDevToolsWindow ComProc
136 | AddContainsFullScreenElementChanged ComProc
137 | RemoveContainsFullScreenElementChanged ComProc
138 | GetContainsFullScreenElement ComProc
139 | AddWebResourceRequested ComProc
140 | RemoveWebResourceRequested ComProc
141 | AddWebResourceRequestedFilter ComProc
142 | RemoveWebResourceRequestedFilter ComProc
143 | AddWindowCloseRequested ComProc
144 | RemoveWindowCloseRequested ComProc
145 | }
146 |
147 | type ICoreWebView2 struct {
148 | vtbl *iCoreWebView2Vtbl
149 | }
150 |
151 | func (i *ICoreWebView2) GetSettings() (*ICoreWebViewSettings, error) {
152 | var err error
153 | var settings *ICoreWebViewSettings
154 | _, _, err = i.vtbl.GetSettings.Call(
155 | uintptr(unsafe.Pointer(i)),
156 | uintptr(unsafe.Pointer(&settings)),
157 | )
158 | if err != windows.ERROR_SUCCESS {
159 | return nil, err
160 | }
161 | return settings, nil
162 | }
163 |
164 | // ICoreWebView2Environment
165 |
166 | type iCoreWebView2EnvironmentVtbl struct {
167 | _IUnknownVtbl
168 | CreateCoreWebView2Controller ComProc
169 | CreateWebResourceResponse ComProc
170 | GetBrowserVersionString ComProc
171 | AddNewBrowserVersionAvailable ComProc
172 | RemoveNewBrowserVersionAvailable ComProc
173 | }
174 |
175 | type ICoreWebView2Environment struct {
176 | vtbl *iCoreWebView2EnvironmentVtbl
177 | }
178 |
179 | func (e *ICoreWebView2Environment) CreateWebResourceResponse(content []byte, statusCode int, reasonPhrase string, headers string) (*ICoreWebView2WebResourceResponse, error) {
180 | var err error
181 | var stream uintptr
182 |
183 | if len(content) > 0 {
184 | // Create stream for response
185 | stream, err = w32.SHCreateMemStream(content)
186 | if err != nil {
187 | return nil, err
188 | }
189 | }
190 |
191 | // Convert string 'uri' to *uint16
192 | _reason, err := windows.UTF16PtrFromString(reasonPhrase)
193 | if err != nil {
194 | return nil, err
195 | }
196 | // Convert string 'uri' to *uint16
197 | _headers, err := windows.UTF16PtrFromString(headers)
198 | if err != nil {
199 | return nil, err
200 | }
201 | var response *ICoreWebView2WebResourceResponse
202 | _, _, err = e.vtbl.CreateWebResourceResponse.Call(
203 | uintptr(unsafe.Pointer(e)),
204 | stream,
205 | uintptr(statusCode),
206 | uintptr(unsafe.Pointer(_reason)),
207 | uintptr(unsafe.Pointer(_headers)),
208 | uintptr(unsafe.Pointer(&response)),
209 | )
210 | if err != windows.ERROR_SUCCESS {
211 | return nil, err
212 | }
213 | return response, nil
214 |
215 | }
216 |
217 | // ICoreWebView2WebMessageReceivedEventArgs
218 |
219 | type iCoreWebView2WebMessageReceivedEventArgsVtbl struct {
220 | _IUnknownVtbl
221 | GetSource ComProc
222 | GetWebMessageAsJSON ComProc
223 | TryGetWebMessageAsString ComProc
224 | }
225 |
226 | type iCoreWebView2WebMessageReceivedEventArgs struct {
227 | vtbl *iCoreWebView2WebMessageReceivedEventArgsVtbl
228 | }
229 |
230 | // ICoreWebView2PermissionRequestedEventArgs
231 |
232 | type iCoreWebView2PermissionRequestedEventArgsVtbl struct {
233 | _IUnknownVtbl
234 | GetURI ComProc
235 | GetPermissionKind ComProc
236 | GetIsUserInitiated ComProc
237 | GetState ComProc
238 | PutState ComProc
239 | GetDeferral ComProc
240 | }
241 |
242 | type iCoreWebView2PermissionRequestedEventArgs struct {
243 | vtbl *iCoreWebView2PermissionRequestedEventArgsVtbl
244 | }
245 |
246 | // ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
247 |
248 | type iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerImpl interface {
249 | _IUnknownImpl
250 | EnvironmentCompleted(res uintptr, env *ICoreWebView2Environment) uintptr
251 | }
252 |
253 | type iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl struct {
254 | _IUnknownVtbl
255 | Invoke ComProc
256 | }
257 |
258 | type iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler struct {
259 | vtbl *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl
260 | impl iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerImpl
261 | }
262 |
263 | func _ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownQueryInterface(this *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, refiid, object uintptr) uintptr {
264 | return this.impl.QueryInterface(refiid, object)
265 | }
266 |
267 | func _ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownAddRef(this *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) uintptr {
268 | return this.impl.AddRef()
269 | }
270 |
271 | func _ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownRelease(this *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) uintptr {
272 | return this.impl.Release()
273 | }
274 |
275 | func _ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerInvoke(this *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, res uintptr, env *ICoreWebView2Environment) uintptr {
276 | return this.impl.EnvironmentCompleted(res, env)
277 | }
278 |
279 | var iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerFn = iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl{
280 | _IUnknownVtbl{
281 | NewComProc(_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownQueryInterface),
282 | NewComProc(_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownAddRef),
283 | NewComProc(_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownRelease),
284 | },
285 | NewComProc(_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerInvoke),
286 | }
287 |
288 | func newICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler(impl iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerImpl) *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler {
289 | return &iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler{
290 | vtbl: &iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerFn,
291 | impl: impl,
292 | }
293 | }
294 |
295 | // ICoreWebView2WebMessageReceivedEventHandler
296 |
297 | type iCoreWebView2WebMessageReceivedEventHandlerImpl interface {
298 | _IUnknownImpl
299 | MessageReceived(sender *ICoreWebView2, args *iCoreWebView2WebMessageReceivedEventArgs) uintptr
300 | }
301 |
302 | type iCoreWebView2WebMessageReceivedEventHandlerVtbl struct {
303 | _IUnknownVtbl
304 | Invoke ComProc
305 | }
306 |
307 | type iCoreWebView2WebMessageReceivedEventHandler struct {
308 | vtbl *iCoreWebView2WebMessageReceivedEventHandlerVtbl
309 | impl iCoreWebView2WebMessageReceivedEventHandlerImpl
310 | }
311 |
312 | func _ICoreWebView2WebMessageReceivedEventHandlerIUnknownQueryInterface(this *iCoreWebView2WebMessageReceivedEventHandler, refiid, object uintptr) uintptr {
313 | return this.impl.QueryInterface(refiid, object)
314 | }
315 |
316 | func _ICoreWebView2WebMessageReceivedEventHandlerIUnknownAddRef(this *iCoreWebView2WebMessageReceivedEventHandler) uintptr {
317 | return this.impl.AddRef()
318 | }
319 |
320 | func _ICoreWebView2WebMessageReceivedEventHandlerIUnknownRelease(this *iCoreWebView2WebMessageReceivedEventHandler) uintptr {
321 | return this.impl.Release()
322 | }
323 |
324 | func _ICoreWebView2WebMessageReceivedEventHandlerInvoke(this *iCoreWebView2WebMessageReceivedEventHandler, sender *ICoreWebView2, args *iCoreWebView2WebMessageReceivedEventArgs) uintptr {
325 | return this.impl.MessageReceived(sender, args)
326 | }
327 |
328 | var iCoreWebView2WebMessageReceivedEventHandlerFn = iCoreWebView2WebMessageReceivedEventHandlerVtbl{
329 | _IUnknownVtbl{
330 | NewComProc(_ICoreWebView2WebMessageReceivedEventHandlerIUnknownQueryInterface),
331 | NewComProc(_ICoreWebView2WebMessageReceivedEventHandlerIUnknownAddRef),
332 | NewComProc(_ICoreWebView2WebMessageReceivedEventHandlerIUnknownRelease),
333 | },
334 | NewComProc(_ICoreWebView2WebMessageReceivedEventHandlerInvoke),
335 | }
336 |
337 | func newICoreWebView2WebMessageReceivedEventHandler(impl iCoreWebView2WebMessageReceivedEventHandlerImpl) *iCoreWebView2WebMessageReceivedEventHandler {
338 | return &iCoreWebView2WebMessageReceivedEventHandler{
339 | vtbl: &iCoreWebView2WebMessageReceivedEventHandlerFn,
340 | impl: impl,
341 | }
342 | }
343 |
344 | // ICoreWebView2PermissionRequestedEventHandler
345 |
346 | type iCoreWebView2PermissionRequestedEventHandlerImpl interface {
347 | _IUnknownImpl
348 | PermissionRequested(sender *ICoreWebView2, args *iCoreWebView2PermissionRequestedEventArgs) uintptr
349 | }
350 |
351 | type iCoreWebView2PermissionRequestedEventHandlerVtbl struct {
352 | _IUnknownVtbl
353 | Invoke ComProc
354 | }
355 |
356 | type iCoreWebView2PermissionRequestedEventHandler struct {
357 | vtbl *iCoreWebView2PermissionRequestedEventHandlerVtbl
358 | impl iCoreWebView2PermissionRequestedEventHandlerImpl
359 | }
360 |
361 | func _ICoreWebView2PermissionRequestedEventHandlerIUnknownQueryInterface(this *iCoreWebView2PermissionRequestedEventHandler, refiid, object uintptr) uintptr {
362 | return this.impl.QueryInterface(refiid, object)
363 | }
364 |
365 | func _ICoreWebView2PermissionRequestedEventHandlerIUnknownAddRef(this *iCoreWebView2PermissionRequestedEventHandler) uintptr {
366 | return this.impl.AddRef()
367 | }
368 |
369 | func _ICoreWebView2PermissionRequestedEventHandlerIUnknownRelease(this *iCoreWebView2PermissionRequestedEventHandler) uintptr {
370 | return this.impl.Release()
371 | }
372 |
373 | func _ICoreWebView2PermissionRequestedEventHandlerInvoke(this *iCoreWebView2PermissionRequestedEventHandler, sender *ICoreWebView2, args *iCoreWebView2PermissionRequestedEventArgs) uintptr {
374 | return this.impl.PermissionRequested(sender, args)
375 | }
376 |
377 | var iCoreWebView2PermissionRequestedEventHandlerFn = iCoreWebView2PermissionRequestedEventHandlerVtbl{
378 | _IUnknownVtbl{
379 | NewComProc(_ICoreWebView2PermissionRequestedEventHandlerIUnknownQueryInterface),
380 | NewComProc(_ICoreWebView2PermissionRequestedEventHandlerIUnknownAddRef),
381 | NewComProc(_ICoreWebView2PermissionRequestedEventHandlerIUnknownRelease),
382 | },
383 | NewComProc(_ICoreWebView2PermissionRequestedEventHandlerInvoke),
384 | }
385 |
386 | func newICoreWebView2PermissionRequestedEventHandler(impl iCoreWebView2PermissionRequestedEventHandlerImpl) *iCoreWebView2PermissionRequestedEventHandler {
387 | return &iCoreWebView2PermissionRequestedEventHandler{
388 | vtbl: &iCoreWebView2PermissionRequestedEventHandlerFn,
389 | impl: impl,
390 | }
391 | }
392 |
393 | func (i *ICoreWebView2) AddWebResourceRequestedFilter(uri string, resourceContext COREWEBVIEW2_WEB_RESOURCE_CONTEXT) error {
394 | var err error
395 | // Convert string 'uri' to *uint16
396 | _uri, err := windows.UTF16PtrFromString(uri)
397 | if err != nil {
398 | return err
399 | }
400 | _, _, err = i.vtbl.AddWebResourceRequestedFilter.Call(
401 | uintptr(unsafe.Pointer(i)),
402 | uintptr(unsafe.Pointer(_uri)),
403 | uintptr(resourceContext),
404 | )
405 | if err != windows.ERROR_SUCCESS {
406 | return err
407 | }
408 | return nil
409 | }
410 | func (i *ICoreWebView2) AddNavigationCompleted(eventHandler *ICoreWebView2NavigationCompletedEventHandler, token *_EventRegistrationToken) error {
411 | var err error
412 | _, _, err = i.vtbl.AddNavigationCompleted.Call(
413 | uintptr(unsafe.Pointer(i)),
414 | uintptr(unsafe.Pointer(eventHandler)),
415 | uintptr(unsafe.Pointer(&token)),
416 | )
417 | if err != windows.ERROR_SUCCESS {
418 | return err
419 | }
420 | return nil
421 | }
422 |
--------------------------------------------------------------------------------
/go-webview2/pkg/edge/guid.go:
--------------------------------------------------------------------------------
1 | package edge
2 |
3 | // This code has been adapted from: https://github.com/go-ole/go-ole
4 |
5 | /*
6 |
7 | The MIT License (MIT)
8 |
9 | Copyright © 2013-2017 Yasuhiro Matsumoto,
10 |
11 | Permission is hereby granted, free of charge, to any person obtaining a copy of
12 | this software and associated documentation files (the “Software”), to deal in
13 | the Software without restriction, including without limitation the rights to
14 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
15 | of the Software, and to permit persons to whom the Software is furnished to do
16 | so, subject to the following conditions:
17 |
18 | The above copyright notice and this permission notice shall be included in all
19 | copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 | SOFTWARE.
28 |
29 | */
30 |
31 | const hextable = "0123456789ABCDEF"
32 | const emptyGUID = "{00000000-0000-0000-0000-000000000000}"
33 |
34 | // GUID is Windows API specific GUID type.
35 | //
36 | // This exists to match Windows GUID type for direct passing for COM.
37 | // Format is in xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx.
38 | type GUID struct {
39 | Data1 uint32
40 | Data2 uint16
41 | Data3 uint16
42 | Data4 [8]byte
43 | }
44 |
45 | // NewGUID converts the given string into a globally unique identifier that is
46 | // compliant with the Windows API.
47 | //
48 | // The supplied string may be in any of these formats:
49 | //
50 | // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
51 | // XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
52 | // {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
53 | //
54 | // The conversion of the supplied string is not case-sensitive.
55 | func NewGUID(guid string) *GUID {
56 | d := []byte(guid)
57 | var d1, d2, d3, d4a, d4b []byte
58 |
59 | switch len(d) {
60 | case 38:
61 | if d[0] != '{' || d[37] != '}' {
62 | return nil
63 | }
64 | d = d[1:37]
65 | fallthrough
66 | case 36:
67 | if d[8] != '-' || d[13] != '-' || d[18] != '-' || d[23] != '-' {
68 | return nil
69 | }
70 | d1 = d[0:8]
71 | d2 = d[9:13]
72 | d3 = d[14:18]
73 | d4a = d[19:23]
74 | d4b = d[24:36]
75 | case 32:
76 | d1 = d[0:8]
77 | d2 = d[8:12]
78 | d3 = d[12:16]
79 | d4a = d[16:20]
80 | d4b = d[20:32]
81 | default:
82 | return nil
83 | }
84 |
85 | var g GUID
86 | var ok1, ok2, ok3, ok4 bool
87 | g.Data1, ok1 = decodeHexUint32(d1)
88 | g.Data2, ok2 = decodeHexUint16(d2)
89 | g.Data3, ok3 = decodeHexUint16(d3)
90 | g.Data4, ok4 = decodeHexByte64(d4a, d4b)
91 | if ok1 && ok2 && ok3 && ok4 {
92 | return &g
93 | }
94 | return nil
95 | }
96 |
97 | func decodeHexUint32(src []byte) (value uint32, ok bool) {
98 | var b1, b2, b3, b4 byte
99 | var ok1, ok2, ok3, ok4 bool
100 | b1, ok1 = decodeHexByte(src[0], src[1])
101 | b2, ok2 = decodeHexByte(src[2], src[3])
102 | b3, ok3 = decodeHexByte(src[4], src[5])
103 | b4, ok4 = decodeHexByte(src[6], src[7])
104 | value = (uint32(b1) << 24) | (uint32(b2) << 16) | (uint32(b3) << 8) | uint32(b4)
105 | ok = ok1 && ok2 && ok3 && ok4
106 | return
107 | }
108 |
109 | func decodeHexUint16(src []byte) (value uint16, ok bool) {
110 | var b1, b2 byte
111 | var ok1, ok2 bool
112 | b1, ok1 = decodeHexByte(src[0], src[1])
113 | b2, ok2 = decodeHexByte(src[2], src[3])
114 | value = (uint16(b1) << 8) | uint16(b2)
115 | ok = ok1 && ok2
116 | return
117 | }
118 |
119 | func decodeHexByte64(s1 []byte, s2 []byte) (value [8]byte, ok bool) {
120 | var ok1, ok2, ok3, ok4, ok5, ok6, ok7, ok8 bool
121 | value[0], ok1 = decodeHexByte(s1[0], s1[1])
122 | value[1], ok2 = decodeHexByte(s1[2], s1[3])
123 | value[2], ok3 = decodeHexByte(s2[0], s2[1])
124 | value[3], ok4 = decodeHexByte(s2[2], s2[3])
125 | value[4], ok5 = decodeHexByte(s2[4], s2[5])
126 | value[5], ok6 = decodeHexByte(s2[6], s2[7])
127 | value[6], ok7 = decodeHexByte(s2[8], s2[9])
128 | value[7], ok8 = decodeHexByte(s2[10], s2[11])
129 | ok = ok1 && ok2 && ok3 && ok4 && ok5 && ok6 && ok7 && ok8
130 | return
131 | }
132 |
133 | func decodeHexByte(c1, c2 byte) (value byte, ok bool) {
134 | var n1, n2 byte
135 | var ok1, ok2 bool
136 | n1, ok1 = decodeHexChar(c1)
137 | n2, ok2 = decodeHexChar(c2)
138 | value = (n1 << 4) | n2
139 | ok = ok1 && ok2
140 | return
141 | }
142 |
143 | func decodeHexChar(c byte) (byte, bool) {
144 | switch {
145 | case '0' <= c && c <= '9':
146 | return c - '0', true
147 | case 'a' <= c && c <= 'f':
148 | return c - 'a' + 10, true
149 | case 'A' <= c && c <= 'F':
150 | return c - 'A' + 10, true
151 | }
152 |
153 | return 0, false
154 | }
155 |
156 | // String converts the GUID to string form. It will adhere to this pattern:
157 | //
158 | // {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
159 | //
160 | // If the GUID is nil, the string representation of an empty GUID is returned:
161 | //
162 | // {00000000-0000-0000-0000-000000000000}
163 | func (guid *GUID) String() string {
164 | if guid == nil {
165 | return emptyGUID
166 | }
167 |
168 | var c [38]byte
169 | c[0] = '{'
170 | putUint32Hex(c[1:9], guid.Data1)
171 | c[9] = '-'
172 | putUint16Hex(c[10:14], guid.Data2)
173 | c[14] = '-'
174 | putUint16Hex(c[15:19], guid.Data3)
175 | c[19] = '-'
176 | putByteHex(c[20:24], guid.Data4[0:2])
177 | c[24] = '-'
178 | putByteHex(c[25:37], guid.Data4[2:8])
179 | c[37] = '}'
180 | return string(c[:])
181 | }
182 |
183 | func putUint32Hex(b []byte, v uint32) {
184 | b[0] = hextable[byte(v>>24)>>4]
185 | b[1] = hextable[byte(v>>24)&0x0f]
186 | b[2] = hextable[byte(v>>16)>>4]
187 | b[3] = hextable[byte(v>>16)&0x0f]
188 | b[4] = hextable[byte(v>>8)>>4]
189 | b[5] = hextable[byte(v>>8)&0x0f]
190 | b[6] = hextable[byte(v)>>4]
191 | b[7] = hextable[byte(v)&0x0f]
192 | }
193 |
194 | func putUint16Hex(b []byte, v uint16) {
195 | b[0] = hextable[byte(v>>8)>>4]
196 | b[1] = hextable[byte(v>>8)&0x0f]
197 | b[2] = hextable[byte(v)>>4]
198 | b[3] = hextable[byte(v)&0x0f]
199 | }
200 |
201 | func putByteHex(dst, src []byte) {
202 | for i := 0; i < len(src); i++ {
203 | dst[i*2] = hextable[src[i]>>4]
204 | dst[i*2+1] = hextable[src[i]&0x0f]
205 | }
206 | }
207 |
208 | // IsEqualGUID compares two GUID.
209 | //
210 | // Not constant time comparison.
211 | func IsEqualGUID(guid1 *GUID, guid2 *GUID) bool {
212 | return guid1.Data1 == guid2.Data1 &&
213 | guid1.Data2 == guid2.Data2 &&
214 | guid1.Data3 == guid2.Data3 &&
215 | guid1.Data4[0] == guid2.Data4[0] &&
216 | guid1.Data4[1] == guid2.Data4[1] &&
217 | guid1.Data4[2] == guid2.Data4[2] &&
218 | guid1.Data4[3] == guid2.Data4[3] &&
219 | guid1.Data4[4] == guid2.Data4[4] &&
220 | guid1.Data4[5] == guid2.Data4[5] &&
221 | guid1.Data4[6] == guid2.Data4[6] &&
222 | guid1.Data4[7] == guid2.Data4[7]
223 | }
224 |
--------------------------------------------------------------------------------
/go-webview2/webview.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package webview2
5 |
6 | import (
7 | "encoding/json"
8 | "errors"
9 | "log"
10 | "reflect"
11 | "strconv"
12 | "sync"
13 | "unsafe"
14 |
15 | "github.com/eyasliu/desktop/go-webview2/internal/w32"
16 | "github.com/eyasliu/desktop/go-webview2/pkg/edge"
17 |
18 | "golang.org/x/sys/windows"
19 | )
20 |
21 | var (
22 | windowContext = map[uintptr]interface{}{}
23 | windowContextSync sync.RWMutex
24 | )
25 |
26 | func getWindowContext(wnd uintptr) interface{} {
27 | windowContextSync.RLock()
28 | defer windowContextSync.RUnlock()
29 | return windowContext[wnd]
30 | }
31 |
32 | func setWindowContext(wnd uintptr, data interface{}) {
33 | windowContextSync.Lock()
34 | defer windowContextSync.Unlock()
35 | windowContext[wnd] = data
36 | }
37 |
38 | // Loads an image from file to be shown in tray or menu item.
39 | // LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx
40 | func loadIconFrom(src string) (windows.Handle, error) {
41 | const IMAGE_ICON = 1 // Loads an icon
42 | const LR_LOADFROMFILE = 0x00000010 // Loads the stand-alone image from the file
43 | const LR_DEFAULTSIZE = 0x00000040 // Loads default-size icon for windows(SM_CXICON x SM_CYICON) if cx, cy are set to zero
44 |
45 | // Save and reuse handles of loaded images
46 | // t.muLoadedImages.RLock()
47 | // h, ok := t.loadedImages[src]
48 | // t.muLoadedImages.RUnlock()
49 | // if !ok {
50 | srcPtr, err := windows.UTF16PtrFromString(src)
51 | if err != nil {
52 | return 0, err
53 | }
54 | res, _, err := w32.User32LoadImageW.Call(
55 | 0,
56 | uintptr(unsafe.Pointer(srcPtr)),
57 | IMAGE_ICON,
58 | 0,
59 | 0,
60 | LR_LOADFROMFILE|LR_DEFAULTSIZE,
61 | )
62 | if res == 0 {
63 | return 0, err
64 | }
65 | h := windows.Handle(res)
66 | // t.muLoadedImages.Lock()
67 | // t.loadedImages[src] = h
68 | // t.muLoadedImages.Unlock()
69 | // }
70 | return h, nil
71 | }
72 |
73 | type navigationCompletedArg struct {
74 | Success bool
75 | }
76 |
77 | type browser interface {
78 | Embed(hwnd uintptr) bool
79 | Resize()
80 | Navigate(url string) error
81 | NavigateToString(htmlContent string)
82 | Init(script string)
83 | Eval(script string)
84 | NotifyParentWindowPositionChanged() error
85 | Focus()
86 | }
87 |
88 | type webview struct {
89 | hwnd uintptr
90 | mainthread uintptr
91 | browser browser
92 | autofocus bool
93 | hideOnClose bool
94 | maxsz w32.Point
95 | minsz w32.Point
96 | m sync.Mutex
97 | bindings map[string]interface{}
98 | dispatchq []func()
99 | logger logger
100 | }
101 |
102 | type logger interface {
103 | Info(v ...interface{})
104 | }
105 |
106 | type WindowOptions struct {
107 | Title string
108 | Width uint
109 | Height uint
110 | IconId uint
111 | Icon string
112 | Center bool
113 | Frameless bool
114 | }
115 |
116 | type WebViewOptions struct {
117 | Window unsafe.Pointer
118 | StartURL string
119 | FallbackPage string
120 | StartHTML string
121 | HideWindowOnClose bool
122 |
123 | // if true, enable context menu and chrome devtools
124 | Debug bool
125 |
126 | // DataPath specifies the datapath for the WebView2 runtime to use for the
127 | // browser instance.
128 | DataPath string
129 |
130 | // AutoFocus will try to keep the WebView2 widget focused when the window
131 | // is focused.
132 | AutoFocus bool
133 |
134 | Logger logger
135 |
136 | // WindowOptions customizes the window that is created to embed the
137 | // WebView2 widget.
138 | WindowOptions WindowOptions
139 | }
140 |
141 | // New creates a new webview in a new window.
142 | func New(debug bool) WebView { return NewWithOptions(WebViewOptions{Debug: debug}) }
143 |
144 | // NewWindow creates a new webview using an existing window.
145 | //
146 | // Deprecated: Use NewWithOptions.
147 | func NewWindow(debug bool, window unsafe.Pointer) WebView {
148 | return NewWithOptions(WebViewOptions{Debug: debug, Window: window})
149 | }
150 |
151 | // NewWithOptions creates a new webview using the provided options.
152 | func NewWithOptions(options WebViewOptions) WebView {
153 | w := &webview{}
154 | w.logger = options.Logger
155 | w.bindings = map[string]interface{}{}
156 | w.autofocus = options.AutoFocus
157 | w.hideOnClose = options.HideWindowOnClose
158 |
159 | chromium := edge.NewChromium()
160 | chromium.MessageCallback = w.msgcb
161 | chromium.DataPath = options.DataPath
162 | chromium.SetPermission(edge.CoreWebView2PermissionKindClipboardRead, edge.CoreWebView2PermissionStateAllow)
163 |
164 | if ok := chromium.CheckOrInstallWv2(); !ok {
165 | return nil
166 | }
167 |
168 | w.browser = chromium
169 | w.mainthread, _, _ = w32.Kernel32GetCurrentThreadID.Call()
170 | if !w.CreateWithOptions(options.WindowOptions) {
171 | return nil
172 | }
173 |
174 | settings, err := chromium.GetSettings()
175 | if err != nil {
176 | log.Fatal(err)
177 | }
178 |
179 | if !options.Debug {
180 | // disable context menu
181 | err = settings.PutAreDefaultContextMenusEnabled(options.Debug)
182 | if err != nil {
183 | log.Fatal(err)
184 | }
185 |
186 | // disable developer tools
187 | err = settings.PutAreDevToolsEnabled(options.Debug)
188 | if err != nil {
189 | log.Fatal(err)
190 | }
191 | }
192 |
193 | if options.FallbackPage != "" {
194 | w.SetFallbackPage(options.FallbackPage)
195 | }
196 |
197 | if options.StartURL != "" {
198 | w.Navigate(options.StartURL)
199 | } else if options.StartHTML != "" {
200 | w.SetHtml(options.StartHTML)
201 | }
202 |
203 | return w
204 | }
205 |
206 | type rpcMessage struct {
207 | ID int `json:"id"`
208 | Method string `json:"method"`
209 | Params []json.RawMessage `json:"params"`
210 | }
211 |
212 | func jsString(v interface{}) string { b, _ := json.Marshal(v); return string(b) }
213 |
214 | func (w *webview) msgcb(msg string) {
215 | d := rpcMessage{}
216 | if err := json.Unmarshal([]byte(msg), &d); err != nil {
217 | log.Printf("invalid RPC message: %v", err)
218 | return
219 | }
220 |
221 | id := strconv.Itoa(d.ID)
222 | if res, err := w.callbinding(d); err != nil {
223 | w.Dispatch(func() {
224 | w.Eval("window._rpc[" + id + "].reject(" + jsString(err.Error()) + "); window._rpc[" + id + "] = undefined")
225 | })
226 | } else if b, err := json.Marshal(res); err != nil {
227 | w.Dispatch(func() {
228 | w.Eval("window._rpc[" + id + "].reject(" + jsString(err.Error()) + "); window._rpc[" + id + "] = undefined")
229 | })
230 | } else {
231 | w.Dispatch(func() {
232 | w.Eval("window._rpc[" + id + "].resolve(" + string(b) + "); window._rpc[" + id + "] = undefined")
233 | })
234 | }
235 | }
236 |
237 | func (w *webview) callbinding(d rpcMessage) (interface{}, error) {
238 | w.m.Lock()
239 | f, ok := w.bindings[d.Method]
240 | w.m.Unlock()
241 | if !ok {
242 | return nil, nil
243 | }
244 |
245 | v := reflect.ValueOf(f)
246 | isVariadic := v.Type().IsVariadic()
247 | numIn := v.Type().NumIn()
248 | if (isVariadic && len(d.Params) < numIn-1) || (!isVariadic && len(d.Params) != numIn) {
249 | return nil, errors.New("function arguments mismatch")
250 | }
251 | args := []reflect.Value{}
252 | for i := range d.Params {
253 | var arg reflect.Value
254 | if isVariadic && i >= numIn-1 {
255 | arg = reflect.New(v.Type().In(numIn - 1).Elem())
256 | } else {
257 | arg = reflect.New(v.Type().In(i))
258 | }
259 | if err := json.Unmarshal(d.Params[i], arg.Interface()); err != nil {
260 | return nil, err
261 | }
262 | args = append(args, arg.Elem())
263 | }
264 |
265 | errorType := reflect.TypeOf((*error)(nil)).Elem()
266 | res := v.Call(args)
267 | switch len(res) {
268 | case 0:
269 | // No results from the function, just return nil
270 | return nil, nil
271 |
272 | case 1:
273 | // One result may be a value, or an error
274 | if res[0].Type().Implements(errorType) {
275 | if res[0].Interface() != nil {
276 | return nil, res[0].Interface().(error)
277 | }
278 | return nil, nil
279 | }
280 | return res[0].Interface(), nil
281 |
282 | case 2:
283 | // Two results: first one is value, second is error
284 | if !res[1].Type().Implements(errorType) {
285 | return nil, errors.New("second return value must be an error")
286 | }
287 | if res[1].Interface() == nil {
288 | return res[0].Interface(), nil
289 | }
290 | return res[0].Interface(), res[1].Interface().(error)
291 |
292 | default:
293 | return nil, errors.New("unexpected number of return values")
294 | }
295 | }
296 |
297 | // 实现通过 css 样式定义 -webkit-app-region: drag 可拖动窗口
298 | func (w *webview) appRegion() {
299 | w.Init(`window.addEventListener('DOMContentLoaded', () => {
300 | document.body.addEventListener('mousedown', evt => {
301 | const { target } = evt;
302 | const appRegion = getComputedStyle(target)['-webkit-app-region'];
303 |
304 | if (appRegion === 'drag') {
305 | window.__drapAppRegion();
306 | evt.preventDefault();
307 | evt.stopPropagation();
308 | }
309 | });
310 | });`)
311 | w.Bind("__drapAppRegion", w.dragAppRegion)
312 | }
313 |
314 | func (w *webview) dragAppRegion() {
315 | w32.User32ReleaseCapture.Call(w.hwnd)
316 | w32.User32PostMessageW.Call(w.hwnd, 161, 2, 0)
317 | }
318 |
319 | func (w *webview) updateWinForDpi(hwnd uintptr) {
320 | var bounds w32.Rect
321 | w32.User32GetWindowRect.Call(hwnd, uintptr(unsafe.Pointer(&bounds)))
322 |
323 | if bounds.Right == 0 || bounds.Bottom == 0 {
324 | return
325 | }
326 |
327 | posX := bounds.Left
328 | posY := bounds.Top
329 | width := bounds.Right - bounds.Left
330 | height := bounds.Bottom - bounds.Top
331 |
332 | _iDpi, _, _ := w32.User32GetDpiForWindow.Call(hwnd)
333 | iDpi := int32(_iDpi)
334 | dpiScaledX := uintptr((posX * iDpi) / 960)
335 | dpiScaledY := uintptr((posY * iDpi) / 960)
336 |
337 | dpiScaledWidth := uintptr((width * iDpi) / 96)
338 | dpiScaledHeight := uintptr((height * iDpi) / 96)
339 | w32.User32SetWindowPos.Call(hwnd, 0, dpiScaledX, dpiScaledY, dpiScaledWidth, dpiScaledHeight, 0x0010|0x0004)
340 | }
341 |
342 | func (w *webview) wndproc(hwnd, msg, wp, lp uintptr) uintptr {
343 | if w, ok := getWindowContext(hwnd).(*webview); ok {
344 | switch msg {
345 | case w32.WMCreate, w32.WMDpiChanged:
346 | w.updateWinForDpi(hwnd)
347 | case w32.WMMove, w32.WMMoving:
348 | _ = w.browser.NotifyParentWindowPositionChanged()
349 | case w32.WMNCLButtonDown:
350 | _, _, _ = w32.User32SetFocus.Call(w.hwnd)
351 | r, _, _ := w32.User32DefWindowProcW.Call(hwnd, msg, wp, lp)
352 | return r
353 | case w32.WMSize:
354 | w.browser.Resize()
355 | case w32.WMActivate:
356 | if wp == w32.WAInactive {
357 | break
358 | }
359 | if w.autofocus {
360 | w.browser.Focus()
361 | }
362 | case w32.WMClose:
363 | if w.hideOnClose {
364 | w.Hide()
365 | } else {
366 | _, _, _ = w32.User32DestroyWindow.Call(hwnd)
367 | }
368 | case w32.WMDestroy:
369 | w.Terminate()
370 | case w32.WMGetMinMaxInfo:
371 | lpmmi := (*w32.MinMaxInfo)(unsafe.Pointer(lp))
372 | if w.maxsz.X > 0 && w.maxsz.Y > 0 {
373 | lpmmi.PtMaxSize = w.maxsz
374 | lpmmi.PtMaxTrackSize = w.maxsz
375 | }
376 | if w.minsz.X > 0 && w.minsz.Y > 0 {
377 | lpmmi.PtMinTrackSize = w.minsz
378 | }
379 | default:
380 | r, _, _ := w32.User32DefWindowProcW.Call(hwnd, msg, wp, lp)
381 | return r
382 | }
383 | return 0
384 | }
385 | r, _, _ := w32.User32DefWindowProcW.Call(hwnd, msg, wp, lp)
386 | return r
387 | }
388 |
389 | func (w *webview) CreateWithOptions(opts WindowOptions) bool {
390 | _, _, _ = w32.ShcoreSetProcessDpiAwareness.Call(2)
391 | var hinstance windows.Handle
392 | _ = windows.GetModuleHandleEx(0, nil, &hinstance)
393 |
394 | var icon uintptr
395 | if len(opts.Icon) > 0 {
396 | hicon, err := loadIconFrom(opts.Icon)
397 | println(err)
398 | icon = uintptr(hicon)
399 | } else if opts.IconId == 0 {
400 | // load default icon
401 | icow, _, _ := w32.User32GetSystemMetrics.Call(w32.SystemMetricsCxIcon)
402 | icoh, _, _ := w32.User32GetSystemMetrics.Call(w32.SystemMetricsCyIcon)
403 | icon, _, _ = w32.User32LoadImageW.Call(uintptr(hinstance), 32512, icow, icoh, 0)
404 | } else {
405 | // load icon from resource
406 | icon, _, _ = w32.User32LoadImageW.Call(uintptr(hinstance), uintptr(opts.IconId), 1, 0, 0, w32.LR_DEFAULTSIZE|w32.LR_SHARED)
407 | }
408 |
409 | className, _ := windows.UTF16PtrFromString("webview")
410 | wc := w32.WndClassExW{
411 | CbSize: uint32(unsafe.Sizeof(w32.WndClassExW{})),
412 | HInstance: hinstance,
413 | LpszClassName: className,
414 | HIcon: windows.Handle(icon),
415 | HIconSm: windows.Handle(icon),
416 | LpfnWndProc: windows.NewCallback(w.wndproc),
417 | }
418 | _, _, _ = w32.User32RegisterClassExW.Call(uintptr(unsafe.Pointer(&wc)))
419 |
420 | windowName, _ := windows.UTF16PtrFromString(opts.Title)
421 |
422 | windowWidth := opts.Width
423 | if windowWidth == 0 {
424 | windowWidth = 640
425 | }
426 | windowHeight := opts.Height
427 | if windowHeight == 0 {
428 | windowHeight = 480
429 | }
430 |
431 | var posX, posY uint
432 | if opts.Center {
433 | // get screen size
434 | screenWidth, _, _ := w32.User32GetSystemMetrics.Call(w32.SM_CXSCREEN)
435 | screenHeight, _, _ := w32.User32GetSystemMetrics.Call(w32.SM_CYSCREEN)
436 | // calculate window position
437 | posX = (uint(screenWidth) - windowWidth) / 2
438 | posY = (uint(screenHeight) - windowHeight) / 2
439 | } else {
440 | // use default position
441 | posX = w32.CW_USEDEFAULT
442 | posY = w32.CW_USEDEFAULT
443 | }
444 |
445 | var winSetting uintptr = w32.WSOverlappedWindow
446 | if opts.Frameless {
447 | winSetting = w32.WSPopupWindow | w32.WSMinimizeBox | w32.WSMaximizeBox | w32.WSSizeBox
448 | }
449 |
450 | w.hwnd, _, _ = w32.User32CreateWindowExW.Call(
451 | 0,
452 | uintptr(unsafe.Pointer(className)),
453 | uintptr(unsafe.Pointer(windowName)),
454 | winSetting, // 0xCF0000, // WS_OVERLAPPEDWINDOW
455 | uintptr(posX),
456 | uintptr(posY),
457 | uintptr(windowWidth),
458 | uintptr(windowHeight),
459 | 0,
460 | 0,
461 | uintptr(hinstance),
462 | 0,
463 | )
464 | setWindowContext(w.hwnd, w)
465 | w.updateWinForDpi(w.hwnd)
466 |
467 | _, _, _ = w32.User32ShowWindow.Call(w.hwnd, w32.SWShow)
468 | _, _, _ = w32.User32UpdateWindow.Call(w.hwnd)
469 | _, _, _ = w32.User32SetFocus.Call(w.hwnd)
470 |
471 | if !w.browser.Embed(w.hwnd) {
472 | return false
473 | }
474 | w.browser.Resize()
475 |
476 | w.appRegion()
477 | return true
478 | }
479 |
480 | func (w *webview) Destroy() {
481 | _, _, _ = w32.User32PostMessageW.Call(w.hwnd, w32.WMClose, 0, 0)
482 | }
483 |
484 | func (w *webview) Run() {}
485 |
486 | func (w *webview) Terminate() {
487 | _, _, _ = w32.User32PostQuitMessage.Call(0)
488 | }
489 |
490 | func (w *webview) Window() unsafe.Pointer {
491 | return unsafe.Pointer(w.hwnd)
492 | }
493 |
494 | func (w *webview) Navigate(url string) {
495 | err := w.browser.Navigate(url)
496 | w.logger.Info("browser.Navigate:"+url+" ", err)
497 | }
498 |
499 | func (w *webview) SetHtml(html string) {
500 | w.browser.NavigateToString(html)
501 | }
502 |
503 | func (w *webview) SetTitle(title string) {
504 | _title, err := windows.UTF16FromString(title)
505 | if err != nil {
506 | _title, _ = windows.UTF16FromString("")
507 | }
508 | _, _, _ = w32.User32SetWindowTextW.Call(w.hwnd, uintptr(unsafe.Pointer(&_title[0])))
509 | }
510 |
511 | func (w *webview) SetSize(width int, height int, hints Hint) {
512 | index := w32.GWLStyle
513 | style, _, _ := w32.User32GetWindowLongPtrW.Call(w.hwnd, uintptr(index))
514 | if hints == HintFixed {
515 | style &^= (w32.WSThickFrame | w32.WSMaximizeBox)
516 | } else {
517 | style |= (w32.WSThickFrame | w32.WSMaximizeBox)
518 | }
519 | _, _, _ = w32.User32SetWindowLongPtrW.Call(w.hwnd, uintptr(index), style)
520 |
521 | if hints == HintMax {
522 | w.maxsz.X = int32(width)
523 | w.maxsz.Y = int32(height)
524 | } else if hints == HintMin {
525 | w.minsz.X = int32(width)
526 | w.minsz.Y = int32(height)
527 | } else {
528 | r := w32.Rect{}
529 | r.Left = 0
530 | r.Top = 0
531 | r.Right = int32(width)
532 | r.Bottom = int32(height)
533 | _, _, _ = w32.User32AdjustWindowRect.Call(uintptr(unsafe.Pointer(&r)), w32.WSOverlappedWindow, 0)
534 | _, _, _ = w32.User32SetWindowPos.Call(
535 | w.hwnd, 0, uintptr(r.Left), uintptr(r.Top), uintptr(r.Right-r.Left), uintptr(r.Bottom-r.Top),
536 | w32.SWPNoZOrder|w32.SWPNoActivate|w32.SWPNoMove|w32.SWPFrameChanged)
537 | w.browser.Resize()
538 | }
539 | }
540 |
541 | func (w *webview) Init(js string) {
542 | w.browser.Init(js)
543 | }
544 |
545 | func (w *webview) Eval(js string) {
546 | w.browser.Eval(js)
547 | }
548 |
549 | func (w *webview) Dispatch(f func()) {
550 | w.m.Lock()
551 | w.dispatchq = append(w.dispatchq, f)
552 | w.m.Unlock()
553 | _, _, _ = w32.User32PostThreadMessageW.Call(w.mainthread, w32.WMApp, 0, 0)
554 | }
555 |
556 | func (w *webview) Bind(name string, f interface{}) error {
557 | v := reflect.ValueOf(f)
558 | if v.Kind() != reflect.Func {
559 | return errors.New("only functions can be bound")
560 | }
561 | if n := v.Type().NumOut(); n > 2 {
562 | return errors.New("function may only return a value or a value+error")
563 | }
564 | w.m.Lock()
565 | w.bindings[name] = f
566 | w.m.Unlock()
567 |
568 | w.Init("(function() { var name = " + jsString(name) + ";" + `
569 | var RPC = window._rpc = (window._rpc || {nextSeq: 1});
570 | window[name] = function() {
571 | var seq = RPC.nextSeq++;
572 | var promise = new Promise(function(resolve, reject) {
573 | RPC[seq] = {
574 | resolve: resolve,
575 | reject: reject,
576 | };
577 | });
578 | window.external.invoke(JSON.stringify({
579 | id: seq,
580 | method: name,
581 | params: Array.prototype.slice.call(arguments),
582 | }));
583 | return promise;
584 | }
585 | })()`)
586 |
587 | return nil
588 | }
589 |
590 | func (w *webview) Hide() {
591 | w32.User32ShowWindow.Call(w.hwnd, w32.SWHide)
592 | }
593 |
594 | func (w *webview) Show() {
595 | w32.User32ShowWindow.Call(w.hwnd, w32.SWShow)
596 | w32.User32SwitchToThisWindow.Call(w.hwnd, uintptr(1))
597 | }
598 |
599 | func (w *webview) onNavigationCompleted(h func(args *navigationCompletedArg)) {
600 | w.browser.(*edge.Chromium).OnNavigationCompleted(func(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) {
601 | h(&navigationCompletedArg{Success: args.IsSuccess()})
602 | })
603 | }
604 |
605 | func (w *webview) SetFallbackPage(html string) error {
606 | chromium := w.browser.(*edge.Chromium)
607 | chromium.PutIsBuiltInErrorPageEnabled(false)
608 | w.onNavigationCompleted(func(args *navigationCompletedArg) {
609 | if !args.Success {
610 | w.SetHtml(html)
611 | }
612 | })
613 | return nil
614 | }
615 |
--------------------------------------------------------------------------------
/go-webview2/webviewloader/README.md:
--------------------------------------------------------------------------------
1 | This directory contains the embedded WebView2Loader SDK files, as well as the code for loading the WebView2Loader.
2 |
3 | The WebView2 SDK is redistributed under the 3-Clause BSD License. A copy of the license is included in the sdk folder.
4 |
--------------------------------------------------------------------------------
/go-webview2/webviewloader/install.go:
--------------------------------------------------------------------------------
1 | package webviewloader
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "os"
7 | "os/exec"
8 | "path/filepath"
9 | "syscall"
10 | )
11 |
12 | func downloadBootstrapper() (string, error) {
13 | bootstrapperURL := `https://go.microsoft.com/fwlink/p/?LinkId=2124703`
14 | installer := filepath.Join(os.TempDir(), `MicrosoftEdgeWebview2Setup.exe`)
15 |
16 | // Download installer
17 | out, err := os.Create(installer)
18 | defer out.Close()
19 | if err != nil {
20 | return "", err
21 | }
22 | resp, err := http.Get(bootstrapperURL)
23 | if err != nil {
24 | err = out.Close()
25 | return "", err
26 | }
27 | defer resp.Body.Close()
28 | _, err = io.Copy(out, resp.Body)
29 | if err != nil {
30 | return "", err
31 | }
32 |
33 | return installer, nil
34 | }
35 |
36 | func runInstaller(installer string) (bool, error) {
37 | // Credit: https://stackoverflow.com/a/10385867
38 | cmd := exec.Command(installer)
39 | if err := cmd.Start(); err != nil {
40 | return false, err
41 | }
42 | if err := cmd.Wait(); err != nil {
43 | if exiterr, ok := err.(*exec.ExitError); ok {
44 | if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
45 | return status.ExitStatus() == 0, nil
46 | }
47 | }
48 | }
49 | return true, nil
50 | }
51 |
52 | // InstallUsingBootstrapper will extract the embedded bootstrapper from Microsoft and run it to install
53 | // the latest version of the runtime.
54 | // Returns true if the installer ran successfully.
55 | // Returns an error if something goes wrong
56 | func InstallUsingBootstrapper() (bool, error) {
57 |
58 | installer, err := downloadBootstrapper()
59 | if err != nil {
60 | return false, err
61 | }
62 |
63 | result, err := runInstaller(installer)
64 | if err != nil {
65 | return false, err
66 | }
67 |
68 | return result, os.Remove(installer)
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/go-webview2/webviewloader/module.go:
--------------------------------------------------------------------------------
1 | package webviewloader
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "unsafe"
7 |
8 | "github.com/jchv/go-winloader"
9 | "golang.org/x/sys/windows"
10 | )
11 |
12 | var (
13 | nativeModule = windows.NewLazyDLL("WebView2Loader")
14 | nativeCreate = nativeModule.NewProc("CreateCoreWebView2EnvironmentWithOptions")
15 | nativeCompareBrowserVersions = nativeModule.NewProc("CompareBrowserVersions")
16 | nativeGetAvailableCoreWebView2BrowserVersionString = nativeModule.NewProc("GetAvailableCoreWebView2BrowserVersionString")
17 |
18 | memOnce sync.Once
19 | memModule winloader.Module
20 | memCreate winloader.Proc
21 | memCompareBrowserVersions winloader.Proc
22 | memGetAvailableCoreWebView2BrowserVersionString winloader.Proc
23 | memErr error
24 | )
25 |
26 | // CompareBrowserVersions will compare the 2 given versions and return:
27 | //
28 | // -1 = v1 < v2
29 | // 0 = v1 == v2
30 | // 1 = v1 > v2
31 | func CompareBrowserVersions(v1 string, v2 string) (int, error) {
32 |
33 | _v1, err := windows.UTF16PtrFromString(v1)
34 | if err != nil {
35 | return 0, err
36 | }
37 | _v2, err := windows.UTF16PtrFromString(v2)
38 | if err != nil {
39 | return 0, err
40 | }
41 |
42 | nativeErr := nativeModule.Load()
43 | if nativeErr == nil {
44 | nativeErr = nativeCompareBrowserVersions.Find()
45 | }
46 | var result int
47 | if nativeErr != nil {
48 | err = loadFromMemory(nativeErr)
49 | if err != nil {
50 | return 0, fmt.Errorf("Unable to load WebView2Loader.dll from disk: %v -- or from memory: %w", nativeErr, memErr)
51 | }
52 | _, _, err = memCompareBrowserVersions.Call(
53 | uint64(uintptr(unsafe.Pointer(_v1))),
54 | uint64(uintptr(unsafe.Pointer(_v2))),
55 | uint64(uintptr(unsafe.Pointer(&result))))
56 | } else {
57 | _, _, err = nativeCompareBrowserVersions.Call(
58 | uintptr(unsafe.Pointer(_v1)),
59 | uintptr(unsafe.Pointer(_v2)),
60 | uintptr(unsafe.Pointer(&result)))
61 | }
62 | if err != windows.ERROR_SUCCESS {
63 | return result, err
64 | }
65 | return result, nil
66 | }
67 |
68 | // GetInstalledVersion returns the installed version of the webview2 runtime.
69 | // If there is no version installed, a blank string is returned.
70 | func GetInstalledVersion() (string, error) {
71 | // GetAvailableCoreWebView2BrowserVersionString is documented as:
72 | // public STDAPI GetAvailableCoreWebView2BrowserVersionString(PCWSTR browserExecutableFolder, LPWSTR * versionInfo)
73 | // where winnt.h defines STDAPI as:
74 | // EXTERN_C HRESULT STDAPICALLTYPE
75 | // the first part (EXTERN_C) can be ignored since it's only relevent to C++,
76 | // HRESULT is return type which means it returns an integer that will be 0 (S_OK) on success,
77 | // and finally STDAPICALLTYPE tells us the function uses the stdcall calling convention (what Go assumes for syscalls).
78 |
79 | nativeErr := nativeModule.Load()
80 | if nativeErr == nil {
81 | nativeErr = nativeGetAvailableCoreWebView2BrowserVersionString.Find()
82 | }
83 | var hr uintptr
84 | var result *uint16
85 | if nativeErr != nil {
86 | if err := loadFromMemory(nativeErr); err != nil {
87 | return "", fmt.Errorf("Unable to load WebView2Loader.dll from disk: %v -- or from memory: %w", nativeErr, memErr)
88 | }
89 | hr64, _, _ := memGetAvailableCoreWebView2BrowserVersionString.Call(
90 | uint64(uintptr(unsafe.Pointer(nil))),
91 | uint64(uintptr(unsafe.Pointer(&result))))
92 | hr = uintptr(hr64) // The return size of the HRESULT will be whatver native size is (i.e uintptr) and not 64-bits on 32-bit systems. In both cases it should be interpreted as 32-bits (a LONG).
93 | } else {
94 | hr, _, _ = nativeGetAvailableCoreWebView2BrowserVersionString.Call(
95 | uintptr(unsafe.Pointer(nil)),
96 | uintptr(unsafe.Pointer(&result)))
97 | }
98 | defer windows.CoTaskMemFree(unsafe.Pointer(result)) // Safe even if result is nil
99 | if hr != uintptr(windows.S_OK) {
100 | if hr&0xFFFF == uintptr(windows.ERROR_FILE_NOT_FOUND) {
101 | // The lower 16-bits (the error code itself) of the HRESULT is ERROR_FILE_NOT_FOUND which means the system isn't installed.
102 | return "", nil // Return a blank string but no error since we successfully detected no install.
103 | }
104 | return "", fmt.Errorf("GetAvailableCoreWebView2BrowserVersionString returned HRESULT 0x%X", hr)
105 | }
106 | version := windows.UTF16PtrToString(result) // Safe even if result is nil
107 | return version, nil
108 | }
109 |
110 | // CreateCoreWebView2EnvironmentWithOptions tries to load WebviewLoader2 and
111 | // call the CreateCoreWebView2EnvironmentWithOptions routine.
112 | func CreateCoreWebView2EnvironmentWithOptions(browserExecutableFolder, userDataFolder *uint16, environmentOptions uintptr, environmentCompletedHandle uintptr) (uintptr, error) {
113 | nativeErr := nativeModule.Load()
114 | if nativeErr == nil {
115 | nativeErr = nativeCreate.Find()
116 | }
117 | if nativeErr != nil {
118 | err := loadFromMemory(nativeErr)
119 | if err != nil {
120 | return 0, err
121 | }
122 | res, _, _ := memCreate.Call(
123 | uint64(uintptr(unsafe.Pointer(browserExecutableFolder))),
124 | uint64(uintptr(unsafe.Pointer(userDataFolder))),
125 | uint64(environmentOptions),
126 | uint64(environmentCompletedHandle),
127 | )
128 | return uintptr(res), nil
129 | }
130 | res, _, _ := nativeCreate.Call(
131 | uintptr(unsafe.Pointer(browserExecutableFolder)),
132 | uintptr(unsafe.Pointer(userDataFolder)),
133 | environmentOptions,
134 | environmentCompletedHandle,
135 | )
136 | return res, nil
137 | }
138 |
139 | func loadFromMemory(nativeErr error) error {
140 | var err error
141 | // DLL is not available natively. Try loading embedded copy.
142 | memOnce.Do(func() {
143 | memModule, memErr = winloader.LoadFromMemory(WebView2Loader)
144 | if memErr != nil {
145 | err = fmt.Errorf("Unable to load WebView2Loader.dll from disk: %v -- or from memory: %w", nativeErr, memErr)
146 | return
147 | }
148 | memCreate = memModule.Proc("CreateCoreWebView2EnvironmentWithOptions")
149 | memCompareBrowserVersions = memModule.Proc("CompareBrowserVersions")
150 | memGetAvailableCoreWebView2BrowserVersionString = memModule.Proc("GetAvailableCoreWebView2BrowserVersionString")
151 | })
152 | return err
153 | }
154 |
--------------------------------------------------------------------------------
/go-webview2/webviewloader/module_386.go:
--------------------------------------------------------------------------------
1 | package webviewloader
2 |
3 | import _ "embed"
4 |
5 | //go:embed sdk/x86/WebView2Loader.dll
6 | var WebView2Loader []byte
7 |
--------------------------------------------------------------------------------
/go-webview2/webviewloader/module_amd64.go:
--------------------------------------------------------------------------------
1 | package webviewloader
2 |
3 | import _ "embed"
4 |
5 | //go:embed sdk/x64/WebView2Loader.dll
6 | var WebView2Loader []byte
7 |
--------------------------------------------------------------------------------
/go-webview2/webviewloader/module_arm64.go:
--------------------------------------------------------------------------------
1 | package webviewloader
2 |
3 | import _ "embed"
4 |
5 | //go:embed sdk/arm64/WebView2Loader.dll
6 | var WebView2Loader []byte
7 |
--------------------------------------------------------------------------------
/go-webview2/webviewloader/sdk/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * The name of Microsoft Corporation, or the names of its contributors
14 | may not be used to endorse or promote products derived from this
15 | software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/go-webview2/webviewloader/sdk/arm64/WebView2Loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyasliu/desktop/330d29a8acece96646a2ce5236ec6fed77c46c82/go-webview2/webviewloader/sdk/arm64/WebView2Loader.dll
--------------------------------------------------------------------------------
/go-webview2/webviewloader/sdk/x64/WebView2Loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyasliu/desktop/330d29a8acece96646a2ce5236ec6fed77c46c82/go-webview2/webviewloader/sdk/x64/WebView2Loader.dll
--------------------------------------------------------------------------------
/go-webview2/webviewloader/sdk/x86/WebView2Loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyasliu/desktop/330d29a8acece96646a2ce5236ec6fed77c46c82/go-webview2/webviewloader/sdk/x86/WebView2Loader.dll
--------------------------------------------------------------------------------
/go-webview2/window.go:
--------------------------------------------------------------------------------
1 | package webview2
2 |
3 | import (
4 | "runtime"
5 | "sync"
6 | "unsafe"
7 |
8 | "github.com/eyasliu/desktop/go-webview2/internal/w32"
9 | "github.com/eyasliu/desktop/tray"
10 | )
11 |
12 | type eventName byte
13 |
14 | const (
15 | eventInit eventName = iota
16 | eventTerminate
17 | eventDispatch
18 | eventDestroy
19 | eventSetTitle
20 | eventSetSize
21 | eventNavigate
22 | eventSetHtml
23 | eventEval
24 | eventBind
25 | eventHide
26 | eventShow
27 | )
28 |
29 | type winEvent struct {
30 | name eventName
31 | data any
32 | }
33 |
34 | type setSizeParam struct {
35 | width int
36 | height int
37 | hint Hint
38 | }
39 |
40 | type bindParam struct {
41 | name string
42 | fn any
43 | }
44 |
45 | type Window struct {
46 | webview *webview
47 | ready bool
48 | readyMu sync.Mutex
49 | preReady []func()
50 | hasTray bool
51 | }
52 |
53 | func NewWin(option WebViewOptions, trayOpt *tray.Tray) *Window {
54 | runtime.LockOSThread()
55 | win := &Window{
56 | hasTray: trayOpt != nil,
57 | }
58 | win.webview = NewWithOptions(option).(*webview)
59 |
60 | return win
61 | }
62 |
63 | // 启动事件循环
64 | func (w *Window) Run() {
65 | w.preRun()
66 | var msg w32.Msg
67 | for {
68 | _, _, _ = w32.User32GetMessageW.Call(
69 | uintptr(unsafe.Pointer(&msg)),
70 | 0,
71 | 0,
72 | 0,
73 | )
74 | if msg.Message == w32.WMApp {
75 | w.webview.m.Lock()
76 | q := append([]func(){}, w.webview.dispatchq...)
77 | w.webview.dispatchq = []func(){}
78 | w.webview.m.Unlock()
79 | for _, v := range q {
80 | v()
81 | }
82 | } else if msg.Message == w32.WMQuit {
83 | break
84 | } else if msg.Message == 10086 {
85 | event := (*winEvent)(unsafe.Pointer(msg.WParam))
86 | switch event.name {
87 | case eventInit:
88 | js := event.data.(string)
89 | w.webview.Init(js)
90 | case eventTerminate:
91 | w.webview.Terminate()
92 | case eventDispatch:
93 | fn := event.data.(func())
94 | w.webview.Dispatch(fn)
95 | case eventDestroy:
96 | if w.hasTray {
97 | tray.Quit()
98 | }
99 | w.webview.Destroy()
100 | case eventSetTitle:
101 | title := event.data.(string)
102 | w.webview.SetTitle(title)
103 | case eventSetSize:
104 | size := event.data.(setSizeParam)
105 | w.webview.SetSize(size.width, size.height, size.hint)
106 | case eventNavigate:
107 | u := event.data.(string)
108 | w.webview.Navigate(u)
109 | case eventSetHtml:
110 | html := event.data.(string)
111 | w.webview.SetHtml(html)
112 | case eventEval:
113 | js := event.data.(string)
114 | w.webview.Eval(js)
115 | case eventBind:
116 | b := event.data.(bindParam)
117 | w.webview.Bind(b.name, b.fn)
118 | case eventHide:
119 | w.webview.Hide()
120 | case eventShow:
121 | w.webview.Show()
122 | }
123 | continue
124 | }
125 | r, _, _ := w32.User32GetAncestor.Call(uintptr(msg.Hwnd), w32.GARoot)
126 | r, _, _ = w32.User32IsDialogMessage.Call(r, uintptr(unsafe.Pointer(&msg)))
127 | if r != 0 {
128 | continue
129 | }
130 | _, _, _ = w32.User32TranslateMessage.Call(uintptr(unsafe.Pointer(&msg)))
131 | _, _, _ = w32.User32DispatchMessageW.Call(uintptr(unsafe.Pointer(&msg)))
132 | }
133 |
134 | runtime.UnlockOSThread()
135 | }
136 |
137 | func (w *Window) onReady() {
138 | if w.ready {
139 | return
140 | }
141 | w.readyMu.Lock()
142 | w.ready = true
143 | q := append([]func(){}, w.preReady...)
144 | w.preReady = nil // 用完后就没用了
145 | w.readyMu.Unlock()
146 | for _, v := range q {
147 | v()
148 | }
149 | }
150 |
151 | // 为了触发 w.onReady
152 | func (w *Window) preRun() {
153 | w.webview.onNavigationCompleted(func(args *navigationCompletedArg) {
154 | w.onReady()
155 | })
156 | }
157 |
158 | // 当 webview 启动了,但是没有完全启动好,就 postmessage 的话,会导致初始化异常,导致奇怪bug
159 | // 所以用 w.ready 之前的消息先存起来,等准备好了之后一起发送
160 | func (w *Window) dispatch(name eventName, data any) {
161 | if !w.ready {
162 | w.readyMu.Lock()
163 | w.preReady = append(w.preReady, func() {
164 | w.dispatch(name, data)
165 | })
166 | w.readyMu.Unlock()
167 | return
168 | }
169 | w32.User32PostThreadMessageW.Call(
170 | w.webview.mainthread,
171 | 10086,
172 | uintptr(unsafe.Pointer(&winEvent{name, data})),
173 | 0,
174 | )
175 | }
176 |
177 | func (w *Window) Terminate() {
178 | w.dispatch(eventTerminate, nil)
179 | }
180 | func (w *Window) Dispatch(f func()) {
181 | w.dispatch(eventDispatch, f)
182 | }
183 | func (w *Window) Destroy() {
184 | w.dispatch(eventDestroy, nil)
185 | }
186 | func (w *Window) Window() unsafe.Pointer {
187 | return w.webview.Window()
188 | }
189 | func (w *Window) SetTitle(title string) {
190 | w.dispatch(eventSetTitle, title)
191 | }
192 | func (w *Window) SetSize(width int, height int, hint Hint) {
193 | w.dispatch(eventSetSize, setSizeParam{width, height, hint})
194 | }
195 |
196 | func (w *Window) Init(js string) {
197 | w.dispatch(eventInit, js)
198 | }
199 | func (w *Window) Bind(name string, f interface{}) {
200 | w.dispatch(eventBind, bindParam{name, f})
201 | }
202 |
203 | func (w *Window) Navigate(url string) {
204 | w.dispatch(eventNavigate, url)
205 | }
206 |
207 | func (w *Window) SetHtml(html string) {
208 | w.dispatch(eventSetHtml, html)
209 | }
210 |
211 | func (w *Window) Eval(js string) {
212 | w.dispatch(eventEval, js)
213 | }
214 |
215 | func (w *Window) Show() {
216 | w.dispatch(eventShow, nil)
217 | }
218 |
219 | func (w *Window) Hide() {
220 | w.dispatch(eventHide, nil)
221 | }
222 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/eyasliu/desktop
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
7 | golang.org/x/sys v0.11.0 // indirect
8 | )
9 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
2 | github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
3 | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
4 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
5 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # 桌面开发
2 |
3 | 一套可定制化的桌面开发工具,集成了 webview 窗口、系统托盘的机制
4 |
5 | **仅支持 windows 系统**
6 |
7 | ## 特性
8 |
9 | - 基于 webview 的桌面开发工具,使用 webview2 驱动,纯 Go 语言实现,无 CGO 依赖
10 | - 启动窗口时自动检测 webview2 环境,如果未安装,则自动运行安装 webview2 引导
11 | - 支持 webview 常规操作,如跳转,注入 js,js 与 go 交互等操作
12 | - 支持多个窗口管理,支持无边框窗口
13 | - 支持 css 设置 `-webkit-app-region: drag` 后拖拽窗口
14 | - 系统托盘支持,托盘支持菜单,支持无限级子菜单
15 | - TODO: 自更新机制
16 |
17 | # DEMO
18 |
19 |
20 |
21 | https://github.com/eyasliu/desktop/assets/4774683/d07c2d09-fe41-4ea1-ad5f-b095e9915e19
22 |
23 |
24 |
25 | 可使用以下命令快速启动示例程序
26 |
27 | ```
28 | go run github.com/eyasliu/desktop/example/basic
29 | ```
30 |
31 | demo 源码
32 |
33 | ```go
34 | func main() {
35 | var app desktop.WebView
36 | app = desktop.New(&desktop.Options{
37 | Debug: true,
38 | AutoFocus: true,
39 | Width: 1280,
40 | Height: 768,
41 | HideWindowOnClose: true,
42 | StartURL: "https://www.wps.cn",
43 | Tray: &tray.Tray{
44 | Title: "托盘演示",
45 | Tooltip: "提示文字,点击激活显示窗口",
46 | OnClick: func() {
47 | app.Show() // 显示窗口
48 | },
49 | Items: []*tray.TrayItem{
50 | {
51 | Title: "跳转到 wps 365 官网",
52 | OnClick: func() { app.Navigate("https://365.wps.cn") },
53 | },
54 | {
55 | Title: "打开本地页面",
56 | OnClick: func() { app.SetHtml("这是个本地页面
") },
57 | },
58 | {
59 | Title: "执行js alert('hello')",
60 | OnClick: func() { app.Eval("alert('hello')") },
61 | },
62 |
63 | {
64 | Title: "窗口操作",
65 | Items: []*tray.TrayItem{
66 | {
67 | Title: "新窗口打开 WPS AI",
68 | OnClick: func() {
69 | go func() {
70 | wpsai := desktop.New(&desktop.Options{
71 | StartURL: "https://ai.wps.cn",
72 | Frameless: true, // 去掉边框
73 | })
74 | wpsai.Run()
75 | }()
76 | },
77 | },
78 | {
79 | Title: "显示窗口",
80 | OnClick: func() {
81 | app.Show()
82 | },
83 | },
84 | {
85 | Title: "隐藏窗口",
86 | OnClick: func() {
87 | app.Hide()
88 | },
89 | },
90 | {
91 | Title: "设置窗口标题",
92 | OnClick: func() {
93 | app.SetTitle("这是新的标题")
94 | },
95 | },
96 | },
97 | },
98 | {
99 | Title: "退出程序",
100 | OnClick: func() {
101 | app.Destroy()
102 | },
103 | },
104 | },
105 | },
106 | })
107 |
108 | app.Run()
109 | }
110 |
111 | ```
112 |
113 | # API
114 |
115 | [点击查看文档](https://pkg.go.dev/github.com/eyasliu/desktop)
116 |
117 | # 注意事项
118 |
119 | windows 的桌面开发对于操作线程
120 |
121 | - desktop.New 和 app.Run 必须要在同一个 goroutine 协程执行
122 | - 一个 goroutine 只能 Run 一个 desktop.WebView
123 |
--------------------------------------------------------------------------------
/tray/systray/systray.go:
--------------------------------------------------------------------------------
1 | /*
2 | From github.com/getlantern/systray
3 | Package systray is a cross-platform Go library to place an icon and menu in the notification area.
4 | */
5 | package systray
6 |
7 | import (
8 | "fmt"
9 | "runtime"
10 | "sync"
11 | "sync/atomic"
12 | )
13 |
14 | var (
15 | // systrayReady func()
16 | systrayExit func()
17 | clickHandler func()
18 | menuItems = make(map[uint32]*MenuItem)
19 | menuItemsLock sync.RWMutex
20 |
21 | currentID = uint32(0)
22 | quitOnce sync.Once
23 | )
24 |
25 | func init() {
26 | runtime.LockOSThread()
27 | }
28 |
29 | // MenuItem is used to keep track each menu item of systray.
30 | // Don't create it directly, use the one systray.AddMenuItem() returned
31 | type MenuItem struct {
32 | // ClickedCh is the channel which will be notified when the menu item is clicked
33 | ClickedCh chan struct{}
34 |
35 | // id uniquely identify a menu item, not supposed to be modified
36 | id uint32
37 | // title is the text shown on menu item
38 | title string
39 | // tooltip is the text shown when pointing to menu item
40 | tooltip string
41 | // disabled menu item is grayed out and has no effect when clicked
42 | disabled bool
43 | // checked menu item has a tick before the title
44 | checked bool
45 | // has the menu item a checkbox (Linux)
46 | isCheckable bool
47 | // parent item, for sub menus
48 | parent *MenuItem
49 | }
50 |
51 | func (item *MenuItem) String() string {
52 | if item.parent == nil {
53 | return fmt.Sprintf("MenuItem[%d, %q]", item.id, item.title)
54 | }
55 | return fmt.Sprintf("MenuItem[%d, parent %d, %q]", item.id, item.parent.id, item.title)
56 | }
57 |
58 | // newMenuItem returns a populated MenuItem object
59 | func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem {
60 | return &MenuItem{
61 | ClickedCh: make(chan struct{}),
62 | id: atomic.AddUint32(¤tID, 1),
63 | title: title,
64 | tooltip: tooltip,
65 | disabled: false,
66 | checked: false,
67 | isCheckable: false,
68 | parent: parent,
69 | }
70 | }
71 |
72 | // Run initializes GUI and starts the event loop, then invokes the onReady
73 | // callback. It blocks until systray.Quit() is called.
74 | func Run(onReady func(), onExit func()) {
75 | Register(onReady, onExit)
76 | nativeLoop()
77 | }
78 |
79 | // Register initializes GUI and registers the callbacks but relies on the
80 | // caller to run the event loop somewhere else. It's useful if the program
81 | // needs to show other UI elements, for example, webview.
82 | // To overcome some OS weirdness, On macOS versions before Catalina, calling
83 | // this does exactly the same as Run().
84 | func Register(onReady func(), onExit func()) {
85 | registerSystray()
86 | if onReady == nil {
87 | // systrayReady = func() {}
88 | } else {
89 | // Run onReady on separate goroutine to avoid blocking event loop
90 | // readyCh := make(chan interface{})
91 | // go func() {
92 | // <-readyCh
93 | onReady()
94 | // }()
95 | // systrayReady = func() {
96 | // close(readyCh)
97 | // }
98 | }
99 | // unlike onReady, onExit runs in the event loop to make sure it has time to
100 | // finish before the process terminates
101 | if onExit == nil {
102 | onExit = func() {}
103 | }
104 | systrayExit = onExit
105 |
106 | }
107 |
108 | // Quit the systray
109 | func Quit() {
110 | quitOnce.Do(quit)
111 | }
112 |
113 | func SetOnClick(handler func()) {
114 | clickHandler = handler
115 | }
116 |
117 | // AddMenuItem adds a menu item with the designated title and tooltip.
118 | // It can be safely invoked from different goroutines.
119 | // Created menu items are checkable on Windows and OSX by default. For Linux you have to use AddMenuItemCheckbox
120 | func AddMenuItem(title string, tooltip string) *MenuItem {
121 | item := newMenuItem(title, tooltip, nil)
122 | item.Update()
123 | return item
124 | }
125 |
126 | // AddMenuItemCheckbox adds a menu item with the designated title and tooltip and a checkbox for Linux.
127 | // It can be safely invoked from different goroutines.
128 | // On Windows and OSX this is the same as calling AddMenuItem
129 | func AddMenuItemCheckbox(title string, tooltip string, checked bool) *MenuItem {
130 | item := newMenuItem(title, tooltip, nil)
131 | item.isCheckable = true
132 | item.checked = checked
133 | item.Update()
134 | return item
135 | }
136 |
137 | // AddSeparator adds a separator bar to the menu
138 | func AddSeparator() {
139 | addSeparator(atomic.AddUint32(¤tID, 1))
140 | }
141 |
142 | // AddSubMenuItem adds a nested sub-menu item with the designated title and tooltip.
143 | // It can be safely invoked from different goroutines.
144 | // Created menu items are checkable on Windows and OSX by default. For Linux you have to use AddSubMenuItemCheckbox
145 | func (item *MenuItem) AddSubMenuItem(title string, tooltip string) *MenuItem {
146 | child := newMenuItem(title, tooltip, item)
147 | child.Update()
148 | return child
149 | }
150 |
151 | // AddSubMenuItemCheckbox adds a nested sub-menu item with the designated title and tooltip and a checkbox for Linux.
152 | // It can be safely invoked from different goroutines.
153 | // On Windows and OSX this is the same as calling AddSubMenuItem
154 | func (item *MenuItem) AddSubMenuItemCheckbox(title string, tooltip string, checked bool) *MenuItem {
155 | child := newMenuItem(title, tooltip, item)
156 | child.isCheckable = true
157 | child.checked = checked
158 | child.Update()
159 | return child
160 | }
161 |
162 | func (item *MenuItem) SetInfo(title string, tooltip string, checked bool, checkbox bool, disable bool) {
163 | item.title = title
164 | item.tooltip = tooltip
165 | item.checked = checked
166 | item.isCheckable = checkbox
167 | item.disabled = disable
168 | item.Update()
169 | }
170 |
171 | // SetTitle set the text to display on a menu item
172 | func (item *MenuItem) SetTitle(title string) {
173 | item.title = title
174 | item.Update()
175 | }
176 |
177 | // SetTooltip set the tooltip to show when mouse hover
178 | func (item *MenuItem) SetTooltip(tooltip string) {
179 | item.tooltip = tooltip
180 | item.Update()
181 | }
182 |
183 | // Disabled checks if the menu item is disabled
184 | func (item *MenuItem) Disabled() bool {
185 | return item.disabled
186 | }
187 |
188 | // Enable a menu item regardless if it's previously enabled or not
189 | func (item *MenuItem) Enable() {
190 | item.disabled = false
191 | item.Update()
192 | }
193 |
194 | // Disable a menu item regardless if it's previously disabled or not
195 | func (item *MenuItem) Disable() {
196 | item.disabled = true
197 | item.Update()
198 | }
199 |
200 | // Hide hides a menu item
201 | func (item *MenuItem) Hide() {
202 | hideMenuItem(item)
203 | }
204 |
205 | // Show shows a previously hidden menu item
206 | func (item *MenuItem) Show() {
207 | showMenuItem(item)
208 | }
209 |
210 | // Checked returns if the menu item has a check mark
211 | func (item *MenuItem) Checked() bool {
212 | return item.checked
213 | }
214 |
215 | // Check a menu item regardless if it's previously checked or not
216 | func (item *MenuItem) Check() {
217 | item.checked = true
218 | item.Update()
219 | }
220 |
221 | // Uncheck a menu item regardless if it's previously unchecked or not
222 | func (item *MenuItem) Uncheck() {
223 | item.checked = false
224 | item.Update()
225 | }
226 |
227 | // update propagates changes on a menu item to systray
228 | func (item *MenuItem) Update() {
229 | menuItemsLock.Lock()
230 | menuItems[item.id] = item
231 | menuItemsLock.Unlock()
232 | addOrUpdateMenuItem(item)
233 | }
234 |
235 | func systrayMenuItemSelected(id uint32) {
236 | menuItemsLock.RLock()
237 | item, ok := menuItems[id]
238 | menuItemsLock.RUnlock()
239 | if !ok {
240 | fmt.Printf("[tray]No menu item with ID %v\n", id)
241 | return
242 | }
243 | select {
244 | case item.ClickedCh <- struct{}{}:
245 | // in case no one waiting for the channel
246 | default:
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/tray/systray/systray_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package systray
5 |
6 | import (
7 | "crypto/md5"
8 | "encoding/hex"
9 | "fmt"
10 | "io/ioutil"
11 | "os"
12 | "path/filepath"
13 | "sort"
14 | "sync"
15 | "syscall"
16 | "unsafe"
17 |
18 | "golang.org/x/sys/windows"
19 | )
20 |
21 | // Helpful sources: https://github.com/golang/exp/blob/master/shiny/driver/internal/win32
22 |
23 | var (
24 | kernel32 = windows.NewLazySystemDLL("kernel32")
25 | kernel32GetCurrentThreadID = kernel32.NewProc("GetCurrentThreadId")
26 | g32 = windows.NewLazySystemDLL("Gdi32.dll")
27 | pCreateCompatibleBitmap = g32.NewProc("CreateCompatibleBitmap")
28 | pCreateCompatibleDC = g32.NewProc("CreateCompatibleDC")
29 | pDeleteDC = g32.NewProc("DeleteDC")
30 | pSelectObject = g32.NewProc("SelectObject")
31 |
32 | k32 = windows.NewLazySystemDLL("Kernel32.dll")
33 | pGetModuleHandle = k32.NewProc("GetModuleHandleW")
34 |
35 | s32 = windows.NewLazySystemDLL("Shell32.dll")
36 | pShellNotifyIcon = s32.NewProc("Shell_NotifyIconW")
37 |
38 | u32 = windows.NewLazySystemDLL("User32.dll")
39 | pCreateMenu = u32.NewProc("CreateMenu")
40 | pCreatePopupMenu = u32.NewProc("CreatePopupMenu")
41 | pCreateWindowEx = u32.NewProc("CreateWindowExW")
42 | pDefWindowProc = u32.NewProc("DefWindowProcW")
43 | pRemoveMenu = u32.NewProc("RemoveMenu")
44 | pDestroyWindow = u32.NewProc("DestroyWindow")
45 | pDispatchMessage = u32.NewProc("DispatchMessageW")
46 | pDrawIconEx = u32.NewProc("DrawIconEx")
47 | pGetCursorPos = u32.NewProc("GetCursorPos")
48 | pGetDC = u32.NewProc("GetDC")
49 | pGetMessage = u32.NewProc("GetMessageW")
50 | pGetSystemMetrics = u32.NewProc("GetSystemMetrics")
51 | pInsertMenuItem = u32.NewProc("InsertMenuItemW")
52 | pLoadCursor = u32.NewProc("LoadCursorW")
53 | pLoadIcon = u32.NewProc("LoadIconW")
54 | pLoadImage = u32.NewProc("LoadImageW")
55 | pPostMessage = u32.NewProc("PostMessageW")
56 | pPostQuitMessage = u32.NewProc("PostQuitMessage")
57 | pPostThreadMessage = u32.NewProc("PostThreadMessageW")
58 | pRegisterClass = u32.NewProc("RegisterClassExW")
59 | pRegisterWindowMessage = u32.NewProc("RegisterWindowMessageW")
60 | pReleaseDC = u32.NewProc("ReleaseDC")
61 | pSetForegroundWindow = u32.NewProc("SetForegroundWindow")
62 | pSetMenuInfo = u32.NewProc("SetMenuInfo")
63 | pSetMenuItemInfo = u32.NewProc("SetMenuItemInfoW")
64 | pShowWindow = u32.NewProc("ShowWindow")
65 | pTrackPopupMenu = u32.NewProc("TrackPopupMenu")
66 | pTranslateMessage = u32.NewProc("TranslateMessage")
67 | pUnregisterClass = u32.NewProc("UnregisterClassW")
68 | pUpdateWindow = u32.NewProc("UpdateWindow")
69 | )
70 |
71 | // Contains window class information.
72 | // It is used with the RegisterClassEx and GetClassInfoEx functions.
73 | // https://msdn.microsoft.com/en-us/library/ms633577.aspx
74 | type wndClassEx struct {
75 | Size, Style uint32
76 | WndProc uintptr
77 | ClsExtra, WndExtra int32
78 | Instance, Icon, Cursor, Background windows.Handle
79 | MenuName, ClassName *uint16
80 | IconSm windows.Handle
81 | }
82 |
83 | // Registers a window class for subsequent use in calls to the CreateWindow or CreateWindowEx function.
84 | // https://msdn.microsoft.com/en-us/library/ms633587.aspx
85 | func (w *wndClassEx) register() error {
86 | w.Size = uint32(unsafe.Sizeof(*w))
87 | res, _, err := pRegisterClass.Call(uintptr(unsafe.Pointer(w)))
88 | if res == 0 {
89 | return err
90 | }
91 | return nil
92 | }
93 |
94 | // Unregisters a window class, freeing the memory required for the class.
95 | // https://msdn.microsoft.com/en-us/library/ms644899.aspx
96 | func (w *wndClassEx) unregister() error {
97 | res, _, err := pUnregisterClass.Call(
98 | uintptr(unsafe.Pointer(w.ClassName)),
99 | uintptr(w.Instance),
100 | )
101 | if res == 0 {
102 | return err
103 | }
104 | return nil
105 | }
106 |
107 | // Contains information that the system needs to display notifications in the notification area.
108 | // Used by Shell_NotifyIcon.
109 | // https://msdn.microsoft.com/en-us/library/windows/desktop/bb773352(v=vs.85).aspx
110 | // https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159
111 | type notifyIconData struct {
112 | Size uint32
113 | Wnd windows.Handle
114 | ID, Flags, CallbackMessage uint32
115 | Icon windows.Handle
116 | Tip [128]uint16
117 | State, StateMask uint32
118 | Info [256]uint16
119 | Timeout, Version uint32
120 | InfoTitle [64]uint16
121 | InfoFlags uint32
122 | GuidItem windows.GUID
123 | BalloonIcon windows.Handle
124 | }
125 |
126 | func (nid *notifyIconData) add() error {
127 | const NIM_ADD = 0x00000000
128 | res, _, err := pShellNotifyIcon.Call(
129 | uintptr(NIM_ADD),
130 | uintptr(unsafe.Pointer(nid)),
131 | )
132 | if res == 0 {
133 | return err
134 | }
135 | return nil
136 | }
137 |
138 | func (nid *notifyIconData) modify() error {
139 | const NIM_MODIFY = 0x00000001
140 | res, _, err := pShellNotifyIcon.Call(
141 | uintptr(NIM_MODIFY),
142 | uintptr(unsafe.Pointer(nid)),
143 | )
144 | if res == 0 {
145 | return err
146 | }
147 | return nil
148 | }
149 |
150 | func (nid *notifyIconData) delete() error {
151 | const NIM_DELETE = 0x00000002
152 | res, _, err := pShellNotifyIcon.Call(
153 | uintptr(NIM_DELETE),
154 | uintptr(unsafe.Pointer(nid)),
155 | )
156 | if res == 0 {
157 | return err
158 | }
159 | return nil
160 | }
161 |
162 | // Contains information about a menu item.
163 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
164 | type menuItemInfo struct {
165 | Size, Mask, Type, State uint32
166 | ID uint32
167 | SubMenu, Checked, Unchecked windows.Handle
168 | ItemData uintptr
169 | TypeData *uint16
170 | Cch uint32
171 | BMPItem windows.Handle
172 | }
173 |
174 | // The POINT structure defines the x- and y- coordinates of a point.
175 | // https://msdn.microsoft.com/en-us/library/windows/desktop/dd162805(v=vs.85).aspx
176 | type point struct {
177 | X, Y int32
178 | }
179 |
180 | // Contains information about loaded resources
181 | type winTray struct {
182 | instance,
183 | icon,
184 | cursor,
185 | window windows.Handle
186 |
187 | loadedImages map[string]windows.Handle
188 | muLoadedImages sync.RWMutex
189 | // menus keeps track of the submenus keyed by the menu item ID, plus 0
190 | // which corresponds to the main popup menu.
191 | menus map[uint32]windows.Handle
192 | muMenus sync.RWMutex
193 | // menuOf keeps track of the menu each menu item belongs to.
194 | menuOf map[uint32]windows.Handle
195 | muMenuOf sync.RWMutex
196 | // menuItemIcons maintains the bitmap of each menu item (if applies). It's
197 | // needed to show the icon correctly when showing a previously hidden menu
198 | // item again.
199 | menuItemIcons map[uint32]windows.Handle
200 | muMenuItemIcons sync.RWMutex
201 | visibleItems map[uint32][]uint32
202 | muVisibleItems sync.RWMutex
203 |
204 | nid *notifyIconData
205 | muNID sync.RWMutex
206 | wcex *wndClassEx
207 |
208 | wmSystrayMessage,
209 | wmTaskbarCreated uint32
210 | mainthread uintptr
211 | }
212 |
213 | // Loads an image from file and shows it in tray.
214 | // Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx
215 | func (t *winTray) setIcon(src string) error {
216 | const NIF_ICON = 0x00000002
217 |
218 | h, err := t.loadIconFrom(src)
219 | if err != nil {
220 | return err
221 | }
222 |
223 | t.muNID.Lock()
224 | defer t.muNID.Unlock()
225 | t.nid.Icon = h
226 | t.nid.Flags |= NIF_ICON
227 | t.nid.Size = uint32(unsafe.Sizeof(*t.nid))
228 |
229 | return t.nid.modify()
230 | }
231 |
232 | // Sets tooltip on icon.
233 | // Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx
234 | func (t *winTray) setTooltip(src string) error {
235 | const NIF_TIP = 0x00000004
236 | b, err := windows.UTF16FromString(src)
237 | if err != nil {
238 | return err
239 | }
240 |
241 | t.muNID.Lock()
242 | defer t.muNID.Unlock()
243 | copy(t.nid.Tip[:], b[:])
244 | t.nid.Flags |= NIF_TIP
245 | t.nid.Size = uint32(unsafe.Sizeof(*t.nid))
246 |
247 | return t.nid.modify()
248 | }
249 |
250 | var wt winTray
251 |
252 | // WindowProc callback function that processes messages sent to a window.
253 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
254 | func (t *winTray) wndProc(hWnd windows.Handle, message uint32, wParam, lParam uintptr) (lResult uintptr) {
255 | const (
256 | WM_LBUTTONDBLCLK = 0x0203
257 | WM_RBUTTONUP = 0x0205
258 | WM_LBUTTONUP = 0x0202
259 | WM_COMMAND = 0x0111
260 | WM_ENDSESSION = 0x0016
261 | WM_CLOSE = 0x0010
262 | WM_DESTROY = 0x0002
263 | )
264 | switch message {
265 | case WM_COMMAND:
266 | menuItemId := int32(wParam)
267 | // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command#menus
268 | if menuItemId != -1 {
269 | systrayMenuItemSelected(uint32(wParam))
270 | }
271 | case WM_CLOSE:
272 | pDestroyWindow.Call(uintptr(t.window))
273 | t.wcex.unregister()
274 | case WM_DESTROY:
275 | // same as WM_ENDSESSION, but throws 0 exit code after all
276 | defer pPostQuitMessage.Call(uintptr(int32(0)))
277 | fallthrough
278 | case WM_ENDSESSION:
279 | t.muNID.Lock()
280 | if t.nid != nil {
281 | t.nid.delete()
282 | }
283 | t.muNID.Unlock()
284 | systrayExit()
285 | case t.wmSystrayMessage:
286 | switch lParam {
287 | case WM_LBUTTONUP, WM_LBUTTONDBLCLK:
288 | if clickHandler != nil {
289 | clickHandler()
290 | } else {
291 | t.showMenu()
292 | }
293 | case WM_RBUTTONUP:
294 | t.showMenu()
295 | }
296 | case t.wmTaskbarCreated: // on explorer.exe restarts
297 | t.muNID.Lock()
298 | t.nid.add()
299 | t.muNID.Unlock()
300 | default:
301 | // Calls the default window procedure to provide default processing for any window messages that an application does not process.
302 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633572(v=vs.85).aspx
303 | lResult, _, _ = pDefWindowProc.Call(
304 | uintptr(hWnd),
305 | uintptr(message),
306 | uintptr(wParam),
307 | uintptr(lParam),
308 | )
309 | }
310 | return
311 | }
312 |
313 | func (t *winTray) initInstance() error {
314 | const IDI_APPLICATION = 32512
315 | const IDC_ARROW = 32512 // Standard arrow
316 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
317 | const SW_HIDE = 0
318 | const CW_USEDEFAULT = 0x80000000
319 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx
320 | const (
321 | WS_CAPTION = 0x00C00000
322 | WS_MAXIMIZEBOX = 0x00010000
323 | WS_MINIMIZEBOX = 0x00020000
324 | WS_OVERLAPPED = 0x00000000
325 | WS_SYSMENU = 0x00080000
326 | WS_THICKFRAME = 0x00040000
327 |
328 | WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
329 | )
330 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ff729176
331 | const (
332 | CS_HREDRAW = 0x0002
333 | CS_VREDRAW = 0x0001
334 | )
335 | const NIF_MESSAGE = 0x00000001
336 |
337 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644931(v=vs.85).aspx
338 | const WM_USER = 0x0400
339 |
340 | const (
341 | className = "SystrayClass"
342 | windowName = ""
343 | )
344 |
345 | t.wmSystrayMessage = WM_USER + 1
346 | t.visibleItems = make(map[uint32][]uint32)
347 | t.menus = make(map[uint32]windows.Handle)
348 | t.menuOf = make(map[uint32]windows.Handle)
349 | t.menuItemIcons = make(map[uint32]windows.Handle)
350 |
351 | taskbarEventNamePtr, _ := windows.UTF16PtrFromString("TaskbarCreated")
352 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644947
353 | res, _, err := pRegisterWindowMessage.Call(
354 | uintptr(unsafe.Pointer(taskbarEventNamePtr)),
355 | )
356 | t.wmTaskbarCreated = uint32(res)
357 |
358 | t.loadedImages = make(map[string]windows.Handle)
359 |
360 | instanceHandle, _, err := pGetModuleHandle.Call(0)
361 | if instanceHandle == 0 {
362 | return err
363 | }
364 | t.instance = windows.Handle(instanceHandle)
365 |
366 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648072(v=vs.85).aspx
367 | iconHandle, _, err := pLoadIcon.Call(0, uintptr(IDI_APPLICATION))
368 | if iconHandle == 0 {
369 | return err
370 | }
371 | t.icon = windows.Handle(iconHandle)
372 |
373 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
374 | cursorHandle, _, err := pLoadCursor.Call(0, uintptr(IDC_ARROW))
375 | if cursorHandle == 0 {
376 | return err
377 | }
378 | t.cursor = windows.Handle(cursorHandle)
379 |
380 | classNamePtr, err := windows.UTF16PtrFromString(className)
381 | if err != nil {
382 | return err
383 | }
384 |
385 | windowNamePtr, err := windows.UTF16PtrFromString(windowName)
386 | if err != nil {
387 | return err
388 | }
389 |
390 | t.wcex = &wndClassEx{
391 | Style: CS_HREDRAW | CS_VREDRAW,
392 | WndProc: windows.NewCallback(t.wndProc),
393 | Instance: t.instance,
394 | Icon: t.icon,
395 | Cursor: t.cursor,
396 | Background: windows.Handle(6), // (COLOR_WINDOW + 1)
397 | ClassName: classNamePtr,
398 | IconSm: t.icon,
399 | }
400 | if err := t.wcex.register(); err != nil {
401 | return err
402 | }
403 |
404 | windowHandle, _, err := pCreateWindowEx.Call(
405 | uintptr(0),
406 | uintptr(unsafe.Pointer(classNamePtr)),
407 | uintptr(unsafe.Pointer(windowNamePtr)),
408 | uintptr(WS_OVERLAPPEDWINDOW),
409 | uintptr(CW_USEDEFAULT),
410 | uintptr(CW_USEDEFAULT),
411 | uintptr(CW_USEDEFAULT),
412 | uintptr(CW_USEDEFAULT),
413 | uintptr(0),
414 | uintptr(0),
415 | uintptr(t.instance),
416 | uintptr(0),
417 | )
418 | if windowHandle == 0 {
419 | return err
420 | }
421 | t.window = windows.Handle(windowHandle)
422 |
423 | pShowWindow.Call(
424 | uintptr(t.window),
425 | uintptr(SW_HIDE),
426 | )
427 |
428 | pUpdateWindow.Call(
429 | uintptr(t.window),
430 | )
431 |
432 | t.muNID.Lock()
433 | defer t.muNID.Unlock()
434 | t.nid = ¬ifyIconData{
435 | Wnd: windows.Handle(t.window),
436 | ID: 100,
437 | Flags: NIF_MESSAGE,
438 | CallbackMessage: t.wmSystrayMessage,
439 | }
440 | t.nid.Size = uint32(unsafe.Sizeof(*t.nid))
441 |
442 | return t.nid.add()
443 | }
444 |
445 | func (t *winTray) createMenu() error {
446 | const MIM_APPLYTOSUBMENUS = 0x80000000 // Settings apply to the menu and all of its submenus
447 |
448 | menuHandle, _, err := pCreatePopupMenu.Call()
449 | if menuHandle == 0 {
450 | return err
451 | }
452 | t.menus[0] = windows.Handle(menuHandle)
453 |
454 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647575(v=vs.85).aspx
455 | mi := struct {
456 | Size, Mask, Style, Max uint32
457 | Background windows.Handle
458 | ContextHelpID uint32
459 | MenuData uintptr
460 | }{
461 | Mask: MIM_APPLYTOSUBMENUS,
462 | }
463 | mi.Size = uint32(unsafe.Sizeof(mi))
464 |
465 | res, _, err := pSetMenuInfo.Call(
466 | uintptr(t.menus[0]),
467 | uintptr(unsafe.Pointer(&mi)),
468 | )
469 | if res == 0 {
470 | return err
471 | }
472 | return nil
473 | }
474 |
475 | func (t *winTray) convertToSubMenu(menuItemId uint32) (windows.Handle, error) {
476 | const MIIM_SUBMENU = 0x00000004
477 |
478 | res, _, err := pCreateMenu.Call()
479 | if res == 0 {
480 | return 0, err
481 | }
482 | menu := windows.Handle(res)
483 |
484 | mi := menuItemInfo{Mask: MIIM_SUBMENU, SubMenu: menu}
485 | mi.Size = uint32(unsafe.Sizeof(mi))
486 | t.muMenuOf.RLock()
487 | hMenu := t.menuOf[menuItemId]
488 | t.muMenuOf.RUnlock()
489 | res, _, err = pSetMenuItemInfo.Call(
490 | uintptr(hMenu),
491 | uintptr(menuItemId),
492 | 0,
493 | uintptr(unsafe.Pointer(&mi)),
494 | )
495 | if res == 0 {
496 | return 0, err
497 | }
498 | t.muMenus.Lock()
499 | t.menus[menuItemId] = menu
500 | t.muMenus.Unlock()
501 | return menu, nil
502 | }
503 |
504 | func (t *winTray) addOrUpdateMenuItem(menuItemId uint32, parentId uint32, title string, disabled, checked bool) error {
505 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
506 | const (
507 | MIIM_FTYPE = 0x00000100
508 | MIIM_BITMAP = 0x00000080
509 | MIIM_STRING = 0x00000040
510 | MIIM_SUBMENU = 0x00000004
511 | MIIM_ID = 0x00000002
512 | MIIM_STATE = 0x00000001
513 | )
514 | const MFT_STRING = 0x00000000
515 | const (
516 | MFS_CHECKED = 0x00000008
517 | MFS_DISABLED = 0x00000003
518 | )
519 | titlePtr, err := windows.UTF16PtrFromString(title)
520 | if err != nil {
521 | return err
522 | }
523 |
524 | mi := menuItemInfo{
525 | Mask: MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_STATE,
526 | Type: MFT_STRING,
527 | ID: uint32(menuItemId),
528 | TypeData: titlePtr,
529 | Cch: uint32(len(title)),
530 | }
531 | mi.Size = uint32(unsafe.Sizeof(mi))
532 | if disabled {
533 | mi.State |= MFS_DISABLED
534 | }
535 | if checked {
536 | mi.State |= MFS_CHECKED
537 | }
538 | t.muMenuItemIcons.RLock()
539 | hIcon := t.menuItemIcons[menuItemId]
540 | t.muMenuItemIcons.RUnlock()
541 | if hIcon > 0 {
542 | mi.Mask |= MIIM_BITMAP
543 | mi.BMPItem = hIcon
544 | }
545 |
546 | var res uintptr
547 | t.muMenus.RLock()
548 | menu, exists := t.menus[parentId]
549 | t.muMenus.RUnlock()
550 | if !exists {
551 | menu, err = t.convertToSubMenu(parentId)
552 | if err != nil {
553 | return err
554 | }
555 | t.muMenus.Lock()
556 | t.menus[parentId] = menu
557 | t.muMenus.Unlock()
558 | } else if t.getVisibleItemIndex(parentId, menuItemId) != -1 {
559 | // We set the menu item info based on the menuID
560 | res, _, err = pSetMenuItemInfo.Call(
561 | uintptr(menu),
562 | uintptr(menuItemId),
563 | 0,
564 | uintptr(unsafe.Pointer(&mi)),
565 | )
566 | }
567 |
568 | if res == 0 {
569 | // Menu item does not already exist, create it
570 | t.muMenus.RLock()
571 | submenu, exists := t.menus[menuItemId]
572 | t.muMenus.RUnlock()
573 | if exists {
574 | mi.Mask |= MIIM_SUBMENU
575 | mi.SubMenu = submenu
576 | }
577 | t.addToVisibleItems(parentId, menuItemId)
578 | position := t.getVisibleItemIndex(parentId, menuItemId)
579 | res, _, err = pInsertMenuItem.Call(
580 | uintptr(menu),
581 | uintptr(position),
582 | 1,
583 | uintptr(unsafe.Pointer(&mi)),
584 | )
585 | if res == 0 {
586 | t.delFromVisibleItems(parentId, menuItemId)
587 | return err
588 | }
589 | t.muMenuOf.Lock()
590 | t.menuOf[menuItemId] = menu
591 | t.muMenuOf.Unlock()
592 | }
593 |
594 | return nil
595 | }
596 |
597 | func (t *winTray) addSeparatorMenuItem(menuItemId, parentId uint32) error {
598 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
599 | const (
600 | MIIM_FTYPE = 0x00000100
601 | MIIM_ID = 0x00000002
602 | MIIM_STATE = 0x00000001
603 | )
604 | const MFT_SEPARATOR = 0x00000800
605 |
606 | mi := menuItemInfo{
607 | Mask: MIIM_FTYPE | MIIM_ID | MIIM_STATE,
608 | Type: MFT_SEPARATOR,
609 | ID: uint32(menuItemId),
610 | }
611 |
612 | mi.Size = uint32(unsafe.Sizeof(mi))
613 |
614 | t.addToVisibleItems(parentId, menuItemId)
615 | position := t.getVisibleItemIndex(parentId, menuItemId)
616 | t.muMenus.RLock()
617 | menu := uintptr(t.menus[parentId])
618 | t.muMenus.RUnlock()
619 | res, _, err := pInsertMenuItem.Call(
620 | menu,
621 | uintptr(position),
622 | 1,
623 | uintptr(unsafe.Pointer(&mi)),
624 | )
625 | if res == 0 {
626 | return err
627 | }
628 |
629 | return nil
630 | }
631 |
632 | func (t *winTray) hideMenuItem(menuItemId, parentId uint32) error {
633 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-removemenu
634 | const MF_BYCOMMAND = 0x00000000
635 | const ERROR_SUCCESS syscall.Errno = 0
636 |
637 | t.muMenus.RLock()
638 | menu := uintptr(t.menus[parentId])
639 | t.muMenus.RUnlock()
640 | res, _, err := pRemoveMenu.Call(
641 | menu,
642 | uintptr(menuItemId),
643 | MF_BYCOMMAND,
644 | )
645 | if res == 0 && err.(syscall.Errno) != ERROR_SUCCESS {
646 | return err
647 | }
648 | t.delFromVisibleItems(parentId, menuItemId)
649 |
650 | return nil
651 | }
652 |
653 | func (t *winTray) showMenu() error {
654 | const (
655 | TPM_BOTTOMALIGN = 0x0020
656 | TPM_LEFTALIGN = 0x0000
657 | )
658 | p := point{}
659 | res, _, err := pGetCursorPos.Call(uintptr(unsafe.Pointer(&p)))
660 | if res == 0 {
661 | return err
662 | }
663 | pSetForegroundWindow.Call(uintptr(t.window))
664 |
665 | res, _, err = pTrackPopupMenu.Call(
666 | uintptr(t.menus[0]),
667 | TPM_BOTTOMALIGN|TPM_LEFTALIGN,
668 | uintptr(p.X),
669 | uintptr(p.Y),
670 | 0,
671 | uintptr(t.window),
672 | 0,
673 | )
674 | if res == 0 {
675 | return err
676 | }
677 |
678 | return nil
679 | }
680 |
681 | func (t *winTray) delFromVisibleItems(parent, val uint32) {
682 | t.muVisibleItems.Lock()
683 | defer t.muVisibleItems.Unlock()
684 | visibleItems := t.visibleItems[parent]
685 | for i, itemval := range visibleItems {
686 | if val == itemval {
687 | t.visibleItems[parent] = append(visibleItems[:i], visibleItems[i+1:]...)
688 | break
689 | }
690 | }
691 | }
692 |
693 | func (t *winTray) addToVisibleItems(parent, val uint32) {
694 | t.muVisibleItems.Lock()
695 | defer t.muVisibleItems.Unlock()
696 | if visibleItems, exists := t.visibleItems[parent]; !exists {
697 | t.visibleItems[parent] = []uint32{val}
698 | } else {
699 | newvisible := append(visibleItems, val)
700 | sort.Slice(newvisible, func(i, j int) bool { return newvisible[i] < newvisible[j] })
701 | t.visibleItems[parent] = newvisible
702 | }
703 | }
704 |
705 | func (t *winTray) getVisibleItemIndex(parent, val uint32) int {
706 | t.muVisibleItems.RLock()
707 | defer t.muVisibleItems.RUnlock()
708 | for i, itemval := range t.visibleItems[parent] {
709 | if val == itemval {
710 | return i
711 | }
712 | }
713 | return -1
714 | }
715 |
716 | // Loads an image from file to be shown in tray or menu item.
717 | // LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx
718 | func (t *winTray) loadIconFrom(src string) (windows.Handle, error) {
719 | const IMAGE_ICON = 1 // Loads an icon
720 | const LR_LOADFROMFILE = 0x00000010 // Loads the stand-alone image from the file
721 | const LR_DEFAULTSIZE = 0x00000040 // Loads default-size icon for windows(SM_CXICON x SM_CYICON) if cx, cy are set to zero
722 |
723 | // Save and reuse handles of loaded images
724 | t.muLoadedImages.RLock()
725 | h, ok := t.loadedImages[src]
726 | t.muLoadedImages.RUnlock()
727 | if !ok {
728 | srcPtr, err := windows.UTF16PtrFromString(src)
729 | if err != nil {
730 | return 0, err
731 | }
732 | res, _, err := pLoadImage.Call(
733 | 0,
734 | uintptr(unsafe.Pointer(srcPtr)),
735 | IMAGE_ICON,
736 | 0,
737 | 0,
738 | LR_LOADFROMFILE|LR_DEFAULTSIZE,
739 | )
740 | if res == 0 {
741 | return 0, err
742 | }
743 | h = windows.Handle(res)
744 | t.muLoadedImages.Lock()
745 | t.loadedImages[src] = h
746 | t.muLoadedImages.Unlock()
747 | }
748 | return h, nil
749 | }
750 |
751 | func (t *winTray) iconToBitmap(hIcon windows.Handle) (windows.Handle, error) {
752 | const SM_CXSMICON = 49
753 | const SM_CYSMICON = 50
754 | const DI_NORMAL = 0x3
755 | hDC, _, err := pGetDC.Call(uintptr(0))
756 | if hDC == 0 {
757 | return 0, err
758 | }
759 | defer pReleaseDC.Call(uintptr(0), hDC)
760 | hMemDC, _, err := pCreateCompatibleDC.Call(hDC)
761 | if hMemDC == 0 {
762 | return 0, err
763 | }
764 | defer pDeleteDC.Call(hMemDC)
765 | cx, _, _ := pGetSystemMetrics.Call(SM_CXSMICON)
766 | cy, _, _ := pGetSystemMetrics.Call(SM_CYSMICON)
767 | hMemBmp, _, err := pCreateCompatibleBitmap.Call(hDC, cx, cy)
768 | if hMemBmp == 0 {
769 | return 0, err
770 | }
771 | hOriginalBmp, _, _ := pSelectObject.Call(hMemDC, hMemBmp)
772 | defer pSelectObject.Call(hMemDC, hOriginalBmp)
773 | res, _, err := pDrawIconEx.Call(hMemDC, 0, 0, uintptr(hIcon), cx, cy, 0, uintptr(0), DI_NORMAL)
774 | if res == 0 {
775 | return 0, err
776 | }
777 | return windows.Handle(hMemBmp), nil
778 | }
779 |
780 | func registerSystray() {
781 | if err := wt.initInstance(); err != nil {
782 | fmt.Printf("[tray]Unable to init instance: %v\n", err)
783 | return
784 | }
785 |
786 | if err := wt.createMenu(); err != nil {
787 | fmt.Printf("[tray]Unable to create menu: %v\n", err)
788 | return
789 | }
790 |
791 | // systrayReady()
792 | }
793 |
794 | const msgUpdateMenuEvent = 10087
795 |
796 | func nativeLoop() {
797 | wt.mainthread, _, _ = kernel32GetCurrentThreadID.Call()
798 |
799 | // Main message pump.
800 | m := &struct {
801 | WindowHandle windows.Handle
802 | Message uint32
803 | Wparam uintptr
804 | Lparam uintptr
805 | Time uint32
806 | Pt point
807 | }{}
808 | for {
809 | ret, _, err := pGetMessage.Call(uintptr(unsafe.Pointer(m)), 0, 0, 0)
810 | switch m.Message {
811 | case msgUpdateMenuEvent:
812 | item := (*MenuItem)(unsafe.Pointer(m.Wparam))
813 | wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
814 | }
815 | // If the function retrieves a message other than WM_QUIT, the return value is nonzero.
816 | // If the function retrieves the WM_QUIT message, the return value is zero.
817 | // If there is an error, the return value is -1
818 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx
819 | switch int32(ret) {
820 | case -1:
821 | fmt.Printf("[tray]Error at message loop: %v\n", err)
822 | return
823 | case 0:
824 | return
825 | default:
826 | pTranslateMessage.Call(uintptr(unsafe.Pointer(m)))
827 | pDispatchMessage.Call(uintptr(unsafe.Pointer(m)))
828 | }
829 |
830 | }
831 | }
832 |
833 | func quit() {
834 | const WM_CLOSE = 0x0010
835 |
836 | pPostMessage.Call(
837 | uintptr(wt.window),
838 | WM_CLOSE,
839 | 0,
840 | 0,
841 | )
842 | }
843 |
844 | func iconBytesToFilePath(iconBytes []byte) (string, error) {
845 | bh := md5.Sum(iconBytes)
846 | dataHash := hex.EncodeToString(bh[:])
847 | iconFilePath := filepath.Join(os.TempDir(), "systray_temp_icon_"+dataHash)
848 |
849 | if _, err := os.Stat(iconFilePath); os.IsNotExist(err) {
850 | if err := ioutil.WriteFile(iconFilePath, iconBytes, 0644); err != nil {
851 | return "", err
852 | }
853 | }
854 | return iconFilePath, nil
855 | }
856 |
857 | // SetIcon sets the systray icon.
858 | // iconBytes should be the content of .ico for windows and .ico/.jpg/.png
859 | // for other platforms.
860 | func SetIcon(iconBytes []byte) {
861 | iconFilePath, err := iconBytesToFilePath(iconBytes)
862 | if err != nil {
863 | fmt.Printf("[tray]Unable to write icon data to temp file: %v\n", err)
864 | return
865 | }
866 | if err := wt.setIcon(iconFilePath); err != nil {
867 | fmt.Printf("[tray]Unable to set icon: %v\n", err)
868 | return
869 | }
870 | }
871 |
872 | func SetIconPath(iconFilePath string) {
873 | if err := wt.setIcon(iconFilePath); err != nil {
874 | fmt.Printf("[tray]Unable to set icon: %v\n", err)
875 | return
876 | }
877 | }
878 |
879 | // SetTemplateIcon sets the systray icon as a template icon (on macOS), falling back
880 | // to a regular icon on other platforms.
881 | // templateIconBytes and iconBytes should be the content of .ico for windows and
882 | // .ico/.jpg/.png for other platforms.
883 | func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
884 | SetIcon(regularIconBytes)
885 | }
886 |
887 | // SetTitle sets the systray title, only available on Mac and Linux.
888 | func SetTitle(title string) {
889 | // do nothing
890 | }
891 |
892 | func (item *MenuItem) parentId() uint32 {
893 | if item.parent != nil {
894 | return uint32(item.parent.id)
895 | }
896 | return 0
897 | }
898 |
899 | // SetIcon sets the icon of a menu item. Only works on macOS and Windows.
900 | // iconBytes should be the content of .ico/.jpg/.png
901 | func (item *MenuItem) SetIcon(iconBytes []byte) {
902 | iconFilePath, err := iconBytesToFilePath(iconBytes)
903 | if err != nil {
904 | fmt.Printf("[tray]Unable to write icon data to temp file: %v\n", err)
905 | return
906 | }
907 |
908 | h, err := wt.loadIconFrom(iconFilePath)
909 | if err != nil {
910 | fmt.Printf("[tray]Unable to load icon from temp file: %v\n", err)
911 | return
912 | }
913 |
914 | h, err = wt.iconToBitmap(h)
915 | if err != nil {
916 | fmt.Printf("[tray]Unable to convert icon to bitmap: %v\n", err)
917 | return
918 | }
919 | wt.muMenuItemIcons.Lock()
920 | wt.menuItemIcons[uint32(item.id)] = h
921 | wt.muMenuItemIcons.Unlock()
922 |
923 | err = wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
924 | if err != nil {
925 | fmt.Printf("[tray]Unable to addOrUpdateMenuItem: %v\n", err)
926 | return
927 | }
928 | }
929 |
930 | // SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
931 | // only available on Mac and Windows.
932 | func SetTooltip(tooltip string) {
933 | if err := wt.setTooltip(tooltip); err != nil {
934 | fmt.Printf("[tray]Unable to set tooltip: %v\n", err)
935 | return
936 | }
937 | }
938 |
939 | func addOrUpdateMenuItem(item *MenuItem) {
940 | if wt.mainthread > 0 {
941 | pPostThreadMessage.Call(
942 | wt.mainthread,
943 | msgUpdateMenuEvent,
944 | uintptr(unsafe.Pointer(item)),
945 | 0,
946 | )
947 | return
948 | }
949 |
950 | err := wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
951 | if err != nil {
952 | fmt.Printf("[tray]Unable to addOrUpdateMenuItem: %v\n", err)
953 | return
954 | }
955 | }
956 |
957 | // SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it
958 | // falls back to the regular icon bytes and on Linux it does nothing.
959 | // templateIconBytes and regularIconBytes should be the content of .ico for windows and
960 | // .ico/.jpg/.png for other platforms.
961 | func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
962 | item.SetIcon(regularIconBytes)
963 | }
964 |
965 | func addSeparator(id uint32) {
966 | err := wt.addSeparatorMenuItem(id, 0)
967 | if err != nil {
968 | fmt.Printf("[tray]Unable to addSeparator: %v\n", err)
969 | return
970 | }
971 | }
972 |
973 | func hideMenuItem(item *MenuItem) {
974 | err := wt.hideMenuItem(uint32(item.id), item.parentId())
975 | if err != nil {
976 | fmt.Printf("[tray]Unable to hideMenuItem: %v\n", err)
977 | return
978 | }
979 | }
980 |
981 | func showMenuItem(item *MenuItem) {
982 | addOrUpdateMenuItem(item)
983 | }
984 |
--------------------------------------------------------------------------------
/tray/tray_other.go:
--------------------------------------------------------------------------------
1 | //go:build !windows
2 | // +build !windows
3 |
4 | // only windows support
5 |
6 | package tray
7 |
8 | type TrayItem struct {
9 | Title string
10 | Tooltip string
11 | Checkbox bool
12 | Checked bool
13 | Disable bool
14 | OnClick func()
15 | Items []TrayItem
16 | }
17 |
18 | type Tray struct {
19 | Icon []byte
20 | Title string
21 | Tooltip string
22 | OnClick func()
23 | Items []*TrayItem
24 | }
25 |
26 | func Quit() {}
27 |
--------------------------------------------------------------------------------
/tray/tray_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 |
4 | package tray
5 |
6 | import (
7 | "runtime"
8 |
9 | "github.com/eyasliu/desktop/tray/systray"
10 | )
11 |
12 | // 托盘菜单项
13 | type TrayItem struct {
14 | ins *systray.MenuItem
15 | parent *TrayItem
16 | // 菜单标题,显示在菜单列表
17 | Title string
18 | // 菜单提示文字,好像没有显示
19 | Tooltip string
20 | // 是否有复选框
21 | Checkbox bool
22 | // 复选框是否已选中
23 | Checked bool
24 | // 是否被禁用,禁用后不可点击
25 | Disable bool
26 | // 点击菜单触发的回调函数
27 | OnClick func()
28 | // 子菜单项
29 | Items []*TrayItem
30 | }
31 |
32 | func (ti *TrayItem) register() {
33 | if ti.parent == nil {
34 | if ti.Checkbox {
35 | ti.ins = systray.AddMenuItemCheckbox(ti.Title, ti.Tooltip, ti.Checked)
36 | } else {
37 | ti.ins = systray.AddMenuItem(ti.Title, ti.Tooltip)
38 | }
39 | } else {
40 | if ti.Checkbox {
41 | ti.ins = ti.parent.ins.AddSubMenuItemCheckbox(ti.Title, ti.Tooltip, ti.Checked)
42 | } else {
43 | ti.ins = ti.parent.ins.AddSubMenuItem(ti.Title, ti.Tooltip)
44 | }
45 | }
46 |
47 | if ti.Disable {
48 | ti.ins.Disable()
49 | }
50 | if ti.OnClick != nil {
51 | go func(menu *TrayItem) {
52 | for {
53 | <-ti.ins.ClickedCh
54 | ti.OnClick()
55 | }
56 | }(ti)
57 | }
58 | ti.setSubItems()
59 | }
60 |
61 | func (ti *TrayItem) setSubItems() {
62 | if len(ti.Items) == 0 {
63 | return
64 | }
65 | for _, sub := range ti.Items {
66 | sub.parent = ti
67 | sub.register()
68 | }
69 | }
70 |
71 | // Update 更新托盘菜单状态,调用前自行修改 TrayItem 实例的属性值
72 | func (ti *TrayItem) Update() {
73 | ti.ins.SetInfo(ti.Title, ti.Tooltip, ti.Checked, ti.Checkbox, ti.Disable)
74 | }
75 |
76 | // Tray 系统托盘配置
77 | type Tray struct {
78 | // 托盘图标路径,请注意要使用 ico 格式的图片,会继承自 desktop.Option,如果设置了会覆盖
79 | IconPath string
80 | // 托盘图标内容,请注意要使用 ico 格式的图片,会继承自 desktop.Option,如果设置了会覆盖
81 | IconBytes []byte
82 | // 托盘标题,也不知道在哪显示
83 | Title string
84 | // 托盘提示文字,鼠标移到托盘图标时显示
85 | Tooltip string
86 | // 右键托盘图标显示的菜单项
87 | Items []*TrayItem
88 | // 单机托盘图标时触发的回调函数
89 | OnClick func()
90 | }
91 |
92 | // SetIconBytes 设置图标内容,请注意要使用 ico 格式的图片
93 | func (t *Tray) SetIconBytes(img []byte) {
94 | t.IconBytes = img
95 | systray.SetIcon(t.IconBytes)
96 | }
97 |
98 | // SetIconPath 设置图标路径,请注意要使用 ico 格式的图片
99 | func (t *Tray) SetIconPath(path string) {
100 | t.IconPath = path
101 | systray.SetIconPath(t.IconPath)
102 | }
103 |
104 | // SetTooltip 更新托盘提示文字
105 | func (t *Tray) SetTooltip(text string) {
106 | t.Tooltip = text
107 | systray.SetTooltip(t.Tooltip)
108 | }
109 |
110 | // SetTitle 更新托盘标题
111 | func (t *Tray) SetTitle(text string) {
112 | t.Title = text
113 | systray.SetTitle(t.Title)
114 | }
115 |
116 | // Run 开始初始化托盘功能,该方法是阻塞的
117 | func Run(t *Tray) {
118 | runtime.LockOSThread()
119 | systray.Run(t.onReady, nil)
120 | runtime.UnlockOSThread()
121 | }
122 |
123 | func (t *Tray) onReady() {
124 | if t.IconPath != "" {
125 | systray.SetIconPath(t.IconPath)
126 | } else if len(t.IconBytes) > 1 {
127 | systray.SetIcon(t.IconBytes)
128 | }
129 | if t.Title != "" {
130 | systray.SetTitle(t.Title)
131 | }
132 | if t.Tooltip != "" {
133 | systray.SetTooltip(t.Tooltip)
134 | }
135 | if t.OnClick != nil {
136 | systray.SetOnClick(t.OnClick)
137 | }
138 | if len(t.Items) > 0 {
139 | for _, menu := range t.Items {
140 | menu.register()
141 | }
142 | }
143 | }
144 |
145 | // Quit 退出托盘功能
146 | func Quit() {
147 | systray.Quit()
148 | }
149 |
--------------------------------------------------------------------------------
/types.go:
--------------------------------------------------------------------------------
1 | package desktop
2 |
3 | import (
4 | _ "embed"
5 | "fmt"
6 |
7 | "github.com/eyasliu/desktop/tray"
8 | )
9 |
10 | //
11 | type logger interface {
12 | Info(v ...interface{})
13 | }
14 |
15 | type defaultLogger struct{}
16 |
17 | func (l *defaultLogger) Info(v ...interface{}) {
18 | fmt.Println(v...)
19 | }
20 |
21 | var _ logger = &defaultLogger{}
22 |
23 | // Options 打开的窗口和系统托盘配置
24 | type Options struct {
25 | // 系统托盘图片设置,可使用 IconPath 和 IconBytes 二选其一方式设置
26 | // 系统托盘图标路径,建议使用绝对路径,如果相对路径取执行文件相对位置
27 | IconPath string
28 | // 系统托盘图标文件内容
29 | IconBytes []byte
30 | // 是否启用调试模式,启用后webview支持右键菜单,并且支持打开 chrome 开发工具
31 | Debug bool
32 | // 启动webview后访问的url
33 | StartURL string
34 | // 当webview访问错误时显示的错误页面
35 | FallbackPage string
36 | // webview2 底层使用的用户数据目录
37 | DataPath string
38 | // webview 窗口默认显示的标题文字
39 | Title string
40 | // webview窗口宽度
41 | Width int
42 | // webview 窗口高度
43 | Height int
44 | // 刚启动webview时是否隐藏状态,可通过 Show() 方法显示
45 | StartHidden bool
46 | // 当用户关闭了webview窗口时是否视为隐藏窗口,如果为false,用户关闭窗口后会触发销毁应用
47 | HideWindowOnClose bool
48 | // webview 窗口是否总是在最顶层
49 | AlwaysOnTop bool
50 | // 系统托盘设置
51 | Tray *tray.Tray
52 | // 打印日志的实例
53 | Logger logger
54 | // 是否去掉webview窗口的边框,注意无边框会把右上角最大化最小化等按钮去掉
55 | Frameless bool
56 | // 打开窗口时是否自动在屏幕中间
57 | Center bool
58 | // 打开窗口时是否自动聚焦
59 | AutoFocus bool
60 | }
61 |
62 | //go:embed desktop.ico
63 | var defaultTrayIcon []byte
64 |
65 | // GetIcon 获取托盘图标路径
66 | func (o *Options) GetIcon() string {
67 | if o.IconPath != "" {
68 | return o.IconPath
69 | } else if len(o.IconBytes) > 1 {
70 | iconpath, err := iconBytesToFilePath(o.IconBytes)
71 | if err == nil {
72 | return iconpath
73 | }
74 | }
75 | ip, _ := iconBytesToFilePath(defaultTrayIcon)
76 | return ip
77 | }
78 |
79 | // WebView 定义webview 功能操作,暴露给外部调用
80 | type WebView interface {
81 |
82 | // Run 开始运行webview,调用后 webview 才会显示
83 | //
84 | // 注: 必须要和 New 在同一个 goroutine 执行
85 | Run()
86 |
87 | // Destroy 销毁当前的窗口
88 | Destroy()
89 |
90 | // Terminate 销毁当前的窗口
91 | Terminate()
92 |
93 | // SetTitle 设置窗口的标题文字
94 | SetTitle(title string)
95 |
96 | // Navigate webview窗口跳转到指定url
97 | Navigate(url string)
98 |
99 | // SetHtml webview窗口显示为指定的 html 内容
100 | SetHtml(html string)
101 |
102 | // Init 在页面初始化的时候注入的js代码,页面无论是跳转还是刷新后都会重新执行 Init 注入的代码
103 | // 触发的时机会在 window.onload 之前
104 | Init(js string)
105 |
106 | // Eval 在webview页面执行js代码
107 | Eval(js string)
108 |
109 | // Bind 注入JS函数,底层通过 Init 实现,用于往页面注入函数,实现 JS 和 Go 互相调用
110 | // name 是函数名,
111 | // fn 必须是 go 函数,否则无效,
112 | // 注入的函数调用后返回 Promise,在Promise resolve 获取go函数返回值,
113 | // 注入的函数参数个数必须和js调用时传入的参数类型和个数一致,否则 reject
114 | Bind(name string, f interface{})
115 |
116 | // Hide 隐藏窗口
117 | Hide()
118 | // Show 显示窗口
119 | Show()
120 | }
121 |
--------------------------------------------------------------------------------
/util.go:
--------------------------------------------------------------------------------
1 | package desktop
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "io/ioutil"
7 | "os"
8 | "path/filepath"
9 | "runtime"
10 | )
11 |
12 | func iconBytesToFilePath(iconBytes []byte) (string, error) {
13 | bh := md5.Sum(iconBytes)
14 | dataHash := hex.EncodeToString(bh[:])
15 | iconFilePath := filepath.Join(os.TempDir(), "systray_temp_icon_"+dataHash+".ico")
16 |
17 | if _, err := os.Stat(iconFilePath); os.IsNotExist(err) {
18 | if err := ioutil.WriteFile(iconFilePath, iconBytes, 0644); err != nil {
19 | return "", err
20 | }
21 | }
22 | return iconFilePath, nil
23 | }
24 |
25 | func IsHeadless() bool {
26 | if len(os.Getenv("SSH_CONNECTION")) > 0 {
27 | return true
28 | }
29 | if runtime.GOOS == "windows" {
30 | return false
31 | }
32 | if runtime.GOOS == "darwin" {
33 | return len(os.Getenv("XPC_FLAGS")) == 0
34 | }
35 | if len(os.Getenv("DISPLAY")) == 0 {
36 | return true
37 | }
38 | return false
39 | }
40 |
41 | func IsSupportTray() bool {
42 | if IsHeadless() {
43 | return false
44 | }
45 | return runtime.GOOS == "windows"
46 | }
47 |
--------------------------------------------------------------------------------