mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 05:11:29 +08:00

* Application refactor * [windows] refactor out main loop. Create new application struct. Refactor assethandler/assetserver signatures. * Refactor darwin app * Refactor app for linux * Update v2/internal/frontend/assetserver/assethandler.go Co-authored-by: stffabi <stffabi@users.noreply.github.com> * Update v2/internal/frontend/assetserver/assethandler.go Co-authored-by: stffabi <stffabi@users.noreply.github.com> * Update v2/internal/frontend/assetserver/assetserver.go Co-authored-by: stffabi <stffabi@users.noreply.github.com> * Update v2/internal/frontend/assetserver/assetserver.go Co-authored-by: stffabi <stffabi@users.noreply.github.com> Co-authored-by: stffabi <stffabi@users.noreply.github.com>
470 lines
9.9 KiB
Go
470 lines
9.9 KiB
Go
//go:build darwin
|
|
// +build darwin
|
|
|
|
package darwin
|
|
|
|
/*
|
|
#cgo CFLAGS: -x objective-c
|
|
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
|
#import <Foundation/Foundation.h>
|
|
#import "Application.h"
|
|
#import "WailsContext.h"
|
|
|
|
#include <stdlib.h>
|
|
*/
|
|
import "C"
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strconv"
|
|
"unsafe"
|
|
|
|
"github.com/wailsapp/wails/v2/internal/binding"
|
|
"github.com/wailsapp/wails/v2/internal/frontend"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
|
|
"github.com/wailsapp/wails/v2/internal/logger"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
)
|
|
|
|
const startURL = "wails://wails/"
|
|
|
|
var messageBuffer = make(chan string, 100)
|
|
var requestBuffer = make(chan *request, 100)
|
|
var callbackBuffer = make(chan uint, 10)
|
|
|
|
type Frontend struct {
|
|
|
|
// Context
|
|
ctx context.Context
|
|
|
|
frontendOptions *options.App
|
|
logger *logger.Logger
|
|
debug bool
|
|
|
|
// Assets
|
|
assets *assetserver.AssetServer
|
|
startURL *url.URL
|
|
|
|
// main window handle
|
|
mainWindow *Window
|
|
bindings *binding.Bindings
|
|
dispatcher frontend.Dispatcher
|
|
}
|
|
|
|
func (f *Frontend) RunMainLoop() {
|
|
C.RunMainLoop()
|
|
}
|
|
|
|
func (f *Frontend) WindowClose() {
|
|
C.ReleaseContext(f.mainWindow.context)
|
|
}
|
|
|
|
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
|
|
result := &Frontend{
|
|
frontendOptions: appoptions,
|
|
logger: myLogger,
|
|
bindings: appBindings,
|
|
dispatcher: dispatcher,
|
|
ctx: ctx,
|
|
}
|
|
result.startURL, _ = url.Parse(startURL)
|
|
|
|
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
|
|
result.startURL = _starturl
|
|
} else {
|
|
var bindings string
|
|
var err error
|
|
if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
|
|
bindings, err = appBindings.ToJSON()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
appBindings.DB().UpdateObfuscatedCallMap()
|
|
}
|
|
assets, err := assetserver.NewAssetServer(ctx, appoptions.Assets, appoptions.AssetsHandler, bindings)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
result.assets = assets
|
|
|
|
go result.startRequestProcessor()
|
|
}
|
|
|
|
go result.startMessageProcessor()
|
|
go result.startCallbackProcessor()
|
|
|
|
return result
|
|
}
|
|
|
|
func (f *Frontend) startMessageProcessor() {
|
|
for message := range messageBuffer {
|
|
f.processMessage(message)
|
|
}
|
|
}
|
|
func (f *Frontend) startRequestProcessor() {
|
|
for request := range requestBuffer {
|
|
f.processRequest(request)
|
|
}
|
|
}
|
|
func (f *Frontend) startCallbackProcessor() {
|
|
for callback := range callbackBuffer {
|
|
err := f.handleCallback(callback)
|
|
if err != nil {
|
|
println(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *Frontend) WindowReload() {
|
|
f.ExecJS("runtime.WindowReload();")
|
|
}
|
|
|
|
func (f *Frontend) WindowReloadApp() {
|
|
f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
|
|
}
|
|
|
|
func (f *Frontend) WindowSetSystemDefaultTheme() {
|
|
return
|
|
}
|
|
|
|
func (f *Frontend) WindowSetLightTheme() {
|
|
return
|
|
}
|
|
|
|
func (f *Frontend) WindowSetDarkTheme() {
|
|
return
|
|
}
|
|
|
|
func (f *Frontend) Run(ctx context.Context) error {
|
|
|
|
f.ctx = context.WithValue(ctx, "frontend", f)
|
|
|
|
var _debug = ctx.Value("debug")
|
|
if _debug != nil {
|
|
f.debug = _debug.(bool)
|
|
}
|
|
|
|
mainWindow := NewWindow(f.frontendOptions, f.debug)
|
|
f.mainWindow = mainWindow
|
|
f.mainWindow.Center()
|
|
|
|
go func() {
|
|
if f.frontendOptions.OnStartup != nil {
|
|
f.frontendOptions.OnStartup(f.ctx)
|
|
}
|
|
}()
|
|
mainWindow.Run(f.startURL.String())
|
|
return nil
|
|
}
|
|
|
|
func (f *Frontend) WindowCenter() {
|
|
f.mainWindow.Center()
|
|
}
|
|
func (f *Frontend) WindowSetAlwaysOnTop(onTop bool) {
|
|
f.mainWindow.SetAlwaysOnTop(onTop)
|
|
}
|
|
|
|
func (f *Frontend) WindowSetPosition(x, y int) {
|
|
f.mainWindow.SetPosition(x, y)
|
|
}
|
|
func (f *Frontend) WindowGetPosition() (int, int) {
|
|
return f.mainWindow.GetPosition()
|
|
}
|
|
|
|
func (f *Frontend) WindowSetSize(width, height int) {
|
|
f.mainWindow.SetSize(width, height)
|
|
}
|
|
|
|
func (f *Frontend) WindowGetSize() (int, int) {
|
|
return f.mainWindow.Size()
|
|
}
|
|
|
|
func (f *Frontend) WindowSetTitle(title string) {
|
|
f.mainWindow.SetTitle(title)
|
|
}
|
|
|
|
func (f *Frontend) WindowFullscreen() {
|
|
f.mainWindow.Fullscreen()
|
|
}
|
|
|
|
func (f *Frontend) WindowUnfullscreen() {
|
|
f.mainWindow.UnFullscreen()
|
|
}
|
|
|
|
func (f *Frontend) WindowShow() {
|
|
f.mainWindow.Show()
|
|
}
|
|
|
|
func (f *Frontend) WindowHide() {
|
|
f.mainWindow.Hide()
|
|
}
|
|
func (f *Frontend) Show() {
|
|
f.mainWindow.ShowApplication()
|
|
}
|
|
|
|
func (f *Frontend) Hide() {
|
|
f.mainWindow.HideApplication()
|
|
}
|
|
func (f *Frontend) WindowMaximise() {
|
|
f.mainWindow.Maximise()
|
|
}
|
|
func (f *Frontend) WindowToggleMaximise() {
|
|
f.mainWindow.ToggleMaximise()
|
|
}
|
|
func (f *Frontend) WindowUnmaximise() {
|
|
f.mainWindow.UnMaximise()
|
|
}
|
|
func (f *Frontend) WindowMinimise() {
|
|
f.mainWindow.Minimise()
|
|
}
|
|
func (f *Frontend) WindowUnminimise() {
|
|
f.mainWindow.UnMinimise()
|
|
}
|
|
|
|
func (f *Frontend) WindowSetMinSize(width int, height int) {
|
|
f.mainWindow.SetMinSize(width, height)
|
|
}
|
|
func (f *Frontend) WindowSetMaxSize(width int, height int) {
|
|
f.mainWindow.SetMaxSize(width, height)
|
|
}
|
|
|
|
func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
|
|
if col == nil {
|
|
return
|
|
}
|
|
f.mainWindow.SetBackgroundColour(col.R, col.G, col.B, col.A)
|
|
}
|
|
|
|
func (f *Frontend) ScreenGetAll() ([]frontend.Screen, error) {
|
|
return GetAllScreens(f.mainWindow.context)
|
|
}
|
|
|
|
func (f *Frontend) WindowIsMaximised() bool {
|
|
return f.mainWindow.IsMaximised()
|
|
}
|
|
|
|
func (f *Frontend) WindowIsMinimised() bool {
|
|
return f.mainWindow.IsMinimised()
|
|
}
|
|
|
|
func (f *Frontend) WindowIsNormal() bool {
|
|
return f.mainWindow.IsNormal()
|
|
}
|
|
|
|
func (f *Frontend) WindowIsFullscreen() bool {
|
|
return f.mainWindow.IsFullScreen()
|
|
}
|
|
|
|
func (f *Frontend) Quit() {
|
|
if f.frontendOptions.OnBeforeClose != nil {
|
|
go func() {
|
|
if !f.frontendOptions.OnBeforeClose(f.ctx) {
|
|
f.mainWindow.Quit()
|
|
}
|
|
}()
|
|
return
|
|
}
|
|
f.mainWindow.Quit()
|
|
}
|
|
|
|
type EventNotify struct {
|
|
Name string `json:"name"`
|
|
Data []interface{} `json:"data"`
|
|
}
|
|
|
|
func (f *Frontend) Notify(name string, data ...interface{}) {
|
|
notification := EventNotify{
|
|
Name: name,
|
|
Data: data,
|
|
}
|
|
payload, err := json.Marshal(notification)
|
|
if err != nil {
|
|
f.logger.Error(err.Error())
|
|
return
|
|
}
|
|
f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
|
|
}
|
|
|
|
func (f *Frontend) processMessage(message string) {
|
|
|
|
if message == "DomReady" {
|
|
if f.frontendOptions.OnDomReady != nil {
|
|
f.frontendOptions.OnDomReady(f.ctx)
|
|
}
|
|
return
|
|
}
|
|
|
|
if message == "runtime:ready" {
|
|
cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue)
|
|
f.ExecJS(cmd)
|
|
return
|
|
}
|
|
|
|
//if strings.HasPrefix(message, "systemevent:") {
|
|
// f.processSystemEvent(message)
|
|
// return
|
|
//}
|
|
|
|
go func() {
|
|
result, err := f.dispatcher.ProcessMessage(message, f)
|
|
if err != nil {
|
|
f.logger.Error(err.Error())
|
|
f.Callback(result)
|
|
return
|
|
}
|
|
if result == "" {
|
|
return
|
|
}
|
|
|
|
switch result[0] {
|
|
case 'c':
|
|
// Callback from a method call
|
|
f.Callback(result[1:])
|
|
default:
|
|
f.logger.Info("Unknown message returned from dispatcher: %+v", result)
|
|
}
|
|
}()
|
|
|
|
}
|
|
|
|
func (f *Frontend) Callback(message string) {
|
|
f.ExecJS(`window.wails.Callback(` + strconv.Quote(message) + `);`)
|
|
}
|
|
|
|
func (f *Frontend) ExecJS(js string) {
|
|
f.mainWindow.ExecJS(js)
|
|
}
|
|
|
|
func (f *Frontend) processRequest(r *request) {
|
|
rw := httptest.NewRecorder()
|
|
f.assets.ProcessHTTPRequest(
|
|
r.url,
|
|
rw,
|
|
func() (*http.Request, error) {
|
|
req, err := r.GetHttpRequest()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if req.URL.Host != f.startURL.Host {
|
|
if req.Body != nil {
|
|
req.Body.Close()
|
|
}
|
|
|
|
return nil, fmt.Errorf("Expected host '%s' in request, but was '%s'", f.startURL.Host, req.URL.Host)
|
|
}
|
|
return req, nil
|
|
},
|
|
)
|
|
|
|
header := map[string]string{}
|
|
for k := range rw.Header() {
|
|
header[k] = rw.Header().Get(k)
|
|
}
|
|
headerData, _ := json.Marshal(header)
|
|
|
|
var content unsafe.Pointer
|
|
var contentLen int
|
|
if _contents := rw.Body.Bytes(); _contents != nil {
|
|
content = unsafe.Pointer(&_contents[0])
|
|
contentLen = len(_contents)
|
|
}
|
|
|
|
var headers unsafe.Pointer
|
|
var headersLen int
|
|
if len(headerData) != 0 {
|
|
headers = unsafe.Pointer(&headerData[0])
|
|
headersLen = len(headerData)
|
|
}
|
|
|
|
C.ProcessURLResponse(r.ctx, r.id, C.int(rw.Code), headers, C.int(headersLen), content, C.int(contentLen))
|
|
}
|
|
|
|
//func (f *Frontend) processSystemEvent(message string) {
|
|
// sl := strings.Split(message, ":")
|
|
// if len(sl) != 2 {
|
|
// f.logger.Error("Invalid system message: %s", message)
|
|
// return
|
|
// }
|
|
// switch sl[1] {
|
|
// case "fullscreen":
|
|
// f.mainWindow.DisableSizeConstraints()
|
|
// case "unfullscreen":
|
|
// f.mainWindow.EnableSizeConstraints()
|
|
// default:
|
|
// f.logger.Error("Unknown system message: %s", message)
|
|
// }
|
|
//}
|
|
|
|
type request struct {
|
|
id C.ulonglong
|
|
url string
|
|
method string
|
|
headers string
|
|
body []byte
|
|
|
|
ctx unsafe.Pointer
|
|
}
|
|
|
|
func (r *request) GetHttpRequest() (*http.Request, error) {
|
|
var body io.Reader
|
|
if len(r.body) != 0 {
|
|
body = bytes.NewReader(r.body)
|
|
}
|
|
|
|
req, err := http.NewRequest(r.method, r.url, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if r.headers != "" {
|
|
var h map[string]string
|
|
if err := json.Unmarshal([]byte(r.headers), &h); err != nil {
|
|
return nil, fmt.Errorf("Unable to unmarshal request headers: %s", err)
|
|
}
|
|
|
|
for k, v := range h {
|
|
req.Header.Add(k, v)
|
|
}
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
//export processMessage
|
|
func processMessage(message *C.char) {
|
|
goMessage := C.GoString(message)
|
|
messageBuffer <- goMessage
|
|
}
|
|
|
|
//export processURLRequest
|
|
func processURLRequest(ctx unsafe.Pointer, requestId C.ulonglong, url *C.char, method *C.char, headers *C.char, body unsafe.Pointer, bodyLen C.int) {
|
|
var goBody []byte
|
|
if body != nil && bodyLen != 0 {
|
|
goBody = C.GoBytes(body, bodyLen)
|
|
}
|
|
|
|
requestBuffer <- &request{
|
|
id: requestId,
|
|
url: C.GoString(url),
|
|
method: C.GoString(method),
|
|
headers: C.GoString(headers),
|
|
body: goBody,
|
|
ctx: ctx,
|
|
}
|
|
}
|
|
|
|
//export processCallback
|
|
func processCallback(callbackID uint) {
|
|
callbackBuffer <- callbackID
|
|
}
|