feat(stubs): rebuild Wails v3 stub bridge from clean dev — 15 files, 479 exports
Some checks failed
Security Scan / security (push) Failing after 24s

Rebuilt from scratch on current dev (post-fleet AX passes).

Stub files:
- application.go (expanded App with 12 managers, WebviewWindow satisfies Window)
- application_options.go (Options, Mac/Win/Linux/iOS/Android, Server, TLS, Assets)
- browser_manager.go, browser_window.go (~47 no-op methods)
- clipboard.go, context_menu.go, dialog.go (full dialog builder API)
- environment.go, events.go (EventManager with On/Off/Emit/Reset)
- keybinding.go, menuitem.go (42 Role constants)
- screen.go (Rect/Point/Size geometry), services.go (generic Service[T])
- webview_window_options.go (full platform types)
- window.go (Window interface ~50 methods)

Wails v3 submodule at internal/wails3/ pinned to alpha 74.
All 16 gui packages build and test clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude 2026-03-31 17:20:22 +01:00
parent 4533018f80
commit d9fa59ab04
No known key found for this signature in database
GPG key ID: AF404715446AEB41
17 changed files with 3561 additions and 196 deletions

1
internal/wails3 Submodule

@ -0,0 +1 @@
Subproject commit bb4fbf95744fafe5acf84e143a419bfffc2159e6

View file

