//go:build windows // +build windows package windows import ( "context" "encoding/json" "fmt" "log" "net" "net/url" "os" "runtime" "strings" "sync" "text/template" "time" "unsafe" "github.com/bep/debounce" "github.com/wailsapp/go-webview2/pkg/edge" "github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime" "github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/system/operatingsystem" "github.com/wailsapp/wails/v2/pkg/assetserver" "github.com/wailsapp/wails/v2/pkg/assetserver/webview" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/windows" ) const startURL = "http://wails.localhost/" var secondInstanceBuffer = make(chan options.SecondInstanceData, 1) type Screen = frontend.Screen type Frontend struct { // Context ctx context.Context frontendOptions *options.App logger *logger.Logger chromium *edge.Chromium debug bool devtoolsEnabled bool // Assets assets *assetserver.AssetServer startURL *url.URL // main window handle mainWindow *Window bindings *binding.Bindings dispatcher frontend.Dispatcher hasStarted bool // Windows build number versionInfo *operatingsystem.WindowsVersionInfo resizeDebouncer func(f func()) } func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend { // Get Windows build number versionInfo, _ := operatingsystem.GetWindowsVersionInfo() result := &Frontend{ frontendOptions: appoptions, logger: myLogger, bindings: appBindings, dispatcher: dispatcher, ctx: ctx, versionInfo: versionInfo, } if appoptions.Windows != nil { if appoptions.Windows.ResizeDebounceMS > 0 { result.resizeDebouncer = debounce.New(time.Duration(appoptions.Windows.ResizeDebounceMS) * time.Millisecond) } } // We currently can't use wails://wails/ as other platforms do, therefore we map the assets sever onto the following url. result.startURL, _ = url.Parse(startURL) if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil { result.startURL = _starturl return result } if port, _ := ctx.Value("assetserverport").(string); port != "" { result.startURL.Host = net.JoinHostPort(result.startURL.Host, port) } var bindings string var err error if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated { bindings, err = appBindings.ToJSON() if err != nil { log.Fatal(err) } } else { appBindings.DB().UpdateObfuscatedCallMap() } assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle) if err != nil { log.Fatal(err) } result.assets = assets go result.startSecondInstanceProcessor() return result } func (f *Frontend) WindowReload() { f.ExecJS("runtime.WindowReload();") } func (f *Frontend) WindowSetSystemDefaultTheme() { f.mainWindow.SetTheme(windows.SystemDefault) } func (f *Frontend) WindowSetLightTheme() { f.mainWindow.SetTheme(windows.Light) } func (f *Frontend) WindowSetDarkTheme() { f.mainWindow.SetTheme(windows.Dark) } func (f *Frontend) Run(ctx context.Context) error { f.ctx = ctx f.chromium = edge.NewChromium() if f.frontendOptions.SingleInstanceLock != nil { SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId) } mainWindow := NewWindow(nil, f.frontendOptions, f.versionInfo, f.chromium) f.mainWindow = mainWindow var _debug = ctx.Value("debug") var _devtoolsEnabled = ctx.Value("devtoolsEnabled") if _debug != nil { f.debug = _debug.(bool) } if _devtoolsEnabled != nil { f.devtoolsEnabled = _devtoolsEnabled.(bool) } f.WindowCenter() f.setupChromium() mainWindow.OnSize().Bind(func(arg *winc.Event) { if f.frontendOptions.Frameless { // If the window is frameless and we are minimizing, then we need to suppress the Resize on the // WebView2. If we don't do this, restoring does not work as expected and first restores with some wrong // size during the restore animation and only fully renders when the animation is done. This highly // depends on the content in the WebView, see https://github.com/wailsapp/wails/issues/1319 event, _ := arg.Data.(*winc.SizeEventData) if event != nil && event.Type == w32.SIZE_MINIMIZED { // Set minimizing flag to prevent unnecessary redraws during minimize/restore for frameless windows // 设置最小化标志以防止无边框窗口在最小化/恢复过程中的不必要重绘 // This fixes window flickering when minimizing/restoring frameless windows // 这修复了无边框窗口在最小化/恢复时的闪烁问题 // Reference: https://github.com/wailsapp/wails/issues/3951 f.mainWindow.isMinimizing = true return } } // Clear minimizing flag for all non-minimize size events // 对于所有非最小化的尺寸变化事件,清除最小化标志 // Reference: https://github.com/wailsapp/wails/issues/3951 f.mainWindow.isMinimizing = false if f.resizeDebouncer != nil { f.resizeDebouncer(func() { f.mainWindow.Invoke(func() { f.chromium.Resize() }) }) } else { f.chromium.Resize() } }) mainWindow.OnClose().Bind(func(arg *winc.Event) { if f.frontendOptions.HideWindowOnClose { f.WindowHide() } else { f.Quit() } }) go func() { if f.frontendOptions.OnStartup != nil { f.frontendOptions.OnStartup(f.ctx) } }() mainWindow.UpdateTheme() return nil } func (f *Frontend) WindowClose() { if f.mainWindow != nil { f.mainWindow.Close() } } func (f *Frontend) RunMainLoop() { _ = winc.RunMainLoop() } func (f *Frontend) WindowCenter() { runtime.LockOSThread() defer runtime.UnlockOSThread() f.mainWindow.Center() } func (f *Frontend) WindowSetAlwaysOnTop(b bool) { runtime.LockOSThread() defer runtime.UnlockOSThread() f.mainWindow.SetAlwaysOnTop(b) } func (f *Frontend) WindowSetPosition(x, y int) { runtime.LockOSThread() defer runtime.UnlockOSThread() f.mainWindow.SetPos(x, y) } func (f *Frontend) WindowGetPosition() (int, int) { runtime.LockOSThread() defer runtime.UnlockOSThread() return f.mainWindow.Pos() } func (f *Frontend) WindowSetSize(width, height int) { runtime.LockOSThread() defer runtime.UnlockOSThread() f.mainWindow.SetSize(width, height) } func (f *Frontend) WindowGetSize() (int, int) { runtime.LockOSThread() defer runtime.UnlockOSThread() return f.mainWindow.Size() } func (f *Frontend) WindowSetTitle(title string) { runtime.LockOSThread() defer runtime.UnlockOSThread() f.mainWindow.SetText(title) } func (f *Frontend) WindowFullscreen() { runtime.LockOSThread() defer runtime.UnlockOSThread() if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { f.ExecJS("window.wails.flags.enableResize = false;") } f.mainWindow.Fullscreen() } func (f *Frontend) WindowReloadApp() { f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL)) } func (f *Frontend) WindowUnfullscreen() { runtime.LockOSThread() defer runtime.UnlockOSThread() if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { f.ExecJS("window.wails.flags.enableResize = true;") } f.mainWindow.UnFullscreen() } func (f *Frontend) WindowShow() { runtime.LockOSThread() defer runtime.UnlockOSThread() f.ShowWindow() } func (f *Frontend) WindowHide() { runtime.LockOSThread() defer runtime.UnlockOSThread() f.mainWindow.Hide() } func (f *Frontend) WindowMaximise() { runtime.LockOSThread() defer runtime.UnlockOSThread() if f.hasStarted { if !f.frontendOptions.DisableResize { f.mainWindow.Maximise() } } else { f.frontendOptions.WindowStartState = options.Maximised } } func (f *Frontend) WindowToggleMaximise() { runtime.LockOSThread() defer runtime.UnlockOSThread() if !f.hasStarted { return } if f.mainWindow.IsMaximised() { f.WindowUnmaximise() } else { f.WindowMaximise() } } func (f *Frontend) WindowUnmaximise() { runtime.LockOSThread() defer runtime.UnlockOSThread() if f.mainWindow.Form.IsFullScreen() { return } f.mainWindow.Restore() } func (f *Frontend) WindowMinimise() { runtime.LockOSThread() defer runtime.UnlockOSThread() if f.hasStarted { f.mainWindow.Minimise() } else { f.frontendOptions.WindowStartState = options.Minimised } } func (f *Frontend) WindowUnminimise() { runtime.LockOSThread() defer runtime.UnlockOSThread() if f.mainWindow.Form.IsFullScreen() { return } f.mainWindow.Restore() } func (f *Frontend) WindowSetMinSize(width int, height int) { runtime.LockOSThread() defer runtime.UnlockOSThread() f.mainWindow.SetMinSize(width, height) } func (f *Frontend) WindowSetMaxSize(width int, height int) { runtime.LockOSThread() defer runtime.UnlockOSThread() f.mainWindow.SetMaxSize(width, height) } func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) { if col == nil { return } f.mainWindow.Invoke(func() { win32.SetBackgroundColour(f.mainWindow.Handle(), col.R, col.G, col.B) controller := f.chromium.GetController() controller2 := controller.GetICoreWebView2Controller2() backgroundCol := edge.COREWEBVIEW2_COLOR{ A: col.A, R: col.R, G: col.G, B: col.B, } // WebView2 only has 0 and 255 as valid values. if backgroundCol.A > 0 && backgroundCol.A < 255 { backgroundCol.A = 255 } if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.WebviewIsTransparent { backgroundCol.A = 0 } err := controller2.PutDefaultBackgroundColor(backgroundCol) if err != nil { log.Fatal(err) } }) } func (f *Frontend) ScreenGetAll() ([]Screen, error) { var wg sync.WaitGroup wg.Add(1) screens := []Screen{} err := error(nil) f.mainWindow.Invoke(func() { screens, err = GetAllScreens(f.mainWindow.Handle()) wg.Done() }) wg.Wait() return screens, err } func (f *Frontend) Show() { f.mainWindow.Show() } func (f *Frontend) Hide() { f.mainWindow.Hide() } func (f *Frontend) WindowIsMaximised() bool { return f.mainWindow.IsMaximised() } func (f *Frontend) WindowIsMinimised() bool { return f.mainWindow.IsMinimised() } func (f *Frontend) WindowIsNormal() bool { return f.mainWindow.IsNormal() } func (f *Frontend) WindowIsFullscreen() bool { return f.mainWindow.IsFullScreen() } func (f *Frontend) Quit() { if f.frontendOptions.OnBeforeClose != nil && f.frontendOptions.OnBeforeClose(f.ctx) { return } // Exit must be called on the Main-Thread. It calls PostQuitMessage which sends the WM_QUIT message to the thread's // message queue and our message queue runs on the Main-Thread. f.mainWindow.Invoke(winc.Exit) } func (f *Frontend) WindowPrint() { f.ExecJS("window.print();") } func (f *Frontend) setupChromium() { chromium := f.chromium disableFeatues := []string{} if !f.frontendOptions.EnableFraudulentWebsiteDetection { disableFeatues = append(disableFeatues, "msSmartScreenProtection") } if opts := f.frontendOptions.Windows; opts != nil { chromium.DataPath = opts.WebviewUserDataPath chromium.BrowserPath = opts.WebviewBrowserPath if opts.WebviewGpuIsDisabled { chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, "--disable-gpu") } if opts.WebviewDisableRendererCodeIntegrity { disableFeatues = append(disableFeatues, "RendererCodeIntegrity") } } if len(disableFeatues) > 0 { arg := fmt.Sprintf("--disable-features=%s", strings.Join(disableFeatues, ",")) chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg) } if f.frontendOptions.DragAndDrop != nil && f.frontendOptions.DragAndDrop.DisableWebViewDrop { if err := chromium.AllowExternalDrag(false); err != nil { f.logger.Warning("WebView failed to set AllowExternalDrag to false!") } } chromium.MessageCallback = f.processMessage chromium.MessageWithAdditionalObjectsCallback = f.processMessageWithAdditionalObjects chromium.WebResourceRequestedCallback = f.processRequest chromium.NavigationCompletedCallback = f.navigationCompleted chromium.AcceleratorKeyCallback = func(vkey uint) bool { if vkey == w32.VK_F12 && f.devtoolsEnabled { var keyState [256]byte if w32.GetKeyboardState(keyState[:]) { // Check if CTRL is pressed if keyState[w32.VK_CONTROL]&0x80 != 0 && keyState[w32.VK_SHIFT]&0x80 != 0 { chromium.OpenDevToolsWindow() return true } } else { f.logger.Error("Call to GetKeyboardState failed") } } w32.PostMessage(f.mainWindow.Handle(), w32.WM_KEYDOWN, uintptr(vkey), 0) return false } chromium.ProcessFailedCallback = func(sender *edge.ICoreWebView2, args *edge.ICoreWebView2ProcessFailedEventArgs) { kind, err := args.GetProcessFailedKind() if err != nil { f.logger.Error("GetProcessFailedKind: %s", err) return } f.logger.Error("WebVie2wProcess failed with kind %d", kind) switch kind { case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED: // => The app has to recreate a new WebView to recover from this failure. messages := windows.DefaultMessages() if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.Messages != nil { messages = f.frontendOptions.Windows.Messages } winc.Errorf(f.mainWindow, messages.WebView2ProcessCrash) os.Exit(-1) case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED, edge.COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED: // => A new render process is created automatically and navigated to an error page. // => Make sure that the error page is shown. if !f.hasStarted { // NavgiationCompleted didn't come in, make sure the chromium is shown chromium.Show() } if !f.mainWindow.hasBeenShown { // The window has never been shown, make sure to show it f.ShowWindow() } } } chromium.Embed(f.mainWindow.Handle()) if chromium.HasCapability(edge.SwipeNavigation) { swipeGesturesEnabled := f.frontendOptions.Windows != nil && f.frontendOptions.Windows.EnableSwipeGestures err := chromium.PutIsSwipeNavigationEnabled(swipeGesturesEnabled) if err != nil { log.Fatal(err) } } chromium.Resize() settings, err := chromium.GetSettings() if err != nil { log.Fatal(err) } err = settings.PutAreDefaultContextMenusEnabled(f.debug || f.frontendOptions.EnableDefaultContextMenu) if err != nil { log.Fatal(err) } err = settings.PutAreDevToolsEnabled(f.devtoolsEnabled) if err != nil { log.Fatal(err) } if opts := f.frontendOptions.Windows; opts != nil { if opts.ZoomFactor > 0.0 { chromium.PutZoomFactor(opts.ZoomFactor) } err = settings.PutIsZoomControlEnabled(opts.IsZoomControlEnabled) if err != nil { log.Fatal(err) } err = settings.PutIsPinchZoomEnabled(!opts.DisablePinchZoom) if err != nil { log.Fatal(err) } } err = settings.PutIsStatusBarEnabled(false) if err != nil { log.Fatal(err) } err = settings.PutAreBrowserAcceleratorKeysEnabled(false) if err != nil { log.Fatal(err) } if f.debug && f.frontendOptions.Debug.OpenInspectorOnStartup { chromium.OpenDevToolsWindow() } // Setup focus event handler onFocus := f.mainWindow.OnSetFocus() onFocus.Bind(f.onFocus) // Set background colour f.WindowSetBackgroundColour(f.frontendOptions.BackgroundColour) chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow) chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL) chromium.Navigate(f.startURL.String()) } type EventNotify struct { Name string `json:"name"` Data []interface{} `json:"data"` } func (f *Frontend) Notify(name string, data ...interface{}) { notification := EventNotify{ Name: name, Data: data, } payload, err := json.Marshal(notification) if err != nil { f.logger.Error(err.Error()) return } f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`) } func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) { // Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but // we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request. if reqHeaders, err := req.GetHeaders(); err == nil { useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent) useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ") reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent) reqHeaders.Release() } if f.assets == nil { // We are using the devServer let the WebView2 handle the request with its default handler return } //Get the request uri, _ := req.GetUri() reqUri, err := url.ParseRequestURI(uri) if err != nil { f.logger.Error("Unable to parse equest uri %s: %s", uri, err) return } if reqUri.Scheme != f.startURL.Scheme { // Let the WebView2 handle the request with its default handler return } else if reqUri.Host != f.startURL.Host { // Let the WebView2 handle the request with its default handler return } webviewRequest, err := webview.NewRequest( f.chromium.Environment(), args, func(fn func()) { runtime.LockOSThread() defer runtime.UnlockOSThread() if f.mainWindow.InvokeRequired() { var wg sync.WaitGroup wg.Add(1) f.mainWindow.Invoke(func() { fn() wg.Done() }) wg.Wait() } else { fn() } }) if err != nil { f.logger.Error("%s: NewRequest failed: %s", uri, err) return } f.assets.ServeWebViewRequest(webviewRequest) } var edgeMap = map[string]uintptr{ "n-resize": w32.HTTOP, "ne-resize": w32.HTTOPRIGHT, "e-resize": w32.HTRIGHT, "se-resize": w32.HTBOTTOMRIGHT, "s-resize": w32.HTBOTTOM, "sw-resize": w32.HTBOTTOMLEFT, "w-resize": w32.HTLEFT, "nw-resize": w32.HTTOPLEFT, } func (f *Frontend) processMessage(message string) { if message == "drag" { if !f.mainWindow.IsFullScreen() { err := f.startDrag() if err != nil { f.logger.Error(err.Error()) } } return } if message == "runtime:ready" { cmd := fmt.Sprintf( "window.wails.setCSSDragProperties('%s', '%s');\n"+ "window.wails.setCSSDropProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue, f.frontendOptions.DragAndDrop.CSSDropProperty, f.frontendOptions.DragAndDrop.CSSDropValue, ) f.ExecJS(cmd) return } if strings.HasPrefix(message, "resize:") { if !f.mainWindow.IsFullScreen() { sl := strings.Split(message, ":") if len(sl) != 2 { f.logger.Info("Unknown message returned from dispatcher: %+v", message) return } edge := edgeMap[sl[1]] err := f.startResize(edge) if err != nil { f.logger.Error(err.Error()) } } return } go f.dispatchMessage(message) } func (f *Frontend) processMessageWithAdditionalObjects(message string, sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs) { if strings.HasPrefix(message, "file:drop") { if !f.frontendOptions.DragAndDrop.EnableFileDrop { return } objs, err := args.GetAdditionalObjects() if err != nil { f.logger.Error(err.Error()) return } defer objs.Release() count, err := objs.GetCount() if err != nil { f.logger.Error(err.Error()) return } files := make([]string, count) for i := uint32(0); i < count; i++ { _file, err := objs.GetValueAtIndex(i) if err != nil { f.logger.Error("cannot get value at %d : %s", i, err.Error()) return } if _file == nil { f.logger.Warning("object at %d is not a file", i) continue } file := (*edge.ICoreWebView2File)(unsafe.Pointer(_file)) defer file.Release() filepath, err := file.GetPath() if err != nil { f.logger.Error("cannot get path for object at %d : %s", i, err.Error()) return } files[i] = filepath } var ( x = "0" y = "0" ) coords := strings.SplitN(message[10:], ":", 2) if len(coords) == 2 { x = coords[0] y = coords[1] } go f.dispatchMessage(fmt.Sprintf("DD:%s:%s:%s", x, y, strings.Join(files, "\n"))) return } } func (f *Frontend) dispatchMessage(message string) { result, err := f.dispatcher.ProcessMessage(message, f) if err != nil { f.logger.Error(err.Error()) f.Callback(result) return } if result == "" { return } switch result[0] { case 'c': // Callback from a method call f.Callback(result[1:]) default: f.logger.Info("Unknown message returned from dispatcher: %+v", result) } } func (f *Frontend) Callback(message string) { escaped, err := json.Marshal(message) if err != nil { panic(err) } f.mainWindow.Invoke(func() { f.chromium.Eval(`window.wails.Callback(` + string(escaped) + `);`) }) } func (f *Frontend) startDrag() error { if !w32.ReleaseCapture() { return fmt.Errorf("unable to release mouse capture") } // Use PostMessage because we don't want to block the caller until dragging has been finished. w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0) return nil } func (f *Frontend) startResize(border uintptr) error { if !w32.ReleaseCapture() { return fmt.Errorf("unable to release mouse capture") } // Use PostMessage because we don't want to block the caller until resizing has been finished. w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, border, 0) return nil } func (f *Frontend) ExecJS(js string) { f.mainWindow.Invoke(func() { f.chromium.Eval(js) }) } func (f *Frontend) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) { if f.frontendOptions.OnDomReady != nil { go f.frontendOptions.OnDomReady(f.ctx) } if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { f.ExecJS("window.wails.flags.enableResize = true;") } if f.frontendOptions.DragAndDrop != nil && f.frontendOptions.DragAndDrop.EnableFileDrop { f.ExecJS("window.wails.flags.enableWailsDragAndDrop = true;") } if f.hasStarted { return } f.hasStarted = true // Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026 err := f.chromium.Hide() if err != nil { log.Fatal(err) } err = f.chromium.Show() if err != nil { log.Fatal(err) } if f.frontendOptions.StartHidden { return } switch f.frontendOptions.WindowStartState { case options.Maximised: if !f.frontendOptions.DisableResize { win32.ShowWindowMaximised(f.mainWindow.Handle()) } else { win32.ShowWindow(f.mainWindow.Handle()) } case options.Minimised: win32.ShowWindowMinimised(f.mainWindow.Handle()) case options.Fullscreen: f.mainWindow.Fullscreen() win32.ShowWindow(f.mainWindow.Handle()) default: if f.frontendOptions.Fullscreen { f.mainWindow.Fullscreen() } win32.ShowWindow(f.mainWindow.Handle()) } f.mainWindow.hasBeenShown = true } func (f *Frontend) ShowWindow() { f.mainWindow.Invoke(func() { if !f.mainWindow.hasBeenShown { f.mainWindow.hasBeenShown = true switch f.frontendOptions.WindowStartState { case options.Maximised: if !f.frontendOptions.DisableResize { win32.ShowWindowMaximised(f.mainWindow.Handle()) } else { win32.ShowWindow(f.mainWindow.Handle()) } case options.Minimised: win32.RestoreWindow(f.mainWindow.Handle()) case options.Fullscreen: f.mainWindow.Fullscreen() win32.ShowWindow(f.mainWindow.Handle()) default: if f.frontendOptions.Fullscreen { f.mainWindow.Fullscreen() } win32.ShowWindow(f.mainWindow.Handle()) } } else { if win32.IsWindowMinimised(f.mainWindow.Handle()) { win32.RestoreWindow(f.mainWindow.Handle()) } else { win32.ShowWindow(f.mainWindow.Handle()) } } w32.SetForegroundWindow(f.mainWindow.Handle()) w32.SetFocus(f.mainWindow.Handle()) }) } func (f *Frontend) onFocus(arg *winc.Event) { f.chromium.Focus() } func (f *Frontend) startSecondInstanceProcessor() { for secondInstanceData := range secondInstanceBuffer { if f.frontendOptions.SingleInstanceLock != nil && f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) } } }