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 }