@ -2,12 +2,36 @@ package application
import (
"sync"
"unsafe"
"github.com/wailsapp/wails/v3/pkg/events"
)
// Context mirrors the callback context type exposed by Wails.
type Context struct{}
//
// item.OnClick(func(ctx *Context) { openPrefs() })
type Context struct {
clickedMenuItem *MenuItem
contextMenuData *ContextMenuData
checked bool
}
func newContext() *Context { return &Context{} }
func (ctx *Context) withClickedMenuItem(item *MenuItem) *Context {
ctx.clickedMenuItem = item
return ctx
}
func (ctx *Context) withContextMenuData(data *ContextMenuData) *Context {
ctx.contextMenuData = data
return ctx
}
func (ctx *Context) withChecked(checked bool) *Context {
ctx.checked = checked
return ctx
}
// Logger is a minimal logger surface used by the GUI packages.
type Logger struct{}
@ -15,90 +39,92 @@ type Logger struct{}
func (l Logger) Info(message string, args ...any) {}
// RGBA stores a colour with alpha.
//
// colour := NewRGBA(255, 128, 0, 255) // opaque orange
type RGBA struct {
Red, Green, Blue, Alpha uint8
}
// NewRGBA constructs an RGBA value.
//
// colour := NewRGBA(255, 128, 0, 255) // opaque orange
func NewRGBA(red, green, blue, alpha uint8) RGBA {
return RGBA{Red: red, Green: green, Blue: blue, Alpha: alpha}
}
// MenuRole identifies a platform menu role.
type MenuRole int
const (
AppMenu MenuRole = iota
FileMenu
EditMenu
ViewMenu
WindowMenu
HelpMenu
)
// MenuItem is a minimal menu item implementation.
type MenuItem struct {
Label string
Accelerator string
Tooltip string
Checked bool
Enabled bool
onClick func(*Context)
}
func (mi *MenuItem) SetAccelerator(accel string) { mi.Accelerator = accel }
func (mi *MenuItem) SetTooltip(text string) { mi.Tooltip = text }
func (mi *MenuItem) SetChecked(checked bool) { mi.Checked = checked }
func (mi *MenuItem) SetEnabled(enabled bool) { mi.Enabled = enabled }
func (mi *MenuItem) OnClick(fn func(*Context)) { mi.onClick = fn }
// Menu is a minimal menu tree used by the GUI wrappers.
// Menu is a menu tree used by the GUI wrappers.
//
// menu := NewMenu()
// menu.Add("Save").SetAccelerator("CmdOrCtrl+S").OnClick(func(ctx *Context) { save() })
type Menu struct {
label string
Items []*MenuItem
}
// NewMenu creates a new, empty Menu.
//
// menu := NewMenu()
func NewMenu() *Menu { return &Menu{} }
// Add appends a new text item with the given label.
func (m *Menu) Add(label string) *MenuItem {
item := &MenuItem{Label: label, Enabled: true}
item := NewMenuItem(label)
item.disabled = false
m.Items = append(m.Items, item)
return item
}
// AddSeparator appends a separator item.
func (m *Menu) AddSeparator() {
m.Items = append(m.Items, &MenuItem{Label: "---"})
m.Items = append(m.Items, NewMenuItemSeparator())
}
// AddSubmenu appends a submenu item and returns the child Menu.
func (m *Menu) AddSubmenu(label string) *Menu {
submenu := &Menu{}
m.Items = append(m.Items, &MenuItem{Label: label})
return submenu
item := NewSubMenuItem(label)
m.Items = append(m.Items, item)
return item.submenu
}
func (m *Menu) AddRole(role MenuRole) {
m.Items = append(m.Items, &MenuItem{Label: role.String(), Enabled: true})
// AddRole appends a platform-role item.
func (m *Menu) AddRole(role Role) {
m.Items = append(m.Items, NewRole(role))
}
func (role MenuRole) String() string {
switch role {
case AppMenu:
return "app"
case FileMenu:
return "file"
case EditMenu:
return "edit"
case ViewMenu:
return "view"
case WindowMenu:
return "window"
case HelpMenu:
return "help"
default:
return "unknown"
// AppendItem appends an already-constructed MenuItem.
func (m *Menu) AppendItem(item *MenuItem) {
m.Items = append(m.Items, item)
}
// Clone returns a deep copy of the menu tree.
func (m *Menu) Clone() *Menu {
cloned := &Menu{label: m.label}
for _, item := range m.Items {
cloned.Items = append(cloned.Items, item.Clone())
}
return cloned
}
// Destroy frees all items in the menu.
func (m *Menu) Destroy() {
for _, item := range m.Items {
item.Destroy()
}
m.Items = nil
}
func (m *Menu) setContextData(data *ContextMenuData) {
for _, item := range m.Items {
item.contextMenuData = data
if item.submenu != nil {
item.submenu.setContextData(data)
}
}
}
// MenuManager owns the application menu.
//
// app.Menu.SetApplicationMenu(menu)
type MenuManager struct {
applicationMenu *Menu
}
@ -112,7 +138,7 @@ type SystemTray struct {
tooltip string
label string
menu *Menu
attachedWindow *WebviewWindow
attachedWindow Window
}
func (t *SystemTray) SetIcon(data []byte) { t.icon = append([]byte(nil), data...) }
@ -120,9 +146,9 @@ func (t *SystemTray) SetTemplateIcon(data []byte) { t.templateIcon = append([]by
func (t *SystemTray) SetTooltip(text string) { t.tooltip = text }
func (t *SystemTray) SetLabel(text string) { t.label = text }
func (t *SystemTray) SetMenu(menu *Menu) { t.menu = menu }
func (t *SystemTray) AttachWindow(w *WebviewWindow) {
t.attachedWindow = w
}
// AttachWindow associates a window with the tray icon (shown on click).
func (t *SystemTray) AttachWindow(w Window) { t.attachedWindow = w }
// SystemTrayManager creates tray instances.
type SystemTrayManager struct{}
@ -164,159 +190,408 @@ func (e *WindowEvent) Context() *WindowEventContext {
return e.ctx
}
// WebviewWindowOptions configures a window instance.
type WebviewWindowOptions struct {
Name string
Title string
URL string
Width, Height int
X, Y int
MinWidth, MinHeight int
MaxWidth, MaxHeight int
Frameless bool
Hidden bool
AlwaysOnTop bool
DisableResize bool
EnableFileDrop bool
BackgroundColour RGBA
// WebviewWindow is a lightweight, in-memory window implementation
// that satisfies the Window interface.
type WebviewWindow struct {
mu sync.RWMutex
opts WebviewWindowOptions
windowID uint
title string
posX, posY int
sizeW, sizeH int
maximised bool
focused bool
visible bool
alwaysOnTop bool
isFullscreen bool
closed bool
zoom float64
resizable bool
ignoreMouseEvents bool
enabled bool
eventHandlers map[events.WindowEventType][]func(*WindowEvent)
}
// WebviewWindow is a lightweight, in-memory window implementation.
type WebviewWindow struct {
mu sync.RWMutex
opts WebviewWindowOptions
title string
x, y int
width, height int
maximised bool
focused bool
visible bool
alwaysOnTop bool
fullscreen bool
closed bool
eventHandlers map[events.WindowEventType][]func(*WindowEvent)
var globalWindowID uint
var globalWindowIDMu sync.Mutex
func nextWindowID() uint {
globalWindowIDMu.Lock()
defer globalWindowIDMu.Unlock()
globalWindowID++
return globalWindowID
}
func newWebviewWindow(options WebviewWindowOptions) *WebviewWindow {
return &WebviewWindow{
opts: options,
windowID: nextWindowID(),
title: options.Title,
x: options.X,
y: options.Y,
width: options.Width,
height: options.Height,
posX: options.X,
posY: options.Y,
sizeW: options.Width,
sizeH: options.Height,
visible: !options.Hidden,
alwaysOnTop: options.AlwaysOnTop,
zoom: options.Zoom,
resizable: !options.DisableResize,
enabled: true,
eventHandlers: make(map[events.WindowEventType][]func(*WindowEvent)),
}
}
// ID returns the unique numeric identifier for the window.
func (w *WebviewWindow) ID() uint { return w.windowID }
// Name returns the window name set in WebviewWindowOptions.
func (w *WebviewWindow) Name() string { return w.opts.Name }
func (w *WebviewWindow) Title() string {
// Show makes the window visible and returns it for chaining.
func (w *WebviewWindow) Show() Window {
w.mu.Lock()
w.visible = true
w.mu.Unlock()
return w
}
// Hide makes the window invisible and returns it for chaining.
func (w *WebviewWindow) Hide() Window {
w.mu.Lock()
w.visible = false
w.mu.Unlock()
return w
}
// IsVisible reports whether the window is currently visible.
func (w *WebviewWindow) IsVisible() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.title
}
func (w *WebviewWindow) Position() (int, int) {
w.mu.RLock()
defer w.mu.RUnlock()
return w.x, w.y
}
func (w *WebviewWindow) Size() (int, int) {
w.mu.RLock()
defer w.mu.RUnlock()
return w.width, w.height
}
func (w *WebviewWindow) IsMaximised() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.maximised
}
func (w *WebviewWindow) IsFocused() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.focused
}
func (w *WebviewWindow) SetTitle(title string) {
w.mu.Lock()
w.title = title
w.mu.Unlock()
}
func (w *WebviewWindow) SetPosition(x, y int) {
w.mu.Lock()
w.x = x
w.y = y
w.mu.Unlock()
}
func (w *WebviewWindow) SetSize(width, height int) {
w.mu.Lock()
w.width = width
w.height = height
w.mu.Unlock()
}
func (w *WebviewWindow) SetBackgroundColour(colour RGBA) {}
func (w *WebviewWindow) SetAlwaysOnTop(alwaysOnTop bool) {
w.mu.Lock()
w.alwaysOnTop = alwaysOnTop
w.mu.Unlock()
}
func (w *WebviewWindow) Maximise() {
w.mu.Lock()
w.maximised = true
w.mu.Unlock()
}
func (w *WebviewWindow) Restore() {
w.mu.Lock()
w.maximised = false
w.fullscreen = false
w.mu.Unlock()
}
func (w *WebviewWindow) Minimise() {}
func (w *WebviewWindow) Focus() {
w.mu.Lock()
w.focused = true
w.mu.Unlock()
return w.visible
}
// Close marks the window as closed.
func (w *WebviewWindow) Close() {
w.mu.Lock()
w.closed = true
w.mu.Unlock()
}
func (w *WebviewWindow) Show() {
// Focus marks the window as focused.
func (w *WebviewWindow) Focus() {
w.mu.Lock()
w.focused = true
w.mu.Unlock()
}
// Run is a no-op in the stub (the real implementation enters the run loop).
func (w *WebviewWindow) Run() {}
// Center is a no-op in the stub.
func (w *WebviewWindow) Center() {}
// Position returns the current x/y position.
func (w *WebviewWindow) Position() (int, int) {
w.mu.RLock()
defer w.mu.RUnlock()
return w.posX, w.posY
}
// RelativePosition returns the position relative to the screen.
func (w *WebviewWindow) RelativePosition() (int, int) {
w.mu.RLock()
defer w.mu.RUnlock()
return w.posX, w.posY
}
// Size returns the current width and height.
func (w *WebviewWindow) Size() (int, int) {
w.mu.RLock()
defer w.mu.RUnlock()
return w.sizeW, w.sizeH
}
// Width returns the current window width.
func (w *WebviewWindow) Width() int {
w.mu.RLock()
defer w.mu.RUnlock()
return w.sizeW
}
// Height returns the current window height.
func (w *WebviewWindow) Height() int {
w.mu.RLock()
defer w.mu.RUnlock()
return w.sizeH
}
// Bounds returns the window's position and size as a Rect.
func (w *WebviewWindow) Bounds() Rect {
w.mu.RLock()
defer w.mu.RUnlock()
return Rect{X: w.posX, Y: w.posY, Width: w.sizeW, Height: w.sizeH}
}
// SetPosition sets the top-left corner position.
func (w *WebviewWindow) SetPosition(x, y int) {
w.mu.Lock()
w.posX, w.posY = x, y
w.mu.Unlock()
}
// SetRelativePosition sets position relative to the screen and returns the window.
func (w *WebviewWindow) SetRelativePosition(x, y int) Window {
w.SetPosition(x, y)
return w
}
// SetSize sets the window dimensions and returns the window.
func (w *WebviewWindow) SetSize(width, height int) Window {
w.mu.Lock()
w.sizeW, w.sizeH = width, height
w.mu.Unlock()
return w
}
// SetBounds sets position and size in one call.
func (w *WebviewWindow) SetBounds(bounds Rect) {
w.mu.Lock()
w.posX, w.posY = bounds.X, bounds.Y
w.sizeW, w.sizeH = bounds.Width, bounds.Height
w.mu.Unlock()
}
// SetMaxSize is a no-op in the stub.
func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) Window { return w }
// SetMinSize is a no-op in the stub.
func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) Window { return w }
// EnableSizeConstraints is a no-op in the stub.
func (w *WebviewWindow) EnableSizeConstraints() {}
// DisableSizeConstraints is a no-op in the stub.
func (w *WebviewWindow) DisableSizeConstraints() {}
// Resizable reports whether the user can resize the window.
func (w *WebviewWindow) Resizable() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.resizable
}
// SetResizable enables or disables user resizing and returns the window.
func (w *WebviewWindow) SetResizable(b bool) Window {
w.mu.Lock()
w.resizable = b
w.mu.Unlock()
return w
}
// Maximise maximises the window and returns it.
func (w *WebviewWindow) Maximise() Window {
w.mu.Lock()
w.maximised = true
w.mu.Unlock()
return w
}
// UnMaximise restores from maximised state.
func (w *WebviewWindow) UnMaximise() {
w.mu.Lock()
w.maximised = false
w.mu.Unlock()
}
// ToggleMaximise toggles between maximised and normal.
func (w *WebviewWindow) ToggleMaximise() {
w.mu.Lock()
w.maximised = !w.maximised
w.mu.Unlock()
}
// IsMaximised reports whether the window is maximised.
func (w *WebviewWindow) IsMaximised() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.maximised
}
// Minimise minimises the window and returns it.
func (w *WebviewWindow) Minimise() Window {
w.mu.Lock()
w.visible = false
w.mu.Unlock()
return w
}
// UnMinimise restores from minimised state.
func (w *WebviewWindow) UnMinimise() {
w.mu.Lock()
w.visible = true
w.mu.Unlock()
}
func (w *WebviewWindow) Hide() {
// IsMinimised always returns false in the stub.
func (w *WebviewWindow) IsMinimised() bool { return false }
// Fullscreen enters fullscreen and returns the window.
func (w *WebviewWindow) Fullscreen() Window {
w.mu.Lock()
w.visible = false
w.mu.Unlock()
}
func (w *WebviewWindow) Fullscreen() {
w.mu.Lock()
w.fullscreen = true
w.isFullscreen = true
w.mu.Unlock()
return w
}
// UnFullscreen exits fullscreen.
func (w *WebviewWindow) UnFullscreen() {
w.mu.Lock()
w.fullscreen = false
w.isFullscreen = false
w.mu.Unlock()
}
// ToggleFullscreen toggles between fullscreen and normal.
func (w *WebviewWindow) ToggleFullscreen() {
w.mu.Lock()
w.isFullscreen = !w.isFullscreen
w.mu.Unlock()
}
// IsFullscreen reports whether the window is in fullscreen mode.
func (w *WebviewWindow) IsFullscreen() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.isFullscreen
}
// Restore exits both fullscreen and maximised states.
func (w *WebviewWindow) Restore() {
w.mu.Lock()
w.maximised = false
w.isFullscreen = false
w.mu.Unlock()
}
// SnapAssist is a no-op in the stub.
func (w *WebviewWindow) SnapAssist() {}
// SetTitle updates the window title and returns the window.
func (w *WebviewWindow) SetTitle(title string) Window {
w.mu.Lock()
w.title = title
w.mu.Unlock()
return w
}
// SetURL is a no-op in the stub.
func (w *WebviewWindow) SetURL(s string) Window { return w }
// SetHTML is a no-op in the stub.
func (w *WebviewWindow) SetHTML(html string) Window { return w }
// SetMinimiseButtonState is a no-op in the stub.
func (w *WebviewWindow) SetMinimiseButtonState(state ButtonState) Window { return w }
// SetMaximiseButtonState is a no-op in the stub.
func (w *WebviewWindow) SetMaximiseButtonState(state ButtonState) Window { return w }
// SetCloseButtonState is a no-op in the stub.
func (w *WebviewWindow) SetCloseButtonState(state ButtonState) Window { return w }
// SetMenu is a no-op in the stub.
func (w *WebviewWindow) SetMenu(menu *Menu) {}
// ShowMenuBar is a no-op in the stub.
func (w *WebviewWindow) ShowMenuBar() {}
// HideMenuBar is a no-op in the stub.
func (w *WebviewWindow) HideMenuBar() {}
// ToggleMenuBar is a no-op in the stub.
func (w *WebviewWindow) ToggleMenuBar() {}
// SetBackgroundColour is a no-op in the stub.
func (w *WebviewWindow) SetBackgroundColour(colour RGBA) Window { return w }
// SetAlwaysOnTop sets the always-on-top flag and returns the window.
func (w *WebviewWindow) SetAlwaysOnTop(b bool) Window {
w.mu.Lock()
w.alwaysOnTop = b
w.mu.Unlock()
return w
}
// SetFrameless is a no-op in the stub.
func (w *WebviewWindow) SetFrameless(frameless bool) Window { return w }
// ToggleFrameless is a no-op in the stub.
func (w *WebviewWindow) ToggleFrameless() {}
// SetIgnoreMouseEvents sets the mouse-event passthrough flag and returns the window.
func (w *WebviewWindow) SetIgnoreMouseEvents(ignore bool) Window {
w.mu.Lock()
w.ignoreMouseEvents = ignore
w.mu.Unlock()
return w
}
// IsIgnoreMouseEvents reports whether mouse events are being ignored.
func (w *WebviewWindow) IsIgnoreMouseEvents() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.ignoreMouseEvents
}
// SetContentProtection is a no-op in the stub.
func (w *WebviewWindow) SetContentProtection(protection bool) Window { return w }
// GetZoom returns the current zoom magnification.
func (w *WebviewWindow) GetZoom() float64 {
w.mu.RLock()
defer w.mu.RUnlock()
return w.zoom
}
// SetZoom sets the zoom magnification and returns the window.
func (w *WebviewWindow) SetZoom(magnification float64) Window {
w.mu.Lock()
w.zoom = magnification
w.mu.Unlock()
return w
}
// Zoom is a no-op in the stub.
func (w *WebviewWindow) Zoom() {}
// ZoomIn is a no-op in the stub.
func (w *WebviewWindow) ZoomIn() {}
// ZoomOut is a no-op in the stub.
func (w *WebviewWindow) ZoomOut() {}
// ZoomReset resets zoom to 1.0 and returns the window.
func (w *WebviewWindow) ZoomReset() Window {
w.mu.Lock()
w.zoom = 1.0
w.mu.Unlock()
return w
}
// GetBorderSizes returns zero insets in the stub.
func (w *WebviewWindow) GetBorderSizes() *LRTB { return &LRTB{} }
// GetScreen returns nil in the stub.
func (w *WebviewWindow) GetScreen() (*Screen, error) { return nil, nil }
// ExecJS is a no-op in the stub.
func (w *WebviewWindow) ExecJS(js string) {}
// EmitEvent always returns false in the stub.
func (w *WebviewWindow) EmitEvent(name string, data ...any) bool { return false }
// DispatchWailsEvent is a no-op in the stub.
func (w *WebviewWindow) DispatchWailsEvent(event *CustomEvent) {}
// OnWindowEvent registers an event callback and returns an unsubscribe function.
func (w *WebviewWindow) OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() {
w.mu.Lock()
w.eventHandlers[eventType] = append(w.eventHandlers[eventType], callback)
@ -324,40 +599,180 @@ func (w *WebviewWindow) OnWindowEvent(eventType events.WindowEventType, callback
return func() {}
}
// WindowManager manages in-memory windows.
type WindowManager struct {
mu sync.RWMutex
windows []*WebviewWindow
// RegisterHook is an alias for OnWindowEvent.
func (w *WebviewWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() {
return w.OnWindowEvent(eventType, callback)
}
// handleDragAndDropMessage is a no-op in the stub.
func (w *WebviewWindow) handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) {}
// InitiateFrontendDropProcessing is a no-op in the stub.
func (w *WebviewWindow) InitiateFrontendDropProcessing(filenames []string, x int, y int) {}
// HandleMessage is a no-op in the stub.
func (w *WebviewWindow) HandleMessage(message string) {}
// HandleWindowEvent is a no-op in the stub.
func (w *WebviewWindow) HandleWindowEvent(id uint) {}
// HandleKeyEvent is a no-op in the stub.
func (w *WebviewWindow) HandleKeyEvent(acceleratorString string) {}
// OpenContextMenu is a no-op in the stub.
func (w *WebviewWindow) OpenContextMenu(data *ContextMenuData) {}
// AttachModal is a no-op in the stub.
func (w *WebviewWindow) AttachModal(modalWindow Window) {}
// OpenDevTools is a no-op in the stub.
func (w *WebviewWindow) OpenDevTools() {}
// Print always returns nil in the stub.
func (w *WebviewWindow) Print() error { return nil }
// Flash is a no-op in the stub.
func (w *WebviewWindow) Flash(enabled bool) {}
// IsFocused reports whether the window has focus.
func (w *WebviewWindow) IsFocused() bool {
w.mu.RLock()
defer w.mu.RUnlock()
return w.focused
}
// NativeWindow returns nil in the stub.
func (w *WebviewWindow) NativeWindow() unsafe.Pointer { return nil }
// SetEnabled sets the window's enabled state.
func (w *WebviewWindow) SetEnabled(enabled bool) {
w.mu.Lock()
w.enabled = enabled
w.mu.Unlock()
}
// Reload is a no-op in the stub.
func (w *WebviewWindow) Reload() {}
// ForceReload is a no-op in the stub.
func (w *WebviewWindow) ForceReload() {}
// Info is a no-op in the stub.
func (w *WebviewWindow) Info(message string, args ...any) {}
// Error is a no-op in the stub.
func (w *WebviewWindow) Error(message string, args ...any) {}
// shouldUnconditionallyClose always returns false in the stub.
func (w *WebviewWindow) shouldUnconditionallyClose() bool { return false }
// Internal editing stubs — satisfy the Window interface.
func (w *WebviewWindow) cut() {}
func (w *WebviewWindow) copy() {}
func (w *WebviewWindow) paste() {}
func (w *WebviewWindow) undo() {}
func (w *WebviewWindow) redo() {}
func (w *WebviewWindow) delete() {}
func (w *WebviewWindow) selectAll() {}
// Title returns the current window title.
func (w *WebviewWindow) Title() string {
w.mu.RLock()
defer w.mu.RUnlock()
return w.title
}
// WindowManager manages in-memory windows.
//
// win := app.Window.NewWithOptions(application.WebviewWindowOptions{Title: "Main"})
type WindowManager struct {
mu sync.RWMutex
windows map[uint]*WebviewWindow
}
func (wm *WindowManager) init() {
if wm.windows == nil {
wm.windows = make(map[uint]*WebviewWindow)
}
}
// NewWithOptions creates and registers a new window.
func (wm *WindowManager) NewWithOptions(options WebviewWindowOptions) *WebviewWindow {
window := newWebviewWindow(options)
wm.mu.Lock()
wm.windows = append(wm.windows, window)
wm.init()
wm.windows[window.windowID] = window
wm.mu.Unlock()
return window
}
func (wm *WindowManager) GetAll() []any {
// New creates a window with default options.
func (wm *WindowManager) New() *WebviewWindow {
return wm.NewWithOptions(WebviewWindowOptions{})
}
// GetAll returns all managed windows as the Window interface slice.
func (wm *WindowManager) GetAll() []Window {
wm.mu.RLock()
defer wm.mu.RUnlock()
out := make([]any, 0, len(wm.windows))
out := make([]Window, 0, len(wm.windows))
for _, window := range wm.windows {
out = append(out, window)
}
return out
}
// App is the top-level application object used by the GUI packages.
type App struct {
Logger Logger
Window WindowManager
Menu MenuManager
SystemTray SystemTrayManager
// GetByName finds a window by name, returning it and whether it was found.
func (wm *WindowManager) GetByName(name string) (Window, bool) {
wm.mu.RLock()
defer wm.mu.RUnlock()
for _, window := range wm.windows {
if window.opts.Name == name {
return window, true
}
}
return nil, false
}
// GetByID finds a window by its numeric ID.
func (wm *WindowManager) GetByID(id uint) (Window, bool) {
wm.mu.RLock()
defer wm.mu.RUnlock()
window, exists := wm.windows[id]
return window, exists
}
// Remove unregisters a window by ID.
func (wm *WindowManager) Remove(windowID uint) {
wm.mu.Lock()
wm.init()
delete(wm.windows, windowID)
wm.mu.Unlock()
}
// App is the top-level application object used by the GUI packages.
//
// app := &application.App{}
// win := app.Window.NewWithOptions(application.WebviewWindowOptions{Title: "Main"})
type App struct {
Logger Logger
Window WindowManager
Menu MenuManager
SystemTray SystemTrayManager
Dialog DialogManager
Event EventManager
Browser BrowserManager
Clipboard ClipboardManager
ContextMenu ContextMenuManager
Environment EnvironmentManager
Screen ScreenManager
KeyBinding KeyBindingManager
}
// Quit is a no-op in the stub.
func (a *App) Quit() {}
// NewMenu creates a new empty Menu.
func (a *App) NewMenu() *Menu {
return NewMenu()
}

View file

@ -0,0 +1,348 @@
package application
import (
"net/http"
"time"
)
// Options is the top-level application configuration passed to New().
// app := application.New(application.Options{Name: "MyApp", Services: []Service{...}})
type Options struct {
// Name is displayed in the default about box.
Name string
// Description is displayed in the default about box.
Description string
// Icon is the application icon shown in the about box.
Icon []byte
// Mac contains macOS-specific options.
Mac MacOptions
// Windows contains Windows-specific options.
Windows WindowsOptions
// Linux contains Linux-specific options.
Linux LinuxOptions
// IOS contains iOS-specific options.
IOS IOSOptions
// Android contains Android-specific options.
Android AndroidOptions
// Services lists bound Go services exposed to the frontend.
Services []Service
// MarshalError serialises service method errors to JSON.
// Return nil to fall back to the default error handler.
MarshalError func(error) []byte
// BindAliases maps alias IDs to bound method IDs.
// Example: map[uint32]uint32{1: 1411160069}
BindAliases map[uint32]uint32
// Assets configures the embedded asset server.
Assets AssetOptions
// Flags are key/value pairs available to the frontend at startup.
Flags map[string]any
// PanicHandler is called when an unhandled panic occurs.
PanicHandler func(*PanicDetails)
// DisableDefaultSignalHandler prevents Wails from handling OS signals.
DisableDefaultSignalHandler bool
// KeyBindings maps accelerator strings to window callbacks.
// Example: map[string]func(Window){"Ctrl+Q": func(w Window) { w.Close() }}
KeyBindings map[string]func(window Window)
// OnShutdown is called before the application terminates.
OnShutdown func()
// PostShutdown is called after shutdown, just before process exit.
PostShutdown func()
// ShouldQuit is called when the user attempts to quit.
// Return false to prevent the application from quitting.
ShouldQuit func() bool
// RawMessageHandler handles raw messages from the frontend.
RawMessageHandler func(window Window, message string, originInfo *OriginInfo)
// WarningHandler is called when a non-fatal warning occurs.
WarningHandler func(string)
// ErrorHandler is called when a non-fatal error occurs.
ErrorHandler func(err error)
// FileAssociations lists file extensions associated with this application.
// Each extension must include the leading dot, e.g. ".txt".
FileAssociations []string
// SingleInstance configures single-instance enforcement.
SingleInstance *SingleInstanceOptions
// Server configures the headless HTTP server (enabled via the "server" build tag).
Server ServerOptions
}
// ServerOptions configures the headless HTTP server started in server mode.
// opts.Server = application.ServerOptions{Host: "0.0.0.0", Port: 8080}
type ServerOptions struct {
// Host is the bind address. Defaults to "localhost".
Host string
// Port is the TCP port. Defaults to 8080.
Port int
// ReadTimeout is the maximum duration for reading a complete request.
ReadTimeout time.Duration
// WriteTimeout is the maximum duration before timing out a response write.
WriteTimeout time.Duration
// IdleTimeout is the maximum duration to wait for the next request.
IdleTimeout time.Duration
// ShutdownTimeout is the maximum duration to wait for active connections on shutdown.
ShutdownTimeout time.Duration
// TLS configures HTTPS. If nil, plain HTTP is used.
TLS *TLSOptions
}
// TLSOptions configures HTTPS for the headless server.
// opts.Server.TLS = &application.TLSOptions{CertFile: "cert.pem", KeyFile: "key.pem"}
type TLSOptions struct {
// CertFile is the path to the TLS certificate file.
CertFile string
// KeyFile is the path to the TLS private key file.
KeyFile string
}
// AssetOptions configures the embedded asset server.
// opts.Assets = application.AssetOptions{Handler: http.FileServer(http.FS(assets))}
type AssetOptions struct {
// Handler serves all content to the WebView.
Handler http.Handler
// Middleware injects into the asset server request chain before Wails internals.
Middleware Middleware
// DisableLogging suppresses per-request asset server log output.
DisableLogging bool
}
// Middleware is an HTTP middleware function for the asset server.
// type Middleware func(next http.Handler) http.Handler
type Middleware func(next http.Handler) http.Handler
// ChainMiddleware composes multiple Middleware values into a single Middleware.
// chain := application.ChainMiddleware(authMiddleware, loggingMiddleware)
func ChainMiddleware(middleware ...Middleware) Middleware {
return func(handler http.Handler) http.Handler {
for index := len(middleware) - 1; index >= 0; index-- {
handler = middleware[index](handler)
}
return handler
}
}
// PanicDetails carries information about an unhandled panic.
// opts.PanicHandler = func(d *application.PanicDetails) { log(d.StackTrace) }
type PanicDetails struct {
StackTrace string
Error error
FullStackTrace string
}
// OriginInfo carries frame-origin metadata for raw message handling.
// opts.RawMessageHandler = func(w Window, msg string, o *application.OriginInfo) { ... }
type OriginInfo struct {
Origin string
TopOrigin string
IsMainFrame bool
}
// SingleInstanceOptions configures single-instance enforcement.
// opts.SingleInstance = &application.SingleInstanceOptions{UniqueID: "com.example.myapp"}
type SingleInstanceOptions struct {
// UniqueID identifies the application instance, e.g. "com.example.myapp".
UniqueID string
// OnSecondInstanceLaunch is called when a second instance attempts to start.
OnSecondInstanceLaunch func(data SecondInstanceData)
// AdditionalData passes custom data from the second instance to the first.
AdditionalData map[string]string
}
// SecondInstanceData describes a second-instance launch event.
type SecondInstanceData struct {
Args []string `json:"args"`
WorkingDir string `json:"workingDir"`
AdditionalData map[string]string `json:"additionalData,omitempty"`
}
// ActivationPolicy controls when a macOS application activates.
// opts.Mac.ActivationPolicy = application.ActivationPolicyAccessory
type ActivationPolicy int
const (
// ActivationPolicyRegular is used for applications with a main window.
ActivationPolicyRegular ActivationPolicy = iota
// ActivationPolicyAccessory is used for menu-bar or background applications.
ActivationPolicyAccessory
// ActivationPolicyProhibited prevents the application from activating.
ActivationPolicyProhibited
)
// MacOptions contains macOS-specific application options.
// opts.Mac = application.MacOptions{ActivationPolicy: application.ActivationPolicyRegular}
type MacOptions struct {
// ActivationPolicy controls how and when the application becomes active.
ActivationPolicy ActivationPolicy
// ApplicationShouldTerminateAfterLastWindowClosed quits the app when
// the last window closes, matching standard macOS behaviour.
ApplicationShouldTerminateAfterLastWindowClosed bool
}
// WindowsOptions contains Windows-specific application options.
// opts.Windows = application.WindowsOptions{WndClass: "MyAppWindow"}
type WindowsOptions struct {
// WndClass is the Windows window class name. Defaults to "WailsWebviewWindow".
WndClass string
// WndProcInterceptor intercepts all Win32 messages in the main message loop.
WndProcInterceptor func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnCode uintptr, shouldReturn bool)
// DisableQuitOnLastWindowClosed prevents auto-quit when the last window closes.
DisableQuitOnLastWindowClosed bool
// WebviewUserDataPath sets the WebView2 user-data directory.
// Defaults to %APPDATA%\[BinaryName.exe].
WebviewUserDataPath string
// WebviewBrowserPath sets the directory containing WebView2 executables.
// Defaults to the system-installed WebView2.
WebviewBrowserPath string
// EnabledFeatures lists WebView2 feature flags to enable.
EnabledFeatures []string
// DisabledFeatures lists WebView2 feature flags to disable.
DisabledFeatures []string
// AdditionalBrowserArgs are extra Chromium command-line arguments.
// Each argument must include the "--" prefix, e.g. "--remote-debugging-port=9222".
AdditionalBrowserArgs []string
}
// LinuxOptions contains Linux-specific application options.
// opts.Linux = application.LinuxOptions{ProgramName: "myapp"}
type LinuxOptions struct {
// DisableQuitOnLastWindowClosed prevents auto-quit when the last window closes.
DisableQuitOnLastWindowClosed bool
// ProgramName is passed to g_set_prgname() for window grouping in GTK.
ProgramName string
}
// IOSOptions contains iOS-specific application options.
// opts.IOS = application.IOSOptions{EnableInlineMediaPlayback: true}
type IOSOptions struct {
// DisableInputAccessoryView hides the Next/Previous/Done toolbar above the keyboard.
DisableInputAccessoryView bool
// DisableScroll disables WebView scrolling.
DisableScroll bool
// DisableBounce disables the iOS overscroll bounce effect.
DisableBounce bool
// DisableScrollIndicators hides scroll indicator bars.
DisableScrollIndicators bool
// EnableBackForwardNavigationGestures enables swipe navigation gestures.
EnableBackForwardNavigationGestures bool
// DisableLinkPreview disables long-press link preview (peek and pop).
DisableLinkPreview bool
// EnableInlineMediaPlayback allows video to play inline rather than full-screen.
EnableInlineMediaPlayback bool
// EnableAutoplayWithoutUserAction allows media to autoplay without a user gesture.
EnableAutoplayWithoutUserAction bool
// DisableInspectable disables the Safari remote web inspector.
DisableInspectable bool
// UserAgent overrides the WKWebView user agent string.
UserAgent string
// ApplicationNameForUserAgent appends a name to the user agent. Defaults to "wails.io".
ApplicationNameForUserAgent string
// AppBackgroundColourSet enables BackgroundColour for the main iOS window.
AppBackgroundColourSet bool
// BackgroundColour is applied to the iOS app window before any WebView is created.
BackgroundColour RGBA
// EnableNativeTabs enables a native UITabBar at the bottom of the screen.
EnableNativeTabs bool
// NativeTabsItems configures the labels and SF Symbol icons for the native UITabBar.
NativeTabsItems []NativeTabItem
}
// NativeTabItem describes a single tab in the iOS native UITabBar.
// item := application.NativeTabItem{Title: "Home", SystemImage: application.NativeTabIconHouse}
type NativeTabItem struct {
Title string `json:"Title"`
SystemImage NativeTabIcon `json:"SystemImage"`
}
// NativeTabIcon is an SF Symbols name used for a UITabBar icon.
// tab := application.NativeTabItem{SystemImage: application.NativeTabIconGear}
type NativeTabIcon string
const (
NativeTabIconNone NativeTabIcon = ""
NativeTabIconHouse NativeTabIcon = "house"
NativeTabIconGear NativeTabIcon = "gear"
NativeTabIconStar NativeTabIcon = "star"
NativeTabIconPerson NativeTabIcon = "person"
NativeTabIconBell NativeTabIcon = "bell"
NativeTabIconMagnify NativeTabIcon = "magnifyingglass"
NativeTabIconList NativeTabIcon = "list.bullet"
NativeTabIconFolder NativeTabIcon = "folder"
)
// AndroidOptions contains Android-specific application options.
// opts.Android = application.AndroidOptions{EnableZoom: true}
type AndroidOptions struct {
// DisableScroll disables WebView scrolling.
DisableScroll bool
// DisableOverscroll disables the overscroll bounce effect.
DisableOverscroll bool
// EnableZoom enables pinch-to-zoom in the WebView.
EnableZoom bool
// UserAgent overrides the WebView user agent string.
UserAgent string
// BackgroundColour sets the WebView background colour.
BackgroundColour RGBA
// DisableHardwareAcceleration disables GPU acceleration for the WebView.
DisableHardwareAcceleration bool
}

View file

@ -0,0 +1,21 @@
package application
// BrowserManager handles opening URLs and files in the system browser.
//
// manager.OpenURL("https://lthn.io")
// manager.OpenFile("/home/user/document.pdf")
type BrowserManager struct{}
// OpenURL opens the given URL in the default browser.
//
// err := manager.OpenURL("https://lthn.io")
func (bm *BrowserManager) OpenURL(url string) error {
return nil
}
// OpenFile opens the given file path in the default browser or file handler.
//
// err := manager.OpenFile("/home/user/report.html")
func (bm *BrowserManager) OpenFile(path string) error {
return nil
}

View file

@ -0,0 +1,230 @@
package application
import (
"sync"
"unsafe"
"github.com/wailsapp/wails/v3/pkg/events"
)
// ButtonState represents the visual state of a window control button.
//
// window.SetMinimiseButtonState(ButtonHidden)
type ButtonState int
const (
ButtonEnabled ButtonState = 0
ButtonDisabled ButtonState = 1
ButtonHidden ButtonState = 2
)
// LRTB holds left/right/top/bottom border sizes in pixels.
//
// sizes := window.GetBorderSizes()
type LRTB struct {
Left int
Right int
Top int
Bottom int
}
// ContextMenuData carries context-menu position and metadata from the frontend.
//
// window.OpenContextMenu(&ContextMenuData{Id: "file-menu"})
type ContextMenuData struct {
Id string `json:"id"`
X int `json:"x"`
Y int `json:"y"`
Data string `json:"data"`
}
func (c *ContextMenuData) clone() *ContextMenuData {
if c == nil {
return nil
}
copy := *c
return &copy
}
// CustomEvent carries a named event with arbitrary data from the frontend.
//
// window.DispatchWailsEvent(&CustomEvent{Name: "ready", Data: nil})
type CustomEvent struct {
Name string `json:"name"`
Data any `json:"data"`
Sender string `json:"sender,omitempty"`
cancelled bool
}
// Cancel prevents the event from reaching further listeners.
func (e *CustomEvent) Cancel() { e.cancelled = true }
// IsCancelled reports whether Cancel has been called.
func (e *CustomEvent) IsCancelled() bool { return e.cancelled }
// BrowserWindow represents a browser client connection in server mode.
// It satisfies the Window interface so browser clients are treated
// uniformly with native windows throughout the codebase.
//
// bw := NewBrowserWindow(1, "nano-abc123")
// bw.Focus() // no-op in browser mode
type BrowserWindow struct {
mu sync.RWMutex
id uint
name string
clientID string
}
// NewBrowserWindow constructs a BrowserWindow with the given numeric ID and client nano-ID.
//
// bw := NewBrowserWindow(1, "nano-abc123")
func NewBrowserWindow(id uint, clientID string) *BrowserWindow {
return &BrowserWindow{
id: id,
name: "browser-window",
clientID: clientID,
}
}
// ID returns the numeric window identifier.
func (b *BrowserWindow) ID() uint { return b.id }
// Name returns the window name.
func (b *BrowserWindow) Name() string {
b.mu.RLock()
defer b.mu.RUnlock()
return b.name
}
// ClientID returns the runtime nano-ID for this client.
func (b *BrowserWindow) ClientID() string {
b.mu.RLock()
defer b.mu.RUnlock()
return b.clientID
}
// No-op windowing methods — browser windows have no native chrome.
func (b *BrowserWindow) Center() {}
func (b *BrowserWindow) Close() {}
func (b *BrowserWindow) DisableSizeConstraints() {}
func (b *BrowserWindow) EnableSizeConstraints() {}
func (b *BrowserWindow) ExecJS(_ string) {}
func (b *BrowserWindow) Focus() {}
func (b *BrowserWindow) ForceReload() {}
func (b *BrowserWindow) HideMenuBar() {}
func (b *BrowserWindow) OpenDevTools() {}
func (b *BrowserWindow) Reload() {}
func (b *BrowserWindow) Restore() {}
func (b *BrowserWindow) Run() {}
func (b *BrowserWindow) SetPosition(_ int, _ int) {}
func (b *BrowserWindow) ShowMenuBar() {}
func (b *BrowserWindow) SnapAssist() {}
func (b *BrowserWindow) ToggleFrameless() {}
func (b *BrowserWindow) ToggleFullscreen() {}
func (b *BrowserWindow) ToggleMaximise() {}
func (b *BrowserWindow) ToggleMenuBar() {}
func (b *BrowserWindow) UnFullscreen() {}
func (b *BrowserWindow) UnMaximise() {}
func (b *BrowserWindow) UnMinimise() {}
func (b *BrowserWindow) SetEnabled(_ bool) {}
func (b *BrowserWindow) Flash(_ bool) {}
func (b *BrowserWindow) SetMenu(_ *Menu) {}
func (b *BrowserWindow) SetBounds(_ Rect) {}
func (b *BrowserWindow) Zoom() {}
func (b *BrowserWindow) ZoomIn() {}
func (b *BrowserWindow) ZoomOut() {}
func (b *BrowserWindow) OpenContextMenu(_ *ContextMenuData) {}
func (b *BrowserWindow) HandleMessage(_ string) {}
func (b *BrowserWindow) HandleWindowEvent(_ uint) {}
func (b *BrowserWindow) HandleKeyEvent(_ string) {}
func (b *BrowserWindow) AttachModal(_ Window) {}
// Internal editing stubs.
func (b *BrowserWindow) cut() {}
func (b *BrowserWindow) copy() {}
func (b *BrowserWindow) paste() {}
func (b *BrowserWindow) undo() {}
func (b *BrowserWindow) redo() {}
func (b *BrowserWindow) delete() {}
func (b *BrowserWindow) selectAll() {}
// shouldUnconditionallyClose always returns false for browser windows.
func (b *BrowserWindow) shouldUnconditionallyClose() bool { return false }
// Methods returning Window for chaining — always no-op for browser windows.
func (b *BrowserWindow) Fullscreen() Window { return b }
func (b *BrowserWindow) Hide() Window { return b }
func (b *BrowserWindow) Maximise() Window { return b }
func (b *BrowserWindow) Minimise() Window { return b }
func (b *BrowserWindow) Show() Window { return b }
func (b *BrowserWindow) SetAlwaysOnTop(_ bool) Window { return b }
func (b *BrowserWindow) SetBackgroundColour(_ RGBA) Window { return b }
func (b *BrowserWindow) SetFrameless(_ bool) Window { return b }
func (b *BrowserWindow) SetHTML(_ string) Window { return b }
func (b *BrowserWindow) SetMinimiseButtonState(_ ButtonState) Window { return b }
func (b *BrowserWindow) SetMaximiseButtonState(_ ButtonState) Window { return b }
func (b *BrowserWindow) SetCloseButtonState(_ ButtonState) Window { return b }
func (b *BrowserWindow) SetMaxSize(_ int, _ int) Window { return b }
func (b *BrowserWindow) SetMinSize(_ int, _ int) Window { return b }
func (b *BrowserWindow) SetRelativePosition(_ int, _ int) Window { return b }
func (b *BrowserWindow) SetResizable(_ bool) Window { return b }
func (b *BrowserWindow) SetIgnoreMouseEvents(_ bool) Window { return b }
func (b *BrowserWindow) SetSize(_ int, _ int) Window { return b }
func (b *BrowserWindow) SetTitle(_ string) Window { return b }
func (b *BrowserWindow) SetURL(_ string) Window { return b }
func (b *BrowserWindow) SetZoom(_ float64) Window { return b }
func (b *BrowserWindow) SetContentProtection(_ bool) Window { return b }
func (b *BrowserWindow) ZoomReset() Window { return b }
// Methods returning simple zero values.
func (b *BrowserWindow) GetBorderSizes() *LRTB { return nil }
func (b *BrowserWindow) GetScreen() (*Screen, error) { return nil, nil }
func (b *BrowserWindow) GetZoom() float64 { return 1.0 }
func (b *BrowserWindow) Height() int { return 0 }
func (b *BrowserWindow) Width() int { return 0 }
func (b *BrowserWindow) IsFocused() bool { return false }
func (b *BrowserWindow) IsFullscreen() bool { return false }
func (b *BrowserWindow) IsIgnoreMouseEvents() bool { return false }
func (b *BrowserWindow) IsMaximised() bool { return false }
func (b *BrowserWindow) IsMinimised() bool { return false }
func (b *BrowserWindow) IsVisible() bool { return true }
func (b *BrowserWindow) Resizable() bool { return false }
func (b *BrowserWindow) Position() (int, int) { return 0, 0 }
func (b *BrowserWindow) RelativePosition() (int, int) { return 0, 0 }
func (b *BrowserWindow) Size() (int, int) { return 0, 0 }
func (b *BrowserWindow) Bounds() Rect { return Rect{} }
func (b *BrowserWindow) NativeWindow() unsafe.Pointer { return nil }
func (b *BrowserWindow) Print() error { return nil }
// DispatchWailsEvent is a no-op for browser windows; events are broadcast via WebSocket.
func (b *BrowserWindow) DispatchWailsEvent(_ *CustomEvent) {}
// EmitEvent broadcasts a named event; always returns false in the stub.
func (b *BrowserWindow) EmitEvent(_ string, _ ...any) bool { return false }
// Error logs an error message (no-op in the stub).
func (b *BrowserWindow) Error(_ string, _ ...any) {}
// Info logs an info message (no-op in the stub).
func (b *BrowserWindow) Info(_ string, _ ...any) {}
// OnWindowEvent registers a callback for a window event type; returns an unsubscribe func.
//
// unsubscribe := bw.OnWindowEvent(events.Common.WindowClosing, fn)
func (b *BrowserWindow) OnWindowEvent(_ events.WindowEventType, _ func(*WindowEvent)) func() {
return func() {}
}
// RegisterHook registers a lifecycle hook; returns an unsubscribe func.
func (b *BrowserWindow) RegisterHook(_ events.WindowEventType, _ func(*WindowEvent)) func() {
return func() {}
}
// handleDragAndDropMessage is a no-op for browser windows.
func (b *BrowserWindow) handleDragAndDropMessage(_ []string, _ *DropTargetDetails) {}
// InitiateFrontendDropProcessing is a no-op for browser windows.
func (b *BrowserWindow) InitiateFrontendDropProcessing(_ []string, _ int, _ int) {}

View file

@ -0,0 +1,65 @@
package application
import "sync"
// Clipboard provides direct read/write access to the system clipboard.
//
// ok := clipboard.SetText("hello")
// text, ok := clipboard.Text()
type Clipboard struct {
mu sync.RWMutex
text string
}
// SetText writes the given text to the clipboard and returns true on success.
//
// ok := clipboard.SetText("copied text")
func (c *Clipboard) SetText(text string) bool {
c.mu.Lock()
c.text = text
c.mu.Unlock()
return true
}
// Text reads the current clipboard text. Returns the text and true on success.
//
// text, ok := clipboard.Text()
func (c *Clipboard) Text() (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
return c.text, true
}
// ClipboardManager is the application-level clipboard surface.
// Lazily initialises the underlying Clipboard on first use.
//
// manager.SetText("hello")
// text, ok := manager.Text()
type ClipboardManager struct {
mu sync.Mutex
clipboard *Clipboard
}
// SetText writes text to the clipboard and returns true on success.
//
// ok := manager.SetText("hello")
func (cm *ClipboardManager) SetText(text string) bool {
return cm.instance().SetText(text)
}
// Text reads the current clipboard text. Returns the text and true on success.
//
// text, ok := manager.Text()
func (cm *ClipboardManager) Text() (string, bool) {
return cm.instance().Text()
}
// instance returns the Clipboard, creating it if it does not yet exist.
func (cm *ClipboardManager) instance() *Clipboard {
cm.mu.Lock()
defer cm.mu.Unlock()
if cm.clipboard == nil {
cm.clipboard = &Clipboard{}
}
return cm.clipboard
}

View file

@ -0,0 +1,73 @@
package application
import "sync"
// ContextMenu is a named Menu used as a right-click context menu.
//
// cm := manager.New()
// cm.Add("Cut").OnClick(func(*Context) { ... })
type ContextMenu struct {
*Menu
name string
}
// ContextMenuManager manages named context menus for the application.
//
// manager.Add("fileList", cm)
// menu, ok := manager.Get("fileList")
type ContextMenuManager struct {
mu sync.RWMutex
menus map[string]*ContextMenu
}
// New creates an empty, unnamed ContextMenu ready for population.
//
// cm := manager.New()
// cm.Add("Open")
func (cmm *ContextMenuManager) New() *ContextMenu {
return &ContextMenu{Menu: NewMenu()}
}
// Add registers a ContextMenu under the given name, replacing any existing entry.
//
// manager.Add("fileList", cm)
func (cmm *ContextMenuManager) Add(name string, menu *ContextMenu) {
cmm.mu.Lock()
defer cmm.mu.Unlock()
if cmm.menus == nil {
cmm.menus = make(map[string]*ContextMenu)
}
cmm.menus[name] = menu
}
// Remove unregisters the context menu with the given name.
//
// manager.Remove("fileList")
func (cmm *ContextMenuManager) Remove(name string) {
cmm.mu.Lock()
defer cmm.mu.Unlock()
delete(cmm.menus, name)
}
// Get retrieves a registered context menu by name.
//
// menu, ok := manager.Get("fileList")
func (cmm *ContextMenuManager) Get(name string) (*ContextMenu, bool) {
cmm.mu.RLock()
defer cmm.mu.RUnlock()
menu, exists := cmm.menus[name]
return menu, exists
}
// GetAll returns all registered context menus as a slice.
//
// for _, cm := range manager.GetAll() { ... }
func (cmm *ContextMenuManager) GetAll() []*ContextMenu {
cmm.mu.RLock()
defer cmm.mu.RUnlock()
result := make([]*ContextMenu, 0, len(cmm.menus))
for _, menu := range cmm.menus {
result = append(result, menu)
}
return result
}

View file

@ -0,0 +1,481 @@
package application
// DialogType identifies the visual style of a message dialog.
type DialogType int
const (
InfoDialogType DialogType = iota
QuestionDialogType DialogType = iota
WarningDialogType DialogType = iota
ErrorDialogType DialogType = iota
)
// FileFilter describes a file type filter for open/save dialogs.
//
// filter := FileFilter{DisplayName: "Images (*.png;*.jpg)", Pattern: "*.png;*.jpg"}
type FileFilter struct {
DisplayName string
Pattern string
}
// Button is a labelled action in a MessageDialog.
//
// btn := dialog.AddButton("OK")
// btn.SetAsDefault().OnClick(func() { ... })
type Button struct {
Label string
IsCancel bool
IsDefault bool
Callback func()
}
// OnClick registers a click handler on the button and returns itself for chaining.
//
// btn.OnClick(func() { saveFile() })
func (b *Button) OnClick(callback func()) *Button {
b.Callback = callback
return b
}
// SetAsDefault marks this button as the default (Enter key) action.
func (b *Button) SetAsDefault() *Button {
b.IsDefault = true
return b
}
// SetAsCancel marks this button as the cancel (Escape key) action.
func (b *Button) SetAsCancel() *Button {
b.IsCancel = true
return b
}
// MessageDialogOptions holds configuration for a MessageDialog.
type MessageDialogOptions struct {
DialogType DialogType
Title string
Message string
Buttons []*Button
Icon []byte
}
// MessageDialog is an in-memory message dialog (info / question / warning / error).
//
// dialog.Info().SetTitle("Done").SetMessage("File saved.").Show()
type MessageDialog struct {
MessageDialogOptions
}
// SetTitle sets the dialog window title.
//
// dialog.SetTitle("Confirm Delete")
func (d *MessageDialog) SetTitle(title string) *MessageDialog {
d.Title = title
return d
}
// SetMessage sets the body text shown in the dialog.
//
// dialog.SetMessage("Are you sure?")
func (d *MessageDialog) SetMessage(message string) *MessageDialog {
d.Message = message
return d
}
// SetIcon sets the icon bytes shown in the dialog.
func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog {
d.Icon = icon
return d
}
// AddButton appends a labelled button and returns it for further configuration.
//
// btn := dialog.AddButton("Yes")
func (d *MessageDialog) AddButton(label string) *Button {
btn := &Button{Label: label}
d.Buttons = append(d.Buttons, btn)
return btn
}
// AddButtons replaces the button list in bulk.
func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog {
d.Buttons = buttons
return d
}
// SetDefaultButton marks the given button as the default action.
func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog {
for _, b := range d.Buttons {
b.IsDefault = false
}
button.IsDefault = true
return d
}
// SetCancelButton marks the given button as the cancel action.
func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog {
for _, b := range d.Buttons {
b.IsCancel = false
}
button.IsCancel = true
return d
}
// AttachToWindow associates the dialog with a parent window (no-op in the stub).
func (d *MessageDialog) AttachToWindow(window *WebviewWindow) *MessageDialog {
return d
}
// Show presents the dialog. No-op in the stub.
func (d *MessageDialog) Show() {}
func newMessageDialog(dialogType DialogType) *MessageDialog {
return &MessageDialog{
MessageDialogOptions: MessageDialogOptions{DialogType: dialogType},
}
}
// OpenFileDialogOptions configures an OpenFileDialogStruct.
type OpenFileDialogOptions struct {
CanChooseDirectories bool
CanChooseFiles bool
CanCreateDirectories bool
ShowHiddenFiles bool
ResolvesAliases bool
AllowsMultipleSelection bool
HideExtension bool
CanSelectHiddenExtension bool
TreatsFilePackagesAsDirectories bool
AllowsOtherFileTypes bool
Filters []FileFilter
Title string
Message string
ButtonText string
Directory string
}
// OpenFileDialogStruct is a builder for file-open dialogs.
//
// path, err := manager.OpenFile().SetTitle("Pick a file").PromptForSingleSelection()
type OpenFileDialogStruct struct {
canChooseDirectories bool
canChooseFiles bool
canCreateDirectories bool
showHiddenFiles bool
resolvesAliases bool
allowsMultipleSelection bool
hideExtension bool
canSelectHiddenExtension bool
treatsFilePackagesAsDirectories bool
allowsOtherFileTypes bool
filters []FileFilter
title string
message string
buttonText string
directory string
}
func newOpenFileDialog() *OpenFileDialogStruct {
return &OpenFileDialogStruct{
canChooseFiles: true,
canCreateDirectories: true,
}
}
// SetOptions applies all fields from OpenFileDialogOptions to the dialog.
func (d *OpenFileDialogStruct) SetOptions(options *OpenFileDialogOptions) {
d.title = options.Title
d.message = options.Message
d.buttonText = options.ButtonText
d.directory = options.Directory
d.canChooseDirectories = options.CanChooseDirectories
d.canChooseFiles = options.CanChooseFiles
d.canCreateDirectories = options.CanCreateDirectories
d.showHiddenFiles = options.ShowHiddenFiles
d.resolvesAliases = options.ResolvesAliases
d.allowsMultipleSelection = options.AllowsMultipleSelection
d.hideExtension = options.HideExtension
d.canSelectHiddenExtension = options.CanSelectHiddenExtension
d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories
d.allowsOtherFileTypes = options.AllowsOtherFileTypes
d.filters = options.Filters
}
func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct {
d.title = title
return d
}
func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct {
d.message = message
return d
}
func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct {
d.buttonText = text
return d
}
func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct {
d.directory = directory
return d
}
func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct {
d.canChooseFiles = canChooseFiles
return d
}
func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct {
d.canChooseDirectories = canChooseDirectories
return d
}
func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct {
d.canCreateDirectories = canCreateDirectories
return d
}
func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct {
d.showHiddenFiles = showHiddenFiles
return d
}
func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct {
d.hideExtension = hideExtension
return d
}
func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct {
d.canSelectHiddenExtension = canSelectHiddenExtension
return d
}
func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct {
d.resolvesAliases = resolvesAliases
return d
}
func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct {
d.allowsOtherFileTypes = allowsOtherFileTypes
return d
}
func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treats bool) *OpenFileDialogStruct {
d.treatsFilePackagesAsDirectories = treats
return d
}
// AddFilter appends a file type filter to the dialog.
//
// dialog.AddFilter("Images", "*.png;*.jpg")
func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct {
d.filters = append(d.filters, FileFilter{DisplayName: displayName, Pattern: pattern})
return d
}
func (d *OpenFileDialogStruct) AttachToWindow(window *WebviewWindow) *OpenFileDialogStruct {
return d
}
// PromptForSingleSelection shows the dialog and returns the chosen path.
// Always returns ("", nil) in the stub.
//
// path, err := dialog.PromptForSingleSelection()
func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) {
return "", nil
}
// PromptForMultipleSelection shows the dialog and returns all chosen paths.
// Always returns (nil, nil) in the stub.
//
// paths, err := dialog.PromptForMultipleSelection()
func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) {
return nil, nil
}
// SaveFileDialogOptions configures a SaveFileDialogStruct.
type SaveFileDialogOptions struct {
CanCreateDirectories bool
ShowHiddenFiles bool
CanSelectHiddenExtension bool
AllowOtherFileTypes bool
HideExtension bool
TreatsFilePackagesAsDirectories bool
Title string
Message string
Directory string
Filename string
ButtonText string
Filters []FileFilter
}
// SaveFileDialogStruct is a builder for file-save dialogs.
//
// path, err := manager.SaveFile().SetTitle("Save As").PromptForSingleSelection()
type SaveFileDialogStruct struct {
canCreateDirectories bool
showHiddenFiles bool
canSelectHiddenExtension bool
allowOtherFileTypes bool
hideExtension bool
treatsFilePackagesAsDirectories bool
title string
message string
directory string
filename string
buttonText string
filters []FileFilter
}
func newSaveFileDialog() *SaveFileDialogStruct {
return &SaveFileDialogStruct{canCreateDirectories: true}
}
// SetOptions applies all fields from SaveFileDialogOptions to the dialog.
func (d *SaveFileDialogStruct) SetOptions(options *SaveFileDialogOptions) {
d.title = options.Title
d.canCreateDirectories = options.CanCreateDirectories
d.showHiddenFiles = options.ShowHiddenFiles
d.canSelectHiddenExtension = options.CanSelectHiddenExtension
d.allowOtherFileTypes = options.AllowOtherFileTypes
d.hideExtension = options.HideExtension
d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories
d.message = options.Message
d.directory = options.Directory
d.filename = options.Filename
d.buttonText = options.ButtonText
d.filters = options.Filters
}
func (d *SaveFileDialogStruct) SetTitle(title string) *SaveFileDialogStruct {
d.title = title
return d
}
func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct {
d.message = message
return d
}
func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct {
d.directory = directory
return d
}
func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct {
d.filename = filename
return d
}
func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct {
d.buttonText = text
return d
}
func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct {
d.canCreateDirectories = canCreateDirectories
return d
}
func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct {
d.showHiddenFiles = showHiddenFiles
return d
}
func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct {
d.canSelectHiddenExtension = canSelectHiddenExtension
return d
}
func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct {
d.allowOtherFileTypes = allowOtherFileTypes
return d
}
func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct {
d.hideExtension = hideExtension
return d
}
func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treats bool) *SaveFileDialogStruct {
d.treatsFilePackagesAsDirectories = treats
return d
}
// AddFilter appends a file type filter to the dialog.
//
// dialog.AddFilter("Text files", "*.txt")
func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct {
d.filters = append(d.filters, FileFilter{DisplayName: displayName, Pattern: pattern})
return d
}
func (d *SaveFileDialogStruct) AttachToWindow(window *WebviewWindow) *SaveFileDialogStruct {
return d
}
// PromptForSingleSelection shows the save dialog and returns the chosen path.
// Always returns ("", nil) in the stub.
//
// path, err := dialog.PromptForSingleSelection()
func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) {
return "", nil
}
// DialogManager exposes factory methods for all dialog types.
//
// manager.Info().SetMessage("Saved!").Show()
// path, _ := manager.OpenFile().PromptForSingleSelection()
type DialogManager struct{}
// OpenFile creates a file-open dialog builder.
func (dm *DialogManager) OpenFile() *OpenFileDialogStruct {
return newOpenFileDialog()
}
// OpenFileWithOptions creates a file-open dialog builder pre-populated from options.
func (dm *DialogManager) OpenFileWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct {
result := newOpenFileDialog()
result.SetOptions(options)
return result
}
// SaveFile creates a file-save dialog builder.
func (dm *DialogManager) SaveFile() *SaveFileDialogStruct {
return newSaveFileDialog()
}
// SaveFileWithOptions creates a file-save dialog builder pre-populated from options.
func (dm *DialogManager) SaveFileWithOptions(options *SaveFileDialogOptions) *SaveFileDialogStruct {
result := newSaveFileDialog()
result.SetOptions(options)
return result
}
// Info creates an information message dialog.
//
// manager.Info().SetMessage("Done").Show()
func (dm *DialogManager) Info() *MessageDialog {
return newMessageDialog(InfoDialogType)
}
// Question creates a question message dialog.
//
// manager.Question().SetMessage("Continue?").Show()
func (dm *DialogManager) Question() *MessageDialog {
return newMessageDialog(QuestionDialogType)
}
// Warning creates a warning message dialog.
//
// manager.Warning().SetMessage("Low disk space").Show()
func (dm *DialogManager) Warning() *MessageDialog {
return newMessageDialog(WarningDialogType)
}
// Error creates an error message dialog.
//
// manager.Error().SetMessage("Write failed").Show()
func (dm *DialogManager) Error() *MessageDialog {
return newMessageDialog(ErrorDialogType)
}

