wails/v3/tasks/events/generate.go
Lea Anthony 637713fae6 feat: adapt iOS and Android message processors to RuntimeRequest transport
Transport layer refactor adaptations for mobile platforms:

- Refactor processIOSMethod to use RuntimeRequest signature
- Refactor processAndroidMethod to use RuntimeRequest signature
- Add androidRequest constant (12) and handler to messageprocessor.go
- Update messageprocessor_mobile_stub.go for non-mobile builds
- Fix undefined windowID variable (use req.WebviewWindowID)
- Add iOS event generation to tasks/events/generate.go
- Add InvalidIOSCallError and InvalidAndroidCallError to errs package
- Update iOS delegate and webview files with generated event handlers

iOS methods refactored: Haptics.Impact, Device.Info, Scroll settings,
Navigation gestures, Link previews, Debug inspector, UserAgent

Android methods refactored: Haptics.Vibrate, Device.Info, Toast.Show

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 21:27:04 +11:00

569 lines
18 KiB
Go

package main
import (
"bytes"
"os"
"strconv"
"strings"
)
const eventsGo = `package events
type ApplicationEventType uint
type WindowEventType uint
var Common = newCommonEvents()
type commonEvents struct {
$$COMMONEVENTSDECL}
func newCommonEvents() commonEvents {
return commonEvents{
$$COMMONEVENTSVALUES }
}
var Linux = newLinuxEvents()
type linuxEvents struct {
$$LINUXEVENTSDECL}
func newLinuxEvents() linuxEvents {
return linuxEvents{
$$LINUXEVENTSVALUES }
}
var Mac = newMacEvents()
type macEvents struct {
$$MACEVENTSDECL}
func newMacEvents() macEvents {
return macEvents{
$$MACEVENTSVALUES }
}
var Windows = newWindowsEvents()
type windowsEvents struct {
$$WINDOWSEVENTSDECL}
func newWindowsEvents() windowsEvents {
return windowsEvents{
$$WINDOWSEVENTSVALUES }
}
var iOS = newIOSEvents()
type iosEvents struct {
$$IOSEVENTSDECL}
func newIOSEvents() iosEvents {
return iosEvents{
$$IOSEVENTSVALUES }
}
func JSEvent(event uint) string {
return eventToJS[event]
}
var eventToJS = map[uint]string{
$$EVENTTOJS}
`
const darwinEventsH = `//go:build darwin
#ifndef _events_h
#define _events_h
extern void processApplicationEvent(unsigned int, void* data);
extern void processWindowEvent(unsigned int, unsigned int);
$$CHEADEREVENTS
#endif`
const linuxEventsH = `//go:build linux
#ifndef _events_h
#define _events_h
extern void processApplicationEvent(unsigned int, void* data);
extern void processWindowEvent(unsigned int, unsigned int);
$$CHEADEREVENTS
#endif`
const iosEventsH = `//go:build ios
#ifndef _events_h
#define _events_h
extern void processApplicationEvent(unsigned int, void* data);
extern void processWindowEvent(unsigned int, unsigned int);
$$CHEADEREVENTS
#endif`
const eventsTS = `/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ ` + "`" + `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export const Types = Object.freeze({
Windows: Object.freeze({
$$WINDOWSJSEVENTS }),
Mac: Object.freeze({
$$MACJSEVENTS }),
Linux: Object.freeze({
$$LINUXJSEVENTS }),
iOS: Object.freeze({
$$IOSJSEVENTS }),
Common: Object.freeze({
$$COMMONJSEVENTS }),
});
`
func main() {
eventNames, err := os.ReadFile("../../pkg/events/events.txt")
if err != nil {
panic(err)
}
linuxEventsDecl := bytes.NewBufferString("")
linuxEventsValues := bytes.NewBufferString("")
linuxCHeaderEvents := bytes.NewBufferString("")
macEventsDecl := bytes.NewBufferString("")
macEventsValues := bytes.NewBufferString("")
macCHeaderEvents := bytes.NewBufferString("")
windowDelegateEvents := bytes.NewBufferString("")
applicationDelegateEvents := bytes.NewBufferString("")
webviewDelegateEvents := bytes.NewBufferString("")
windowsEventsDecl := bytes.NewBufferString("")
windowsEventsValues := bytes.NewBufferString("")
iosEventsDecl := bytes.NewBufferString("")
iosEventsValues := bytes.NewBufferString("")
iosCHeaderEvents := bytes.NewBufferString("")
iosApplicationDelegateEvents := bytes.NewBufferString("")
iosWebviewDelegateEvents := bytes.NewBufferString("")
commonEventsDecl := bytes.NewBufferString("")
commonEventsValues := bytes.NewBufferString("")
linuxTSEvents := bytes.NewBufferString("")
macTSEvents := bytes.NewBufferString("")
windowsTSEvents := bytes.NewBufferString("")
iosTSEvents := bytes.NewBufferString("")
commonTSEvents := bytes.NewBufferString("")
eventToJS := bytes.NewBufferString("")
var id int
// var maxLinuxEvents int
var maxMacEvents int
var maxLinuxEvents int
var maxIOSEvents int
var line []byte
// Loop over each line in the file
for id, line = range bytes.Split(eventNames, []byte{'\n'}) {
// First 1024 is reserved
id = id + 1024
// Skip empty lines
if len(line) == 0 {
continue
}
// split on the colon
split := bytes.Split(line, []byte{':'})
platform := strings.TrimSpace(string(split[0]))
event := strings.TrimSpace(string(split[1]))
var ignoreEvent bool
if strings.HasSuffix(event, "!") {
event = strings.TrimSuffix(event, "!")
ignoreEvent = true
}
// Strip last byte of line if it's a "!" character
if line[len(line)-1] == '!' {
line = line[:len(line)-1]
}
// Title case the event name
eventTitle := string(bytes.ToUpper([]byte{event[0]})) + event[1:]
// delegate function name has a lowercase first character
delegateEventFunction := string(bytes.ToLower([]byte{event[0]})) + event[1:]
// Add to buffer
switch platform {
case "linux":
eventType := "ApplicationEventType"
if strings.HasPrefix(event, "Window") {
eventType = "WindowEventType"
}
if strings.HasPrefix(event, "WebView") {
eventType = "WindowEventType"
}
linuxEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
linuxEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
linuxTSEvents.WriteString("\t\t" + event + ": \"linux:" + event + "\",\n")
eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"linux:" + event + "\",\n")
maxLinuxEvents = id
linuxCHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n")
case "mac":
eventType := "ApplicationEventType"
if strings.HasPrefix(event, "Window") {
eventType = "WindowEventType"
}
if strings.HasPrefix(event, "WebView") {
eventType = "WindowEventType"
}
macEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
macEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
macTSEvents.WriteString("\t\t" + event + ": \"mac:" + event + "\",\n")
macCHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n")
eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"mac:" + event + "\",\n")
maxMacEvents = id
if ignoreEvent {
continue
}
// Check if this is a window event
if strings.HasPrefix(event, "Window") {
windowDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification {
if( hasListeners(Event` + eventTitle + `) ) {
processWindowEvent(self.windowId, Event` + eventTitle + `);
}
}
`)
}
// Check if this is a webview event
if strings.HasPrefix(event, "WebView") {
webViewFunction := strings.TrimPrefix(event, "WebView")
webViewFunction = string(bytes.ToLower([]byte{webViewFunction[0]})) + webViewFunction[1:]
webviewDelegateEvents.WriteString(`- (void)webView:(WKWebView *)webview ` + webViewFunction + `:(WKNavigation *)navigation {
if( hasListeners(Event` + eventTitle + `) ) {
processWindowEvent(self.windowId, Event` + eventTitle + `);
}
}
`)
}
if strings.HasPrefix(event, "Application") {
applicationDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification {
if( hasListeners(Event` + eventTitle + `) ) {
processApplicationEvent(Event` + eventTitle + `, NULL);
}
}
`)
}
case "common":
eventType := "ApplicationEventType"
if strings.HasPrefix(event, "Window") {
eventType = "WindowEventType"
}
if strings.HasPrefix(event, "WebView") {
eventType = "WindowEventType"
}
commonEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
commonEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
commonTSEvents.WriteString("\t\t" + event + ": \"common:" + event + "\",\n")
eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"common:" + event + "\",\n")
case "windows":
eventType := "ApplicationEventType"
if strings.HasPrefix(event, "Window") {
eventType = "WindowEventType"
}
if strings.HasPrefix(event, "WebView") {
eventType = "WindowEventType"
}
windowsEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
windowsEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
windowsTSEvents.WriteString("\t\t" + event + ": \"windows:" + event + "\",\n")
eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"windows:" + event + "\",\n")
case "ios":
eventType := "ApplicationEventType"
if strings.HasPrefix(event, "Window") {
eventType = "WindowEventType"
}
if strings.HasPrefix(event, "WebView") {
eventType = "WindowEventType"
}
iosEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
iosEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
iosTSEvents.WriteString("\t\t" + event + ": \"ios:" + event + "\",\n")
iosCHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n")
eventToJS.WriteString("\t" + strconv.Itoa(id) + ": \"ios:" + event + "\",\n")
maxIOSEvents = id
// Note: iOS Window and Touch events are not auto-generated as delegate methods
// because they need to be integrated into existing UIViewController lifecycle methods
// and touch handling code. These events should be manually triggered where appropriate.
// Check if this is a webview navigation event
if strings.HasPrefix(event, "WebView") {
// Convert to WKNavigationDelegate method format
webViewMethod := strings.TrimPrefix(event, "WebView")
webViewMethod = string(bytes.ToLower([]byte{webViewMethod[0]})) + webViewMethod[1:]
// Map to actual WKNavigationDelegate methods
var delegateMethod string
switch webViewMethod {
case "didStartNavigation":
delegateMethod = "didStartProvisionalNavigation"
case "didFinishNavigation":
delegateMethod = "didFinishNavigation"
case "didFailNavigation":
delegateMethod = "didFailProvisionalNavigation"
case "decidePolicyForNavigationAction":
// This needs special handling with decisionHandler
iosWebviewDelegateEvents.WriteString(`- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if( hasListeners(Event` + eventTitle + `) ) {
processWindowEvent(self.windowID, Event` + eventTitle + `);
}
decisionHandler(WKNavigationActionPolicyAllow);
}
`)
// Skip the default delegate method generation below
goto skipDefaultWebViewDelegate
default:
delegateMethod = webViewMethod
}
iosWebviewDelegateEvents.WriteString(`- (void)webView:(WKWebView *)webView ` + delegateMethod + `:(WKNavigation *)navigation {
if( hasListeners(Event` + eventTitle + `) ) {
processWindowEvent(self.windowID, Event` + eventTitle + `);
}
}
`)
skipDefaultWebViewDelegate:
}
// Check if this is an application event
if strings.HasPrefix(event, "Application") {
// Convert to UIApplicationDelegate method format
// e.g. "ApplicationDidBecomeActive" -> "applicationDidBecomeActive"
methodName := "application" + strings.TrimPrefix(event, "Application")
methodName = string(bytes.ToLower([]byte{methodName[0]})) + methodName[1:]
iosApplicationDelegateEvents.WriteString(`- (void)` + methodName + `:(UIApplication *)application {
if( hasListeners(Event` + eventTitle + `) ) {
processApplicationEvent(Event` + eventTitle + `, NULL);
}
}
`)
}
}
}
macCHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(maxMacEvents+1) + "\n")
linuxCHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(maxLinuxEvents+1) + "\n")
iosCHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(maxIOSEvents+1) + "\n")
// Save the eventsGo template substituting the values and decls
templateToWrite := strings.ReplaceAll(eventsGo, "$$LINUXEVENTSDECL", linuxEventsDecl.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$LINUXEVENTSVALUES", linuxEventsValues.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$MACEVENTSDECL", macEventsDecl.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$MACEVENTSVALUES", macEventsValues.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSDECL", windowsEventsDecl.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSVALUES", windowsEventsValues.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$IOSEVENTSDECL", iosEventsDecl.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$IOSEVENTSVALUES", iosEventsValues.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONEVENTSDECL", commonEventsDecl.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONEVENTSVALUES", commonEventsValues.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$EVENTTOJS", eventToJS.String())
err = os.WriteFile("../../pkg/events/events.go", []byte(templateToWrite), 0644)
if err != nil {
panic(err)
}
// Save the eventsTS template substituting the values and decls
templateToWrite = strings.ReplaceAll(eventsTS, "$$MACJSEVENTS", macTSEvents.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSJSEVENTS", windowsTSEvents.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$LINUXJSEVENTS", linuxTSEvents.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$IOSJSEVENTS", iosTSEvents.String())
templateToWrite = strings.ReplaceAll(templateToWrite, "$$COMMONJSEVENTS", commonTSEvents.String())
err = os.WriteFile("../../internal/runtime/desktop/@wailsio/runtime/src/event_types.ts", []byte(templateToWrite), 0644)
if err != nil {
panic(err)
}
// Save the darwinEventsH template substituting the values and decls
templateToWrite = strings.ReplaceAll(darwinEventsH, "$$CHEADEREVENTS", macCHeaderEvents.String())
err = os.WriteFile("../../pkg/events/events_darwin.h", []byte(templateToWrite), 0644)
if err != nil {
panic(err)
}
// Save the linuxEventsH template substituting the values and decls
templateToWrite = strings.ReplaceAll(linuxEventsH, "$$CHEADEREVENTS", linuxCHeaderEvents.String())
err = os.WriteFile("../../pkg/events/events_linux.h", []byte(templateToWrite), 0644)
if err != nil {
panic(err)
}
// Save the iosEventsH template substituting the values and decls
templateToWrite = strings.ReplaceAll(iosEventsH, "$$CHEADEREVENTS", iosCHeaderEvents.String())
err = os.WriteFile("../../pkg/events/events_ios.h", []byte(templateToWrite), 0644)
if err != nil {
panic(err)
}
// Declare buffer and flag for processing delegate files
var buffer bytes.Buffer
var inGeneratedEvents bool
// Load the iOS app_delegate.m file
iosAppDelegate, err := os.ReadFile("../../pkg/application/application_ios_delegate.m")
if err != nil {
panic(err)
}
// iterate over the lines until we reach a line that says "// GENERATED EVENTS START"
// then we insert the events
// then we iterate until we reach a line that says "// GENERATED EVENTS END"
// then we write the file
buffer.Reset()
inGeneratedEvents = false
for _, line := range bytes.Split(iosAppDelegate, []byte{'\n'}) {
if bytes.Contains(line, []byte("// GENERATED EVENTS START")) {
inGeneratedEvents = true
buffer.WriteString("// GENERATED EVENTS START\n")
buffer.WriteString(iosApplicationDelegateEvents.String())
continue
}
if bytes.Contains(line, []byte("// GENERATED EVENTS END")) {
inGeneratedEvents = false
buffer.WriteString("// GENERATED EVENTS END\n")
continue
}
if !inGeneratedEvents {
if len(line) > 0 {
buffer.Write(line)
buffer.WriteString("\n")
}
}
}
err = os.WriteFile("../../pkg/application/application_ios_delegate.m", buffer.Bytes(), 0755)
if err != nil {
panic(err)
}
// Load the iOS webview_window.m file
iosWebviewWindow, err := os.ReadFile("../../pkg/application/webview_window_ios.m")
if err != nil {
panic(err)
}
// iterate over the lines until we reach a line that says "// GENERATED EVENTS START"
// then we insert the events
// then we iterate until we reach a line that says "// GENERATED EVENTS END"
// then we write the file
buffer.Reset()
for _, line := range bytes.Split(iosWebviewWindow, []byte{'\n'}) {
if bytes.Contains(line, []byte("// GENERATED EVENTS START")) {
inGeneratedEvents = true
buffer.WriteString("// GENERATED EVENTS START\n")
// Only write webview delegate events, not view controller lifecycle events
buffer.WriteString(iosWebviewDelegateEvents.String())
continue
}
if bytes.Contains(line, []byte("// GENERATED EVENTS END")) {
inGeneratedEvents = false
buffer.WriteString("// GENERATED EVENTS END\n")
continue
}
if !inGeneratedEvents {
if len(line) > 0 {
buffer.Write(line)
buffer.WriteString("\n")
}
}
}
err = os.WriteFile("../../pkg/application/webview_window_ios.m", buffer.Bytes(), 0755)
if err != nil {
panic(err)
}
// Load the window_delegate.m file
windowDelegate, err := os.ReadFile("../../pkg/application/webview_window_darwin.m")
if err != nil {
panic(err)
}
// iterate over the lines until we reach a line that says "// GENERATED EVENTS START"
// then we insert the events
// then we iterate until we reach a line that says "// GENERATED EVENTS END"
// then we write the file
buffer.Reset()
for _, line := range bytes.Split(windowDelegate, []byte{'\n'}) {
if bytes.Contains(line, []byte("// GENERATED EVENTS START")) {
inGeneratedEvents = true
buffer.WriteString("// GENERATED EVENTS START\n")
buffer.WriteString(windowDelegateEvents.String())
buffer.WriteString(webviewDelegateEvents.String())
continue
}
if bytes.Contains(line, []byte("// GENERATED EVENTS END")) {
inGeneratedEvents = false
buffer.WriteString("// GENERATED EVENTS END\n")
continue
}
if !inGeneratedEvents {
if len(line) > 0 {
buffer.Write(line)
buffer.WriteString("\n")
}
}
}
err = os.WriteFile("../../pkg/application/webview_window_darwin.m", buffer.Bytes(), 0755)
if err != nil {
panic(err)
}
// Load the app_delegate.m file
appDelegate, err := os.ReadFile("../../pkg/application/application_darwin_delegate.m")
if err != nil {
panic(err)
}
// iterate over the lines until we reach a line that says "// GENERATED EVENTS START"
// then we insert the events
// then we iterate until we reach a line that says "// GENERATED EVENTS END"
// then we write the file
buffer.Reset()
for _, line := range bytes.Split(appDelegate, []byte{'\n'}) {
if bytes.Contains(line, []byte("// GENERATED EVENTS START")) {
inGeneratedEvents = true
buffer.WriteString("// GENERATED EVENTS START\n")
buffer.WriteString(applicationDelegateEvents.String())
continue
}
if bytes.Contains(line, []byte("// GENERATED EVENTS END")) {
inGeneratedEvents = false
buffer.WriteString("// GENERATED EVENTS END\n")
continue
}
if !inGeneratedEvents {
if len(line) > 0 {
buffer.Write(line)
buffer.WriteString("\n")
}
}
}
err = os.WriteFile("../../pkg/application/application_darwin_delegate.m", buffer.Bytes(), 0755)
if err != nil {
panic(err)
}
}