mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +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>
225 lines
5.1 KiB
Go
225 lines
5.1 KiB
Go
//go:build android && !cgo && !server
|
|
|
|
package application
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"encoding/json"
|
|
)
|
|
|
|
var (
|
|
// Global reference to the app for JNI callbacks
|
|
globalApp *App
|
|
globalAppLock sync.RWMutex
|
|
|
|
// JNI environment and class references
|
|
javaVM unsafe.Pointer
|
|
bridgeObject unsafe.Pointer
|
|
)
|
|
|
|
func init() {
|
|
androidLogf("info", "🤖 [application_android.go] init() called")
|
|
}
|
|
|
|
func androidLogf(level string, format string, a ...interface{}) {
|
|
msg := fmt.Sprintf(format, a...)
|
|
// For now, just use println - we'll connect to Android's Log.* later
|
|
println(fmt.Sprintf("[Android/%s] %s", level, msg))
|
|
}
|
|
|
|
func (a *App) platformRun() {
|
|
androidLogf("info", "🤖 [application_android.go] platformRun() called")
|
|
|
|
// Store global reference for JNI callbacks
|
|
globalAppLock.Lock()
|
|
globalApp = a
|
|
globalAppLock.Unlock()
|
|
|
|
androidLogf("info", "🤖 [application_android.go] Waiting for Android lifecycle...")
|
|
|
|
// Block forever - Android manages the app lifecycle via JNI callbacks
|
|
select {}
|
|
}
|
|
|
|
func (a *App) platformQuit() {
|
|
androidLogf("info", "🤖 [application_android.go] platformQuit() called")
|
|
// Android will handle app termination
|
|
}
|
|
|
|
func (a *App) isDarkMode() bool {
|
|
// TODO: Query Android for dark mode status
|
|
return false
|
|
}
|
|
|
|
func (a *App) isWindows() bool {
|
|
return false
|
|
}
|
|
|
|
// Platform-specific app implementation for Android
|
|
type androidApp struct {
|
|
parent *App
|
|
}
|
|
|
|
func newPlatformApp(app *App) *androidApp {
|
|
androidLogf("info", "🤖 [application_android.go] newPlatformApp() called")
|
|
return &androidApp{
|
|
parent: app,
|
|
}
|
|
}
|
|
|
|
func (a *androidApp) run() error {
|
|
androidLogf("info", "🤖 [application_android.go] androidApp.run() called")
|
|
|
|
// Wire common events
|
|
a.setupCommonEvents()
|
|
|
|
// Emit application started event
|
|
a.parent.Event.Emit("ApplicationStarted")
|
|
|
|
a.parent.platformRun()
|
|
return nil
|
|
}
|
|
|
|
func (a *androidApp) destroy() {
|
|
androidLogf("info", "🤖 [application_android.go] androidApp.destroy() called")
|
|
}
|
|
|
|
func (a *androidApp) setIcon(_ []byte) {
|
|
// Android app icon is set through AndroidManifest.xml
|
|
}
|
|
|
|
func (a *androidApp) name() string {
|
|
return a.parent.options.Name
|
|
}
|
|
|
|
func (a *androidApp) GetFlags(options Options) map[string]any {
|
|
return nil
|
|
}
|
|
|
|
func (a *androidApp) getAccentColor() string {
|
|
return ""
|
|
}
|
|
|
|
func (a *androidApp) getCurrentWindowID() uint {
|
|
return 0
|
|
}
|
|
|
|
func (a *androidApp) hide() {
|
|
// Android manages app visibility
|
|
}
|
|
|
|
func (a *androidApp) isDarkMode() bool {
|
|
return a.parent.isDarkMode()
|
|
}
|
|
|
|
func (a *androidApp) on(_ uint) {
|
|
// Android event handling
|
|
}
|
|
|
|
func (a *androidApp) setApplicationMenu(_ *Menu) {
|
|
// Android doesn't have application menus in the same way
|
|
}
|
|
|
|
func (a *androidApp) show() {
|
|
// Android manages app visibility
|
|
}
|
|
|
|
func (a *androidApp) showAboutDialog(_ string, _ string, _ []byte) {
|
|
// TODO: Implement Android about dialog
|
|
}
|
|
|
|
func (a *androidApp) getPrimaryScreen() (*Screen, error) {
|
|
screens, err := getScreens()
|
|
if err != nil || len(screens) == 0 {
|
|
return nil, err
|
|
}
|
|
return screens[0], nil
|
|
}
|
|
|
|
func (a *androidApp) getScreens() ([]*Screen, error) {
|
|
return getScreens()
|
|
}
|
|
|
|
func (a *App) logPlatformInfo() {
|
|
// Log Android platform info
|
|
androidLogf("info", "Platform: Android")
|
|
}
|
|
|
|
func (a *App) platformEnvironment() map[string]any {
|
|
return map[string]any{
|
|
"platform": "android",
|
|
}
|
|
}
|
|
|
|
func fatalHandler(errFunc func(error)) {
|
|
// Android fatal handler
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func serveAssetForAndroid(app *App, path string) ([]byte, error) {
|
|
// Normalize path
|
|
if path == "" || path == "/" {
|
|
path = "/index.html"
|
|
}
|
|
|
|
// TODO: Use the actual asset server to serve the file
|
|
// For now, return a placeholder
|
|
return nil, fmt.Errorf("asset serving not yet implemented: %s", path)
|
|
}
|
|
|
|
func handleMessageForAndroid(app *App, message string) string {
|
|
// Parse the message
|
|
var msg map[string]interface{}
|
|
if err := json.Unmarshal([]byte(message), &msg); err != nil {
|
|
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
|
|
}
|
|
|
|
// TODO: Route to appropriate handler based on message type
|
|
// For now, return success
|
|
return `{"success":true}`
|
|
}
|
|
|
|
func getMimeTypeForPath(path string) string {
|
|
// Simple MIME type detection based on extension
|
|
switch {
|
|
case endsWith(path, ".html"), endsWith(path, ".htm"):
|
|
return "text/html"
|
|
case endsWith(path, ".js"), endsWith(path, ".mjs"):
|
|
return "application/javascript"
|
|
case endsWith(path, ".css"):
|
|
return "text/css"
|
|
case endsWith(path, ".json"):
|
|
return "application/json"
|
|
case endsWith(path, ".png"):
|
|
return "image/png"
|
|
case endsWith(path, ".jpg"), endsWith(path, ".jpeg"):
|
|
return "image/jpeg"
|
|
case endsWith(path, ".gif"):
|
|
return "image/gif"
|
|
case endsWith(path, ".svg"):
|
|
return "image/svg+xml"
|
|
case endsWith(path, ".ico"):
|
|
return "image/x-icon"
|
|
case endsWith(path, ".woff"):
|
|
return "font/woff"
|
|
case endsWith(path, ".woff2"):
|
|
return "font/woff2"
|
|
case endsWith(path, ".ttf"):
|
|
return "font/ttf"
|
|
default:
|
|
return "application/octet-stream"
|
|
}
|
|
}
|
|
|
|
func endsWith(s, suffix string) bool {
|
|
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
|
|
}
|
|
|
|
// executeJavaScript is a stub for non-cgo builds
|
|
func executeJavaScript(js string) {
|
|
androidLogf("warn", "executeJavaScript called but cgo is not enabled")
|
|
}
|