View file

@ -0,0 +1,63 @@
package application
// EnvironmentInfo holds runtime information about the host OS and build.
//
// info := manager.Info()
// fmt.Println(info.OS, info.Arch)
type EnvironmentInfo struct {
OS string
Arch string
Debug bool
PlatformInfo map[string]any
}
// EnvironmentManager provides queries about the host OS environment.
//
// if manager.IsDarkMode() { applyDarkTheme() }
// accent := manager.GetAccentColor()
type EnvironmentManager struct {
darkMode bool
accentColor string
}
// IsDarkMode returns true when the OS is using a dark colour scheme.
//
// if manager.IsDarkMode() { applyDarkTheme() }
func (em *EnvironmentManager) IsDarkMode() bool {
return em.darkMode
}
// GetAccentColor returns the OS accent colour as an rgb() string.
//
// colour := manager.GetAccentColor() // e.g. "rgb(0,122,255)"
func (em *EnvironmentManager) GetAccentColor() string {
if em.accentColor == "" {
return "rgb(0,122,255)"
}
return em.accentColor
}
// Info returns a snapshot of OS and build environment information.
//
// info := manager.Info()
func (em *EnvironmentManager) Info() EnvironmentInfo {
return EnvironmentInfo{
PlatformInfo: make(map[string]any),
}
}
// OpenFileManager opens the file manager at the given path, optionally selecting the file.
// No-op in the stub.
//
// err := manager.OpenFileManager("/home/user/docs", false)
func (em *EnvironmentManager) OpenFileManager(path string, selectFile bool) error {
return nil
}
// HasFocusFollowsMouse reports whether the Linux desktop is configured for
// focus-follows-mouse. Always returns false in the stub.
//
// if manager.HasFocusFollowsMouse() { adjustMouseBehaviour() }
func (em *EnvironmentManager) HasFocusFollowsMouse() bool {
return false
}

