github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/window.go (about)

     1  //go:build windows
     2  
     3  package windows
     4  
     5  import (
     6  	"github.com/wailsapp/go-webview2/pkg/edge"
     7  	"sync"
     8  	"unsafe"
     9  
    10  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/win32"
    11  	"github.com/secoba/wails/v2/internal/system/operatingsystem"
    12  
    13  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc"
    14  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32"
    15  	"github.com/secoba/wails/v2/pkg/menu"
    16  	"github.com/secoba/wails/v2/pkg/options"
    17  	winoptions "github.com/secoba/wails/v2/pkg/options/windows"
    18  )
    19  
    20  type Window struct {
    21  	winc.Form
    22  	frontendOptions                          *options.App
    23  	applicationMenu                          *menu.Menu
    24  	minWidth, minHeight, maxWidth, maxHeight int
    25  	versionInfo                              *operatingsystem.WindowsVersionInfo
    26  	isDarkMode                               bool
    27  	isActive                                 bool
    28  	hasBeenShown                             bool
    29  
    30  	// Theme
    31  	theme        winoptions.Theme
    32  	themeChanged bool
    33  
    34  	framelessWithDecorations bool
    35  
    36  	OnSuspend func()
    37  	OnResume  func()
    38  
    39  	chromium *edge.Chromium
    40  }
    41  
    42  func NewWindow(parent winc.Controller, appoptions *options.App, versionInfo *operatingsystem.WindowsVersionInfo, chromium *edge.Chromium) *Window {
    43  	windowsOptions := appoptions.Windows
    44  
    45  	result := &Window{
    46  		frontendOptions: appoptions,
    47  		minHeight:       appoptions.MinHeight,
    48  		minWidth:        appoptions.MinWidth,
    49  		maxHeight:       appoptions.MaxHeight,
    50  		maxWidth:        appoptions.MaxWidth,
    51  		versionInfo:     versionInfo,
    52  		isActive:        true,
    53  		themeChanged:    true,
    54  		chromium:        chromium,
    55  
    56  		framelessWithDecorations: appoptions.Frameless && (windowsOptions == nil || !windowsOptions.DisableFramelessWindowDecorations),
    57  	}
    58  	result.SetIsForm(true)
    59  
    60  	var exStyle int
    61  	if windowsOptions != nil {
    62  		exStyle = w32.WS_EX_CONTROLPARENT | w32.WS_EX_APPWINDOW
    63  		if windowsOptions.WindowIsTranslucent {
    64  			exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP
    65  		}
    66  	}
    67  	if appoptions.AlwaysOnTop {
    68  		exStyle |= w32.WS_EX_TOPMOST
    69  	}
    70  
    71  	var dwStyle = w32.WS_OVERLAPPEDWINDOW
    72  
    73  	winc.RegClassOnlyOnce("wailsWindow")
    74  	handle := winc.CreateWindow("wailsWindow", parent, uint(exStyle), uint(dwStyle))
    75  	result.SetHandle(handle)
    76  	winc.RegMsgHandler(result)
    77  	result.SetParent(parent)
    78  
    79  	loadIcon := true
    80  	if windowsOptions != nil && windowsOptions.DisableWindowIcon == true {
    81  		loadIcon = false
    82  	}
    83  	if loadIcon {
    84  		if ico, err := winc.NewIconFromResource(winc.GetAppInstance(), uint16(winc.AppIconID)); err == nil {
    85  			result.SetIcon(0, ico)
    86  		}
    87  	}
    88  
    89  	if appoptions.BackgroundColour != nil {
    90  		win32.SetBackgroundColour(result.Handle(), appoptions.BackgroundColour.R, appoptions.BackgroundColour.G, appoptions.BackgroundColour.B)
    91  	}
    92  
    93  	if windowsOptions != nil {
    94  		result.theme = windowsOptions.Theme
    95  	} else {
    96  		result.theme = winoptions.SystemDefault
    97  	}
    98  
    99  	result.SetSize(appoptions.Width, appoptions.Height)
   100  	result.SetText(appoptions.Title)
   101  	result.EnableSizable(!appoptions.DisableResize)
   102  	if !appoptions.Fullscreen {
   103  		result.EnableMaxButton(!appoptions.DisableResize)
   104  		result.SetMinSize(appoptions.MinWidth, appoptions.MinHeight)
   105  		result.SetMaxSize(appoptions.MaxWidth, appoptions.MaxHeight)
   106  	}
   107  
   108  	result.UpdateTheme()
   109  
   110  	if windowsOptions != nil {
   111  		result.OnSuspend = windowsOptions.OnSuspend
   112  		result.OnResume = windowsOptions.OnResume
   113  		if windowsOptions.WindowIsTranslucent {
   114  			if !win32.SupportsBackdropTypes() {
   115  				result.SetTranslucentBackground()
   116  			} else {
   117  				win32.EnableTranslucency(result.Handle(), win32.BackdropType(windowsOptions.BackdropType))
   118  			}
   119  		}
   120  
   121  		if windowsOptions.DisableWindowIcon {
   122  			result.DisableIcon()
   123  		}
   124  	}
   125  
   126  	// Dlg forces display of focus rectangles, as soon as the user starts to type.
   127  	w32.SendMessage(result.Handle(), w32.WM_CHANGEUISTATE, w32.UIS_INITIALIZE, 0)
   128  
   129  	result.SetFont(winc.DefaultFont)
   130  
   131  	if appoptions.Menu != nil {
   132  		result.SetApplicationMenu(appoptions.Menu)
   133  	}
   134  
   135  	return result
   136  }
   137  
   138  func (w *Window) Fullscreen() {
   139  	if w.Form.IsFullScreen() {
   140  		return
   141  	}
   142  	if w.framelessWithDecorations {
   143  		win32.ExtendFrameIntoClientArea(w.Handle(), false)
   144  	}
   145  	w.Form.SetMaxSize(0, 0)
   146  	w.Form.SetMinSize(0, 0)
   147  	w.Form.Fullscreen()
   148  }
   149  
   150  func (w *Window) UnFullscreen() {
   151  	if !w.Form.IsFullScreen() {
   152  		return
   153  	}
   154  	if w.framelessWithDecorations {
   155  		win32.ExtendFrameIntoClientArea(w.Handle(), true)
   156  	}
   157  	w.Form.UnFullscreen()
   158  	w.SetMinSize(w.minWidth, w.minHeight)
   159  	w.SetMaxSize(w.maxWidth, w.maxHeight)
   160  }
   161  
   162  func (w *Window) Restore() {
   163  	if w.Form.IsFullScreen() {
   164  		w.UnFullscreen()
   165  	} else {
   166  		w.Form.Restore()
   167  	}
   168  }
   169  
   170  func (w *Window) SetMinSize(minWidth int, minHeight int) {
   171  	w.minWidth = minWidth
   172  	w.minHeight = minHeight
   173  	w.Form.SetMinSize(minWidth, minHeight)
   174  }
   175  
   176  func (w *Window) SetMaxSize(maxWidth int, maxHeight int) {
   177  	w.maxWidth = maxWidth
   178  	w.maxHeight = maxHeight
   179  	w.Form.SetMaxSize(maxWidth, maxHeight)
   180  }
   181  
   182  func (w *Window) IsVisible() bool {
   183  	return win32.IsVisible(w.Handle())
   184  }
   185  
   186  func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
   187  
   188  	switch msg {
   189  	case win32.WM_POWERBROADCAST:
   190  		switch wparam {
   191  		case win32.PBT_APMSUSPEND:
   192  			if w.OnSuspend != nil {
   193  				w.OnSuspend()
   194  			}
   195  		case win32.PBT_APMRESUMEAUTOMATIC:
   196  			if w.OnResume != nil {
   197  				w.OnResume()
   198  			}
   199  		}
   200  	case w32.WM_SETTINGCHANGE:
   201  		settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lparam)))
   202  		if settingChanged == "ImmersiveColorSet" {
   203  			w.themeChanged = true
   204  			w.UpdateTheme()
   205  		}
   206  		return 0
   207  	case w32.WM_NCLBUTTONDOWN:
   208  		w32.SetFocus(w.Handle())
   209  	case w32.WM_MOVE, w32.WM_MOVING:
   210  		w.chromium.NotifyParentWindowPositionChanged()
   211  	case w32.WM_ACTIVATE:
   212  		//if !w.frontendOptions.Frameless {
   213  		w.themeChanged = true
   214  		if int(wparam) == w32.WA_INACTIVE {
   215  			w.isActive = false
   216  			w.UpdateTheme()
   217  		} else {
   218  			w.isActive = true
   219  			w.UpdateTheme()
   220  			//}
   221  		}
   222  
   223  	case 0x02E0: //w32.WM_DPICHANGED
   224  		newWindowSize := (*w32.RECT)(unsafe.Pointer(lparam))
   225  		w32.SetWindowPos(w.Handle(),
   226  			uintptr(0),
   227  			int(newWindowSize.Left),
   228  			int(newWindowSize.Top),
   229  			int(newWindowSize.Right-newWindowSize.Left),
   230  			int(newWindowSize.Bottom-newWindowSize.Top),
   231  			w32.SWP_NOZORDER|w32.SWP_NOACTIVATE)
   232  	}
   233  
   234  	if w.frontendOptions.Frameless {
   235  		switch msg {
   236  		case w32.WM_ACTIVATE:
   237  			// If we want to have a frameless window but with the default frame decorations, extend the DWM client area.
   238  			// This Option is not affected by returning 0 in WM_NCCALCSIZE.
   239  			// As a result we have hidden the titlebar but still have the default window frame styling.
   240  			// See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks
   241  			if w.framelessWithDecorations {
   242  				win32.ExtendFrameIntoClientArea(w.Handle(), true)
   243  			}
   244  		case w32.WM_NCCALCSIZE:
   245  			// Disable the standard frame by allowing the client area to take the full
   246  			// window size.
   247  			// See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks
   248  			// This hides the titlebar and also disables the resizing from user interaction because the standard frame is not
   249  			// shown. We still need the WS_THICKFRAME style to enable resizing from the frontend.
   250  			if wparam != 0 {
   251  				rgrc := (*w32.RECT)(unsafe.Pointer(lparam))
   252  				if w.Form.IsFullScreen() {
   253  					// In Full-Screen mode we don't need to adjust anything
   254  					w.chromium.SetPadding(edge.Rect{})
   255  				} else if w.IsMaximised() {
   256  					// If the window is maximized we must adjust the client area to the work area of the monitor. Otherwise
   257  					// some content goes beyond the visible part of the monitor.
   258  					// Make sure to use the provided RECT to get the monitor, because during maximizig there might be
   259  					// a wrong monitor returned in multi screen mode when using MonitorFromWindow.
   260  					// See: https://github.com/MicrosoftEdge/WebView2Feedback/issues/2549
   261  					monitor := w32.MonitorFromRect(rgrc, w32.MONITOR_DEFAULTTONULL)
   262  
   263  					var monitorInfo w32.MONITORINFO
   264  					monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
   265  					if monitor != 0 && w32.GetMonitorInfo(monitor, &monitorInfo) {
   266  						*rgrc = monitorInfo.RcWork
   267  
   268  						maxWidth := w.frontendOptions.MaxWidth
   269  						maxHeight := w.frontendOptions.MaxHeight
   270  						if maxWidth > 0 || maxHeight > 0 {
   271  							var dpiX, dpiY uint
   272  							w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
   273  
   274  							maxWidth := int32(winc.ScaleWithDPI(maxWidth, dpiX))
   275  							if maxWidth > 0 && rgrc.Right-rgrc.Left > maxWidth {
   276  								rgrc.Right = rgrc.Left + maxWidth
   277  							}
   278  
   279  							maxHeight := int32(winc.ScaleWithDPI(maxHeight, dpiY))
   280  							if maxHeight > 0 && rgrc.Bottom-rgrc.Top > maxHeight {
   281  								rgrc.Bottom = rgrc.Top + maxHeight
   282  							}
   283  						}
   284  					}
   285  					w.chromium.SetPadding(edge.Rect{})
   286  				} else {
   287  					// This is needed to workaround the resize flickering in frameless mode with WindowDecorations
   288  					// See: https://stackoverflow.com/a/6558508
   289  					// The workaround originally suggests to decrese the bottom 1px, but that seems to bring up a thin
   290  					// white line on some Windows-Versions, due to DrawBackground using also this reduces ClientSize.
   291  					// Increasing the bottom also worksaround the flickering but we would loose 1px of the WebView content
   292  					// therefore let's pad the content with 1px at the bottom.
   293  					rgrc.Bottom += 1
   294  					w.chromium.SetPadding(edge.Rect{Bottom: 1})
   295  				}
   296  				return 0
   297  			}
   298  		}
   299  	}
   300  	return w.Form.WndProc(msg, wparam, lparam)
   301  }
   302  
   303  func (w *Window) IsMaximised() bool {
   304  	return win32.IsWindowMaximised(w.Handle())
   305  }
   306  
   307  func (w *Window) IsMinimised() bool {
   308  	return win32.IsWindowMinimised(w.Handle())
   309  }
   310  
   311  func (w *Window) IsNormal() bool {
   312  	return win32.IsWindowNormal(w.Handle())
   313  }
   314  
   315  func (w *Window) IsFullScreen() bool {
   316  	return win32.IsWindowFullScreen(w.Handle())
   317  }
   318  
   319  func (w *Window) SetTheme(theme winoptions.Theme) {
   320  	w.theme = theme
   321  	w.themeChanged = true
   322  	w.Invoke(func() {
   323  		w.UpdateTheme()
   324  	})
   325  }
   326  
   327  func invokeSync[T any](cba *Window, fn func() (T, error)) (res T, err error) {
   328  	var wg sync.WaitGroup
   329  	wg.Add(1)
   330  	cba.Invoke(func() {
   331  		res, err = fn()
   332  		wg.Done()
   333  	})
   334  	wg.Wait()
   335  	return res, err
   336  }