github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/frontend.go (about) 1 //go:build windows 2 // +build windows 3 4 package windows 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "log" 11 "net" 12 "net/url" 13 "os" 14 "runtime" 15 "strings" 16 "sync" 17 "text/template" 18 "time" 19 20 "github.com/bep/debounce" 21 "github.com/secoba/wails/v2/internal/binding" 22 "github.com/secoba/wails/v2/internal/frontend" 23 "github.com/secoba/wails/v2/internal/frontend/desktop/windows/win32" 24 "github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc" 25 "github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32" 26 wailsruntime "github.com/secoba/wails/v2/internal/frontend/runtime" 27 "github.com/secoba/wails/v2/internal/logger" 28 "github.com/secoba/wails/v2/internal/system/operatingsystem" 29 "github.com/secoba/wails/v2/pkg/assetserver" 30 "github.com/secoba/wails/v2/pkg/assetserver/webview" 31 "github.com/secoba/wails/v2/pkg/options" 32 "github.com/secoba/wails/v2/pkg/options/windows" 33 "github.com/wailsapp/go-webview2/pkg/edge" 34 ) 35 36 const startURL = "http://wails.localhost/" 37 38 var secondInstanceBuffer = make(chan options.SecondInstanceData, 1) 39 40 type Screen = frontend.Screen 41 42 type Frontend struct { 43 44 // Context 45 ctx context.Context 46 47 frontendOptions *options.App 48 logger *logger.Logger 49 chromium *edge.Chromium 50 debug bool 51 devtoolsEnabled bool 52 53 // Assets 54 assets *assetserver.AssetServer 55 startURL *url.URL 56 57 // main window handle 58 mainWindow *Window 59 bindings *binding.Bindings 60 dispatcher frontend.Dispatcher 61 62 hasStarted bool 63 64 // Windows build number 65 versionInfo *operatingsystem.WindowsVersionInfo 66 resizeDebouncer func(f func()) 67 } 68 69 func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend { 70 71 // Get Windows build number 72 versionInfo, _ := operatingsystem.GetWindowsVersionInfo() 73 74 result := &Frontend{ 75 frontendOptions: appoptions, 76 logger: myLogger, 77 bindings: appBindings, 78 dispatcher: dispatcher, 79 ctx: ctx, 80 versionInfo: versionInfo, 81 } 82 83 if appoptions.Windows != nil { 84 if appoptions.Windows.ResizeDebounceMS > 0 { 85 result.resizeDebouncer = debounce.New(time.Duration(appoptions.Windows.ResizeDebounceMS) * time.Millisecond) 86 } 87 } 88 89 // We currently can't use wails://wails/ as other platforms do, therefore we map the assets sever onto the following url. 90 result.startURL, _ = url.Parse(startURL) 91 92 if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil { 93 result.startURL = _starturl 94 return result 95 } 96 97 if port, _ := ctx.Value("assetserverport").(string); port != "" { 98 result.startURL.Host = net.JoinHostPort(result.startURL.Host, port) 99 } 100 101 var bindings string 102 var err error 103 if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated { 104 bindings, err = appBindings.ToJSON() 105 if err != nil { 106 log.Fatal(err) 107 } 108 } else { 109 appBindings.DB().UpdateObfuscatedCallMap() 110 } 111 112 assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle) 113 if err != nil { 114 log.Fatal(err) 115 } 116 result.assets = assets 117 118 go result.startSecondInstanceProcessor() 119 120 return result 121 } 122 123 func (f *Frontend) WindowReload() { 124 f.ExecJS("runtime.WindowReload();") 125 } 126 127 func (f *Frontend) WindowSetSystemDefaultTheme() { 128 f.mainWindow.SetTheme(windows.SystemDefault) 129 } 130 131 func (f *Frontend) WindowSetLightTheme() { 132 f.mainWindow.SetTheme(windows.Light) 133 } 134 135 func (f *Frontend) WindowSetDarkTheme() { 136 f.mainWindow.SetTheme(windows.Dark) 137 } 138 139 func (f *Frontend) Run(ctx context.Context) error { 140 f.ctx = ctx 141 142 f.chromium = edge.NewChromium() 143 144 if f.frontendOptions.SingleInstanceLock != nil { 145 SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId) 146 } 147 148 mainWindow := NewWindow(nil, f.frontendOptions, f.versionInfo, f.chromium) 149 f.mainWindow = mainWindow 150 151 var _debug = ctx.Value("debug") 152 var _devtoolsEnabled = ctx.Value("devtoolsEnabled") 153 154 if _debug != nil { 155 f.debug = _debug.(bool) 156 } 157 if _devtoolsEnabled != nil { 158 f.devtoolsEnabled = _devtoolsEnabled.(bool) 159 } 160 161 f.WindowCenter() 162 f.setupChromium() 163 164 mainWindow.OnSize().Bind(func(arg *winc.Event) { 165 if f.frontendOptions.Frameless { 166 // If the window is frameless and we are minimizing, then we need to suppress the Resize on the 167 // WebView2. If we don't do this, restoring does not work as expected and first restores with some wrong 168 // size during the restore animation and only fully renders when the animation is done. This highly 169 // depends on the content in the WebView, see https://github.com/wailsapp/wails/issues/1319 170 event, _ := arg.Data.(*winc.SizeEventData) 171 if event != nil && event.Type == w32.SIZE_MINIMIZED { 172 return 173 } 174 } 175 176 if f.resizeDebouncer != nil { 177 f.resizeDebouncer(func() { 178 f.mainWindow.Invoke(func() { 179 f.chromium.Resize() 180 }) 181 }) 182 } else { 183 f.chromium.Resize() 184 } 185 }) 186 187 mainWindow.OnClose().Bind(func(arg *winc.Event) { 188 if f.frontendOptions.HideWindowOnClose { 189 f.WindowHide() 190 } else { 191 f.Quit() 192 } 193 }) 194 195 go func() { 196 if f.frontendOptions.OnStartup != nil { 197 f.frontendOptions.OnStartup(f.ctx) 198 } 199 }() 200 mainWindow.UpdateTheme() 201 return nil 202 } 203 204 func (f *Frontend) WindowClose() { 205 if f.mainWindow != nil { 206 f.mainWindow.Close() 207 } 208 } 209 210 func (f *Frontend) RunMainLoop() { 211 _ = winc.RunMainLoop() 212 } 213 214 func (f *Frontend) WindowCenter() { 215 runtime.LockOSThread() 216 defer runtime.UnlockOSThread() 217 f.mainWindow.Center() 218 } 219 220 func (f *Frontend) WindowSetAlwaysOnTop(b bool) { 221 runtime.LockOSThread() 222 defer runtime.UnlockOSThread() 223 f.mainWindow.SetAlwaysOnTop(b) 224 } 225 226 func (f *Frontend) WindowSetPosition(x, y int) { 227 runtime.LockOSThread() 228 defer runtime.UnlockOSThread() 229 f.mainWindow.SetPos(x, y) 230 } 231 func (f *Frontend) WindowGetPosition() (int, int) { 232 runtime.LockOSThread() 233 defer runtime.UnlockOSThread() 234 return f.mainWindow.Pos() 235 } 236 237 func (f *Frontend) WindowSetSize(width, height int) { 238 runtime.LockOSThread() 239 defer runtime.UnlockOSThread() 240 f.mainWindow.SetSize(width, height) 241 } 242 243 func (f *Frontend) WindowGetSize() (int, int) { 244 runtime.LockOSThread() 245 defer runtime.UnlockOSThread() 246 return f.mainWindow.Size() 247 } 248 249 func (f *Frontend) WindowSetTitle(title string) { 250 runtime.LockOSThread() 251 defer runtime.UnlockOSThread() 252 f.mainWindow.SetText(title) 253 } 254 255 func (f *Frontend) WindowFullscreen() { 256 runtime.LockOSThread() 257 defer runtime.UnlockOSThread() 258 if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { 259 f.ExecJS("window.wails.flags.enableResize = false;") 260 } 261 f.mainWindow.Fullscreen() 262 } 263 264 func (f *Frontend) WindowReloadApp() { 265 f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL)) 266 } 267 268 func (f *Frontend) WindowUnfullscreen() { 269 runtime.LockOSThread() 270 defer runtime.UnlockOSThread() 271 if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { 272 f.ExecJS("window.wails.flags.enableResize = true;") 273 } 274 f.mainWindow.UnFullscreen() 275 } 276 277 func (f *Frontend) WindowShow() { 278 runtime.LockOSThread() 279 defer runtime.UnlockOSThread() 280 f.ShowWindow() 281 } 282 283 func (f *Frontend) WindowHide() { 284 runtime.LockOSThread() 285 defer runtime.UnlockOSThread() 286 f.mainWindow.Hide() 287 } 288 289 func (f *Frontend) WindowMaximise() { 290 runtime.LockOSThread() 291 defer runtime.UnlockOSThread() 292 if f.hasStarted { 293 if !f.frontendOptions.DisableResize { 294 f.mainWindow.Maximise() 295 } 296 } else { 297 f.frontendOptions.WindowStartState = options.Maximised 298 } 299 } 300 301 func (f *Frontend) WindowToggleMaximise() { 302 runtime.LockOSThread() 303 defer runtime.UnlockOSThread() 304 if !f.hasStarted { 305 return 306 } 307 if f.mainWindow.IsMaximised() { 308 f.WindowUnmaximise() 309 } else { 310 f.WindowMaximise() 311 } 312 } 313 314 func (f *Frontend) WindowUnmaximise() { 315 runtime.LockOSThread() 316 defer runtime.UnlockOSThread() 317 if f.mainWindow.Form.IsFullScreen() { 318 return 319 } 320 f.mainWindow.Restore() 321 } 322 323 func (f *Frontend) WindowMinimise() { 324 runtime.LockOSThread() 325 defer runtime.UnlockOSThread() 326 if f.hasStarted { 327 f.mainWindow.Minimise() 328 } else { 329 f.frontendOptions.WindowStartState = options.Minimised 330 } 331 } 332 333 func (f *Frontend) WindowUnminimise() { 334 runtime.LockOSThread() 335 defer runtime.UnlockOSThread() 336 if f.mainWindow.Form.IsFullScreen() { 337 return 338 } 339 f.mainWindow.Restore() 340 } 341 342 func (f *Frontend) WindowSetMinSize(width int, height int) { 343 runtime.LockOSThread() 344 defer runtime.UnlockOSThread() 345 f.mainWindow.SetMinSize(width, height) 346 } 347 func (f *Frontend) WindowSetMaxSize(width int, height int) { 348 runtime.LockOSThread() 349 defer runtime.UnlockOSThread() 350 f.mainWindow.SetMaxSize(width, height) 351 } 352 353 func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) { 354 if col == nil { 355 return 356 } 357 358 f.mainWindow.Invoke(func() { 359 win32.SetBackgroundColour(f.mainWindow.Handle(), col.R, col.G, col.B) 360 361 controller := f.chromium.GetController() 362 controller2 := controller.GetICoreWebView2Controller2() 363 364 backgroundCol := edge.COREWEBVIEW2_COLOR{ 365 A: col.A, 366 R: col.R, 367 G: col.G, 368 B: col.B, 369 } 370 371 // WebView2 only has 0 and 255 as valid values. 372 if backgroundCol.A > 0 && backgroundCol.A < 255 { 373 backgroundCol.A = 255 374 } 375 376 if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.WebviewIsTransparent { 377 backgroundCol.A = 0 378 } 379 380 err := controller2.PutDefaultBackgroundColor(backgroundCol) 381 if err != nil { 382 log.Fatal(err) 383 } 384 }) 385 386 } 387 388 func (f *Frontend) ScreenGetAll() ([]Screen, error) { 389 var wg sync.WaitGroup 390 wg.Add(1) 391 screens := []Screen{} 392 err := error(nil) 393 f.mainWindow.Invoke(func() { 394 screens, err = GetAllScreens(f.mainWindow.Handle()) 395 wg.Done() 396 397 }) 398 wg.Wait() 399 return screens, err 400 } 401 402 func (f *Frontend) Show() { 403 f.mainWindow.Show() 404 } 405 406 func (f *Frontend) Hide() { 407 f.mainWindow.Hide() 408 } 409 410 func (f *Frontend) WindowIsMaximised() bool { 411 return f.mainWindow.IsMaximised() 412 } 413 414 func (f *Frontend) WindowIsMinimised() bool { 415 return f.mainWindow.IsMinimised() 416 } 417 418 func (f *Frontend) WindowIsNormal() bool { 419 return f.mainWindow.IsNormal() 420 } 421 422 func (f *Frontend) WindowIsFullscreen() bool { 423 return f.mainWindow.IsFullScreen() 424 } 425 426 func (f *Frontend) Quit() { 427 if f.frontendOptions.OnBeforeClose != nil && f.frontendOptions.OnBeforeClose(f.ctx) { 428 return 429 } 430 // Exit must be called on the Main-Thread. It calls PostQuitMessage which sends the WM_QUIT message to the thread's 431 // message queue and our message queue runs on the Main-Thread. 432 f.mainWindow.Invoke(winc.Exit) 433 } 434 435 func (f *Frontend) WindowPrint() { 436 f.ExecJS("window.print();") 437 } 438 439 func (f *Frontend) setupChromium() { 440 chromium := f.chromium 441 442 disableFeatues := []string{} 443 if !f.frontendOptions.EnableFraudulentWebsiteDetection { 444 disableFeatues = append(disableFeatues, "msSmartScreenProtection") 445 } 446 447 if opts := f.frontendOptions.Windows; opts != nil { 448 chromium.DataPath = opts.WebviewUserDataPath 449 chromium.BrowserPath = opts.WebviewBrowserPath 450 451 if opts.WebviewGpuIsDisabled { 452 chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, "--disable-gpu") 453 } 454 if opts.WebviewDisableRendererCodeIntegrity { 455 disableFeatues = append(disableFeatues, "RendererCodeIntegrity") 456 } 457 } 458 459 if len(disableFeatues) > 0 { 460 arg := fmt.Sprintf("--disable-features=%s", strings.Join(disableFeatues, ",")) 461 chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg) 462 } 463 464 chromium.MessageCallback = f.processMessage 465 chromium.WebResourceRequestedCallback = f.processRequest 466 chromium.NavigationCompletedCallback = f.navigationCompleted 467 chromium.AcceleratorKeyCallback = func(vkey uint) bool { 468 if vkey == w32.VK_F12 && f.devtoolsEnabled { 469 var keyState [256]byte 470 if w32.GetKeyboardState(keyState[:]) { 471 // Check if CTRL is pressed 472 if keyState[w32.VK_CONTROL]&0x80 != 0 && keyState[w32.VK_SHIFT]&0x80 != 0 { 473 chromium.OpenDevToolsWindow() 474 return true 475 } 476 } else { 477 f.logger.Error("Call to GetKeyboardState failed") 478 } 479 } 480 w32.PostMessage(f.mainWindow.Handle(), w32.WM_KEYDOWN, uintptr(vkey), 0) 481 return false 482 } 483 chromium.ProcessFailedCallback = func(sender *edge.ICoreWebView2, args *edge.ICoreWebView2ProcessFailedEventArgs) { 484 kind, err := args.GetProcessFailedKind() 485 if err != nil { 486 f.logger.Error("GetProcessFailedKind: %s", err) 487 return 488 } 489 490 f.logger.Error("WebVie2wProcess failed with kind %d", kind) 491 switch kind { 492 case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED: 493 // => The app has to recreate a new WebView to recover from this failure. 494 messages := windows.DefaultMessages() 495 if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.Messages != nil { 496 messages = f.frontendOptions.Windows.Messages 497 } 498 winc.Errorf(f.mainWindow, messages.WebView2ProcessCrash) 499 os.Exit(-1) 500 case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED, 501 edge.COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED: 502 // => A new render process is created automatically and navigated to an error page. 503 // => Make sure that the error page is shown. 504 if !f.hasStarted { 505 // NavgiationCompleted didn't come in, make sure the chromium is shown 506 chromium.Show() 507 } 508 if !f.mainWindow.hasBeenShown { 509 // The window has never been shown, make sure to show it 510 f.ShowWindow() 511 } 512 } 513 } 514 515 chromium.Embed(f.mainWindow.Handle()) 516 517 if chromium.HasCapability(edge.SwipeNavigation) { 518 swipeGesturesEnabled := f.frontendOptions.Windows != nil && f.frontendOptions.Windows.EnableSwipeGestures 519 err := chromium.PutIsSwipeNavigationEnabled(swipeGesturesEnabled) 520 if err != nil { 521 log.Fatal(err) 522 } 523 } 524 chromium.Resize() 525 settings, err := chromium.GetSettings() 526 if err != nil { 527 log.Fatal(err) 528 } 529 err = settings.PutAreDefaultContextMenusEnabled(f.debug || f.frontendOptions.EnableDefaultContextMenu) 530 if err != nil { 531 log.Fatal(err) 532 } 533 err = settings.PutAreDevToolsEnabled(f.devtoolsEnabled) 534 if err != nil { 535 log.Fatal(err) 536 } 537 538 if opts := f.frontendOptions.Windows; opts != nil { 539 if opts.ZoomFactor > 0.0 { 540 chromium.PutZoomFactor(opts.ZoomFactor) 541 } 542 err = settings.PutIsZoomControlEnabled(opts.IsZoomControlEnabled) 543 if err != nil { 544 log.Fatal(err) 545 } 546 } 547 548 err = settings.PutIsStatusBarEnabled(false) 549 if err != nil { 550 log.Fatal(err) 551 } 552 err = settings.PutAreBrowserAcceleratorKeysEnabled(false) 553 if err != nil { 554 log.Fatal(err) 555 } 556 557 if f.debug && f.frontendOptions.Debug.OpenInspectorOnStartup { 558 chromium.OpenDevToolsWindow() 559 } 560 561 // Setup focus event handler 562 onFocus := f.mainWindow.OnSetFocus() 563 onFocus.Bind(f.onFocus) 564 565 // Set background colour 566 f.WindowSetBackgroundColour(f.frontendOptions.BackgroundColour) 567 568 chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow) 569 chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL) 570 chromium.Navigate(f.startURL.String()) 571 } 572 573 type EventNotify struct { 574 Name string `json:"name"` 575 Data []interface{} `json:"data"` 576 } 577 578 func (f *Frontend) Notify(name string, data ...interface{}) { 579 notification := EventNotify{ 580 Name: name, 581 Data: data, 582 } 583 payload, err := json.Marshal(notification) 584 if err != nil { 585 f.logger.Error(err.Error()) 586 return 587 } 588 f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`) 589 } 590 591 func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) { 592 // Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but 593 // we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request. 594 if reqHeaders, err := req.GetHeaders(); err == nil { 595 useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent) 596 useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ") 597 reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent) 598 reqHeaders.Release() 599 } 600 601 if f.assets == nil { 602 // We are using the devServer let the WebView2 handle the request with its default handler 603 return 604 } 605 606 //Get the request 607 uri, _ := req.GetUri() 608 reqUri, err := url.ParseRequestURI(uri) 609 if err != nil { 610 f.logger.Error("Unable to parse equest uri %s: %s", uri, err) 611 return 612 } 613 614 if reqUri.Scheme != f.startURL.Scheme { 615 // Let the WebView2 handle the request with its default handler 616 return 617 } else if reqUri.Host != f.startURL.Host { 618 // Let the WebView2 handle the request with its default handler 619 return 620 } 621 622 webviewRequest, err := webview.NewRequest( 623 f.chromium.Environment(), 624 args, 625 func(fn func()) { 626 runtime.LockOSThread() 627 defer runtime.UnlockOSThread() 628 if f.mainWindow.InvokeRequired() { 629 var wg sync.WaitGroup 630 wg.Add(1) 631 f.mainWindow.Invoke(func() { 632 fn() 633 wg.Done() 634 }) 635 wg.Wait() 636 } else { 637 fn() 638 } 639 }) 640 641 if err != nil { 642 f.logger.Error("%s: NewRequest failed: %s", uri, err) 643 return 644 } 645 646 f.assets.ServeWebViewRequest(webviewRequest) 647 } 648 649 var edgeMap = map[string]uintptr{ 650 "n-resize": w32.HTTOP, 651 "ne-resize": w32.HTTOPRIGHT, 652 "e-resize": w32.HTRIGHT, 653 "se-resize": w32.HTBOTTOMRIGHT, 654 "s-resize": w32.HTBOTTOM, 655 "sw-resize": w32.HTBOTTOMLEFT, 656 "w-resize": w32.HTLEFT, 657 "nw-resize": w32.HTTOPLEFT, 658 } 659 660 func (f *Frontend) processMessage(message string) { 661 if message == "drag" { 662 if !f.mainWindow.IsFullScreen() { 663 err := f.startDrag() 664 if err != nil { 665 f.logger.Error(err.Error()) 666 } 667 } 668 return 669 } 670 671 if message == "runtime:ready" { 672 cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue) 673 f.ExecJS(cmd) 674 return 675 } 676 677 if strings.HasPrefix(message, "resize:") { 678 if !f.mainWindow.IsFullScreen() { 679 sl := strings.Split(message, ":") 680 if len(sl) != 2 { 681 f.logger.Info("Unknown message returned from dispatcher: %+v", message) 682 return 683 } 684 edge := edgeMap[sl[1]] 685 err := f.startResize(edge) 686 if err != nil { 687 f.logger.Error(err.Error()) 688 } 689 } 690 return 691 } 692 693 go func() { 694 result, err := f.dispatcher.ProcessMessage(message, f) 695 if err != nil { 696 f.logger.Error(err.Error()) 697 f.Callback(result) 698 return 699 } 700 if result == "" { 701 return 702 } 703 704 switch result[0] { 705 case 'c': 706 // Callback from a method call 707 f.Callback(result[1:]) 708 default: 709 f.logger.Info("Unknown message returned from dispatcher: %+v", result) 710 } 711 }() 712 } 713 714 func (f *Frontend) Callback(message string) { 715 escaped, err := json.Marshal(message) 716 if err != nil { 717 panic(err) 718 } 719 f.mainWindow.Invoke(func() { 720 f.chromium.Eval(`window.wails.Callback(` + string(escaped) + `);`) 721 }) 722 } 723 724 func (f *Frontend) startDrag() error { 725 if !w32.ReleaseCapture() { 726 return fmt.Errorf("unable to release mouse capture") 727 } 728 // Use PostMessage because we don't want to block the caller until dragging has been finished. 729 w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0) 730 return nil 731 } 732 733 func (f *Frontend) startResize(border uintptr) error { 734 if !w32.ReleaseCapture() { 735 return fmt.Errorf("unable to release mouse capture") 736 } 737 // Use PostMessage because we don't want to block the caller until resizing has been finished. 738 w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, border, 0) 739 return nil 740 } 741 742 func (f *Frontend) ExecJS(js string) { 743 f.mainWindow.Invoke(func() { 744 f.chromium.Eval(js) 745 }) 746 } 747 748 func (f *Frontend) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) { 749 if f.frontendOptions.OnDomReady != nil { 750 go f.frontendOptions.OnDomReady(f.ctx) 751 } 752 753 if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { 754 f.ExecJS("window.wails.flags.enableResize = true;") 755 } 756 757 if f.hasStarted { 758 return 759 } 760 f.hasStarted = true 761 762 // Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026 763 err := f.chromium.Hide() 764 if err != nil { 765 log.Fatal(err) 766 } 767 err = f.chromium.Show() 768 if err != nil { 769 log.Fatal(err) 770 } 771 772 if f.frontendOptions.StartHidden { 773 return 774 } 775 776 switch f.frontendOptions.WindowStartState { 777 case options.Maximised: 778 if !f.frontendOptions.DisableResize { 779 win32.ShowWindowMaximised(f.mainWindow.Handle()) 780 } else { 781 win32.ShowWindow(f.mainWindow.Handle()) 782 } 783 case options.Minimised: 784 win32.ShowWindowMinimised(f.mainWindow.Handle()) 785 case options.Fullscreen: 786 f.mainWindow.Fullscreen() 787 win32.ShowWindow(f.mainWindow.Handle()) 788 default: 789 if f.frontendOptions.Fullscreen { 790 f.mainWindow.Fullscreen() 791 } 792 win32.ShowWindow(f.mainWindow.Handle()) 793 } 794 795 f.mainWindow.hasBeenShown = true 796 797 } 798 799 func (f *Frontend) ShowWindow() { 800 f.mainWindow.Invoke(func() { 801 if !f.mainWindow.hasBeenShown { 802 f.mainWindow.hasBeenShown = true 803 switch f.frontendOptions.WindowStartState { 804 case options.Maximised: 805 if !f.frontendOptions.DisableResize { 806 win32.ShowWindowMaximised(f.mainWindow.Handle()) 807 } else { 808 win32.ShowWindow(f.mainWindow.Handle()) 809 } 810 case options.Minimised: 811 win32.RestoreWindow(f.mainWindow.Handle()) 812 case options.Fullscreen: 813 f.mainWindow.Fullscreen() 814 win32.ShowWindow(f.mainWindow.Handle()) 815 default: 816 if f.frontendOptions.Fullscreen { 817 f.mainWindow.Fullscreen() 818 } 819 win32.ShowWindow(f.mainWindow.Handle()) 820 } 821 } else { 822 if win32.IsWindowMinimised(f.mainWindow.Handle()) { 823 win32.RestoreWindow(f.mainWindow.Handle()) 824 } else { 825 win32.ShowWindow(f.mainWindow.Handle()) 826 } 827 } 828 w32.SetForegroundWindow(f.mainWindow.Handle()) 829 w32.SetFocus(f.mainWindow.Handle()) 830 }) 831 832 } 833 834 func (f *Frontend) onFocus(arg *winc.Event) { 835 f.chromium.Focus() 836 } 837 838 func (f *Frontend) startSecondInstanceProcessor() { 839 for secondInstanceData := range secondInstanceBuffer { 840 if f.frontendOptions.SingleInstanceLock != nil && 841 f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { 842 f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) 843 } 844 } 845 }