View file

@ -0,0 +1,297 @@
package application
import (
"sync"
"sync/atomic"
"github.com/wailsapp/wails/v3/pkg/events"
)
// ApplicationEventContext carries structured data for an ApplicationEvent.
//
// files := event.Context().OpenedFiles()
// dark := event.Context().IsDarkMode()
type ApplicationEventContext struct {
data map[string]any
}
// OpenedFiles returns the list of files provided via the event context, or nil.
//
// for _, path := range event.Context().OpenedFiles() { open(path) }
func (c *ApplicationEventContext) OpenedFiles() []string {
if c.data == nil {
return nil
}
files, ok := c.data["openedFiles"]
if !ok {
return nil
}
result, ok := files.([]string)
if !ok {
return nil
}
return result
}
// IsDarkMode returns true when the event context reports dark mode active.
//
// if event.Context().IsDarkMode() { applyDark() }
func (c *ApplicationEventContext) IsDarkMode() bool {
return c.getBool("isDarkMode")
}
// HasVisibleWindows returns true when the event context reports at least one visible window.
//
// if event.Context().HasVisibleWindows() { ... }
func (c *ApplicationEventContext) HasVisibleWindows() bool {
return c.getBool("hasVisibleWindows")
}
// Filename returns the filename value from the event context, or "".
//
// path := event.Context().Filename()
func (c *ApplicationEventContext) Filename() string {
if c.data == nil {
return ""
}
v, ok := c.data["filename"]
if !ok {
return ""
}
result, ok := v.(string)
if !ok {
return ""
}
return result
}
// URL returns the URL value from the event context, or "".
//
// url := event.Context().URL()
func (c *ApplicationEventContext) URL() string {
if c.data == nil {
return ""
}
v, ok := c.data["url"]
if !ok {
return ""
}
result, ok := v.(string)
if !ok {
return ""
}
return result
}
func (c *ApplicationEventContext) getBool(key string) bool {
if c.data == nil {
return false
}
v, ok := c.data[key]
if !ok {
return false
}
result, ok := v.(bool)
if !ok {
return false
}
return result
}
// ApplicationEvent is the event object delivered to OnApplicationEvent listeners.
//
// em.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
// dark := e.Context().IsDarkMode()
// })
type ApplicationEvent struct {
Id uint
ctx *ApplicationEventContext
cancelled atomic.Bool
}
// Context returns the ApplicationEventContext attached to the event.
func (e *ApplicationEvent) Context() *ApplicationEventContext {
if e.ctx == nil {
e.ctx = &ApplicationEventContext{data: make(map[string]any)}
}
return e.ctx
}
// Cancel marks the event as cancelled, preventing further listener dispatch.
func (e *ApplicationEvent) Cancel() {
e.cancelled.Store(true)
}
// IsCancelled reports whether the event has been cancelled.
func (e *ApplicationEvent) IsCancelled() bool {
return e.cancelled.Load()
}
// customEventListener is an internal listener registration.
type customEventListener struct {
callback func(*CustomEvent)
counter int // -1 = unlimited
}
// applicationEventListener is an internal listener registration.
type applicationEventListener struct {
callback func(*ApplicationEvent)
}
// EventManager manages custom and application-level event subscriptions.
//
// em.Emit("build:done", result)
// cancel := em.On("build:done", func(e *application.CustomEvent) { ... })
// em.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { ... })
type EventManager struct {
mu sync.RWMutex
customListeners map[string][]*customEventListener
applicationListeners map[uint][]*applicationEventListener
}
func (em *EventManager) ensureCustomListeners() {
if em.customListeners == nil {
em.customListeners = make(map[string][]*customEventListener)
}
}
func (em *EventManager) ensureApplicationListeners() {
if em.applicationListeners == nil {
em.applicationListeners = make(map[uint][]*applicationEventListener)
}
}
// Emit emits a custom event by name with optional data arguments.
// Returns true if the event was cancelled by a listener.
//
// cancelled := em.Emit("build:done", buildResult)
func (em *EventManager) Emit(name string, data ...any) bool {
event := &CustomEvent{Name: name}
if len(data) == 1 {
event.Data = data[0]
} else if len(data) > 1 {
event.Data = data
}
return em.EmitEvent(event)
}
// EmitEvent emits a pre-constructed CustomEvent.
// Returns true if the event was cancelled by a listener.
//
// cancelled := em.EmitEvent(&application.CustomEvent{Name: "ping"})
func (em *EventManager) EmitEvent(event *CustomEvent) bool {
em.mu.RLock()
listeners := append([]*customEventListener(nil), em.customListeners[event.Name]...)
em.mu.RUnlock()
toRemove := []int{}
for index, listener := range listeners {
if event.IsCancelled() {
break
}
listener.callback(event)
if listener.counter > 0 {
listener.counter--
if listener.counter == 0 {
toRemove = append(toRemove, index)
}
}
}
if len(toRemove) > 0 {
em.mu.Lock()
remaining := em.customListeners[event.Name]
for _, index := range toRemove {
if index < len(remaining) {
remaining = append(remaining[:index], remaining[index+1:]...)
}
}
em.customListeners[event.Name] = remaining
em.mu.Unlock()
}
return event.IsCancelled()
}
// On registers a persistent listener for the named custom event.
// Returns a cancel function that deregisters the listener.
//
// cancel := em.On("build:done", func(e *application.CustomEvent) { ... })
// defer cancel()
func (em *EventManager) On(name string, callback func(event *CustomEvent)) func() {
em.mu.Lock()
em.ensureCustomListeners()
listener := &customEventListener{callback: callback, counter: -1}
em.customListeners[name] = append(em.customListeners[name], listener)
em.mu.Unlock()
return func() {
em.mu.Lock()
defer em.mu.Unlock()
slice := em.customListeners[name]
for i, l := range slice {
if l == listener {
em.customListeners[name] = append(slice[:i], slice[i+1:]...)
return
}
}
}
}
// Off removes all listeners for the named custom event.
//
// em.Off("build:done")
func (em *EventManager) Off(name string) {
em.mu.Lock()
delete(em.customListeners, name)
em.mu.Unlock()
}
// OnMultiple registers a listener that fires at most counter times, then auto-deregisters.
//
// em.OnMultiple("ping", func(e *application.CustomEvent) { ... }, 3)
func (em *EventManager) OnMultiple(name string, callback func(event *CustomEvent), counter int) {
em.mu.Lock()
em.ensureCustomListeners()
em.customListeners[name] = append(em.customListeners[name], &customEventListener{
callback: callback,
counter: counter,
})
em.mu.Unlock()
}
// Reset removes all custom event listeners.
//
// em.Reset()
func (em *EventManager) Reset() {
em.mu.Lock()
em.customListeners = make(map[string][]*customEventListener)
em.mu.Unlock()
}
// OnApplicationEvent registers a listener for a platform application event.
// Returns a cancel function that deregisters the listener.
//
// cancel := em.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { ... })
// defer cancel()
func (em *EventManager) OnApplicationEvent(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() {
eventID := uint(eventType)
em.mu.Lock()
em.ensureApplicationListeners()
listener := &applicationEventListener{callback: callback}
em.applicationListeners[eventID] = append(em.applicationListeners[eventID], listener)
em.mu.Unlock()
return func() {
em.mu.Lock()
defer em.mu.Unlock()
slice := em.applicationListeners[eventID]
for i, l := range slice {
if l == listener {
em.applicationListeners[eventID] = append(slice[:i], slice[i+1:]...)
return
}
}
}
}

View file

@ -0,0 +1,69 @@
package application
import "sync"
// KeyBinding pairs an accelerator string with its callback.
// binding := &KeyBinding{Accelerator: "Ctrl+K", Callback: fn}
type KeyBinding struct {
Accelerator string
Callback func(window Window)
}
// KeyBindingManager holds all registered global key bindings in memory.
// manager.Add("Ctrl+K", fn) — manager.Remove("Ctrl+K") — manager.GetAll()
type KeyBindingManager struct {
mu sync.RWMutex
bindings map[string]func(window Window)
}
// NewKeyBindingManager constructs an empty KeyBindingManager.
// manager := NewKeyBindingManager()
func NewKeyBindingManager() *KeyBindingManager {
return &KeyBindingManager{
bindings: make(map[string]func(window Window)),
}
}
// Add registers a callback for the given accelerator string.
// manager.Add("Ctrl+Shift+P", func(w Window) { w.Focus() })
func (m *KeyBindingManager) Add(accelerator string, callback func(window Window)) {
m.mu.Lock()
m.bindings[accelerator] = callback
m.mu.Unlock()
}
// Remove deletes the binding for the given accelerator.
// manager.Remove("Ctrl+Shift+P")
func (m *KeyBindingManager) Remove(accelerator string) {
m.mu.Lock()
delete(m.bindings, accelerator)
m.mu.Unlock()
}
// Process fires the callback for accelerator if registered, returning true when handled.
// handled := manager.Process("Ctrl+K", window)
func (m *KeyBindingManager) Process(accelerator string, window Window) bool {
m.mu.RLock()
callback, exists := m.bindings[accelerator]
m.mu.RUnlock()
if exists && callback != nil {
callback(window)
return true
}
return false
}
// GetAll returns a snapshot of all registered bindings.
// for _, b := range manager.GetAll() { fmt.Println(b.Accelerator) }
func (m *KeyBindingManager) GetAll() []*KeyBinding {
m.mu.RLock()
defer m.mu.RUnlock()
result := make([]*KeyBinding, 0, len(m.bindings))
for accelerator, callback := range m.bindings {
result = append(result, &KeyBinding{
Accelerator: accelerator,
Callback: callback,
})
}
return result
}

View file

@ -0,0 +1,379 @@
package application
import "sync/atomic"
// Role identifies a platform-defined menu role.
//
// item := NewRole(Quit) // produces the platform quit item
type Role uint
const (
NoRole Role = iota
AppMenu Role = iota
EditMenu Role = iota
ViewMenu Role = iota
WindowMenu Role = iota
ServicesMenu Role = iota
HelpMenu Role = iota
SpeechMenu Role = iota
FileMenu Role = iota
Hide Role = iota
HideOthers Role = iota
ShowAll Role = iota
BringAllToFront Role = iota
UnHide Role = iota
About Role = iota
Undo Role = iota
Redo Role = iota
Cut Role = iota
Copy Role = iota
Paste Role = iota
PasteAndMatchStyle Role = iota
SelectAll Role = iota
Delete Role = iota
Quit Role = iota
CloseWindow Role = iota
Reload Role = iota
ForceReload Role = iota
OpenDevTools Role = iota
ResetZoom Role = iota
ZoomIn Role = iota
ZoomOut Role = iota
ToggleFullscreen Role = iota
Minimise Role = iota
Zoom Role = iota
FullScreen Role = iota
NewFile Role = iota
Open Role = iota
Save Role = iota
SaveAs Role = iota
StartSpeaking Role = iota
StopSpeaking Role = iota
Revert Role = iota
Print Role = iota
PageLayout Role = iota
Find Role = iota
FindAndReplace Role = iota
FindNext Role = iota
FindPrevious Role = iota
Front Role = iota
Help Role = iota
)
// menuItemType classifies what kind of item a MenuItem is.
type menuItemType int
const (
menuItemTypeText menuItemType = iota
menuItemTypeSeparator menuItemType = iota
menuItemTypeCheckbox menuItemType = iota
menuItemTypeRadio menuItemType = iota
menuItemTypeSubmenu menuItemType = iota
)
var globalMenuItemID uintptr
// MenuItem is a node in a menu tree.
//
// item := NewMenuItem("Preferences").
// SetAccelerator("CmdOrCtrl+,").
// OnClick(func(ctx *Context) { openPrefs() })
type MenuItem struct {
id uint
label string
tooltip string
disabled bool
checked bool
hidden bool
bitmap []byte
submenu *Menu
callback func(*Context)
itemType menuItemType
acceleratorStr string
role Role
contextMenuData *ContextMenuData
radioGroupMembers []*MenuItem
}
func nextMenuItemID() uint {
return uint(atomic.AddUintptr(&globalMenuItemID, 1))
}
// NewMenuItem creates a standard clickable menu item with the given label.
//
// item := NewMenuItem("Save").OnClick(func(ctx *Context) { save() })
func NewMenuItem(label string) *MenuItem {
return &MenuItem{
id: nextMenuItemID(),
label: label,
itemType: menuItemTypeText,
disabled: false,
}
}
// NewMenuItemSeparator creates a horizontal separator.
//
// menu.AppendItem(NewMenuItemSeparator())
func NewMenuItemSeparator() *MenuItem {
return &MenuItem{
id: nextMenuItemID(),
itemType: menuItemTypeSeparator,
}
}
// NewMenuItemCheckbox creates a checkable menu item.
//
// item := NewMenuItemCheckbox("Show Toolbar", true)
func NewMenuItemCheckbox(label string, checked bool) *MenuItem {
return &MenuItem{
id: nextMenuItemID(),
label: label,
checked: checked,
itemType: menuItemTypeCheckbox,
}
}
// NewMenuItemRadio creates a radio-group menu item.
//
// light := NewMenuItemRadio("Light Theme", true)
func NewMenuItemRadio(label string, checked bool) *MenuItem {
return &MenuItem{
id: nextMenuItemID(),
label: label,
checked: checked,
itemType: menuItemTypeRadio,
}
}
// NewSubMenuItem creates an item that reveals a child menu on hover.
//
// sub := NewSubMenuItem("Recent Files")
// sub.GetSubmenu().Add("report.pdf")
func NewSubMenuItem(label string) *MenuItem {
return &MenuItem{
id: nextMenuItemID(),
label: label,
itemType: menuItemTypeSubmenu,
submenu: &Menu{label: label},
}
}
// NewRole creates a platform-managed menu item for the given role.
//
// menu.AppendItem(NewRole(Quit))
func NewRole(role Role) *MenuItem {
item := &MenuItem{
id: nextMenuItemID(),
label: roleLabel(role),
itemType: menuItemTypeText,
role: role,
}
return item
}
func roleLabel(role Role) string {
switch role {
case AppMenu:
return "Application"
case EditMenu:
return "Edit"
case ViewMenu:
return "View"
case WindowMenu:
return "Window"
case ServicesMenu:
return "Services"
case HelpMenu:
return "Help"
case SpeechMenu:
return "Speech"
case FileMenu:
return "File"
case Hide:
return "Hide"
case HideOthers:
return "Hide Others"
case ShowAll:
return "Show All"
case BringAllToFront:
return "Bring All to Front"
case UnHide:
return "Unhide"
case About:
return "About"
case Undo:
return "Undo"
case Redo:
return "Redo"
case Cut:
return "Cut"
case Copy:
return "Copy"
case Paste:
return "Paste"
case PasteAndMatchStyle:
return "Paste and Match Style"
case SelectAll:
return "Select All"
case Delete:
return "Delete"
case Quit:
return "Quit"
case CloseWindow:
return "Close Window"
case Reload:
return "Reload"
case ForceReload:
return "Force Reload"
case OpenDevTools:
return "Open Dev Tools"
case ResetZoom:
return "Reset Zoom"
case ZoomIn:
return "Zoom In"
case ZoomOut:
return "Zoom Out"
case ToggleFullscreen:
return "Toggle Fullscreen"
case Minimise:
return "Minimise"
case Zoom:
return "Zoom"
case FullScreen:
return "Fullscreen"
case NewFile:
return "New"
case Open:
return "Open"
case Save:
return "Save"
case SaveAs:
return "Save As"
case StartSpeaking:
return "Start Speaking"
case StopSpeaking:
return "Stop Speaking"
case Revert:
return "Revert"
case Print:
return "Print"
case PageLayout:
return "Page Layout"
case Find:
return "Find"
case FindAndReplace:
return "Find and Replace"
case FindNext:
return "Find Next"
case FindPrevious:
return "Find Previous"
case Front:
return "Bring All to Front"
case Help:
return "Help"
default:
return ""
}
}
// Fluent setters — all return *MenuItem for chaining.
// SetLabel updates the visible label.
func (m *MenuItem) SetLabel(s string) *MenuItem { m.label = s; return m }
// SetAccelerator sets the keyboard shortcut string (e.g. "CmdOrCtrl+S").
func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem {
m.acceleratorStr = shortcut
return m
}
// GetAccelerator returns the raw accelerator string.
func (m *MenuItem) GetAccelerator() string { return m.acceleratorStr }
// RemoveAccelerator clears the keyboard shortcut.
func (m *MenuItem) RemoveAccelerator() { m.acceleratorStr = "" }
// SetTooltip sets the hover tooltip.
func (m *MenuItem) SetTooltip(s string) *MenuItem { m.tooltip = s; return m }
// SetRole assigns a platform role to the item.
func (m *MenuItem) SetRole(role Role) *MenuItem { m.role = role; return m }
// SetEnabled enables or disables the item.
func (m *MenuItem) SetEnabled(enabled bool) *MenuItem { m.disabled = !enabled; return m }
// SetBitmap sets a raw-bytes icon for the item (Windows).
func (m *MenuItem) SetBitmap(bitmap []byte) *MenuItem {
m.bitmap = append([]byte(nil), bitmap...)
return m
}
// SetChecked sets the checked state for checkbox/radio items.
func (m *MenuItem) SetChecked(checked bool) *MenuItem { m.checked = checked; return m }
// SetHidden hides or shows the item without removing it.
func (m *MenuItem) SetHidden(hidden bool) *MenuItem { m.hidden = hidden; return m }
// OnClick registers the callback invoked when the item is activated.
func (m *MenuItem) OnClick(f func(*Context)) *MenuItem { m.callback = f; return m }
// Getters
// Label returns the item's display label.
func (m *MenuItem) Label() string { return m.label }
// Tooltip returns the hover tooltip.
func (m *MenuItem) Tooltip() string { return m.tooltip }
// Enabled reports whether the item is interactive.
func (m *MenuItem) Enabled() bool { return !m.disabled }
// Checked reports whether the item is checked.
func (m *MenuItem) Checked() bool { return m.checked }
// Hidden reports whether the item is hidden.
func (m *MenuItem) Hidden() bool { return m.hidden }
// IsSeparator reports whether this is a separator item.
func (m *MenuItem) IsSeparator() bool { return m.itemType == menuItemTypeSeparator }
// IsSubmenu reports whether this item contains a child menu.
func (m *MenuItem) IsSubmenu() bool { return m.itemType == menuItemTypeSubmenu }
// IsCheckbox reports whether this is a checkbox item.
func (m *MenuItem) IsCheckbox() bool { return m.itemType == menuItemTypeCheckbox }
// IsRadio reports whether this is a radio item.
func (m *MenuItem) IsRadio() bool { return m.itemType == menuItemTypeRadio }
// GetSubmenu returns the submenu, or nil if this is not a submenu item.
func (m *MenuItem) GetSubmenu() *Menu { return m.submenu }
// Clone returns a shallow copy of the MenuItem.
func (m *MenuItem) Clone() *MenuItem {
cloned := *m
if m.submenu != nil {
cloned.submenu = m.submenu.Clone()
}
if m.bitmap != nil {
cloned.bitmap = append([]byte(nil), m.bitmap...)
}
if m.contextMenuData != nil {
cloned.contextMenuData = m.contextMenuData.clone()
}
cloned.radioGroupMembers = nil
return &cloned
}
// Destroy frees resources held by the item and its submenu.
func (m *MenuItem) Destroy() {
if m.submenu != nil {
m.submenu.Destroy()
m.submenu = nil
}
m.callback = nil
m.radioGroupMembers = nil
m.contextMenuData = nil
}

View file

@ -0,0 +1,202 @@
package application
import (
"math"
"sync"
)
// Rect describes an axis-aligned rectangle.
// r := Rect{X: 0, Y: 0, Width: 1920, Height: 1080}
type Rect struct {
X int
Y int
Width int
Height int
}
// Point is a 2D integer coordinate.
// p := Point{X: 100, Y: 200}
type Point struct {
X int
Y int
}
// Size holds a width/height pair.
// s := Size{Width: 1920, Height: 1080}
type Size struct {
Width int
Height int
}
// Origin returns the top-left corner of the rect as a Point.
// origin := r.Origin()
func (r Rect) Origin() Point { return Point{X: r.X, Y: r.Y} }
// Corner returns the exclusive bottom-right corner of the rect.
// corner := r.Corner()
func (r Rect) Corner() Point { return Point{X: r.X + r.Width, Y: r.Y + r.Height} }
// InsideCorner returns the last pixel inside the rect.
// inside := r.InsideCorner()
func (r Rect) InsideCorner() Point { return Point{X: r.X + r.Width - 1, Y: r.Y + r.Height - 1} }
// RectSize returns the size of the rect.
// s := r.RectSize()
func (r Rect) RectSize() Size { return Size{Width: r.Width, Height: r.Height} }
// IsEmpty reports whether the rect has no area.
// if r.IsEmpty() { return }
func (r Rect) IsEmpty() bool { return r.Width <= 0 || r.Height <= 0 }
// Contains reports whether the point lies inside the rect.
// if r.Contains(Point{X: 100, Y: 200}) { ... }
func (r Rect) Contains(point Point) bool {
return point.X >= r.X && point.X < r.X+r.Width &&
point.Y >= r.Y && point.Y < r.Y+r.Height
}
// Intersect returns the overlapping rectangle of r and other.
// overlap := r.Intersect(otherRect)
func (r Rect) Intersect(other Rect) Rect {
if r.IsEmpty() || other.IsEmpty() {
return Rect{}
}
maxLeft := max(r.X, other.X)
maxTop := max(r.Y, other.Y)
minRight := min(r.X+r.Width, other.X+other.Width)
minBottom := min(r.Y+r.Height, other.Y+other.Height)
if minRight > maxLeft && minBottom > maxTop {
return Rect{X: maxLeft, Y: maxTop, Width: minRight - maxLeft, Height: minBottom - maxTop}
}
return Rect{}
}
// Screen describes a physical display.
// primary := manager.GetPrimary()
type Screen struct {
ID string
Name string
ScaleFactor float32
X int
Y int
Size Size
Bounds Rect
PhysicalBounds Rect
WorkArea Rect
PhysicalWorkArea Rect
IsPrimary bool
Rotation float32
}
// Origin returns the logical origin of the screen.
// origin := screen.Origin()
func (s Screen) Origin() Point { return Point{X: s.X, Y: s.Y} }
// scale converts a value between physical and DIP coordinates for this screen.
func (s Screen) scale(value int, toDIP bool) int {
if toDIP {
return int(math.Ceil(float64(value) / float64(s.ScaleFactor)))
}
return int(math.Floor(float64(value) * float64(s.ScaleFactor)))
}
// ScreenManager holds the list of known screens in memory.
// screens := manager.GetAll()
type ScreenManager struct {
mu sync.RWMutex
screens []*Screen
primaryScreen *Screen
}
// NewScreenManager constructs an empty ScreenManager.
// manager := NewScreenManager()
func NewScreenManager() *ScreenManager {
return &ScreenManager{}
}
// SetScreens replaces the tracked screen list; the first screen with IsPrimary == true becomes primary.
// manager.SetScreens([]*Screen{primary, secondary})
func (m *ScreenManager) SetScreens(screens []*Screen) {
m.mu.Lock()
defer m.mu.Unlock()
m.screens = screens
m.primaryScreen = nil
for _, screen := range screens {
if screen.IsPrimary {
m.primaryScreen = screen
break
}
}
}
// GetAll returns all tracked screens.
// for _, s := range manager.GetAll() { fmt.Println(s.Name) }
func (m *ScreenManager) GetAll() []*Screen {
m.mu.RLock()
defer m.mu.RUnlock()
result := make([]*Screen, len(m.screens))
copy(result, m.screens)
return result
}
// GetPrimary returns the primary screen, or nil if none is set.
// primary := manager.GetPrimary()
func (m *ScreenManager) GetPrimary() *Screen {
m.mu.RLock()
defer m.mu.RUnlock()
return m.primaryScreen
}
// GetCurrent returns the screen nearest to the given DIP point.
// current := manager.GetCurrent(Point{X: 500, Y: 300})
func (m *ScreenManager) GetCurrent(dipPoint Point) *Screen {
m.mu.RLock()
defer m.mu.RUnlock()
for _, screen := range m.screens {
if screen.Bounds.Contains(dipPoint) {
return screen
}
}
return m.primaryScreen
}
// DipToPhysicalPoint converts a DIP point to physical pixels using the nearest screen.
// physical := manager.DipToPhysicalPoint(Point{X: 100, Y: 200})
func (m *ScreenManager) DipToPhysicalPoint(dipPoint Point) Point {
screen := m.GetCurrent(dipPoint)
if screen == nil {
return dipPoint
}
relativeX := dipPoint.X - screen.Bounds.X
relativeY := dipPoint.Y - screen.Bounds.Y
return Point{
X: screen.PhysicalBounds.X + screen.scale(relativeX, false),
Y: screen.PhysicalBounds.Y + screen.scale(relativeY, false),
}
}
// PhysicalToDipPoint converts physical pixel coordinates to DIP using the nearest screen.
// dip := manager.PhysicalToDipPoint(Point{X: 200, Y: 400})
func (m *ScreenManager) PhysicalToDipPoint(physicalPoint Point) Point {
m.mu.RLock()
var nearest *Screen
for _, screen := range m.screens {
if screen.PhysicalBounds.Contains(physicalPoint) {
nearest = screen
break
}
}
if nearest == nil {
nearest = m.primaryScreen
}
m.mu.RUnlock()
if nearest == nil {
return physicalPoint
}
relativeX := physicalPoint.X - nearest.PhysicalBounds.X
relativeY := physicalPoint.Y - nearest.PhysicalBounds.Y
return Point{
X: nearest.Bounds.X + nearest.scale(relativeX, true),
Y: nearest.Bounds.Y + nearest.scale(relativeY, true),
}
}

View file

@ -0,0 +1,76 @@
package application
import "context"
// Service wraps a bound type instance for registration with the application.
// svc := NewService(&MyService{})
type Service struct {
instance any
options ServiceOptions
}
// ServiceOptions provides optional configuration for a Service.
// opts := ServiceOptions{Name: "my-service", Route: "/api/my"}
type ServiceOptions struct {
// Name overrides the service name used in logging and debugging.
// Defaults to the value from ServiceName interface, or the type name.
Name string
// Route mounts the service on the internal asset server at this path
// when the instance implements http.Handler.
Route string
// MarshalError serialises errors returned by service methods to JSON.
// Return nil to fall back to the globally configured error handler.
MarshalError func(error) []byte
}
// DefaultServiceOptions is the default service options used by NewService.
var DefaultServiceOptions = ServiceOptions{}
// NewService wraps instance as a Service.
// svc := NewService(&Calculator{})
func NewService[T any](instance *T) Service {
return Service{instance: instance, options: DefaultServiceOptions}
}
// NewServiceWithOptions wraps instance as a Service with explicit options.
// svc := NewServiceWithOptions(&Calculator{}, ServiceOptions{Name: "calculator"})
func NewServiceWithOptions[T any](instance *T, options ServiceOptions) Service {
service := NewService(instance)
service.options = options
return service
}
// Instance returns the underlying pointer passed to NewService.
// raw := svc.Instance().(*Calculator)
func (s Service) Instance() any {
return s.instance
}
// Options returns the ServiceOptions for this service.
// opts := svc.Options()
func (s Service) Options() ServiceOptions {
return s.options
}
// ServiceName is an optional interface that service instances may implement
// to provide a human-readable name for logging and debugging.
// func (s *MyService) ServiceName() string { return "my-service" }
type ServiceName interface {
ServiceName() string
}
// ServiceStartup is an optional interface for services that need initialisation.
// Called in registration order during application startup.
// func (s *MyService) ServiceStartup(ctx context.Context, opts ServiceOptions) error { ... }
type ServiceStartup interface {
ServiceStartup(ctx context.Context, options ServiceOptions) error
}
// ServiceShutdown is an optional interface for services that need cleanup.
// Called in reverse registration order during application shutdown.
// func (s *MyService) ServiceShutdown() error { ... }
type ServiceShutdown interface {
ServiceShutdown() error
}

View file

@ -0,0 +1,471 @@
package application
import "github.com/wailsapp/wails/v3/pkg/events"
// WindowState represents the starting visual state of a window.
type WindowState int
const (
WindowStateNormal WindowState = iota
WindowStateMinimised WindowState = iota
WindowStateMaximised WindowState = iota
WindowStateFullscreen WindowState = iota
)
// WindowStartPosition determines how the initial X/Y coordinates are interpreted.
type WindowStartPosition int
const (
// WindowCentered places the window in the centre of the screen on first show.
WindowCentered WindowStartPosition = 0
// WindowXY places the window at the explicit X/Y coordinates.
WindowXY WindowStartPosition = 1
)
// BackgroundType controls window transparency.
type BackgroundType int
const (
BackgroundTypeSolid BackgroundType = iota
BackgroundTypeTransparent BackgroundType = iota
BackgroundTypeTranslucent BackgroundType = iota
)
// WebviewWindowOptions holds all configuration for a webview window.
//
// opts := WebviewWindowOptions{
// Name: "main",
// Title: "My App",
// Width: 1280,
// Height: 800,
// Mac: MacWindow{TitleBar: MacTitleBarHiddenInset},
// }
type WebviewWindowOptions struct {
// Name is a unique identifier for the window.
Name string
// Title is shown in the title bar.
Title string
// Width is the initial width in logical pixels.
Width int
// Height is the initial height in logical pixels.
Height int
// AlwaysOnTop makes the window float above others.
AlwaysOnTop bool
// URL is the URL to load on creation.
URL string
// HTML is inline HTML to load (alternative to URL).
HTML string
// JS is inline JavaScript to inject.
JS string
// CSS is inline CSS to inject.
CSS string
// DisableResize prevents the user from resizing the window.
DisableResize bool
// Frameless removes the OS window frame.
Frameless bool
// MinWidth is the minimum allowed width.
MinWidth int
// MinHeight is the minimum allowed height.
MinHeight int
// MaxWidth is the maximum allowed width (0 = unlimited).
MaxWidth int
// MaxHeight is the maximum allowed height (0 = unlimited).
MaxHeight int
// StartState sets the visual state when first shown.
StartState WindowState
// BackgroundType controls transparency.
BackgroundType BackgroundType
// BackgroundColour is the solid background fill colour.
BackgroundColour RGBA
// InitialPosition controls how X/Y are interpreted.
InitialPosition WindowStartPosition
// X is the initial horizontal position.
X int
// Y is the initial vertical position.
Y int
// Hidden creates the window without showing it.
Hidden bool
// Zoom sets the initial zoom magnification (0 = default = 1.0).
Zoom float64
// ZoomControlEnabled allows the user to change zoom.
ZoomControlEnabled bool
// EnableFileDrop enables drag-and-drop of files onto the window.
EnableFileDrop bool
// OpenInspectorOnStartup opens the web inspector when first shown.
OpenInspectorOnStartup bool
// DevToolsEnabled exposes the developer tools (default true in debug builds).
DevToolsEnabled bool
// DefaultContextMenuDisabled disables the built-in right-click menu.
DefaultContextMenuDisabled bool
// KeyBindings is a map of accelerator strings to window callbacks.
KeyBindings map[string]func(window Window)
// IgnoreMouseEvents passes all mouse events through (Windows + macOS only).
IgnoreMouseEvents bool
// ContentProtectionEnabled prevents screen capture (Windows + macOS only).
ContentProtectionEnabled bool
// HideOnFocusLost hides the window when it loses focus.
HideOnFocusLost bool
// HideOnEscape hides the window when Escape is pressed.
HideOnEscape bool
// UseApplicationMenu uses the application-level menu for this window.
UseApplicationMenu bool
// MinimiseButtonState controls the minimise button state.
MinimiseButtonState ButtonState
// MaximiseButtonState controls the maximise/zoom button state.
MaximiseButtonState ButtonState
// CloseButtonState controls the close button state.
CloseButtonState ButtonState
// Mac contains macOS-specific window options.
Mac MacWindow
// Windows contains Windows-specific window options.
Windows WindowsWindow
// Linux contains Linux-specific window options.
Linux LinuxWindow
}
// -------------------------
// macOS-specific types
// -------------------------
// MacBackdrop is the backdrop material for a macOS window.
type MacBackdrop int
const (
// MacBackdropNormal uses an opaque background.
MacBackdropNormal MacBackdrop = iota
// MacBackdropTransparent shows the desktop behind the window.
MacBackdropTransparent MacBackdrop = iota
// MacBackdropTranslucent applies a frosted-glass vibrancy effect.
MacBackdropTranslucent MacBackdrop = iota
// MacBackdropLiquidGlass uses the Apple Liquid Glass effect (macOS 15+).
MacBackdropLiquidGlass MacBackdrop = iota
)
// MacToolbarStyle controls toolbar layout relative to the title bar.
type MacToolbarStyle int
const (
MacToolbarStyleAutomatic MacToolbarStyle = iota
MacToolbarStyleExpanded MacToolbarStyle = iota
MacToolbarStylePreference MacToolbarStyle = iota
MacToolbarStyleUnified MacToolbarStyle = iota
MacToolbarStyleUnifiedCompact MacToolbarStyle = iota
)
// MacLiquidGlassStyle defines the tint of the Liquid Glass effect.
type MacLiquidGlassStyle int
const (
LiquidGlassStyleAutomatic MacLiquidGlassStyle = iota
LiquidGlassStyleLight MacLiquidGlassStyle = iota
LiquidGlassStyleDark MacLiquidGlassStyle = iota
LiquidGlassStyleVibrant MacLiquidGlassStyle = iota
)
// NSVisualEffectMaterial maps to the NSVisualEffectMaterial macOS enum.
type NSVisualEffectMaterial int
const (
NSVisualEffectMaterialAppearanceBased NSVisualEffectMaterial = 0
NSVisualEffectMaterialLight NSVisualEffectMaterial = 1
NSVisualEffectMaterialDark NSVisualEffectMaterial = 2
NSVisualEffectMaterialTitlebar NSVisualEffectMaterial = 3
NSVisualEffectMaterialSelection NSVisualEffectMaterial = 4
NSVisualEffectMaterialMenu NSVisualEffectMaterial = 5
NSVisualEffectMaterialPopover NSVisualEffectMaterial = 6
NSVisualEffectMaterialSidebar NSVisualEffectMaterial = 7
NSVisualEffectMaterialHeaderView NSVisualEffectMaterial = 10
NSVisualEffectMaterialSheet NSVisualEffectMaterial = 11
NSVisualEffectMaterialWindowBackground NSVisualEffectMaterial = 12
NSVisualEffectMaterialHUDWindow NSVisualEffectMaterial = 13
NSVisualEffectMaterialFullScreenUI NSVisualEffectMaterial = 15
NSVisualEffectMaterialToolTip NSVisualEffectMaterial = 17
NSVisualEffectMaterialContentBackground NSVisualEffectMaterial = 18
NSVisualEffectMaterialUnderWindowBackground NSVisualEffectMaterial = 21
NSVisualEffectMaterialUnderPageBackground NSVisualEffectMaterial = 22
NSVisualEffectMaterialAuto NSVisualEffectMaterial = -1
)
// MacLiquidGlass configures the Liquid Glass compositor effect.
type MacLiquidGlass struct {
Style MacLiquidGlassStyle
Material NSVisualEffectMaterial
CornerRadius float64
TintColor *RGBA
GroupID string
GroupSpacing float64
}
// MacAppearanceType is the NSAppearance name string for a macOS window.
type MacAppearanceType string
const (
DefaultAppearance MacAppearanceType = ""
NSAppearanceNameAqua MacAppearanceType = "NSAppearanceNameAqua"
NSAppearanceNameDarkAqua MacAppearanceType = "NSAppearanceNameDarkAqua"
NSAppearanceNameVibrantLight MacAppearanceType = "NSAppearanceNameVibrantLight"
NSAppearanceNameAccessibilityHighContrastAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastAqua"
NSAppearanceNameAccessibilityHighContrastDarkAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastDarkAqua"
NSAppearanceNameAccessibilityHighContrastVibrantLight MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantLight"
NSAppearanceNameAccessibilityHighContrastVibrantDark MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantDark"
)
// MacWindowLevel controls Z-ordering relative to other windows.
type MacWindowLevel string
const (
MacWindowLevelNormal MacWindowLevel = "normal"
MacWindowLevelFloating MacWindowLevel = "floating"
MacWindowLevelTornOffMenu MacWindowLevel = "tornOffMenu"
MacWindowLevelModalPanel MacWindowLevel = "modalPanel"
MacWindowLevelMainMenu MacWindowLevel = "mainMenu"
MacWindowLevelStatus MacWindowLevel = "status"
MacWindowLevelPopUpMenu MacWindowLevel = "popUpMenu"
MacWindowLevelScreenSaver MacWindowLevel = "screenSaver"
)
// MacWindowCollectionBehavior is a bitmask controlling Spaces and fullscreen behaviour.
type MacWindowCollectionBehavior int
const (
MacWindowCollectionBehaviorDefault MacWindowCollectionBehavior = 0
MacWindowCollectionBehaviorCanJoinAllSpaces MacWindowCollectionBehavior = 1 << 0
MacWindowCollectionBehaviorMoveToActiveSpace MacWindowCollectionBehavior = 1 << 1
MacWindowCollectionBehaviorManaged MacWindowCollectionBehavior = 1 << 2
MacWindowCollectionBehaviorTransient MacWindowCollectionBehavior = 1 << 3
MacWindowCollectionBehaviorStationary MacWindowCollectionBehavior = 1 << 4
MacWindowCollectionBehaviorParticipatesInCycle MacWindowCollectionBehavior = 1 << 5
MacWindowCollectionBehaviorIgnoresCycle MacWindowCollectionBehavior = 1 << 6
MacWindowCollectionBehaviorFullScreenPrimary MacWindowCollectionBehavior = 1 << 7
MacWindowCollectionBehaviorFullScreenAuxiliary MacWindowCollectionBehavior = 1 << 8
MacWindowCollectionBehaviorFullScreenNone MacWindowCollectionBehavior = 1 << 9
MacWindowCollectionBehaviorFullScreenAllowsTiling MacWindowCollectionBehavior = 1 << 11
MacWindowCollectionBehaviorFullScreenDisallowsTiling MacWindowCollectionBehavior = 1 << 12
)
// MacWebviewPreferences holds WKWebView preference flags for macOS.
// Use integer tristate: 0 = unset, 1 = true, 2 = false.
type MacWebviewPreferences struct {
TabFocusesLinks int
TextInteractionEnabled int
FullscreenEnabled int
AllowsBackForwardNavigationGestures int
}
// MacTitleBar configures the macOS title bar appearance.
type MacTitleBar struct {
// AppearsTransparent removes the title bar background.
AppearsTransparent bool
// Hide removes the title bar entirely.
Hide bool
// HideTitle hides only the text title.
HideTitle bool
// FullSizeContent extends window content into the title bar area.
FullSizeContent bool
// UseToolbar replaces the title bar with an NSToolbar.
UseToolbar bool
// HideToolbarSeparator removes the line between toolbar and content.
HideToolbarSeparator bool
// ShowToolbarWhenFullscreen keeps the toolbar visible in fullscreen.
ShowToolbarWhenFullscreen bool
// ToolbarStyle selects the toolbar layout style.
ToolbarStyle MacToolbarStyle
}
// MacWindow contains macOS-specific window options.
type MacWindow struct {
Backdrop MacBackdrop
DisableShadow bool
TitleBar MacTitleBar
Appearance MacAppearanceType
InvisibleTitleBarHeight int
EventMapping map[events.WindowEventType]events.WindowEventType
EnableFraudulentWebsiteWarnings bool
WebviewPreferences MacWebviewPreferences
WindowLevel MacWindowLevel
CollectionBehavior MacWindowCollectionBehavior
LiquidGlass MacLiquidGlass
}
// Pre-built MacTitleBar configurations — use directly in MacWindow.TitleBar.
// MacTitleBarDefault produces the standard macOS title bar.
//
// Mac: MacWindow{TitleBar: MacTitleBarDefault}
var MacTitleBarDefault = MacTitleBar{}
// MacTitleBarHidden hides the title text while keeping the traffic-light buttons.
//
// Mac: MacWindow{TitleBar: MacTitleBarHidden}
var MacTitleBarHidden = MacTitleBar{
AppearsTransparent: true,
HideTitle: true,
FullSizeContent: true,
}
// MacTitleBarHiddenInset keeps traffic lights slightly inset from the window edge.
//
// Mac: MacWindow{TitleBar: MacTitleBarHiddenInset}
var MacTitleBarHiddenInset = MacTitleBar{
AppearsTransparent: true,
HideTitle: true,
FullSizeContent: true,
UseToolbar: true,
HideToolbarSeparator: true,
}
// MacTitleBarHiddenInsetUnified uses the unified toolbar style for a more compact look.
//
// Mac: MacWindow{TitleBar: MacTitleBarHiddenInsetUnified}
var MacTitleBarHiddenInsetUnified = MacTitleBar{
AppearsTransparent: true,
HideTitle: true,
FullSizeContent: true,
UseToolbar: true,
HideToolbarSeparator: true,
ToolbarStyle: MacToolbarStyleUnified,
}
// -------------------------
// Windows-specific types
// -------------------------
// BackdropType selects the Windows 11 compositor effect.
type BackdropType int32
const (
Auto BackdropType = 0
None BackdropType = 1
Mica BackdropType = 2
Acrylic BackdropType = 3
Tabbed BackdropType = 4
)
// Theme selects dark or light mode on Windows.
type Theme int
const (
SystemDefault Theme = 0
Dark Theme = 1
Light Theme = 2
)
// WindowTheme holds custom title-bar colours for a Windows window.
type WindowTheme struct {
BorderColour *uint32
TitleBarColour *uint32
TitleTextColour *uint32
}
// TextTheme holds foreground/background colour pair for menu text.
type TextTheme struct {
Text *uint32
Background *uint32
}
// MenuBarTheme holds per-state text themes for the Windows menu bar.
type MenuBarTheme struct {
Default *TextTheme
Hover *TextTheme
Selected *TextTheme
}
// ThemeSettings holds custom colours for dark/light mode on Windows.
// Colours use 0x00BBGGRR encoding.
type ThemeSettings struct {
DarkModeActive *WindowTheme
DarkModeInactive *WindowTheme
LightModeActive *WindowTheme
LightModeInactive *WindowTheme
DarkModeMenuBar *MenuBarTheme
LightModeMenuBar *MenuBarTheme
}
// CoreWebView2PermissionKind identifies a WebView2 permission category.
type CoreWebView2PermissionKind uint32
const (
CoreWebView2PermissionKindUnknownPermission CoreWebView2PermissionKind = iota
CoreWebView2PermissionKindMicrophone
CoreWebView2PermissionKindCamera
CoreWebView2PermissionKindGeolocation
CoreWebView2PermissionKindNotifications
CoreWebView2PermissionKindOtherSensors
CoreWebView2PermissionKindClipboardRead
)
// CoreWebView2PermissionState sets whether a permission is granted.
type CoreWebView2PermissionState uint32
const (
CoreWebView2PermissionStateDefault CoreWebView2PermissionState = iota
CoreWebView2PermissionStateAllow
CoreWebView2PermissionStateDeny
)
// WindowsWindow contains Windows-specific window options.
type WindowsWindow struct {
BackdropType BackdropType
DisableIcon bool
Theme Theme
CustomTheme ThemeSettings
DisableFramelessWindowDecorations bool
WindowMask []byte
WindowMaskDraggable bool
ResizeDebounceMS uint16
WindowDidMoveDebounceMS uint16
EventMapping map[events.WindowEventType]events.WindowEventType
HiddenOnTaskbar bool
EnableSwipeGestures bool
Menu *Menu
Permissions map[CoreWebView2PermissionKind]CoreWebView2PermissionState
ExStyle int
GeneralAutofillEnabled bool
PasswordAutosaveEnabled bool
}
// -------------------------
// Linux-specific types
// -------------------------
// WebviewGpuPolicy controls GPU acceleration for the Linux webview.
type WebviewGpuPolicy int
const (
WebviewGpuPolicyAlways WebviewGpuPolicy = iota
WebviewGpuPolicyOnDemand WebviewGpuPolicy = iota
WebviewGpuPolicyNever WebviewGpuPolicy = iota
)
// LinuxMenuStyle selects how the application menu is rendered on Linux.
type LinuxMenuStyle int
const (
LinuxMenuStyleMenuBar LinuxMenuStyle = iota
LinuxMenuStylePrimaryMenu LinuxMenuStyle = iota
)
// LinuxWindow contains Linux-specific window options.
type LinuxWindow struct {
Icon []byte
WindowIsTranslucent bool
WebviewGpuPolicy WebviewGpuPolicy
WindowDidMoveDebounceMS uint16
Menu *Menu
MenuStyle LinuxMenuStyle
}
// NewRGB constructs an RGBA value with full opacity from RGB components.
//
// colour := NewRGB(255, 128, 0)
func NewRGB(red, green, blue uint8) RGBA {
return RGBA{Red: red, Green: green, Blue: blue, Alpha: 255}
}
// NewRGBPtr encodes RGB as a packed uint32 pointer (0x00BBGGRR) for ThemeSettings.
//
// theme.BorderColour = NewRGBPtr(255, 0, 0)
func NewRGBPtr(red, green, blue uint8) *uint32 {
result := uint32(red) | uint32(green)<<8 | uint32(blue)<<16
return &result
}

View file

@ -0,0 +1,160 @@
package application
import (
"unsafe"
"github.com/wailsapp/wails/v3/pkg/events"
)
// Window is the interface satisfied by all window types in the application.
// Fluent mutating methods return Window so callers can chain calls:
//
// app.Window.NewWithOptions(opts).SetTitle("Main").Show()
type Window interface {
// Identity
ID() uint
Name() string
// Visibility
Show() Window
Hide() Window
IsVisible() bool
// Lifecycle
Close()
Focus()
Run()
// Geometry
Center()
Position() (x int, y int)
RelativePosition() (x int, y int)
Size() (width int, height int)
Width() int
Height() int
Bounds() Rect
SetPosition(x, y int)
SetRelativePosition(x, y int) Window
SetSize(width, height int) Window
SetBounds(bounds Rect)
SetMaxSize(maxWidth, maxHeight int) Window
SetMinSize(minWidth, minHeight int) Window
EnableSizeConstraints()
DisableSizeConstraints()
Resizable() bool
SetResizable(b bool) Window
// State
Maximise() Window
UnMaximise()
ToggleMaximise()
IsMaximised() bool
Minimise() Window
UnMinimise()
IsMinimised() bool
Fullscreen() Window
UnFullscreen()
ToggleFullscreen()
IsFullscreen() bool
Restore()
SnapAssist()
// Title and content
SetTitle(title string) Window
SetURL(s string) Window
SetHTML(html string) Window
// Titlebar buttons (macOS / Windows)
SetMinimiseButtonState(state ButtonState) Window
SetMaximiseButtonState(state ButtonState) Window
SetCloseButtonState(state ButtonState) Window
// Menu bar
SetMenu(menu *Menu)
ShowMenuBar()
HideMenuBar()
ToggleMenuBar()
// Appearance
SetBackgroundColour(colour RGBA) Window
SetAlwaysOnTop(b bool) Window
SetFrameless(frameless bool) Window
ToggleFrameless()
SetIgnoreMouseEvents(ignore bool) Window
IsIgnoreMouseEvents() bool
SetContentProtection(protection bool) Window
// Zoom
GetZoom() float64
SetZoom(magnification float64) Window
Zoom()
ZoomIn()
ZoomOut()
ZoomReset() Window
// Border sizes (Windows)
GetBorderSizes() *LRTB
// Screen
GetScreen() (*Screen, error)
// JavaScript / events
ExecJS(js string)
EmitEvent(name string, data ...any) bool
DispatchWailsEvent(event *CustomEvent)
OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func()
RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func()
// Drag-and-drop (internal message bus)
handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails)
InitiateFrontendDropProcessing(filenames []string, x int, y int)
// Message handling (internal)
HandleMessage(message string)
HandleWindowEvent(id uint)
HandleKeyEvent(acceleratorString string)
// Context menu
OpenContextMenu(data *ContextMenuData)
// Modal
AttachModal(modalWindow Window)
// DevTools
OpenDevTools()
// Print
Print() error
// Flash (Windows taskbar flash)
Flash(enabled bool)
// Focus tracking
IsFocused() bool
// Native handle (platform-specific, use with care)
NativeWindow() unsafe.Pointer
// Enabled state
SetEnabled(enabled bool)
// Reload
Reload()
ForceReload()
// Logging (routed to the application logger)
Info(message string, args ...any)
Error(message string, args ...any)
// Internal hooks
shouldUnconditionallyClose() bool
// Editing operations (routed to focused element)
cut()
copy()
paste()
undo()
redo()
delete()
selectAll()
}

