├── .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 | [![Go](https://github.com/jchv/go-webview2/actions/workflows/go.yml/badge.svg)](https://github.com/jchv/go-webview2/actions/workflows/go.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/jchv/go-webview2)](https://goreportcard.com/report/github.com/jchv/go-webview2) [![Go Reference](https://pkg.go.dev/badge/github.com/jchv/go-webview2.svg)](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 | --------------------------------------------------------------------------------