wails/v3/internal/assetserver/webview/responsewriter_linux.go
Lea Anthony 4d8ec29feb feat: Add Android support for Wails v3
This commit adds comprehensive Android support for Wails v3, enabling
Go applications to run as native Android apps with WebView-based UI.

Key features:
- Android-specific application implementation with JNI bridge
- WebView integration via WebViewAssetLoader for serving assets
- JavaScript runtime injection and execution via JNI callbacks
- Binding call support with async result callbacks
- Event system support for Android platform
- Full example Android app with Gradle build system

Technical details:
- Uses CGO with Android NDK for cross-compilation
- Implements JNI callbacks for Go <-> Java communication
- Supports both ARM64 and x86_64 architectures
- WebView debugging support via Chrome DevTools Protocol
- Handles empty response body case in binding calls to prevent panic

Files added:
- v3/pkg/application/*_android.go - Android platform implementations
- v3/pkg/events/events_android.go - Android event definitions
- v3/internal/*/\*_android.go - Android-specific internal packages
- v3/examples/android/ - Complete example Android application
- v3/ANDROID_ARCHITECTURE.md - Architecture documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 21:06:59 +11:00

135 lines
2.7 KiB
Go

//go:build linux && !android
package webview
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 gio-unix-2.0
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
#include "gio/gunixinputstream.h"
*/
import "C"
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"syscall"
"unsafe"
)
type responseWriter struct {
req *C.WebKitURISchemeRequest
header http.Header
wroteHeader bool
finished bool
code int
w io.WriteCloser
wErr error
}
func (rw *responseWriter) Code() int {
return rw.code
}
func (rw *responseWriter) Header() http.Header {
if rw.header == nil {
rw.header = http.Header{}
}
return rw.header
}
func (rw *responseWriter) Write(buf []byte) (int, error) {
if rw.finished {
return 0, errResponseFinished
}
rw.WriteHeader(http.StatusOK)
if rw.wErr != nil {
return 0, rw.wErr
}
return rw.w.Write(buf)
}
func (rw *responseWriter) WriteHeader(code int) {
rw.code = code
if rw.wroteHeader || rw.finished {
return
}
rw.wroteHeader = true
contentLength := int64(-1)
if sLen := rw.Header().Get(HeaderContentLength); sLen != "" {
if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 {
contentLength = pLen
}
}
// We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the
// read FD is given to the InputStream and will be closed there.
// Furthermore we especially don't want to have the FD_CLOEXEC
rFD, w, err := pipe()
if err != nil {
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err))
return
}
rw.w = w
stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1))
defer C.g_object_unref(C.gpointer(stream))
if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil {
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
return
}
}
func (rw *responseWriter) Finish() error {
if !rw.wroteHeader {
rw.WriteHeader(http.StatusNotImplemented)
}
if rw.finished {
return nil
}
rw.finished = true
if rw.w != nil {
rw.w.Close()
}
return nil
}
func (rw *responseWriter) finishWithError(code int, err error) {
if rw.w != nil {
rw.w.Close()
rw.w = &nopCloser{io.Discard}
}
rw.wErr = err
msg := C.CString(err.Error())
gerr := C.g_error_new_literal(C.g_quark_from_string(msg), C.int(code), msg)
C.webkit_uri_scheme_request_finish_error(rw.req, gerr)
C.g_error_free(gerr)
C.free(unsafe.Pointer(msg))
}
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error { return nil }
func pipe() (r int, w *os.File, err error) {
var p [2]int
e := syscall.Pipe2(p[0:], 0)
if e != nil {
return 0, nil, fmt.Errorf("pipe2: %s", e)
}
return p[0], os.NewFile(uintptr(p[1]), "|1"), nil
}