github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/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/AlpineAIO/wails/v2/internal/binding" 21 "github.com/AlpineAIO/wails/v2/internal/frontend" 22 "github.com/AlpineAIO/wails/v2/internal/frontend/desktop/windows/win32" 23 "github.com/AlpineAIO/wails/v2/internal/frontend/desktop/windows/winc" 24 "github.com/AlpineAIO/wails/v2/internal/frontend/desktop/windows/winc/w32" 25 wailsruntime "github.com/AlpineAIO/wails/v2/internal/frontend/runtime" 26 "github.com/AlpineAIO/wails/v2/internal/logger" 27 "github.com/AlpineAIO/wails/v2/internal/system/operatingsystem" 28 "github.com/AlpineAIO/wails/v2/pkg/assetserver" 29 "github.com/AlpineAIO/wails/v2/pkg/assetserver/webview" 30 "github.com/AlpineAIO/wails/v2/pkg/options" 31 "github.com/AlpineAIO/wails/v2/pkg/options/windows" 32 "github.com/bep/debounce" 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/AlpineAIO/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 err = settings.PutIsPinchZoomEnabled(!opts.DisablePinchZoom) 547 if err != nil { 548 log.Fatal(err) 549 } 550 } 551 552 err = settings.PutIsStatusBarEnabled(false) 553 if err != nil { 554 log.Fatal(err) 555 } 556 err = settings.PutAreBrowserAcceleratorKeysEnabled(false) 557 if err != nil { 558 log.Fatal(err) 559 } 560 561 if f.debug && f.frontendOptions.Debug.OpenInspectorOnStartup { 562 chromium.OpenDevToolsWindow() 563 } 564 565 // Setup focus event handler 566 onFocus := f.mainWindow.OnSetFocus() 567 onFocus.Bind(f.onFocus) 568 569 // Set background colour 570 f.WindowSetBackgroundColour(f.frontendOptions.BackgroundColour) 571 572 chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow) 573 chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL) 574 chromium.Navigate(f.startURL.String()) 575 } 576 577 type EventNotify struct { 578 Name string `json:"name"` 579 Data []interface{} `json:"data"` 580 } 581 582 func (f *Frontend) Notify(name string, data ...interface{}) { 583 notification := EventNotify{ 584 Name: name, 585 Data: data, 586 } 587 payload, err := json.Marshal(notification) 588 if err != nil { 589 f.logger.Error(err.Error()) 590 return 591 } 592 f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`) 593 } 594 595 func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) { 596 // Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but 597 // we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request. 598 if reqHeaders, err := req.GetHeaders(); err == nil { 599 useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent) 600 useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ") 601 reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent) 602 reqHeaders.Release() 603 } 604 605 if f.assets == nil { 606 // We are using the devServer let the WebView2 handle the request with its default handler 607 return 608 } 609 610 //Get the request 611 uri, _ := req.GetUri() 612 reqUri, err := url.ParseRequestURI(uri) 613 if err != nil { 614 f.logger.Error("Unable to parse equest uri %s: %s", uri, err) 615 return 616 } 617 618 if reqUri.Scheme != f.startURL.Scheme { 619 // Let the WebView2 handle the request with its default handler 620 return 621 } else if reqUri.Host != f.startURL.Host { 622 // Let the WebView2 handle the request with its default handler 623 return 624 } 625 626 webviewRequest, err := webview.NewRequest( 627 f.chromium.Environment(), 628 args, 629 func(fn func()) { 630 runtime.LockOSThread() 631 defer runtime.UnlockOSThread() 632 if f.mainWindow.InvokeRequired() { 633 var wg sync.WaitGroup 634 wg.Add(1) 635 f.mainWindow.Invoke(func() { 636 fn() 637 wg.Done() 638 }) 639 wg.Wait() 640 } else { 641 fn() 642 } 643 }) 644 645 if err != nil { 646 f.logger.Error("%s: NewRequest failed: %s", uri, err) 647 return 648 } 649 650 f.assets.ServeWebViewRequest(webviewRequest) 651 } 652 653 var edgeMap = map[string]uintptr{ 654 "n-resize": w32.HTTOP, 655 "ne-resize": w32.HTTOPRIGHT, 656 "e-resize": w32.HTRIGHT, 657 "se-resize": w32.HTBOTTOMRIGHT, 658 "s-resize": w32.HTBOTTOM, 659 "sw-resize": w32.HTBOTTOMLEFT, 660 "w-resize": w32.HTLEFT, 661 "nw-resize": w32.HTTOPLEFT, 662 } 663 664 func (f *Frontend) processMessage(message string) { 665 if message == "drag" { 666 if !f.mainWindow.IsFullScreen() { 667 err := f.startDrag() 668 if err != nil { 669 f.logger.Error(err.Error()) 670 } 671 } 672 return 673 } 674 675 if message == "runtime:ready" { 676 cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue) 677 f.ExecJS(cmd) 678 return 679 } 680 681 if strings.HasPrefix(message, "resize:") { 682 if !f.mainWindow.IsFullScreen() { 683 sl := strings.Split(message, ":") 684 if len(sl) != 2 { 685 f.logger.Info("Unknown message returned from dispatcher: %+v", message) 686 return 687 } 688 edge := edgeMap[sl[1]] 689 err := f.startResize(edge) 690 if err != nil { 691 f.logger.Error(err.Error()) 692 } 693 } 694 return 695 } 696 697 go func() { 698 result, err := f.dispatcher.ProcessMessage(message, f) 699 if err != nil { 700 f.logger.Error(err.Error()) 701 f.Callback(result) 702 return 703 } 704 if result == "" { 705 return 706 } 707 708 switch result[0] { 709 case 'c': 710 // Callback from a method call 711 f.Callback(result[1:]) 712 default: 713 f.logger.Info("Unknown message returned from dispatcher: %+v", result) 714 } 715 }() 716 } 717 718 func (f *Frontend) Callback(message string) { 719 escaped, err := json.Marshal(message) 720 if err != nil { 721 panic(err) 722 } 723 f.mainWindow.Invoke(func() { 724 f.chromium.Eval(`window.wails.Callback(` + string(escaped) + `);`) 725 }) 726 } 727 728 func (f *Frontend) startDrag() error { 729 if !w32.ReleaseCapture() { 730 return fmt.Errorf("unable to release mouse capture") 731 } 732 // Use PostMessage because we don't want to block the caller until dragging has been finished. 733 w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0) 734 return nil 735 } 736 737 func (f *Frontend) startResize(border uintptr) error { 738 if !w32.ReleaseCapture() { 739 return fmt.Errorf("unable to release mouse capture") 740 } 741 // Use PostMessage because we don't want to block the caller until resizing has been finished. 742 w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, border, 0) 743 return nil 744 } 745 746 func (f *Frontend) ExecJS(js string) { 747 f.mainWindow.Invoke(func() { 748 f.chromium.Eval(js) 749 }) 750 } 751 752 func (f *Frontend) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) { 753 if f.frontendOptions.OnDomReady != nil { 754 go f.frontendOptions.OnDomReady(f.ctx) 755 } 756 757 if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { 758 f.ExecJS("window.wails.flags.enableResize = true;") 759 } 760 761 if f.hasStarted { 762 return 763 } 764 f.hasStarted = true 765 766 // Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026 767 err := f.chromium.Hide() 768 if err != nil { 769 log.Fatal(err) 770 } 771 err = f.chromium.Show() 772 if err != nil { 773 log.Fatal(err) 774 } 775 776 if f.frontendOptions.StartHidden { 777 return 778 } 779 780 switch f.frontendOptions.WindowStartState { 781 case options.Maximised: 782 if !f.frontendOptions.DisableResize { 783 win32.ShowWindowMaximised(f.mainWindow.Handle()) 784 } else { 785 win32.ShowWindow(f.mainWindow.Handle()) 786 } 787 case options.Minimised: 788 win32.ShowWindowMinimised(f.mainWindow.Handle()) 789 case options.Fullscreen: 790 f.mainWindow.Fullscreen() 791 win32.ShowWindow(f.mainWindow.Handle()) 792 default: 793 if f.frontendOptions.Fullscreen { 794 f.mainWindow.Fullscreen() 795 } 796 win32.ShowWindow(f.mainWindow.Handle()) 797 } 798 799 f.mainWindow.hasBeenShown = true 800 801 } 802 803 func (f *Frontend) ShowWindow() { 804 f.mainWindow.Invoke(func() { 805 if !f.mainWindow.hasBeenShown { 806 f.mainWindow.hasBeenShown = true 807 switch f.frontendOptions.WindowStartState { 808 case options.Maximised: 809 if !f.frontendOptions.DisableResize { 810 win32.ShowWindowMaximised(f.mainWindow.Handle()) 811 } else { 812 win32.ShowWindow(f.mainWindow.Handle()) 813 } 814 case options.Minimised: 815 win32.RestoreWindow(f.mainWindow.Handle()) 816 case options.Fullscreen: 817 f.mainWindow.Fullscreen() 818 win32.ShowWindow(f.mainWindow.Handle()) 819 default: 820 if f.frontendOptions.Fullscreen { 821 f.mainWindow.Fullscreen() 822 } 823 win32.ShowWindow(f.mainWindow.Handle()) 824 } 825 } else { 826 if win32.IsWindowMinimised(f.mainWindow.Handle()) { 827 win32.RestoreWindow(f.mainWindow.Handle()) 828 } else { 829 win32.ShowWindow(f.mainWindow.Handle()) 830 } 831 } 832 w32.SetForegroundWindow(f.mainWindow.Handle()) 833 w32.SetFocus(f.mainWindow.Handle()) 834 }) 835 836 } 837 838 func (f *Frontend) onFocus(arg *winc.Event) { 839 f.chromium.Focus() 840 } 841 842 func (f *Frontend) startSecondInstanceProcessor() { 843 for secondInstanceData := range secondInstanceBuffer { 844 if f.frontendOptions.SingleInstanceLock != nil && 845 f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { 846 f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) 847 } 848 } 849 }