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