5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 21:31:39 +08:00
wails/v3/pkg/application/webview_window_windows.go

566 lines
14 KiB
Go

//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
}