mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-15 15:15:51 +01:00
* feat(v3): add server mode for headless HTTP deployment Server mode allows Wails applications to run as pure HTTP servers without native GUI dependencies. Enable with `-tags server` build tag. Features: - HTTP server with configurable host/port via ServerOptions - WAILS_SERVER_HOST and WAILS_SERVER_PORT env var overrides - WebSocket event broadcasting to connected browsers - Browser clients represented as BrowserWindow (Window interface) - Health check endpoint at /health - Graceful shutdown with configurable timeout - Docker support with Dockerfile.server template and tasks Build and run: wails3 task build:server wails3 task run:server wails3 task build:docker wails3 task run:docker Documentation at docs/guides/server-build.mdx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(v3): add server mode for headless HTTP deployment Server mode allows Wails applications to run as pure HTTP servers without native GUI dependencies. Enable with `-tags server` build tag. Features: - HTTP server with configurable host/port via ServerOptions - WAILS_SERVER_HOST and WAILS_SERVER_PORT env var overrides - WebSocket event broadcasting to connected browsers - Browser clients represented as BrowserWindow (Window interface) - Health check endpoint at /health - Graceful shutdown with configurable timeout - Docker support with Dockerfile.server template and tasks Build and run: wails3 task build:server wails3 task run:server wails3 task build:docker wails3 task run:docker Documentation at docs/guides/server-build.mdx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review comments - Fix corrupted test file with embedded terminal output - Fix module name mismatch in gin-routing (was gin-example) - Fix replace directive version mismatch in gin-service - Fix placeholder module name in ios example (was changeme) - Fix Dockerfile COPY path to work from both build contexts - Fix bare URL in README (MD034 compliance) - Fix comment accuracy in getScreens (returns error, not empty slice) - Remove deprecated docker-compose version field - Add port documentation in Taskfile template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review comments - Add note about healthcheck wget not being available in distroless images - Add !server build constraint to menu_windows.go and menu_darwin.go - Downgrade window-visibility-test go.mod from 1.25 to 1.24 to match CI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
142 lines
3 KiB
Go
142 lines
3 KiB
Go
//go:build windows && !server
|
|
|
|
package application
|
|
|
|
import (
|
|
"github.com/wailsapp/wails/v3/pkg/w32"
|
|
)
|
|
|
|
type windowsMenu struct {
|
|
menu *Menu
|
|
parentWindow *windowsWebviewWindow
|
|
|
|
hWnd w32.HWND
|
|
hMenu w32.HMENU
|
|
currentMenuID int
|
|
menuMapping map[int]*MenuItem
|
|
checkboxItems []*Menu
|
|
}
|
|
|
|
func newMenuImpl(menu *Menu) *windowsMenu {
|
|
result := &windowsMenu{
|
|
menu: menu,
|
|
menuMapping: make(map[int]*MenuItem),
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (w *windowsMenu) update() {
|
|
if w.hMenu != 0 {
|
|
w32.DestroyMenu(w.hMenu)
|
|
}
|
|
w.hMenu = w32.NewPopupMenu()
|
|
w.processMenu(w.hMenu, w.menu)
|
|
}
|
|
|
|
func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
|
|
for _, item := range inputMenu.items {
|
|
w.currentMenuID++
|
|
itemID := w.currentMenuID
|
|
w.menuMapping[itemID] = item
|
|
|
|
menuItemImpl := newMenuItemImpl(item, parentMenu, itemID)
|
|
menuItemImpl.parent = inputMenu
|
|
item.impl = menuItemImpl
|
|
|
|
if item.Hidden() {
|
|
if item.accelerator != nil && item.callback != nil {
|
|
if w.parentWindow != nil {
|
|
w.parentWindow.parent.removeMenuBinding(item.accelerator)
|
|
} else {
|
|
globalApplication.KeyBinding.Remove(item.accelerator.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
flags := uint32(w32.MF_STRING)
|
|
if item.disabled {
|
|
flags = flags | w32.MF_GRAYED
|
|
}
|
|
if item.checked {
|
|
flags = flags | w32.MF_CHECKED
|
|
}
|
|
if item.IsSeparator() {
|
|
flags = flags | w32.MF_SEPARATOR
|
|
}
|
|
if item.itemType == radio {
|
|
flags = flags | w32.MFT_RADIOCHECK
|
|
}
|
|
|
|
if item.submenu != nil {
|
|
flags = flags | w32.MF_POPUP
|
|
newSubmenu := w32.CreateMenu()
|
|
w.processMenu(newSubmenu, item.submenu)
|
|
itemID = int(newSubmenu)
|
|
}
|
|
|
|
thisText := item.Label()
|
|
if item.accelerator != nil && item.callback != nil {
|
|
if w.parentWindow != nil {
|
|
w.parentWindow.parent.addMenuBinding(item.accelerator, item)
|
|
} else {
|
|
globalApplication.KeyBinding.Add(item.accelerator.String(), func(w Window) {
|
|
item.handleClick()
|
|
})
|
|
}
|
|
thisText = thisText + "\t" + item.accelerator.String()
|
|
}
|
|
var menuText = w32.MustStringToUTF16Ptr(thisText)
|
|
|
|
// If the item is hidden, don't append
|
|
if item.Hidden() {
|
|
continue
|
|
}
|
|
|
|
w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText)
|
|
if item.bitmap != nil {
|
|
if err := w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil); err != nil {
|
|
globalApplication.fatal("error setting menu icons: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *windowsMenu) ShowAtCursor() {
|
|
InvokeSync(func() {
|
|
x, y, ok := w32.GetCursorPos()
|
|
if !ok {
|
|
return
|
|
}
|
|
w.ShowAt(x, y)
|
|
})
|
|
}
|
|
|
|
func (w *windowsMenu) ShowAt(x int, y int) {
|
|
w.update()
|
|
w32.TrackPopupMenuEx(w.hMenu,
|
|
w32.TPM_LEFTALIGN,
|
|
int32(x),
|
|
int32(y),
|
|
w.hWnd,
|
|
nil)
|
|
w32.PostMessage(w.hWnd, w32.WM_NULL, 0, 0)
|
|
}
|
|
|
|
func (w *windowsMenu) ProcessCommand(cmdMsgID int) {
|
|
item := w.menuMapping[cmdMsgID]
|
|
if item == nil {
|
|
return
|
|
}
|
|
item.handleClick()
|
|
}
|
|
|
|
func DefaultApplicationMenu() *Menu {
|
|
menu := NewMenu()
|
|
menu.AddRole(FileMenu)
|
|
menu.AddRole(EditMenu)
|
|
menu.AddRole(ViewMenu)
|
|
menu.AddRole(WindowMenu)
|
|
menu.AddRole(HelpMenu)
|
|
return menu
|
|
}
|