package application import ( "fmt" "sync" "time" "github.com/wailsapp/wails/v3/pkg/logger" "github.com/wailsapp/wails/v2/pkg/assetserver" "github.com/wailsapp/wails/v2/pkg/assetserver/webview" assetserveroptions "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v3/internal/runtime" "github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/options" ) type ( webviewWindowImpl interface { setTitle(title string) setSize(width, height int) setAlwaysOnTop(alwaysOnTop bool) setURL(url string) setResizable(resizable bool) setMinSize(width, height int) setMaxSize(width, height int) execJS(js string) restore() setBackgroundColour(color *options.RGBA) run() center() size() (int, int) width() int height() int position() (int, int) destroy() reload() forceReload() toggleDevTools() zoomReset() zoomIn() zoomOut() getZoom() float64 setZoom(zoom float64) close() zoom() minimize() setHTML(html string) setPosition(x int, y int) on(eventID uint) minimise() unminimise() maximise() unmaximise() fullscreen() unfullscreen() isMinimised() bool isMaximised() bool isFullscreen() bool disableSizeConstraints() setFullscreenButtonEnabled(enabled bool) show() hide() getScreen() (*Screen, error) setFrameless(bool) } ) type WebviewWindow struct { options *options.WebviewWindow impl webviewWindowImpl implLock sync.RWMutex id uint assets *assetserver.AssetServer messageProcessor *MessageProcessor eventListeners map[uint][]func() eventListenersLock sync.RWMutex } var windowID uint var windowIDLock sync.RWMutex func getWindowID() uint { windowIDLock.Lock() defer windowIDLock.Unlock() windowID++ return windowID } func NewWindow(options *options.WebviewWindow) *WebviewWindow { if options.Width == 0 { options.Width = 800 } if options.Height == 0 { options.Height = 600 } if options.URL == "" { options.URL = "/" } opts := assetserveroptions.Options{Assets: options.Assets.FS, Handler: options.Assets.Handler, Middleware: options.Assets.Middleware} // TODO Bindings, ServingFrom disk? srv, err := assetserver.NewAssetServer("", opts, false, nil, runtime.RuntimeAssetsBundle) if err != nil { globalApplication.fatal(err.Error()) } result := &WebviewWindow{ id: getWindowID(), options: options, eventListeners: make(map[uint][]func()), assets: srv, } result.messageProcessor = NewMessageProcessor(result) srv.UseRuntimeHandler(result.messageProcessor) return result } func (w *WebviewWindow) SetTitle(title string) *WebviewWindow { w.implLock.RLock() defer w.implLock.RUnlock() w.options.Title = title if w.impl != nil { w.impl.setTitle(title) } return w } func (w *WebviewWindow) SetSize(width, height int) *WebviewWindow { // Don't set size if fullscreen if w.IsFullscreen() { return w } w.options.Width = width w.options.Height = height var newMaxWidth = w.options.MaxWidth var newMaxHeight = w.options.MaxHeight if width > w.options.MaxWidth && w.options.MaxWidth != 0 { newMaxWidth = width } if height > w.options.MaxHeight && w.options.MaxHeight != 0 { newMaxHeight = height } if newMaxWidth != 0 || newMaxHeight != 0 { w.SetMaxSize(newMaxWidth, newMaxHeight) } var newMinWidth = w.options.MinWidth var newMinHeight = w.options.MinHeight if width < w.options.MinWidth && w.options.MinWidth != 0 { newMinWidth = width } if height < w.options.MinHeight && w.options.MinHeight != 0 { newMinHeight = height } if newMinWidth != 0 || newMinHeight != 0 { w.SetMinSize(newMinWidth, newMinHeight) } if w.impl != nil { w.impl.setSize(width, height) } return w } func (w *WebviewWindow) run() { if w.impl != nil { return } w.implLock.Lock() w.impl = newWindowImpl(w) w.implLock.Unlock() w.impl.run() } func (w *WebviewWindow) SetAlwaysOnTop(b bool) *WebviewWindow { w.options.AlwaysOnTop = b if w.impl == nil { w.impl.setAlwaysOnTop(b) } return w } func (w *WebviewWindow) Show() *WebviewWindow { if globalApplication.impl == nil { return w } if w.impl == nil { w.run() return w } w.impl.show() return w } func (w *WebviewWindow) Hide() *WebviewWindow { w.options.Hidden = true if w.impl != nil { w.impl.hide() } return w } func (w *WebviewWindow) SetURL(s string) *WebviewWindow { w.options.URL = s if w.impl != nil { w.impl.setURL(s) } return w } func (w *WebviewWindow) SetZoom(magnification float64) *WebviewWindow { w.options.Zoom = magnification if w.impl != nil { w.impl.setZoom(magnification) } return w } func (w *WebviewWindow) GetZoom() float64 { if w.impl != nil { return w.impl.getZoom() } return 1 } func (w *WebviewWindow) SetResizable(b bool) *WebviewWindow { w.options.DisableResize = !b if w.impl != nil { w.impl.setResizable(b) } return w } func (w *WebviewWindow) Resizable() bool { return !w.options.DisableResize } func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) *WebviewWindow { w.options.MinWidth = minWidth w.options.MinHeight = minHeight currentWidth, currentHeight := w.Size() newWidth, newHeight := currentWidth, currentHeight var newSize bool if minHeight != 0 && currentHeight < minHeight { newHeight = minHeight w.options.Height = newHeight newSize = true } if minWidth != 0 && currentWidth < minWidth { newWidth = minWidth w.options.Width = newWidth newSize = true } if w.impl != nil { if newSize { w.impl.setSize(newWidth, newHeight) } w.impl.setMinSize(minWidth, minHeight) } return w } func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) *WebviewWindow { w.options.MaxWidth = maxWidth w.options.MaxHeight = maxHeight currentWidth, currentHeight := w.Size() newWidth, newHeight := currentWidth, currentHeight var newSize bool if maxHeight != 0 && currentHeight > maxHeight { newHeight = maxHeight w.options.Height = maxHeight newSize = true } if maxWidth != 0 && currentWidth > maxWidth { newWidth = maxWidth w.options.Width = maxWidth newSize = true } if w.impl != nil { if newSize { w.impl.setSize(newWidth, newHeight) } w.impl.setMaxSize(maxWidth, maxHeight) } return w } func (w *WebviewWindow) ExecJS(js string) { if w.impl == nil { return } w.impl.execJS(js) } func (w *WebviewWindow) Fullscreen() *WebviewWindow { if w.impl == nil { w.options.StartState = options.WindowStateFullscreen return w } if !w.IsFullscreen() { w.disableSizeConstraints() w.impl.fullscreen() } return w } func (w *WebviewWindow) SetFullscreenButtonEnabled(enabled bool) *WebviewWindow { w.options.FullscreenButtonEnabled = enabled if w.impl != nil { w.impl.setFullscreenButtonEnabled(enabled) } return w } // IsMinimised returns true if the window is minimised func (w *WebviewWindow) IsMinimised() bool { if w.impl == nil { return false } return w.impl.isMinimised() } // IsMaximised returns true if the window is maximised func (w *WebviewWindow) IsMaximised() bool { if w.impl == nil { return false } return w.impl.isMaximised() } // Size returns the size of the window func (w *WebviewWindow) Size() (width int, height int) { if w.impl == nil { return 0, 0 } return w.impl.size() } // IsFullscreen returns true if the window is fullscreen func (w *WebviewWindow) IsFullscreen() bool { w.implLock.RLock() defer w.implLock.RUnlock() if w.impl == nil { return false } return w.impl.isFullscreen() } func (w *WebviewWindow) SetBackgroundColour(colour *options.RGBA) *WebviewWindow { w.options.BackgroundColour = colour if w.impl != nil { w.impl.setBackgroundColour(colour) } return w } func (w *WebviewWindow) handleMessage(message string) { w.info(message) // Check for special messages if message == "test" { w.SetTitle("Hello World") } w.messageProcessor.ProcessMessage(message) } func (w *WebviewWindow) handleWebViewRequest(request webview.Request) { url, _ := request.URL() w.info("Request: %s", url) w.assets.ServeWebViewRequest(request) } func (w *WebviewWindow) Center() { if w.impl == nil { return } w.impl.center() } func (w *WebviewWindow) On(eventType events.WindowEventType, callback func()) { eventID := uint(eventType) w.eventListenersLock.Lock() defer w.eventListenersLock.Unlock() w.eventListeners[eventID] = append(w.eventListeners[eventID], callback) if w.impl != nil { w.impl.on(eventID) } } func (w *WebviewWindow) handleWindowEvent(id uint) { w.eventListenersLock.RLock() for _, callback := range w.eventListeners[id] { go callback() } w.eventListenersLock.RUnlock() } func (w *WebviewWindow) Width() int { if w.impl == nil { return 0 } return w.impl.width() } func (w *WebviewWindow) Height() int { if w.impl == nil { return 0 } return w.impl.height() } func (w *WebviewWindow) Position() (int, int) { w.implLock.RLock() defer w.implLock.RUnlock() if w.impl == nil { return 0, 0 } return w.impl.position() } func (w *WebviewWindow) Destroy() { if w.impl == nil { return } w.impl.destroy() } func (w *WebviewWindow) Reload() { if w.impl == nil { return } w.impl.reload() } func (w *WebviewWindow) ForceReload() { if w.impl == nil { return } w.impl.forceReload() } func (w *WebviewWindow) ToggleFullscreen() { if w.impl == nil { return } if w.IsFullscreen() { w.UnFullscreen() } else { w.Fullscreen() } } func (w *WebviewWindow) ToggleDevTools() { if w.impl == nil { return } w.impl.toggleDevTools() } func (w *WebviewWindow) ZoomReset() *WebviewWindow { if w.impl != nil { w.impl.zoomReset() } return w } func (w *WebviewWindow) ZoomIn() { if w.impl == nil { return } w.impl.zoomIn() } func (w *WebviewWindow) ZoomOut() { if w.impl == nil { return } w.impl.zoomOut() } func (w *WebviewWindow) Close() { if w.impl == nil { return } w.impl.close() } func (w *WebviewWindow) Minimize() { if w.impl == nil { return } w.impl.minimize() } func (w *WebviewWindow) Zoom() { if w.impl == nil { return } w.impl.zoom() } func (w *WebviewWindow) SetHTML(html string) *WebviewWindow { w.options.HTML = html if w.impl != nil { w.impl.setHTML(html) } return w } func (w *WebviewWindow) SetPosition(x, y int) *WebviewWindow { w.options.X = x w.options.Y = y if w.impl != nil { w.impl.setPosition(x, y) } return w } func (w *WebviewWindow) Minimise() *WebviewWindow { if w.impl == nil { w.options.StartState = options.WindowStateMinimised return w } if !w.IsMinimised() { w.impl.minimise() } return w } func (w *WebviewWindow) Maximise() *WebviewWindow { if w.impl == nil { w.options.StartState = options.WindowStateMaximised return w } if !w.IsMaximised() { w.disableSizeConstraints() w.impl.maximise() } return w } func (w *WebviewWindow) UnMinimise() { if w.impl == nil { return } w.impl.unminimise() } func (w *WebviewWindow) UnMaximise() { if w.impl == nil { return } w.enableSizeConstraints() w.impl.unmaximise() } func (w *WebviewWindow) UnFullscreen() { if w.impl == nil { return } w.enableSizeConstraints() w.impl.unfullscreen() } func (w *WebviewWindow) Restore() { if w.impl == nil { return } if w.IsMinimised() { w.UnMinimise() } else if w.IsMaximised() { w.UnMaximise() } else if w.IsFullscreen() { w.UnFullscreen() } } func (w *WebviewWindow) disableSizeConstraints() { if w.impl == nil { return } w.impl.setMinSize(0, 0) w.impl.setMaxSize(0, 0) } func (w *WebviewWindow) enableSizeConstraints() { if w.impl == nil { return } w.SetMinSize(w.options.MinWidth, w.options.MinHeight) w.SetMaxSize(w.options.MaxWidth, w.options.MaxHeight) } func (w *WebviewWindow) GetScreen() (*Screen, error) { if w.impl == nil { return nil, nil } return w.impl.getScreen() } func (w *WebviewWindow) SetFrameless(frameless bool) *WebviewWindow { w.options.Frameless = frameless if w.impl != nil { w.impl.setFrameless(frameless) } return w } func (w *WebviewWindow) dispatchCustomEvent(event *CustomEvent) { msg := fmt.Sprintf("_wails.dispatchCustomEvent(%s);", event.ToJSON()) w.ExecJS(msg) } func (w *WebviewWindow) info(message string, args ...any) { globalApplication.Log(&logger.Message{ Level: "INFO", Message: message, Data: args, Sender: fmt.Sprintf("window %d", w.id), Time: time.Now(), }) }