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 }