View file

@ -1,5 +1,11 @@
package events
// ApplicationEventType identifies a platform-level application event.
// Matches the type used by the real Wails v3 package.
//
// em.OnApplicationEvent(events.Common.ThemeChanged, handler)
type ApplicationEventType uint
// WindowEventType identifies a window event emitted by the application layer.
type WindowEventType int
@ -14,17 +20,25 @@ const (
// Common matches the event namespace used by the real Wails package.
var Common = struct {
WindowFocus WindowEventType
WindowLostFocus WindowEventType
WindowDidMove WindowEventType
WindowDidResize WindowEventType
WindowClosing WindowEventType
WindowFilesDropped WindowEventType
ApplicationOpenedWithFile ApplicationEventType
ApplicationStarted ApplicationEventType
ApplicationLaunchedWithUrl ApplicationEventType
ThemeChanged ApplicationEventType
WindowFocus WindowEventType
WindowLostFocus WindowEventType
WindowDidMove WindowEventType
WindowDidResize WindowEventType
WindowClosing WindowEventType
WindowFilesDropped WindowEventType
}{
WindowFocus: WindowFocus,
WindowLostFocus: WindowLostFocus,
WindowDidMove: WindowDidMove,
WindowDidResize: WindowDidResize,
WindowClosing: WindowClosing,
WindowFilesDropped: WindowFilesDropped,
ApplicationOpenedWithFile: 1024,
ApplicationStarted: 1025,
ApplicationLaunchedWithUrl: 1026,
ThemeChanged: 1027,
WindowFocus: WindowFocus,
WindowLostFocus: WindowLostFocus,
WindowDidMove: WindowDidMove,
WindowDidResize: WindowDidResize,
WindowClosing: WindowClosing,
WindowFilesDropped: WindowFilesDropped,
}