5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-05 00:30:55 +08:00
wails/v3/pkg/application/webview_window_windows.go
Lea Anthony 6522657893
Refactor menu functions and streamline devtools toggle across systems
This update adds implementation to several menu item functions, replacing their previous 'not implemented' state. This includes actions for close, reload, forcing reload, toggling of fullscreen, reset zoom, and others. The update also includes modifications for the handling of developer tools toggle under different build configurations. This refactoring is aimed to standardize devtools configuration across different operating systems.
2024-01-21 20:52:32 +11:00

1637 lines
48 KiB
Go

//go:build windows
package application
import (
"errors"
"fmt"
"github.com/wailsapp/wails/v3/internal/assetserver"
"net/url"
"strconv"
"strings"
"sync"
"syscall"
"time"
"unicode/utf16"
"unsafe"
"github.com/bep/debounce"
"github.com/wailsapp/go-webview2/webviewloader"
"github.com/wailsapp/wails/v3/internal/assetserver/webview"
"github.com/wailsapp/wails/v3/internal/capabilities"
"github.com/samber/lo"
"github.com/wailsapp/go-webview2/pkg/edge"
"github.com/wailsapp/wails/v3/pkg/events"
"github.com/wailsapp/wails/v3/pkg/w32"
)
var edgeMap = map[string]uintptr{
"n-resize": w32.HTTOP,
"ne-resize": w32.HTTOPRIGHT,
"e-resize": w32.HTRIGHT,
"se-resize": w32.HTBOTTOMRIGHT,
"s-resize": w32.HTBOTTOM,
"sw-resize": w32.HTBOTTOMLEFT,
"w-resize": w32.HTLEFT,
"nw-resize": w32.HTTOPLEFT,
}
type windowsWebviewWindow struct {
windowImpl unsafe.Pointer
parent *WebviewWindow
hwnd w32.HWND
menu *Win32Menu
currentlyOpenContextMenu *Win32Menu
// Fullscreen flags
isCurrentlyFullscreen bool
previousWindowStyle uint32
previousWindowExStyle uint32
previousWindowPlacement w32.WINDOWPLACEMENT
// Webview
chromium *edge.Chromium
hasStarted bool
resizeDebouncer func(func())
// resizeBorder* is the width/height of the resize border in pixels.
resizeBorderWidth int32
resizeBorderHeight int32
focusingChromium bool
dropTarget *w32.DropTarget
onceDo sync.Once
}
func (w *windowsWebviewWindow) handleKeyEvent(_ string) {
// Unused on windows
}
func (w *windowsWebviewWindow) setAbsolutePosition(x int, y int) {
// Set the window's absolute position
w32.SetWindowPos(w.hwnd, 0, x, y, 0, 0, w32.SWP_NOSIZE|w32.SWP_NOZORDER)
}
func (w *windowsWebviewWindow) absolutePosition() (int, int) {
rect := w32.GetWindowRect(w.hwnd)
left, right := w.scaleToDefaultDPI(int(rect.Left), int(rect.Right))
return left, right
}
func (w *windowsWebviewWindow) setEnabled(enabled bool) {
w32.EnableWindow(w.hwnd, enabled)
}
func (w *windowsWebviewWindow) print() error {
w.execJS("window.print();")
return nil
}
func (w *windowsWebviewWindow) startResize(border string) error {
if !w32.ReleaseCapture() {
return fmt.Errorf("unable to release mouse capture")
}
// Use PostMessage because we don't want to block the caller until resizing has been finished.
w32.PostMessage(w.hwnd, w32.WM_NCLBUTTONDOWN, edgeMap[border], 0)
return nil
}
func (w *windowsWebviewWindow) startDrag() error {
if !w32.ReleaseCapture() {
return fmt.Errorf("unable to release mouse capture")
}
// Use PostMessage because we don't want to block the caller until dragging has been finished.
w32.PostMessage(w.hwnd, w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0)
return nil
}
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) {
rect := w32.GetWindowRect(w.hwnd)
width, height = w.scaleWithWindowDPI(width, height)
w32.MoveWindow(w.hwnd, int(rect.Left), int(rect.Top), width, height, true)
w.chromium.Resize()
}
func (w *windowsWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) {
w32.SetWindowPos(w.hwnd,
lo.Ternary(alwaysOnTop, w32.HWND_TOPMOST, w32.HWND_NOTOPMOST),
0,
0,
0,
0,
uint(w32.SWP_NOMOVE|w32.SWP_NOSIZE))
}
func (w *windowsWebviewWindow) setURL(url string) {
// Navigate to the given URL in the webview
w.chromium.Navigate(url)
}
func (w *windowsWebviewWindow) setResizable(resizable bool) {
w.setStyle(resizable, w32.WS_THICKFRAME)
w.execJS(fmt.Sprintf("window._wails.setResizable(%v);", resizable))
}
func (w *windowsWebviewWindow) setMinSize(width, height int) {
w.parent.options.MinWidth = width
w.parent.options.MinHeight = height
}
func (w *windowsWebviewWindow) setMaxSize(width, height int) {
w.parent.options.MaxWidth = width
w.parent.options.MaxHeight = height
}
func (w *windowsWebviewWindow) execJS(js string) {
if w.chromium == nil {
return
}
globalApplication.dispatchOnMainThread(func() {
w.chromium.Eval(js)
})
}
func (w *windowsWebviewWindow) setBackgroundColour(color RGBA) {
w32.SetBackgroundColour(w.hwnd, color.Red, color.Green, color.Blue)
}
func (w *windowsWebviewWindow) framelessWithDecorations() bool {
return w.parent.options.Frameless && !w.parent.options.Windows.DisableFramelessWindowDecorations
}
func (w *windowsWebviewWindow) run() {
options := w.parent.options
w.chromium = edge.NewChromium()
var exStyle uint
exStyle = w32.WS_EX_CONTROLPARENT
if options.BackgroundType != BackgroundTypeSolid {
exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP
if w.parent.options.IgnoreMouseEvents {
exStyle |= w32.WS_EX_TRANSPARENT | w32.WS_EX_LAYERED
}
}
if options.AlwaysOnTop {
exStyle |= w32.WS_EX_TOPMOST
}
// If we're frameless, we need to add the WS_EX_TOOLWINDOW style to hide the window from the taskbar
if options.Windows.HiddenOnTaskbar {
exStyle |= w32.WS_EX_TOOLWINDOW
} else {
exStyle |= w32.WS_EX_APPWINDOW
}
var startX, _ = lo.Coalesce(options.X, w32.CW_USEDEFAULT)
var startY, _ = lo.Coalesce(options.Y, w32.CW_USEDEFAULT)
var appMenu w32.HMENU
// Process Menu
if !options.Windows.DisableMenu && !options.Frameless {
theMenu := globalApplication.ApplicationMenu
// Create the menu if we have one
if w.parent.options.Windows.Menu != nil {
theMenu = w.parent.options.Windows.Menu
}
if theMenu != nil {
w.menu = NewApplicationMenu(w.hwnd, theMenu)
appMenu = w.menu.menu
}
}
var parent w32.HWND
var style uint = w32.WS_OVERLAPPEDWINDOW
w.hwnd = w32.CreateWindowEx(
exStyle,
windowClassName,
w32.MustStringToUTF16Ptr(options.Title),
style,
startX,
startY,
options.Width,
options.Height,
parent,
appMenu,
w32.GetModuleHandle(""),
nil)
if w.hwnd == 0 {
panic("Unable to create window")
}
w.setupChromium()
w.setSize(options.Width, options.Height)
// Register the window with the application
getNativeApplication().registerWindow(w)
w.setResizable(!options.DisableResize)
if options.Frameless {
// Inform the application of the frame change this is needed to trigger the WM_NCCALCSIZE event.
// => https://learn.microsoft.com/en-us/windows/win32/dwm/customframe#removing-the-standard-frame
// This is normally done in WM_CREATE but we can't handle that there because that is emitted during CreateWindowEx
// and at that time we can't yet register the window for calling our WndProc method.
// This must be called after setResizable above!
rcClient := w32.GetWindowRect(w.hwnd)
w32.SetWindowPos(w.hwnd,
0,
int(rcClient.Left),
int(rcClient.Top),
int(rcClient.Right-rcClient.Left),
int(rcClient.Bottom-rcClient.Top),
w32.SWP_FRAMECHANGED)
}
// 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()
}
// Process the theme
switch options.Windows.Theme {
case SystemDefault:
w.updateTheme(w32.IsCurrentlyDarkMode())
w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func(*Event) {
w.updateTheme(w32.IsCurrentlyDarkMode())
})
case Light:
w.updateTheme(false)
case Dark:
w.updateTheme(true)
}
switch options.BackgroundType {
case BackgroundTypeSolid:
var col = options.BackgroundColour
w.setBackgroundColour(col)
w.chromium.SetBackgroundColour(col.Red, col.Green, col.Blue, col.Alpha)
case BackgroundTypeTransparent:
w.chromium.SetBackgroundColour(0, 0, 0, 0)
case BackgroundTypeTranslucent:
w.chromium.SetBackgroundColour(0, 0, 0, 0)
w.setBackdropType(options.Windows.BackdropType)
}
// Process StartState
switch options.StartState {
case WindowStateMaximised:
if w.parent.Resizable() {
w.maximise()
}
case WindowStateMinimised:
w.minimise()
case WindowStateFullscreen:
w.fullscreen()
case WindowStateNormal:
}
// Process window mask
if options.Windows.WindowMask != nil {
w.setWindowMask(options.Windows.WindowMask)
}
if options.Windows.ResizeDebounceMS > 0 {
w.resizeDebouncer = debounce.New(time.Duration(options.Windows.ResizeDebounceMS) * time.Millisecond)
}
if options.Centered {
w.center()
}
if options.Frameless {
// Trigger a resize to ensure the window is sized correctly
w.chromium.Resize()
}
if !options.Hidden {
w.show()
w.update()
}
}
func (w *windowsWebviewWindow) center() {
w32.CenterWindow(w.hwnd)
}
func (w *windowsWebviewWindow) disableSizeConstraints() {
w.setMaxSize(0, 0)
w.setMinSize(0, 0)
}
func (w *windowsWebviewWindow) enableSizeConstraints() {
options := w.parent.options
if options.MinWidth > 0 || options.MinHeight > 0 {
w.setMinSize(options.MinWidth, options.MinHeight)
}
if options.MaxWidth > 0 || options.MaxHeight > 0 {
w.setMaxSize(options.MaxWidth, options.MaxHeight)
}
}
func (w *windowsWebviewWindow) size() (int, int) {
rect := w32.GetWindowRect(w.hwnd)
width := int(rect.Right - rect.Left)
height := int(rect.Bottom - rect.Top)
// Scaling appears to give invalid results...
//width, height = w.scaleToDefaultDPI(width, height)
return width, height
}
func (w *windowsWebviewWindow) update() {
w32.UpdateWindow(w.hwnd)
}
func (w *windowsWebviewWindow) width() int {
width, _ := w.size()
return width
}
func (w *windowsWebviewWindow) height() int {
_, height := w.size()
return height
}
func (w *windowsWebviewWindow) relativePosition() (int, int) {
// Get monitor for window
monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTONEAREST)
var monitorInfo w32.MONITORINFO
monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
w32.GetMonitorInfo(monitor, &monitorInfo)
// Get window rect
rect := w32.GetWindowRect(w.hwnd)
// Calculate relative position
x := int(rect.Left) - int(monitorInfo.RcWork.Left)
y := int(rect.Top) - int(monitorInfo.RcWork.Top)
return w.scaleToDefaultDPI(x, y)
}
func (w *windowsWebviewWindow) destroy() {
w.parent.markAsDestroyed()
if w.dropTarget != nil {
w.dropTarget.Release()
}
}
func (w *windowsWebviewWindow) reload() {
w.execJS("window.location.reload();")
}
func (w *windowsWebviewWindow) forceReload() {
//TODO implement me
panic("implement me")
}
func (w *windowsWebviewWindow) zoomReset() {
w.setZoom(1.0)
}
func (w *windowsWebviewWindow) zoomIn() {
// Increase the zoom level by 0.05
currentZoom := w.getZoom()
if currentZoom == -1 {
return
}
w.setZoom(currentZoom + 0.05)
}
func (w *windowsWebviewWindow) zoomOut() {
// Decrease the zoom level by 0.05
currentZoom := w.getZoom()
if currentZoom == -1 {
return
}
if currentZoom > 1.05 {
// Decrease the zoom level by 0.05
w.setZoom(currentZoom - 0.05)
} else {
// Set the zoom level to 1.0
w.setZoom(1.0)
}
}
func (w *windowsWebviewWindow) getZoom() float64 {
controller := w.chromium.GetController()
factor, err := controller.GetZoomFactor()
if err != nil {
return -1
}
return factor
}
func (w *windowsWebviewWindow) setZoom(zoom float64) {
w.chromium.PutZoomFactor(zoom)
}
func (w *windowsWebviewWindow) close() {
// Unregister the window with the application
windowsApp := globalApplication.impl.(*windowsApp)
windowsApp.unregisterWindow(w)
w32.SendMessage(w.hwnd, w32.WM_CLOSE, 0, 0)
}
func (w *windowsWebviewWindow) zoom() {
//TODO implement me
panic("implement me")
}
func (w *windowsWebviewWindow) setHTML(html string) {
// Render the given HTML in the webview window
w.execJS(fmt.Sprintf("document.documentElement.innerHTML = %q;", html))
}
func (w *windowsWebviewWindow) setRelativePosition(x int, y int) {
//x, y = w.scaleWithWindowDPI(x, y)
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(_ uint) {
// We don't need to worry about this in Windows as we do not need
// to optimise cgo calls
}
func (w *windowsWebviewWindow) minimise() {
w32.ShowWindow(w.hwnd, w32.SW_MINIMIZE)
}
func (w *windowsWebviewWindow) unminimise() {
w.restore()
}
func (w *windowsWebviewWindow) maximise() {
w32.ShowWindow(w.hwnd, w32.SW_MAXIMIZE)
w.chromium.Focus()
}
func (w *windowsWebviewWindow) unmaximise() {
w.restore()
}
func (w *windowsWebviewWindow) restore() {
w32.ShowWindow(w.hwnd, w32.SW_RESTORE)
w.chromium.Focus()
}
func (w *windowsWebviewWindow) fullscreen() {
if w.isFullscreen() {
return
}
if w.framelessWithDecorations() {
w32.ExtendFrameIntoClientArea(w.hwnd, false)
}
w.disableSizeConstraints()
w.previousWindowStyle = uint32(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE))
w.previousWindowExStyle = uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE))
monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTOPRIMARY)
var monitorInfo w32.MONITORINFO
monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
if !w32.GetMonitorInfo(monitor, &monitorInfo) {
return
}
if !w32.GetWindowPlacement(w.hwnd, &w.previousWindowPlacement) {
return
}
// According to https://devblogs.microsoft.com/oldnewthing/20050505-04/?p=35703 one should use w32.WS_POPUP | w32.WS_VISIBLE
w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w.previousWindowStyle & ^uint32(w32.WS_OVERLAPPEDWINDOW) | (w32.WS_POPUP|w32.WS_VISIBLE))
w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, w.previousWindowExStyle & ^uint32(w32.WS_EX_DLGMODALFRAME))
w.isCurrentlyFullscreen = true
w32.SetWindowPos(w.hwnd, w32.HWND_TOP,
int(monitorInfo.RcMonitor.Left),
int(monitorInfo.RcMonitor.Top),
int(monitorInfo.RcMonitor.Right-monitorInfo.RcMonitor.Left),
int(monitorInfo.RcMonitor.Bottom-monitorInfo.RcMonitor.Top),
w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED)
w.chromium.Focus()
}
func (w *windowsWebviewWindow) unfullscreen() {
if !w.isFullscreen() {
return
}
if w.framelessWithDecorations() {
w32.ExtendFrameIntoClientArea(w.hwnd, true)
}
w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w.previousWindowStyle)
w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, w.previousWindowExStyle)
w32.SetWindowPlacement(w.hwnd, &w.previousWindowPlacement)
w.isCurrentlyFullscreen = false
w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0,
w32.SWP_NOMOVE|w32.SWP_NOSIZE|w32.SWP_NOZORDER|w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED)
w.enableSizeConstraints()
}
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) isFocused() bool {
// Returns true if the window is currently focused
return w32.GetForegroundWindow() == w.hwnd
}
func (w *windowsWebviewWindow) isFullscreen() bool {
// TODO: Actually calculate this based on size of window against screen size
// => stffabi: This flag is essential since it indicates that we are in fullscreen mode even before the native properties
// reflect this, e.g. when needing to know if we are in fullscreen during a wndproc message.
// That's also why this flag is set before SetWindowPos in v2 in fullscreen/unfullscreen.
return w.isCurrentlyFullscreen
}
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) focus() {
w32.SetForegroundWindow(w.hwnd)
w.focusingChromium = true
w.chromium.Focus()
w.focusingChromium = false
}
// printStyle takes a windows style and prints it in a human-readable format
// This is for debugging window style issues
func (w *windowsWebviewWindow) printStyle() {
style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE))
fmt.Printf("Style: ")
if style&w32.WS_BORDER != 0 {
fmt.Printf("WS_BORDER ")
}
if style&w32.WS_CAPTION != 0 {
fmt.Printf("WS_CAPTION ")
}
if style&w32.WS_CHILD != 0 {
fmt.Printf("WS_CHILD ")
}
if style&w32.WS_CLIPCHILDREN != 0 {
fmt.Printf("WS_CLIPCHILDREN ")
}
if style&w32.WS_CLIPSIBLINGS != 0 {
fmt.Printf("WS_CLIPSIBLINGS ")
}
if style&w32.WS_DISABLED != 0 {
fmt.Printf("WS_DISABLED ")
}
if style&w32.WS_DLGFRAME != 0 {
fmt.Printf("WS_DLGFRAME ")
}
if style&w32.WS_GROUP != 0 {
fmt.Printf("WS_GROUP ")
}
if style&w32.WS_HSCROLL != 0 {
fmt.Printf("WS_HSCROLL ")
}
if style&w32.WS_MAXIMIZE != 0 {
fmt.Printf("WS_MAXIMIZE ")
}
if style&w32.WS_MAXIMIZEBOX != 0 {
fmt.Printf("WS_MAXIMIZEBOX ")
}
if style&w32.WS_MINIMIZE != 0 {
fmt.Printf("WS_MINIMIZE ")
}
if style&w32.WS_MINIMIZEBOX != 0 {
fmt.Printf("WS_MINIMIZEBOX ")
}
if style&w32.WS_OVERLAPPED != 0 {
fmt.Printf("WS_OVERLAPPED ")
}
if style&w32.WS_POPUP != 0 {
fmt.Printf("WS_POPUP ")
}
if style&w32.WS_SYSMENU != 0 {
fmt.Printf("WS_SYSMENU ")
}
if style&w32.WS_TABSTOP != 0 {
fmt.Printf("WS_TABSTOP ")
}
if style&w32.WS_THICKFRAME != 0 {
fmt.Printf("WS_THICKFRAME ")
}
if style&w32.WS_VISIBLE != 0 {
fmt.Printf("WS_VISIBLE ")
}
if style&w32.WS_VSCROLL != 0 {
fmt.Printf("WS_VSCROLL ")
}
fmt.Printf("\n")
// Do the same for the extended style
extendedStyle := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE))
fmt.Printf("Extended Style: ")
if extendedStyle&w32.WS_EX_ACCEPTFILES != 0 {
fmt.Printf("WS_EX_ACCEPTFILES ")
}
if extendedStyle&w32.WS_EX_APPWINDOW != 0 {
fmt.Printf("WS_EX_APPWINDOW ")
}
if extendedStyle&w32.WS_EX_CLIENTEDGE != 0 {
fmt.Printf("WS_EX_CLIENTEDGE ")
}
if extendedStyle&w32.WS_EX_COMPOSITED != 0 {
fmt.Printf("WS_EX_COMPOSITED ")
}
if extendedStyle&w32.WS_EX_CONTEXTHELP != 0 {
fmt.Printf("WS_EX_CONTEXTHELP ")
}
if extendedStyle&w32.WS_EX_CONTROLPARENT != 0 {
fmt.Printf("WS_EX_CONTROLPARENT ")
}
if extendedStyle&w32.WS_EX_DLGMODALFRAME != 0 {
fmt.Printf("WS_EX_DLGMODALFRAME ")
}
if extendedStyle&w32.WS_EX_LAYERED != 0 {
fmt.Printf("WS_EX_LAYERED ")
}
if extendedStyle&w32.WS_EX_LAYOUTRTL != 0 {
fmt.Printf("WS_EX_LAYOUTRTL ")
}
if extendedStyle&w32.WS_EX_LEFT != 0 {
fmt.Printf("WS_EX_LEFT ")
}
if extendedStyle&w32.WS_EX_LEFTSCROLLBAR != 0 {
fmt.Printf("WS_EX_LEFTSCROLLBAR ")
}
if extendedStyle&w32.WS_EX_LTRREADING != 0 {
fmt.Printf("WS_EX_LTRREADING ")
}
if extendedStyle&w32.WS_EX_MDICHILD != 0 {
fmt.Printf("WS_EX_MDICHILD ")
}
if extendedStyle&w32.WS_EX_NOACTIVATE != 0 {
fmt.Printf("WS_EX_NOACTIVATE ")
}
if extendedStyle&w32.WS_EX_NOINHERITLAYOUT != 0 {
fmt.Printf("WS_EX_NOINHERITLAYOUT ")
}
if extendedStyle&w32.WS_EX_NOPARENTNOTIFY != 0 {
fmt.Printf("WS_EX_NOPARENTNOTIFY ")
}
if extendedStyle&w32.WS_EX_NOREDIRECTIONBITMAP != 0 {
fmt.Printf("WS_EX_NOREDIRECTIONBITMAP ")
}
if extendedStyle&w32.WS_EX_OVERLAPPEDWINDOW != 0 {
fmt.Printf("WS_EX_OVERLAPPEDWINDOW ")
}
if extendedStyle&w32.WS_EX_PALETTEWINDOW != 0 {
fmt.Printf("WS_EX_PALETTEWINDOW ")
}
if extendedStyle&w32.WS_EX_RIGHT != 0 {
fmt.Printf("WS_EX_RIGHT ")
}
if extendedStyle&w32.WS_EX_RIGHTSCROLLBAR != 0 {
fmt.Printf("WS_EX_RIGHTSCROLLBAR ")
}
if extendedStyle&w32.WS_EX_RTLREADING != 0 {
fmt.Printf("WS_EX_RTLREADING ")
}
if extendedStyle&w32.WS_EX_STATICEDGE != 0 {
fmt.Printf("WS_EX_STATICEDGE ")
}
if extendedStyle&w32.WS_EX_TOOLWINDOW != 0 {
fmt.Printf("WS_EX_TOOLWINDOW ")
}
if extendedStyle&w32.WS_EX_TOPMOST != 0 {
fmt.Printf("WS_EX_TOPMOST ")
}
if extendedStyle&w32.WS_EX_TRANSPARENT != 0 {
fmt.Printf("WS_EX_TRANSPARENT ")
}
if extendedStyle&w32.WS_EX_WINDOWEDGE != 0 {
fmt.Printf("WS_EX_WINDOWEDGE ")
}
fmt.Printf("\n")
}
func (w *windowsWebviewWindow) show() {
w32.ShowWindow(w.hwnd, w32.SW_SHOW)
}
func (w *windowsWebviewWindow) hide() {
w32.ShowWindow(w.hwnd, w32.SW_HIDE)
}
func getScreen(hwnd w32.HWND) (*Screen, error) {
hMonitor := w32.MonitorFromWindow(hwnd, w32.MONITOR_DEFAULTTONEAREST)
var mi w32.MONITORINFOEX
mi.CbSize = uint32(unsafe.Sizeof(mi))
w32.GetMonitorInfoEx(hMonitor, &mi)
var thisScreen Screen
thisScreen.X = int(mi.RcMonitor.Left)
thisScreen.Y = int(mi.RcMonitor.Top)
thisScreen.Size = Size{
Width: int(mi.RcMonitor.Right - mi.RcMonitor.Left),
Height: int(mi.RcMonitor.Bottom - mi.RcMonitor.Top),
}
thisScreen.Bounds = Rect{
X: int(mi.RcMonitor.Left),
Y: int(mi.RcMonitor.Top),
Width: int(mi.RcMonitor.Right - mi.RcMonitor.Left),
Height: int(mi.RcMonitor.Bottom - mi.RcMonitor.Top),
}
thisScreen.WorkArea = Rect{
X: int(mi.RcWork.Left),
Y: int(mi.RcWork.Top),
Width: int(mi.RcWork.Right - mi.RcWork.Left),
Height: int(mi.RcWork.Bottom - mi.RcWork.Top),
}
thisScreen.ID = strconv.Itoa(int(hMonitor))
thisScreen.Name = string(utf16.Decode(mi.SzDevice[:]))
var xdpi, ydpi w32.UINT
w32.GetDPIForMonitor(hMonitor, w32.MDT_EFFECTIVE_DPI, &xdpi, &ydpi)
thisScreen.Scale = float32(xdpi) / 96.0
thisScreen.IsPrimary = mi.DwFlags&w32.MONITORINFOF_PRIMARY != 0
// TODO: Get screen rotation
return &thisScreen, nil
}
// Get the screen for the current window
func (w *windowsWebviewWindow) getScreen() (*Screen, error) {
return getScreen(w.hwnd)
}
func (w *windowsWebviewWindow) setFrameless(b bool) {
// Remove or add the frame
if b {
w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w32.WS_VISIBLE|w32.WS_POPUP)
} else {
w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w32.WS_VISIBLE|w32.WS_OVERLAPPEDWINDOW)
}
}
func newWindowImpl(parent *WebviewWindow) *windowsWebviewWindow {
result := &windowsWebviewWindow{
parent: parent,
resizeBorderWidth: int32(w32.GetSystemMetrics(w32.SM_CXSIZEFRAME)),
resizeBorderHeight: int32(w32.GetSystemMetrics(w32.SM_CYSIZEFRAME)),
}
return result
}
func (w *windowsWebviewWindow) openContextMenu(menu *Menu, _ *ContextMenuData) {
// Create the menu
thisMenu := NewPopupMenu(w.hwnd, menu)
thisMenu.Update()
w.currentlyOpenContextMenu = thisMenu
thisMenu.ShowAtCursor()
}
func (w *windowsWebviewWindow) setStyle(b bool, style int) {
currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE))
if currentStyle != 0 {
currentStyle = lo.Ternary(b, currentStyle|style, 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 {
currentStyle = lo.Ternary(b, currentStyle|style, currentStyle&^style)
w32.SetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE, uintptr(currentStyle))
}
}
func (w *windowsWebviewWindow) setBackdropType(backdropType BackdropType) {
if !w32.SupportsBackdropTypes() {
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 = unsafe.Sizeof(accent)
w32.SetWindowCompositionAttribute(w.hwnd, &data)
} else {
w32.EnableTranslucency(w.hwnd, int32(backdropType))
}
}
func (w *windowsWebviewWindow) setIcon(icon w32.HICON) {
w32.SendMessage(w.hwnd, w32.BM_SETIMAGE, w32.IMAGE_ICON, 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_ACTIVATE:
if int(wparam&0xffff) == w32.WA_INACTIVE {
w.parent.emit(events.Windows.WindowInactive)
}
if wparam == w32.WA_ACTIVE {
getNativeApplication().currentWindowID = w.parent.id
w.parent.emit(events.Windows.WindowActive)
}
if wparam == w32.WA_CLICKACTIVE {
getNativeApplication().currentWindowID = w.parent.id
w.parent.emit(events.Windows.WindowClickActive)
}
// If we want to have a frameless window but with the default frame decorations, extend the DWM client area.
// This Option is not affected by returning 0 in WM_NCCALCSIZE.
// As a result we have hidden the titlebar but still have the default window frame styling.
// See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks
if w.framelessWithDecorations() {
w32.ExtendFrameIntoClientArea(w.hwnd, true)
}
case w32.WM_CLOSE:
w.parent.emit(events.Windows.WindowClose)
return 0
case w32.WM_KILLFOCUS:
if w.focusingChromium {
return 0
}
w.parent.emit(events.Windows.WindowKillFocus)
case w32.WM_ENTERSIZEMOVE:
// This is needed to close open dropdowns when moving the window https://github.com/MicrosoftEdge/WebView2Feedback/issues/2290
w32.SetFocus(w.hwnd)
case w32.WM_SETFOCUS:
w.focus()
w.parent.emit(events.Windows.WindowSetFocus)
case w32.WM_MOVE, w32.WM_MOVING:
_ = w.chromium.NotifyParentWindowPositionChanged()
// Check for keypress
case w32.WM_KEYDOWN:
w.processKeyBinding(uint(wparam))
case w32.WM_SIZE:
switch wparam {
case w32.SIZE_MAXIMIZED:
w.parent.emit(events.Windows.WindowMaximise)
case w32.SIZE_RESTORED:
w.parent.emit(events.Windows.WindowRestore)
case w32.SIZE_MINIMIZED:
w.parent.emit(events.Windows.WindowMinimise)
}
if w.parent.options.Frameless && wparam == w32.SIZE_MINIMIZED {
// If the window is frameless, and we are minimizing, then we need to suppress the Resize on the
// WebView2. If we don't do this, restoring does not work as expected and first restores with some wrong
// size during the restore animation and only fully renders when the animation is done. This highly
// depends on the content in the WebView, see https://github.com/wailsapp/wails/issues/1319
} else if w.resizeDebouncer != nil {
w.resizeDebouncer(func() {
InvokeSync(func() {
w.chromium.Resize()
})
})
} else {
w.chromium.Resize()
}
return 0
case w32.WM_GETMINMAXINFO:
mmi := (*w32.MINMAXINFO)(unsafe.Pointer(lparam))
hasConstraints := false
options := w.parent.options
if options.MinWidth > 0 || options.MinHeight > 0 {
hasConstraints = true
width, height := w.scaleWithWindowDPI(options.MinWidth, options.MinHeight)
if width > 0 {
mmi.PtMinTrackSize.X = int32(width)
}
if height > 0 {
mmi.PtMinTrackSize.Y = int32(height)
}
}
if options.MaxWidth > 0 || options.MaxHeight > 0 {
hasConstraints = true
width, height := w.scaleWithWindowDPI(options.MaxWidth, options.MaxHeight)
if width > 0 {
mmi.PtMaxTrackSize.X = int32(width)
}
if height > 0 {
mmi.PtMaxTrackSize.Y = int32(height)
}
}
if hasConstraints {
return 0
}
case w32.WM_DPICHANGED:
newWindowSize := (*w32.RECT)(unsafe.Pointer(lparam))
w32.SetWindowPos(w.hwnd,
uintptr(0),
int(newWindowSize.Left),
int(newWindowSize.Top),
int(newWindowSize.Right-newWindowSize.Left),
int(newWindowSize.Bottom-newWindowSize.Top),
w32.SWP_NOZORDER|w32.SWP_NOACTIVATE)
w.parent.emit(events.Common.WindowDPIChanged)
}
if w.parent.options.Windows.WindowMask != nil {
switch msg {
case w32.WM_NCHITTEST:
if w.parent.options.Windows.WindowMaskDraggable {
return w32.HTCAPTION
}
return w32.HTCLIENT
}
}
if w.menu != nil || w.currentlyOpenContextMenu != nil {
switch msg {
case w32.WM_COMMAND:
cmdMsgID := int(wparam & 0xffff)
switch cmdMsgID {
default:
var processed bool
if w.currentlyOpenContextMenu != nil {
processed = w.currentlyOpenContextMenu.ProcessCommand(cmdMsgID)
w.currentlyOpenContextMenu = nil
}
if !processed && w.menu != nil {
processed = w.menu.ProcessCommand(cmdMsgID)
}
}
}
}
if options := w.parent.options; options.Frameless {
switch msg {
case w32.WM_ACTIVATE:
// If we want to have a frameless window but with the default frame decorations, extend the DWM client area.
// This Option is not affected by returning 0 in WM_NCCALCSIZE.
// As a result we have hidden the titlebar but still have the default window frame styling.
// See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks
if w.framelessWithDecorations() {
w32.ExtendFrameIntoClientArea(w.hwnd, true)
}
case w32.WM_NCCALCSIZE:
// Disable the standard frame by allowing the client area to take the full
// window size.
// See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks
// This hides the titlebar and also disables the resizing from user interaction because the standard frame is not
// shown. We still need the WS_THICKFRAME style to enable resizing from the frontend.
if wparam != 0 {
rgrc := (*w32.RECT)(unsafe.Pointer(lparam))
if w.isCurrentlyFullscreen {
// In Full-Screen mode we don't need to adjust anything
// It essential we have the flag here, that is set before SetWindowPos in fullscreen/unfullscreen
// because the native size might not yet reflect we are in fullscreen during this event!
w.chromium.SetPadding(edge.Rect{})
} else if w.isMaximised() {
// If the window is maximized we must adjust the client area to the work area of the monitor. Otherwise
// some content goes beyond the visible part of the monitor.
// Make sure to use the provided RECT to get the monitor, because during maximizig there might be
// a wrong monitor returned in multiscreen mode when using MonitorFromWindow.
// See: https://github.com/MicrosoftEdge/WebView2Feedback/issues/2549
monitor := w32.MonitorFromRect(rgrc, w32.MONITOR_DEFAULTTONULL)
var monitorInfo w32.MONITORINFO
monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
if monitor != 0 && w32.GetMonitorInfo(monitor, &monitorInfo) {
*rgrc = monitorInfo.RcWork
maxWidth := options.MaxWidth
maxHeight := options.MaxHeight
if maxWidth > 0 || maxHeight > 0 {
var dpiX, dpiY uint
w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
maxWidth := int32(ScaleWithDPI(maxWidth, dpiX))
if maxWidth > 0 && rgrc.Right-rgrc.Left > maxWidth {
rgrc.Right = rgrc.Left + maxWidth
}
maxHeight := int32(ScaleWithDPI(maxHeight, dpiY))
if maxHeight > 0 && rgrc.Bottom-rgrc.Top > maxHeight {
rgrc.Bottom = rgrc.Top + maxHeight
}
}
}
w.chromium.SetPadding(edge.Rect{})
} else {
// This is needed to workaround the resize flickering in frameless mode with WindowDecorations
// See: https://stackoverflow.com/a/6558508
// The workaround originally suggests to decrese the bottom 1px, but that seems to bring up a thin
// white line on some Windows-Versions, due to DrawBackground using also this reduces ClientSize.
// Increasing the bottom also worksaround the flickering but we would loose 1px of the WebView content
// therefore let's pad the content with 1px at the bottom.
rgrc.Bottom += 1
w.chromium.SetPadding(edge.Rect{Bottom: 1})
}
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 (w *windowsWebviewWindow) scaleToDefaultDPI(width, height int) (int, int) {
dpix, dpiy := w.DPI()
scaledWidth := ScaleToDefaultDPI(width, dpix)
scaledHeight := ScaleToDefaultDPI(height, dpiy)
return scaledWidth, scaledHeight
}
func (w *windowsWebviewWindow) setWindowMask(imageData []byte) {
// Set the window to a WS_EX_LAYERED window
newStyle := w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE) | w32.WS_EX_LAYERED
if w.isAlwaysOnTop() {
newStyle |= w32.WS_EX_TOPMOST
}
// Save the current window style
w.previousWindowExStyle = uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE))
w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, uint32(newStyle))
data, err := pngToImage(imageData)
if err != nil {
panic(err)
}
bitmap, err := w32.CreateHBITMAPFromImage(data)
hdc := w32.CreateCompatibleDC(0)
defer w32.DeleteDC(hdc)
oldBitmap := w32.SelectObject(hdc, bitmap)
defer w32.SelectObject(hdc, oldBitmap)
screenDC := w32.GetDC(0)
defer w32.ReleaseDC(0, screenDC)
size := w32.SIZE{CX: int32(data.Bounds().Dx()), CY: int32(data.Bounds().Dy())}
ptSrc := w32.POINT{X: 0, Y: 0}
ptDst := w32.POINT{X: int32(w.width()), Y: int32(w.height())}
blend := w32.BLENDFUNCTION{
BlendOp: w32.AC_SRC_OVER,
BlendFlags: 0,
SourceConstantAlpha: 255,
AlphaFormat: w32.AC_SRC_ALPHA,
}
w32.UpdateLayeredWindow(w.hwnd, screenDC, &ptDst, &size, hdc, &ptSrc, 0, &blend, w32.ULW_ALPHA)
}
func (w *windowsWebviewWindow) isAlwaysOnTop() bool {
return w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)&w32.WS_EX_TOPMOST != 0
}
// processMessage is given a message sent from JS via the postMessage API
// We put it on the global window message buffer to be processed centrally
func (w *windowsWebviewWindow) processMessage(message string) {
// We send all messages to the centralised window message buffer
windowMessageBuffer <- &windowMessage{
windowId: w.parent.id,
message: message,
}
}
func (w *windowsWebviewWindow) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) {
// Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but
// we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request.
if reqHeaders, err := req.GetHeaders(); err == nil {
useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent)
useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ")
err = reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent)
if err != nil {
globalApplication.fatal("Error setting UserAgent header: " + err.Error())
}
err = reqHeaders.SetHeader(webViewRequestHeaderWindowId, strconv.FormatUint(uint64(w.parent.id), 10))
if err != nil {
globalApplication.fatal("Error setting WindowId header: " + err.Error())
}
err = reqHeaders.Release()
if err != nil {
globalApplication.fatal("Error releasing headers: " + err.Error())
}
}
if globalApplication.assets == nil {
// We are using the devServer let the WebView2 handle the request with its default handler
return
}
//Get the request
uri, _ := req.GetUri()
reqUri, err := url.ParseRequestURI(uri)
if err != nil {
globalApplication.error("Unable to parse request uri", "uri", uri, "error", err)
return
}
if reqUri.Scheme != "http" {
// Let the WebView2 handle the request with its default handler
return
} else if !strings.HasPrefix(reqUri.Host, "wails.localhost") {
// Let the WebView2 handle the request with its default handler
return
}
webviewRequest, err := webview.NewRequest(
w.chromium.Environment(),
args,
func(fn func()) {
InvokeSync(fn)
})
if err != nil {
globalApplication.error("%s: NewRequest failed: %s", uri, err)
return
}
webviewRequests <- &webViewAssetRequest{
Request: webviewRequest,
windowId: w.parent.id,
windowName: globalApplication.getWindowForID(w.parent.id).Name(),
}
}
func (w *windowsWebviewWindow) setupChromium() {
chromium := w.chromium
debugMode := globalApplication.isDebugMode
opts := w.parent.options.Windows
webview2version, err := webviewloader.GetAvailableCoreWebView2BrowserVersionString(globalApplication.options.Windows.WebviewBrowserPath)
if err != nil {
globalApplication.error("Error getting WebView2 version: " + err.Error())
return
}
globalApplication.capabilities = capabilities.NewCapabilities(webview2version)
disableFeatues := []string{}
if !opts.EnableFraudulentWebsiteWarnings {
disableFeatues = append(disableFeatues, "msSmartScreenProtection")
}
if opts.WebviewGpuIsDisabled {
chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, "--disable-gpu")
}
if len(disableFeatues) > 0 {
arg := fmt.Sprintf("--disable-features=%s", strings.Join(disableFeatues, ","))
chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg)
}
enableFeatures := []string{"msWebView2BrowserHitTransparent"}
if len(enableFeatures) > 0 {
arg := fmt.Sprintf("--enable-features=%s", strings.Join(enableFeatures, ","))
chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg)
}
chromium.DataPath = globalApplication.options.Windows.WebviewUserDataPath
chromium.BrowserPath = globalApplication.options.Windows.WebviewBrowserPath
if opts.Permissions != nil {
for permission, state := range opts.Permissions {
chromium.SetPermission(edge.CoreWebView2PermissionKind(permission),
edge.CoreWebView2PermissionState(state))
}
}
chromium.MessageCallback = w.processMessage
chromium.MessageWithAdditionalObjectsCallback = w.processMessageWithAdditionalObjects
chromium.WebResourceRequestedCallback = w.processRequest
chromium.ContainsFullScreenElementChangedCallback = w.fullscreenChanged
chromium.NavigationCompletedCallback = w.navigationCompleted
chromium.AcceleratorKeyCallback = w.processKeyBinding
chromium.Embed(w.hwnd)
if chromium.HasCapability(edge.SwipeNavigation) {
err := chromium.PutIsSwipeNavigationEnabled(opts.EnableSwipeGestures)
if err != nil {
globalApplication.fatal(err.Error())
}
}
if chromium.HasCapability(edge.AllowExternalDrop) {
err := chromium.AllowExternalDrag(false)
if err != nil {
globalApplication.fatal(err.Error())
}
}
if w.parent.options.EnableDragAndDrop {
w.dropTarget = w32.NewDropTarget()
w.dropTarget.OnDrop = func(files []string) {
w.parent.emit(events.Windows.WindowDragDrop)
windowDragAndDropBuffer <- &dragAndDropMessage{
windowId: windowID,
filenames: files,
}
}
if opts.OnEnterEffect != 0 {
w.dropTarget.OnEnterEffect = convertEffect(opts.OnEnterEffect)
}
if opts.OnOverEffect != 0 {
w.dropTarget.OnOverEffect = convertEffect(opts.OnOverEffect)
}
w.dropTarget.OnEnter = func() {
w.parent.emit(events.Windows.WindowDragEnter)
}
w.dropTarget.OnLeave = func() {
w.parent.emit(events.Windows.WindowDragLeave)
}
w.dropTarget.OnOver = func() {
w.parent.emit(events.Windows.WindowDragOver)
}
// Enumerate all the child windows for this window and register them as drop targets
w32.EnumChildWindows(w.hwnd, func(hwnd w32.HWND, lparam w32.LPARAM) w32.LRESULT {
// Check if the window class is "Chrome_RenderWidgetHostHWND"
// If it is, then we register it as a drop target
//windowName := w32.GetClassName(hwnd)
//println(windowName)
//if windowName == "Chrome_RenderWidgetHostHWND" {
err := w32.RegisterDragDrop(hwnd, w.dropTarget)
if err != nil && err != syscall.Errno(w32.DRAGDROP_E_ALREADYREGISTERED) {
globalApplication.error("Error registering drag and drop: " + err.Error())
}
//}
return 1
})
}
// We will get round to this
//if chromium.HasCapability(edge.AllowExternalDrop) {
// err := chromium.AllowExternalDrag(w.parent.options.EnableDragAndDrop)
// if err != nil {
// globalApplication.fatal(err.Error())
// }
// if w.parent.options.EnableDragAndDrop {
// chromium.MessageWithAdditionalObjectsCallback = w.processMessageWithAdditionalObjects
// }
//}
chromium.Resize()
settings, err := chromium.GetSettings()
if err != nil {
globalApplication.fatal(err.Error())
}
err = settings.PutAreDefaultContextMenusEnabled(debugMode || !w.parent.options.DefaultContextMenuDisabled)
if err != nil {
globalApplication.fatal(err.Error())
}
w.enableDevTools(settings)
if w.parent.options.Zoom > 0.0 {
chromium.PutZoomFactor(w.parent.options.Zoom)
}
err = settings.PutIsZoomControlEnabled(w.parent.options.ZoomControlEnabled)
if err != nil {
globalApplication.fatal(err.Error())
}
err = settings.PutIsStatusBarEnabled(false)
if err != nil {
globalApplication.fatal(err.Error())
}
err = settings.PutAreBrowserAcceleratorKeysEnabled(false)
if err != nil {
globalApplication.fatal(err.Error())
}
err = settings.PutIsSwipeNavigationEnabled(false)
if err != nil {
globalApplication.fatal(err.Error())
}
if debugMode && w.parent.options.OpenInspectorOnStartup {
chromium.OpenDevToolsWindow()
}
// Set background colour
w.setBackgroundColour(w.parent.options.BackgroundColour)
chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow)
chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)
if w.parent.options.HTML != "" {
var script string
if w.parent.options.JS != "" {
script = w.parent.options.JS
}
if w.parent.options.CSS != "" {
script += fmt.Sprintf("; addEventListener(\"DOMContentLoaded\", (event) => { document.head.appendChild(document.createElement('style')).innerHTML=\"%s\"; });", strings.ReplaceAll(w.parent.options.CSS, `"`, `\"`))
}
chromium.Init(script)
chromium.NavigateToString(w.parent.options.HTML)
} else {
startURL, err := assetserver.GetStartURL(w.parent.options.URL)
if err != nil {
globalApplication.fatal(err.Error())
}
chromium.Navigate(startURL)
}
}
func (w *windowsWebviewWindow) fullscreenChanged(sender *edge.ICoreWebView2, _ *edge.ICoreWebView2ContainsFullScreenElementChangedEventArgs) {
isFullscreen, err := sender.GetContainsFullScreenElement()
if err != nil {
globalApplication.fatal("Fatal error in callback fullscreenChanged: " + err.Error())
}
if isFullscreen {
w.fullscreen()
} else {
w.unfullscreen()
}
}
func convertEffect(effect DragEffect) w32.DWORD {
switch effect {
case DragEffectCopy:
return w32.DROPEFFECT_COPY
case DragEffectMove:
return w32.DROPEFFECT_MOVE
case DragEffectLink:
return w32.DROPEFFECT_LINK
default:
return w32.DROPEFFECT_NONE
}
}
func (w *windowsWebviewWindow) flash(enabled bool) {
w32.FlashWindow(w.hwnd, enabled)
}
func (w *windowsWebviewWindow) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) {
// Emit DomReady Event
windowEvents <- &windowEvent{EventID: uint(events.Windows.WebViewNavigationCompleted), WindowID: w.parent.id}
if w.hasStarted {
// NavigationCompleted is triggered for every Load. If an application uses reloads the Hide/Show will trigger
// a flickering of the window with every reload. So we only do this once for the first NavigationCompleted.
return
}
w.hasStarted = true
wasFocused := w.isFocused()
// Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026
err := w.chromium.Hide()
if err != nil {
globalApplication.fatal(err.Error())
}
err = w.chromium.Show()
if err != nil {
globalApplication.fatal(err.Error())
}
if wasFocused {
w.focus()
}
//f.mainWindow.hasBeenShown = true
}
func (w *windowsWebviewWindow) processKeyBinding(vkey uint) bool {
globalApplication.debug("Processing key binding", "vkey", vkey)
// Get the keyboard state and convert to an accelerator
var keyState [256]byte
if !w32.GetKeyboardState(keyState[:]) {
globalApplication.error("Error getting keyboard state")
return false
}
var acc accelerator
// Check if CTRL is pressed
if keyState[w32.VK_CONTROL]&0x80 != 0 {
acc.Modifiers = append(acc.Modifiers, ControlKey)
}
// Check if ALT is pressed
if keyState[w32.VK_MENU]&0x80 != 0 {
acc.Modifiers = append(acc.Modifiers, OptionOrAltKey)
}
// Check if SHIFT is pressed
if keyState[w32.VK_SHIFT]&0x80 != 0 {
acc.Modifiers = append(acc.Modifiers, ShiftKey)
}
// Check if WIN is pressed
if keyState[w32.VK_LWIN]&0x80 != 0 || keyState[w32.VK_RWIN]&0x80 != 0 {
acc.Modifiers = append(acc.Modifiers, SuperKey)
}
if vkey != w32.VK_CONTROL && vkey != w32.VK_MENU && vkey != w32.VK_SHIFT && vkey != w32.VK_LWIN && vkey != w32.VK_RWIN {
// Convert the vkey to a string
accKey, ok := VirtualKeyCodes[vkey]
if !ok {
return false
}
acc.Key = accKey
}
accKey := acc.String()
globalApplication.debug("Processing key binding", "vkey", vkey, "acc", accKey)
// Process the key binding
if w.parent.processKeyBinding(accKey) {
return true
}
if accKey == "alt+f4" {
w32.PostMessage(w.hwnd, w32.WM_CLOSE, 0, 0)
return true
}
return false
}
func (w *windowsWebviewWindow) processMessageWithAdditionalObjects(message string, sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs) {
if strings.HasPrefix(message, "FilesDropped") {
objs, err := args.GetAdditionalObjects()
if err != nil {
globalApplication.error(err.Error())
return
}
defer func() {
err = objs.Release()
if err != nil {
globalApplication.error("Error releasing objects: " + err.Error())
}
}()
count, err := objs.GetCount()
if err != nil {
globalApplication.error(err.Error())
return
}
var filenames []string
for i := uint32(0); i < count; i++ {
_file, err := objs.GetValueAtIndex(i)
if err != nil {
globalApplication.error("cannot get value at %d : %s", i, err.Error())
return
}
file := (*edge.ICoreWebView2File)(unsafe.Pointer(_file))
// TODO: Fix this
defer file.Release()
filepath, err := file.GetPath()
if err != nil {
globalApplication.error("cannot get path for object at %d : %s", i, err.Error())
return
}
filenames = append(filenames, filepath)
}
addDragAndDropMessage(w.parent.id, filenames)
return
}
}
func ScaleWithDPI(pixels int, dpi uint) int {
return (pixels * int(dpi)) / 96
}
func ScaleToDefaultDPI(pixels int, dpi uint) int {
return (pixels * 96) / int(dpi)
}
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
}