//go:build windows package application import ( "errors" "fmt" "github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/w32" "unsafe" ) var showDevTools = func(window unsafe.Pointer) {} type windowsWebviewWindow struct { windowImpl unsafe.Pointer parent *WebviewWindow hwnd w32.HWND // Size Restrictions minWidth, minHeight int maxWidth, maxHeight int } func (w *windowsWebviewWindow) nativeWindowHandle() uintptr { return w.hwnd } func (w *windowsWebviewWindow) setTitle(title string) { w32.SetWindowText(w.hwnd, title) } func (w *windowsWebviewWindow) setSize(width, height int) { x, y := w.position() // TODO: Take scaling/DPI into consideration w32.MoveWindow(w.hwnd, x, y, width, height, true) } func (w *windowsWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { globalApplication.dispatchOnMainThread(func() { position := w32.HWND_NOTOPMOST if alwaysOnTop { position = w32.HWND_TOPMOST } w32.SetWindowPos(w.hwnd, position, 0, 0, 0, 0, uint(w32.SWP_NOMOVE|w32.SWP_NOSIZE)) }) } func (w *windowsWebviewWindow) setURL(url string) { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) setResizable(resizable bool) { globalApplication.dispatchOnMainThread(func() { w.setStyle(resizable, w32.WS_THICKFRAME) }) } func (w *windowsWebviewWindow) setMinSize(width, height int) { w.minWidth = width w.minHeight = height } func (w *windowsWebviewWindow) setMaxSize(width, height int) { w.maxWidth = width w.maxHeight = height } func (w *windowsWebviewWindow) execJS(js string) { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) setBackgroundColour(color RGBA) { w32.SetBackgroundColour(w.hwnd, color.Red, color.Green, color.Blue) } func (w *windowsWebviewWindow) run() { globalApplication.dispatchOnMainThread(w._run) } func (w *windowsWebviewWindow) _run() { // Copy options options := w.parent.options w.minWidth = options.MinWidth w.minHeight = options.MinHeight w.maxWidth = options.MaxWidth w.maxHeight = options.MaxHeight var exStyle uint exStyle = w32.WS_EX_CONTROLPARENT | w32.WS_EX_APPWINDOW if options.BackgroundType != BackgroundTypeSolid { exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP } if options.AlwaysOnTop { exStyle |= w32.WS_EX_TOPMOST } w.hwnd = w32.CreateWindowEx( exStyle, windowClassName, w32.MustStringToUTF16Ptr(options.Title), w32.WS_OVERLAPPEDWINDOW, w32.CW_USEDEFAULT, w32.CW_USEDEFAULT, options.Width, options.Height, 0, 0, w32.GetModuleHandle(""), nil) if w.hwnd == 0 { panic("Unable to create window") } // Register the window with the application windowsApp := globalApplication.impl.(*windowsApp) windowsApp.registerWindow(w) if options.DisableResize { w.setResizable(false) } // Icon if !options.Windows.DisableIcon { // App icon ID is 3 icon, err := NewIconFromResource(w32.GetModuleHandle(""), uint16(3)) if err == nil { w.setIcon(icon) } } else { w.disableIcon() } switch options.BackgroundType { case BackgroundTypeSolid: w.setBackgroundColour(options.BackgroundColour) case BackgroundTypeTransparent: case BackgroundTypeTranslucent: w.setBackdropType(options.Windows.BackdropType) } w.setForeground() // Process the theme switch options.Windows.Theme { case SystemDefault: w.updateTheme(w32.IsCurrentlyDarkMode()) w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func() { w.updateTheme(w32.IsCurrentlyDarkMode()) }) case Light: w.updateTheme(false) case Dark: w.updateTheme(true) } if !options.Hidden { w.show() w.update() } } func (w *windowsWebviewWindow) center() { w32.CenterWindow(w.hwnd) } func (w *windowsWebviewWindow) size() (int, int) { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) setForeground() { w32.SetForegroundWindow(w.hwnd) } func (w *windowsWebviewWindow) update() { w32.UpdateWindow(w.hwnd) } func (w *windowsWebviewWindow) width() int { rect := w32.GetWindowRect(w.hwnd) return int(rect.Right - rect.Left) } func (w *windowsWebviewWindow) height() int { rect := w32.GetWindowRect(w.hwnd) return int(rect.Bottom - rect.Top) } func (w *windowsWebviewWindow) position() (int, int) { rect := w32.GetWindowRect(w.hwnd) return int(rect.Left), int(rect.Right) } func (w *windowsWebviewWindow) destroy() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) reload() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) forceReload() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) toggleDevTools() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) zoomReset() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) zoomIn() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) zoomOut() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) getZoom() float64 { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) setZoom(zoom float64) { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) close() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) zoom() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) setHTML(html string) { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) setPosition(x int, y int) { info := w32.GetMonitorInfoForWindow(w.hwnd) workRect := info.RcWork w32.SetWindowPos(w.hwnd, w32.HWND_TOP, int(workRect.Left)+x, int(workRect.Top)+y, 0, 0, w32.SWP_NOSIZE) } // on is used to indicate that a particular event should be listened for func (w *windowsWebviewWindow) on(eventID uint) { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) minimise() { globalApplication.dispatchOnMainThread(func() { w32.ShowWindow(w.hwnd, w32.SW_MINIMIZE) }) } func (w *windowsWebviewWindow) unminimise() { w.restore() } func (w *windowsWebviewWindow) maximise() { globalApplication.dispatchOnMainThread(func() { w32.ShowWindow(w.hwnd, w32.SW_MAXIMIZE) }) } func (w *windowsWebviewWindow) unmaximise() { w.restore() } func (w *windowsWebviewWindow) restore() { globalApplication.dispatchOnMainThread(func() { w32.ShowWindow(w.hwnd, w32.SW_RESTORE) }) } func (w *windowsWebviewWindow) fullscreen() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) unfullscreen() { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) isMinimised() bool { style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) return style&w32.WS_MINIMIZE != 0 } func (w *windowsWebviewWindow) isMaximised() bool { style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) return style&w32.WS_MAXIMIZE != 0 } func (w *windowsWebviewWindow) isFullscreen() bool { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) isNormal() bool { return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen() } func (w *windowsWebviewWindow) isVisible() bool { style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE)) return style&w32.WS_VISIBLE != 0 } func (w *windowsWebviewWindow) setFullscreenButtonEnabled(_ bool) { // Unused in Windows } func (w *windowsWebviewWindow) show() { globalApplication.dispatchOnMainThread(func() { w32.ShowWindow(w.hwnd, w32.SW_SHOW) }) } func (w *windowsWebviewWindow) hide() { globalApplication.dispatchOnMainThread(func() { w32.ShowWindow(w.hwnd, w32.SW_HIDE) }) } func (w *windowsWebviewWindow) getScreen() (*Screen, error) { //TODO implement me panic("implement me") } func (w *windowsWebviewWindow) setFrameless(b bool) { //TODO implement me panic("implement me") } func newWindowImpl(parent *WebviewWindow) *windowsWebviewWindow { result := &windowsWebviewWindow{ parent: parent, } return result } func (w *windowsWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) { // Create the menu thisMenu := newMenuImpl(menu) thisMenu.update() //C.windowShowMenu(w.nsWindow, thisMenu.nsMenu, C.int(data.X), C.int(data.Y)) } func (w *windowsWebviewWindow) setStyle(b bool, style int) { currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE)) if currentStyle != 0 { if b { currentStyle |= style } else { currentStyle &^= style } w32.SetWindowLongPtr(w.hwnd, w32.GWL_STYLE, uintptr(currentStyle)) } } func (w *windowsWebviewWindow) setExStyle(b bool, style int) { currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE)) if currentStyle != 0 { if b { currentStyle |= style } else { currentStyle &^= style } w32.SetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE, uintptr(currentStyle)) } } func (w *windowsWebviewWindow) setBackdropType(backdropType BackdropType) { if !w32.IsWindowsVersionAtLeast(10, 0, 22621) { var accent = w32.ACCENT_POLICY{ AccentState: w32.ACCENT_ENABLE_BLURBEHIND, } var data w32.WINDOWCOMPOSITIONATTRIBDATA data.Attrib = w32.WCA_ACCENT_POLICY data.PvData = w32.PVOID(&accent) data.CbData = w32.SIZE_T(unsafe.Sizeof(accent)) w32.SetWindowCompositionAttribute(w.hwnd, &data) } else { backdropValue := backdropType // We default to None, but in w32 None = 1 and Auto = 0 // So we check if the value given was Auto and set it to 0 if backdropType == Auto { backdropValue = None } w32.DwmSetWindowAttribute(w.hwnd, w32.DwmwaSystemBackdropType, w32.LPCVOID(&backdropValue), uint32(unsafe.Sizeof(backdropValue))) } } func (w *windowsWebviewWindow) setIcon(icon w32.HICON) { w32.SendMessage(w.hwnd, w32.BM_SETIMAGE, w32.IMAGE_ICON, uintptr(icon)) } func (w *windowsWebviewWindow) disableIcon() { // TODO: If frameless, return exStyle := w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE) w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, uint32(exStyle|w32.WS_EX_DLGMODALFRAME)) w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0, uint( w32.SWP_FRAMECHANGED| w32.SWP_NOMOVE| w32.SWP_NOSIZE| w32.SWP_NOZORDER), ) } func (w *windowsWebviewWindow) updateTheme(isDarkMode bool) { if w32.IsCurrentlyHighContrastMode() { return } if !w32.SupportsThemes() { return } w32.SetTheme(w.hwnd, isDarkMode) // Custom theme processing customTheme := w.parent.options.Windows.CustomTheme // Custom theme if w32.SupportsCustomThemes() && customTheme != nil { if w.isActive() { if isDarkMode { w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBar) w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleText) w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorder) } else { w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBar) w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleText) w32.SetBorderColour(w.hwnd, customTheme.LightModeBorder) } } else { if isDarkMode { w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBarInactive) w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleTextInactive) w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorderInactive) } else { w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBarInactive) w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleTextInactive) w32.SetBorderColour(w.hwnd, customTheme.LightModeBorderInactive) } } } } func (w *windowsWebviewWindow) isActive() bool { return w32.GetForegroundWindow() == w.hwnd } func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintptr { switch msg { case w32.WM_SIZE: return 0 case w32.WM_CLOSE: w32.PostMessage(w.hwnd, w32.WM_QUIT, 0, 0) // Unregister the window with the application windowsApp := globalApplication.impl.(*windowsApp) windowsApp.unregisterWindow(w) return 0 case w32.WM_GETMINMAXINFO: mmi := (*w32.MINMAXINFO)(unsafe.Pointer(lparam)) hasConstraints := false if w.minWidth > 0 || w.minHeight > 0 { hasConstraints = true width, height := w.scaleWithWindowDPI(w.minWidth, w.minHeight) if width > 0 { mmi.PtMinTrackSize.X = int32(width) } if height > 0 { mmi.PtMinTrackSize.Y = int32(height) } } if w.maxWidth > 0 || w.maxHeight > 0 { hasConstraints = true width, height := w.scaleWithWindowDPI(w.maxWidth, w.maxHeight) if width > 0 { mmi.PtMaxTrackSize.X = int32(width) } if height > 0 { mmi.PtMaxTrackSize.Y = int32(height) } } if hasConstraints { return 0 } } return w32.DefWindowProc(w.hwnd, msg, wparam, lparam) } func (w *windowsWebviewWindow) DPI() (w32.UINT, w32.UINT) { if w32.HasGetDpiForWindowFunc() { // GetDpiForWindow is supported beginning with Windows 10, 1607 and is the most accureate // one, especially it is consistent with the WM_DPICHANGED event. dpi := w32.GetDpiForWindow(w.hwnd) return dpi, dpi } if w32.HasGetDPIForMonitorFunc() { // GetDpiForWindow is supported beginning with Windows 8.1 monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTONEAREST) if monitor == 0 { return 0, 0 } var dpiX, dpiY w32.UINT w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY) return dpiX, dpiY } // If none of the above is supported fallback to the System DPI. screen := w32.GetDC(0) x := w32.GetDeviceCaps(screen, w32.LOGPIXELSX) y := w32.GetDeviceCaps(screen, w32.LOGPIXELSY) w32.ReleaseDC(0, screen) return w32.UINT(x), w32.UINT(y) } func (w *windowsWebviewWindow) scaleWithWindowDPI(width, height int) (int, int) { dpix, dpiy := w.DPI() scaledWidth := ScaleWithDPI(width, dpix) scaledHeight := ScaleWithDPI(height, dpiy) return scaledWidth, scaledHeight } func ScaleWithDPI(pixels int, dpi uint) int { return (pixels * int(dpi)) / 96 } func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (w32.HICON, error) { var err error var result w32.HICON if result = w32.LoadIconWithResourceID(instance, resId); result == 0 { err = errors.New(fmt.Sprintf("Cannot load icon from resource with id %v", resId)) } return result, err }