github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/darwin/frontend.go (about)

     1  //go:build darwin
     2  // +build darwin
     3  
     4  package darwin
     5  
     6  /*
     7  #cgo CFLAGS: -x objective-c
     8  #cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
     9  #import <Foundation/Foundation.h>
    10  #import "Application.h"
    11  #import "CustomProtocol.h"
    12  #import "WailsContext.h"
    13  
    14  #include <stdlib.h>
    15  */
    16  import "C"
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"html/template"
    23  	"log"
    24  	"net"
    25  	"net/url"
    26  	"unsafe"
    27  
    28  	"github.com/secoba/wails/v2/pkg/assetserver"
    29  	"github.com/secoba/wails/v2/pkg/assetserver/webview"
    30  
    31  	"github.com/secoba/wails/v2/internal/binding"
    32  	"github.com/secoba/wails/v2/internal/frontend"
    33  	"github.com/secoba/wails/v2/internal/frontend/runtime"
    34  	"github.com/secoba/wails/v2/internal/logger"
    35  	"github.com/secoba/wails/v2/pkg/options"
    36  )
    37  
    38  const startURL = "wails://wails/"
    39  
    40  var (
    41  	messageBuffer        = make(chan string, 100)
    42  	requestBuffer        = make(chan webview.Request, 100)
    43  	callbackBuffer       = make(chan uint, 10)
    44  	openFilepathBuffer   = make(chan string, 100)
    45  	openUrlBuffer        = make(chan string, 100)
    46  	secondInstanceBuffer = make(chan options.SecondInstanceData, 1)
    47  )
    48  
    49  type Frontend struct {
    50  	// Context
    51  	ctx context.Context
    52  
    53  	frontendOptions *options.App
    54  	logger          *logger.Logger
    55  	debug           bool
    56  	devtoolsEnabled bool
    57  
    58  	// Assets
    59  	assets   *assetserver.AssetServer
    60  	startURL *url.URL
    61  
    62  	// main window handle
    63  	mainWindow *Window
    64  	bindings   *binding.Bindings
    65  	dispatcher frontend.Dispatcher
    66  }
    67  
    68  func (f *Frontend) RunMainLoop() {
    69  	C.RunMainLoop()
    70  }
    71  
    72  func (f *Frontend) WindowClose() {
    73  	C.ReleaseContext(f.mainWindow.context)
    74  }
    75  
    76  func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
    77  	result := &Frontend{
    78  		frontendOptions: appoptions,
    79  		logger:          myLogger,
    80  		bindings:        appBindings,
    81  		dispatcher:      dispatcher,
    82  		ctx:             ctx,
    83  	}
    84  	result.startURL, _ = url.Parse(startURL)
    85  
    86  	// this should be initialized as early as possible to handle first instance launch
    87  	C.StartCustomProtocolHandler()
    88  
    89  	if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
    90  		result.startURL = _starturl
    91  	} else {
    92  		if port, _ := ctx.Value("assetserverport").(string); port != "" {
    93  			result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port)
    94  		}
    95  
    96  		var bindings string
    97  		var err error
    98  		if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
    99  			bindings, err = appBindings.ToJSON()
   100  			if err != nil {
   101  				log.Fatal(err)
   102  			}
   103  		} else {
   104  			appBindings.DB().UpdateObfuscatedCallMap()
   105  		}
   106  
   107  		assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, runtime.RuntimeAssetsBundle)
   108  		if err != nil {
   109  			log.Fatal(err)
   110  		}
   111  		assets.ExpectedWebViewHost = result.startURL.Host
   112  		result.assets = assets
   113  
   114  		go result.startRequestProcessor()
   115  	}
   116  
   117  	go result.startMessageProcessor()
   118  	go result.startCallbackProcessor()
   119  	go result.startFileOpenProcessor()
   120  	go result.startUrlOpenProcessor()
   121  	go result.startSecondInstanceProcessor()
   122  
   123  	return result
   124  }
   125  
   126  func (f *Frontend) startFileOpenProcessor() {
   127  	for filePath := range openFilepathBuffer {
   128  		f.ProcessOpenFileEvent(filePath)
   129  	}
   130  }
   131  
   132  func (f *Frontend) startUrlOpenProcessor() {
   133  	for url := range openUrlBuffer {
   134  		f.ProcessOpenUrlEvent(url)
   135  	}
   136  }
   137  
   138  func (f *Frontend) startSecondInstanceProcessor() {
   139  	for secondInstanceData := range secondInstanceBuffer {
   140  		if f.frontendOptions.SingleInstanceLock != nil &&
   141  			f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil {
   142  			f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData)
   143  		}
   144  	}
   145  }
   146  
   147  func (f *Frontend) startMessageProcessor() {
   148  	for message := range messageBuffer {
   149  		f.processMessage(message)
   150  	}
   151  }
   152  
   153  func (f *Frontend) startRequestProcessor() {
   154  	for request := range requestBuffer {
   155  		f.assets.ServeWebViewRequest(request)
   156  	}
   157  }
   158  
   159  func (f *Frontend) startCallbackProcessor() {
   160  	for callback := range callbackBuffer {
   161  		err := f.handleCallback(callback)
   162  		if err != nil {
   163  			println(err.Error())
   164  		}
   165  	}
   166  }
   167  
   168  func (f *Frontend) WindowReload() {
   169  	f.ExecJS("runtime.WindowReload();")
   170  }
   171  
   172  func (f *Frontend) WindowReloadApp() {
   173  	f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
   174  }
   175  
   176  func (f *Frontend) WindowSetSystemDefaultTheme() {
   177  }
   178  
   179  func (f *Frontend) WindowSetLightTheme() {
   180  }
   181  
   182  func (f *Frontend) WindowSetDarkTheme() {
   183  }
   184  
   185  func (f *Frontend) Run(ctx context.Context) error {
   186  	f.ctx = ctx
   187  
   188  	if f.frontendOptions.SingleInstanceLock != nil {
   189  		SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId)
   190  	}
   191  
   192  	_debug := ctx.Value("debug")
   193  	_devtoolsEnabled := ctx.Value("devtoolsEnabled")
   194  
   195  	if _debug != nil {
   196  		f.debug = _debug.(bool)
   197  	}
   198  	if _devtoolsEnabled != nil {
   199  		f.devtoolsEnabled = _devtoolsEnabled.(bool)
   200  	}
   201  
   202  	mainWindow := NewWindow(f.frontendOptions, f.debug, f.devtoolsEnabled)
   203  	f.mainWindow = mainWindow
   204  	f.mainWindow.Center()
   205  
   206  	go func() {
   207  		if f.frontendOptions.OnStartup != nil {
   208  			f.frontendOptions.OnStartup(f.ctx)
   209  		}
   210  	}()
   211  	mainWindow.Run(f.startURL.String())
   212  	return nil
   213  }
   214  
   215  func (f *Frontend) WindowCenter() {
   216  	f.mainWindow.Center()
   217  }
   218  
   219  func (f *Frontend) WindowSetAlwaysOnTop(onTop bool) {
   220  	f.mainWindow.SetAlwaysOnTop(onTop)
   221  }
   222  
   223  func (f *Frontend) WindowSetPosition(x, y int) {
   224  	f.mainWindow.SetPosition(x, y)
   225  }
   226  
   227  func (f *Frontend) WindowGetPosition() (int, int) {
   228  	return f.mainWindow.GetPosition()
   229  }
   230  
   231  func (f *Frontend) WindowSetSize(width, height int) {
   232  	f.mainWindow.SetSize(width, height)
   233  }
   234  
   235  func (f *Frontend) WindowGetSize() (int, int) {
   236  	return f.mainWindow.Size()
   237  }
   238  
   239  func (f *Frontend) WindowSetTitle(title string) {
   240  	f.mainWindow.SetTitle(title)
   241  }
   242  
   243  func (f *Frontend) WindowFullscreen() {
   244  	f.mainWindow.Fullscreen()
   245  }
   246  
   247  func (f *Frontend) WindowUnfullscreen() {
   248  	f.mainWindow.UnFullscreen()
   249  }
   250  
   251  func (f *Frontend) WindowShow() {
   252  	f.mainWindow.Show()
   253  }
   254  
   255  func (f *Frontend) WindowHide() {
   256  	f.mainWindow.Hide()
   257  }
   258  
   259  func (f *Frontend) Show() {
   260  	f.mainWindow.ShowApplication()
   261  }
   262  
   263  func (f *Frontend) Hide() {
   264  	f.mainWindow.HideApplication()
   265  }
   266  
   267  func (f *Frontend) WindowMaximise() {
   268  	f.mainWindow.Maximise()
   269  }
   270  
   271  func (f *Frontend) WindowToggleMaximise() {
   272  	f.mainWindow.ToggleMaximise()
   273  }
   274  
   275  func (f *Frontend) WindowUnmaximise() {
   276  	f.mainWindow.UnMaximise()
   277  }
   278  
   279  func (f *Frontend) WindowMinimise() {
   280  	f.mainWindow.Minimise()
   281  }
   282  
   283  func (f *Frontend) WindowUnminimise() {
   284  	f.mainWindow.UnMinimise()
   285  }
   286  
   287  func (f *Frontend) WindowSetMinSize(width int, height int) {
   288  	f.mainWindow.SetMinSize(width, height)
   289  }
   290  
   291  func (f *Frontend) WindowSetMaxSize(width int, height int) {
   292  	f.mainWindow.SetMaxSize(width, height)
   293  }
   294  
   295  func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
   296  	if col == nil {
   297  		return
   298  	}
   299  	f.mainWindow.SetBackgroundColour(col.R, col.G, col.B, col.A)
   300  }
   301  
   302  func (f *Frontend) ScreenGetAll() ([]frontend.Screen, error) {
   303  	return GetAllScreens(f.mainWindow.context)
   304  }
   305  
   306  func (f *Frontend) WindowIsMaximised() bool {
   307  	return f.mainWindow.IsMaximised()
   308  }
   309  
   310  func (f *Frontend) WindowIsMinimised() bool {
   311  	return f.mainWindow.IsMinimised()
   312  }
   313  
   314  func (f *Frontend) WindowIsNormal() bool {
   315  	return f.mainWindow.IsNormal()
   316  }
   317  
   318  func (f *Frontend) WindowIsFullscreen() bool {
   319  	return f.mainWindow.IsFullScreen()
   320  }
   321  
   322  func (f *Frontend) Quit() {
   323  	if f.frontendOptions.OnBeforeClose != nil {
   324  		go func() {
   325  			if !f.frontendOptions.OnBeforeClose(f.ctx) {
   326  				f.mainWindow.Quit()
   327  			}
   328  		}()
   329  		return
   330  	}
   331  	f.mainWindow.Quit()
   332  }
   333  
   334  func (f *Frontend) WindowPrint() {
   335  	f.mainWindow.Print()
   336  }
   337  
   338  type EventNotify struct {
   339  	Name string        `json:"name"`
   340  	Data []interface{} `json:"data"`
   341  }
   342  
   343  func (f *Frontend) Notify(name string, data ...interface{}) {
   344  	notification := EventNotify{
   345  		Name: name,
   346  		Data: data,
   347  	}
   348  	payload, err := json.Marshal(notification)
   349  	if err != nil {
   350  		f.logger.Error(err.Error())
   351  		return
   352  	}
   353  	f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
   354  }
   355  
   356  func (f *Frontend) processMessage(message string) {
   357  	if message == "DomReady" {
   358  		if f.frontendOptions.OnDomReady != nil {
   359  			f.frontendOptions.OnDomReady(f.ctx)
   360  		}
   361  		return
   362  	}
   363  
   364  	if message == "runtime:ready" {
   365  		cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue)
   366  		f.ExecJS(cmd)
   367  		return
   368  	}
   369  
   370  	if message == "wails:openInspector" {
   371  		showInspector(f.mainWindow.context)
   372  		return
   373  	}
   374  
   375  	//if strings.HasPrefix(message, "systemevent:") {
   376  	//	f.processSystemEvent(message)
   377  	//	return
   378  	//}
   379  
   380  	go func() {
   381  		result, err := f.dispatcher.ProcessMessage(message, f)
   382  		if err != nil {
   383  			f.logger.Error(err.Error())
   384  			f.Callback(result)
   385  			return
   386  		}
   387  		if result == "" {
   388  			return
   389  		}
   390  
   391  		switch result[0] {
   392  		case 'c':
   393  			// Callback from a method call
   394  			f.Callback(result[1:])
   395  		default:
   396  			f.logger.Info("Unknown message returned from dispatcher: %+v", result)
   397  		}
   398  	}()
   399  }
   400  
   401  func (f *Frontend) ProcessOpenFileEvent(filePath string) {
   402  	if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnFileOpen != nil {
   403  		f.frontendOptions.Mac.OnFileOpen(filePath)
   404  	}
   405  }
   406  
   407  func (f *Frontend) ProcessOpenUrlEvent(url string) {
   408  	if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnUrlOpen != nil {
   409  		f.frontendOptions.Mac.OnUrlOpen(url)
   410  	}
   411  }
   412  
   413  func (f *Frontend) Callback(message string) {
   414  	escaped, err := json.Marshal(message)
   415  	if err != nil {
   416  		panic(err)
   417  	}
   418  	f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`)
   419  }
   420  
   421  func (f *Frontend) ExecJS(js string) {
   422  	f.mainWindow.ExecJS(js)
   423  }
   424  
   425  //func (f *Frontend) processSystemEvent(message string) {
   426  //	sl := strings.Split(message, ":")
   427  //	if len(sl) != 2 {
   428  //		f.logger.Error("Invalid system message: %s", message)
   429  //		return
   430  //	}
   431  //	switch sl[1] {
   432  //	case "fullscreen":
   433  //		f.mainWindow.DisableSizeConstraints()
   434  //	case "unfullscreen":
   435  //		f.mainWindow.EnableSizeConstraints()
   436  //	default:
   437  //		f.logger.Error("Unknown system message: %s", message)
   438  //	}
   439  //}
   440  
   441  //export processMessage
   442  func processMessage(message *C.char) {
   443  	goMessage := C.GoString(message)
   444  	messageBuffer <- goMessage
   445  }
   446  
   447  //export processCallback
   448  func processCallback(callbackID uint) {
   449  	callbackBuffer <- callbackID
   450  }
   451  
   452  //export processURLRequest
   453  func processURLRequest(_ unsafe.Pointer, wkURLSchemeTask unsafe.Pointer) {
   454  	requestBuffer <- webview.NewRequest(wkURLSchemeTask)
   455  }
   456  
   457  //export HandleOpenFile
   458  func HandleOpenFile(filePath *C.char) {
   459  	goFilepath := C.GoString(filePath)
   460  	openFilepathBuffer <- goFilepath
   461  }
   462  
   463  //export HandleCustomProtocol
   464  func HandleCustomProtocol(url *C.char) {
   465  	goUrl := C.GoString(url)
   466  	openUrlBuffer <- goUrl
   467  }