wails/v3/pkg/application/menu_darwin.go
Lea Anthony 9a363d7be5
feat(v3): add server mode for headless HTTP deployment (#4903)
* 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>
2026-01-25 14:33:44 +11:00

135 lines
3 KiB
Go

//go:build darwin && !ios && !server
package application
/*
#cgo CFLAGS: -mmacosx-version-min=10.10 -x objective-c
#cgo LDFLAGS: -framework Cocoa
#include "menuitem_darwin.h"
extern void setMenuItemChecked(void*, unsigned int, bool);
extern void setMenuItemBitmap(void*, unsigned char*, int);
// Clear and release all menu items in the menu
void clearMenu(void* nsMenu) {
NSMenu *menu = (NSMenu *)nsMenu;
[menu removeAllItems];
}
// Create a new NSMenu
void* createNSMenu(char* label) {
NSMenu *menu = [[NSMenu alloc] init];
if( label != NULL && strlen(label) > 0 ) {
menu.title = [NSString stringWithUTF8String:label];
free(label);
}
[menu setAutoenablesItems:NO];
return (void*)menu;
}
void addMenuItem(void* nsMenu, void* nsMenuItem) {
NSMenu *menu = (NSMenu *)nsMenu;
[menu addItem:nsMenuItem];
}
// add seperator to menu
void addMenuSeparator(void* nsMenu) {
NSMenu *menu = (NSMenu *)nsMenu;
[menu addItem:[NSMenuItem separatorItem]];
}
// Set the submenu of a menu item
void setMenuItemSubmenu(void* nsMenuItem, void* nsMenu) {
NSMenuItem *menuItem = (NSMenuItem *)nsMenuItem;
NSMenu *menu = (NSMenu *)nsMenu;
[menuItem setSubmenu:menu];
}
// Add services menu
static void addServicesMenu(void* menu) {
NSMenu *nsMenu = (__bridge NSMenu *)menu;
[NSApp setServicesMenu:nsMenu];
}
// Add windows menu
void addWindowsMenu(void* menu) {
NSMenu *nsMenu = (__bridge NSMenu *)menu;
[NSApp setWindowsMenu:nsMenu];
}
*/
import "C"
import "unsafe"
type macosMenu struct {
menu *Menu
nsMenu unsafe.Pointer
}
func newMenuImpl(menu *Menu) *macosMenu {
result := &macosMenu{
menu: menu,
}
return result
}
func (m *macosMenu) update() {
InvokeSync(func() {
if m.nsMenu == nil {
m.nsMenu = C.createNSMenu(C.CString(m.menu.label))
} else {
C.clearMenu(m.nsMenu)
}
m.processMenu(m.nsMenu, m.menu)
})
}
func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) {
for _, item := range menu.items {
switch item.itemType {
case submenu:
submenu := item.submenu
nsSubmenu := C.createNSMenu(C.CString(item.label))
m.processMenu(nsSubmenu, submenu)
menuItem := newMenuItemImpl(item)
item.impl = menuItem
C.addMenuItem(parent, menuItem.nsMenuItem)
C.setMenuItemSubmenu(menuItem.nsMenuItem, nsSubmenu)
if item.role == ServicesMenu {
C.addServicesMenu(nsSubmenu)
}
if item.role == WindowMenu {
C.addWindowsMenu(nsSubmenu)
}
case text, checkbox, radio:
menuItem := newMenuItemImpl(item)
item.impl = menuItem
if item.hidden {
menuItem.setHidden(true)
}
C.addMenuItem(parent, menuItem.nsMenuItem)
case separator:
C.addMenuSeparator(parent)
}
if item.bitmap != nil {
macMenuItem := item.impl.(*macosMenuItem)
C.setMenuItemBitmap(macMenuItem.nsMenuItem, (*C.uchar)(&item.bitmap[0]), C.int(len(item.bitmap)))
}
}
}
func DefaultApplicationMenu() *Menu {
menu := NewMenu()
menu.AddRole(AppMenu)
menu.AddRole(FileMenu)
menu.AddRole(EditMenu)
menu.AddRole(ViewMenu)
menu.AddRole(WindowMenu)
menu.AddRole(HelpMenu)
return menu
}