github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/linux/frontend.go (about) 1 //go:build linux 2 // +build linux 3 4 package linux 5 6 /* 7 #cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 8 9 #include "gtk/gtk.h" 10 #include "webkit2/webkit2.h" 11 12 // CREDIT: https://github.com/rainycape/magick 13 #include <errno.h> 14 #include <signal.h> 15 #include <stdio.h> 16 #include <string.h> 17 18 static void fix_signal(int signum) 19 { 20 struct sigaction st; 21 22 if (sigaction(signum, NULL, &st) < 0) { 23 goto fix_signal_error; 24 } 25 st.sa_flags |= SA_ONSTACK; 26 if (sigaction(signum, &st, NULL) < 0) { 27 goto fix_signal_error; 28 } 29 return; 30 fix_signal_error: 31 fprintf(stderr, "error fixing handler for signal %d, please " 32 "report this issue to " 33 "https://github.com/wailsapp/wails: %s\n", 34 signum, strerror(errno)); 35 } 36 37 static void install_signal_handlers() 38 { 39 #if defined(SIGCHLD) 40 fix_signal(SIGCHLD); 41 #endif 42 #if defined(SIGHUP) 43 fix_signal(SIGHUP); 44 #endif 45 #if defined(SIGINT) 46 fix_signal(SIGINT); 47 #endif 48 #if defined(SIGQUIT) 49 fix_signal(SIGQUIT); 50 #endif 51 #if defined(SIGABRT) 52 fix_signal(SIGABRT); 53 #endif 54 #if defined(SIGFPE) 55 fix_signal(SIGFPE); 56 #endif 57 #if defined(SIGTERM) 58 fix_signal(SIGTERM); 59 #endif 60 #if defined(SIGBUS) 61 fix_signal(SIGBUS); 62 #endif 63 #if defined(SIGSEGV) 64 fix_signal(SIGSEGV); 65 #endif 66 #if defined(SIGXCPU) 67 fix_signal(SIGXCPU); 68 #endif 69 #if defined(SIGXFSZ) 70 fix_signal(SIGXFSZ); 71 #endif 72 } 73 74 */ 75 import "C" 76 import ( 77 "context" 78 "encoding/json" 79 "errors" 80 "fmt" 81 "log" 82 "net" 83 "net/url" 84 "os" 85 "runtime" 86 "strings" 87 "sync" 88 "text/template" 89 "unsafe" 90 91 "github.com/secoba/wails/v2/pkg/assetserver" 92 "github.com/secoba/wails/v2/pkg/assetserver/webview" 93 94 "github.com/secoba/wails/v2/internal/binding" 95 "github.com/secoba/wails/v2/internal/frontend" 96 wailsruntime "github.com/secoba/wails/v2/internal/frontend/runtime" 97 "github.com/secoba/wails/v2/internal/logger" 98 "github.com/secoba/wails/v2/pkg/options" 99 ) 100 101 var initOnce = sync.Once{} 102 103 const startURL = "wails://wails/" 104 105 var secondInstanceBuffer = make(chan options.SecondInstanceData, 1) 106 107 type Frontend struct { 108 109 // Context 110 ctx context.Context 111 112 frontendOptions *options.App 113 logger *logger.Logger 114 debug bool 115 devtoolsEnabled bool 116 117 // Assets 118 assets *assetserver.AssetServer 119 startURL *url.URL 120 121 // main window handle 122 mainWindow *Window 123 bindings *binding.Bindings 124 dispatcher frontend.Dispatcher 125 } 126 127 func (f *Frontend) RunMainLoop() { 128 C.gtk_main() 129 } 130 131 func (f *Frontend) WindowClose() { 132 f.mainWindow.Destroy() 133 } 134 135 func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend { 136 initOnce.Do(func() { 137 runtime.LockOSThread() 138 139 // Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings 140 if os.Getenv("GDK_BACKEND") == "" && (os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "unspecified" || os.Getenv("XDG_SESSION_TYPE") == "x11") { 141 _ = os.Setenv("GDK_BACKEND", "x11") 142 } 143 144 if ok := C.gtk_init_check(nil, nil); ok != 1 { 145 panic(errors.New("failed to init GTK")) 146 } 147 }) 148 149 result := &Frontend{ 150 frontendOptions: appoptions, 151 logger: myLogger, 152 bindings: appBindings, 153 dispatcher: dispatcher, 154 ctx: ctx, 155 } 156 result.startURL, _ = url.Parse(startURL) 157 158 if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil { 159 result.startURL = _starturl 160 } else { 161 if port, _ := ctx.Value("assetserverport").(string); port != "" { 162 result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port) 163 } 164 165 var bindings string 166 var err error 167 if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated { 168 bindings, err = appBindings.ToJSON() 169 if err != nil { 170 log.Fatal(err) 171 } 172 } else { 173 appBindings.DB().UpdateObfuscatedCallMap() 174 } 175 assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle) 176 if err != nil { 177 log.Fatal(err) 178 } 179 result.assets = assets 180 181 go result.startRequestProcessor() 182 } 183 184 go result.startMessageProcessor() 185 186 var _debug = ctx.Value("debug") 187 var _devtoolsEnabled = ctx.Value("devtoolsEnabled") 188 189 if _debug != nil { 190 result.debug = _debug.(bool) 191 } 192 if _devtoolsEnabled != nil { 193 result.devtoolsEnabled = _devtoolsEnabled.(bool) 194 } 195 196 result.mainWindow = NewWindow(appoptions, result.debug, result.devtoolsEnabled) 197 198 C.install_signal_handlers() 199 200 if appoptions.Linux != nil && appoptions.Linux.ProgramName != "" { 201 prgname := C.CString(appoptions.Linux.ProgramName) 202 C.g_set_prgname(prgname) 203 C.free(unsafe.Pointer(prgname)) 204 } 205 206 go result.startSecondInstanceProcessor() 207 208 return result 209 } 210 211 func (f *Frontend) startMessageProcessor() { 212 for message := range messageBuffer { 213 f.processMessage(message) 214 } 215 } 216 217 func (f *Frontend) WindowReload() { 218 f.ExecJS("runtime.WindowReload();") 219 } 220 221 func (f *Frontend) WindowSetSystemDefaultTheme() { 222 return 223 } 224 225 func (f *Frontend) WindowSetLightTheme() { 226 return 227 } 228 229 func (f *Frontend) WindowSetDarkTheme() { 230 return 231 } 232 233 func (f *Frontend) Run(ctx context.Context) error { 234 f.ctx = ctx 235 236 go func() { 237 if f.frontendOptions.OnStartup != nil { 238 f.frontendOptions.OnStartup(f.ctx) 239 } 240 }() 241 242 if f.frontendOptions.SingleInstanceLock != nil { 243 SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId) 244 } 245 246 f.mainWindow.Run(f.startURL.String()) 247 248 return nil 249 } 250 251 func (f *Frontend) WindowCenter() { 252 f.mainWindow.Center() 253 } 254 255 func (f *Frontend) WindowSetAlwaysOnTop(b bool) { 256 f.mainWindow.SetKeepAbove(b) 257 } 258 259 func (f *Frontend) WindowSetPosition(x, y int) { 260 f.mainWindow.SetPosition(x, y) 261 } 262 func (f *Frontend) WindowGetPosition() (int, int) { 263 return f.mainWindow.GetPosition() 264 } 265 266 func (f *Frontend) WindowSetSize(width, height int) { 267 f.mainWindow.SetSize(width, height) 268 } 269 270 func (f *Frontend) WindowGetSize() (int, int) { 271 return f.mainWindow.Size() 272 } 273 274 func (f *Frontend) WindowSetTitle(title string) { 275 f.mainWindow.SetTitle(title) 276 } 277 278 func (f *Frontend) WindowFullscreen() { 279 if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { 280 f.ExecJS("window.wails.flags.enableResize = false;") 281 } 282 f.mainWindow.Fullscreen() 283 } 284 285 func (f *Frontend) WindowUnfullscreen() { 286 if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { 287 f.ExecJS("window.wails.flags.enableResize = true;") 288 } 289 f.mainWindow.UnFullscreen() 290 } 291 292 func (f *Frontend) WindowReloadApp() { 293 f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL)) 294 } 295 296 func (f *Frontend) WindowShow() { 297 f.mainWindow.Show() 298 } 299 300 func (f *Frontend) WindowHide() { 301 f.mainWindow.Hide() 302 } 303 304 func (f *Frontend) Show() { 305 f.mainWindow.Show() 306 } 307 308 func (f *Frontend) Hide() { 309 f.mainWindow.Hide() 310 } 311 func (f *Frontend) WindowMaximise() { 312 f.mainWindow.Maximise() 313 } 314 func (f *Frontend) WindowToggleMaximise() { 315 f.mainWindow.ToggleMaximise() 316 } 317 func (f *Frontend) WindowUnmaximise() { 318 f.mainWindow.UnMaximise() 319 } 320 func (f *Frontend) WindowMinimise() { 321 f.mainWindow.Minimise() 322 } 323 func (f *Frontend) WindowUnminimise() { 324 f.mainWindow.UnMinimise() 325 } 326 327 func (f *Frontend) WindowSetMinSize(width int, height int) { 328 f.mainWindow.SetMinSize(width, height) 329 } 330 func (f *Frontend) WindowSetMaxSize(width int, height int) { 331 f.mainWindow.SetMaxSize(width, height) 332 } 333 334 func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) { 335 if col == nil { 336 return 337 } 338 f.mainWindow.SetBackgroundColour(col.R, col.G, col.B, col.A) 339 } 340 341 func (f *Frontend) ScreenGetAll() ([]Screen, error) { 342 return GetAllScreens(f.mainWindow.asGTKWindow()) 343 } 344 345 func (f *Frontend) WindowIsMaximised() bool { 346 return f.mainWindow.IsMaximised() 347 } 348 349 func (f *Frontend) WindowIsMinimised() bool { 350 return f.mainWindow.IsMinimised() 351 } 352 353 func (f *Frontend) WindowIsNormal() bool { 354 return f.mainWindow.IsNormal() 355 } 356 357 func (f *Frontend) WindowIsFullscreen() bool { 358 return f.mainWindow.IsFullScreen() 359 } 360 361 func (f *Frontend) Quit() { 362 if f.frontendOptions.OnBeforeClose != nil { 363 go func() { 364 if !f.frontendOptions.OnBeforeClose(f.ctx) { 365 f.mainWindow.Quit() 366 } 367 }() 368 return 369 } 370 f.mainWindow.Quit() 371 } 372 373 func (f *Frontend) WindowPrint() { 374 f.ExecJS("window.print();") 375 } 376 377 type EventNotify struct { 378 Name string `json:"name"` 379 Data []interface{} `json:"data"` 380 } 381 382 func (f *Frontend) Notify(name string, data ...interface{}) { 383 notification := EventNotify{ 384 Name: name, 385 Data: data, 386 } 387 payload, err := json.Marshal(notification) 388 if err != nil { 389 f.logger.Error(err.Error()) 390 return 391 } 392 f.mainWindow.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`) 393 } 394 395 var edgeMap = map[string]uintptr{ 396 "n-resize": C.GDK_WINDOW_EDGE_NORTH, 397 "ne-resize": C.GDK_WINDOW_EDGE_NORTH_EAST, 398 "e-resize": C.GDK_WINDOW_EDGE_EAST, 399 "se-resize": C.GDK_WINDOW_EDGE_SOUTH_EAST, 400 "s-resize": C.GDK_WINDOW_EDGE_SOUTH, 401 "sw-resize": C.GDK_WINDOW_EDGE_SOUTH_WEST, 402 "w-resize": C.GDK_WINDOW_EDGE_WEST, 403 "nw-resize": C.GDK_WINDOW_EDGE_NORTH_WEST, 404 } 405 406 func (f *Frontend) processMessage(message string) { 407 if message == "DomReady" { 408 if f.frontendOptions.OnDomReady != nil { 409 f.frontendOptions.OnDomReady(f.ctx) 410 } 411 return 412 } 413 414 if message == "drag" { 415 if !f.mainWindow.IsFullScreen() { 416 f.startDrag() 417 } 418 return 419 } 420 421 if message == "wails:showInspector" { 422 f.mainWindow.ShowInspector() 423 return 424 } 425 426 if strings.HasPrefix(message, "resize:") { 427 if !f.mainWindow.IsFullScreen() { 428 sl := strings.Split(message, ":") 429 if len(sl) != 2 { 430 f.logger.Info("Unknown message returned from dispatcher: %+v", message) 431 return 432 } 433 edge := edgeMap[sl[1]] 434 err := f.startResize(edge) 435 if err != nil { 436 f.logger.Error(err.Error()) 437 } 438 } 439 return 440 } 441 442 if message == "runtime:ready" { 443 cmd := fmt.Sprintf( 444 "window.wails.setCSSDragProperties('%s', '%s');\n"+ 445 "window.wails.flags.deferDragToMouseMove = true;", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue) 446 f.ExecJS(cmd) 447 448 if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { 449 f.ExecJS("window.wails.flags.enableResize = true;") 450 } 451 return 452 } 453 454 go func() { 455 result, err := f.dispatcher.ProcessMessage(message, f) 456 if err != nil { 457 f.logger.Error(err.Error()) 458 f.Callback(result) 459 return 460 } 461 if result == "" { 462 return 463 } 464 465 switch result[0] { 466 case 'c': 467 // Callback from a method call 468 f.Callback(result[1:]) 469 default: 470 f.logger.Info("Unknown message returned from dispatcher: %+v", result) 471 } 472 }() 473 } 474 475 func (f *Frontend) Callback(message string) { 476 escaped, err := json.Marshal(message) 477 if err != nil { 478 panic(err) 479 } 480 f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`) 481 } 482 483 func (f *Frontend) startDrag() { 484 f.mainWindow.StartDrag() 485 } 486 487 func (f *Frontend) startResize(edge uintptr) error { 488 f.mainWindow.StartResize(edge) 489 return nil 490 } 491 492 func (f *Frontend) ExecJS(js string) { 493 f.mainWindow.ExecJS(js) 494 } 495 496 var messageBuffer = make(chan string, 100) 497 498 //export processMessage 499 func processMessage(message *C.char) { 500 goMessage := C.GoString(message) 501 messageBuffer <- goMessage 502 } 503 504 var requestBuffer = make(chan webview.Request, 100) 505 506 func (f *Frontend) startRequestProcessor() { 507 for request := range requestBuffer { 508 f.assets.ServeWebViewRequest(request) 509 } 510 } 511 512 //export processURLRequest 513 func processURLRequest(request unsafe.Pointer) { 514 requestBuffer <- webview.NewRequest(request) 515 } 516 517 func (f *Frontend) startSecondInstanceProcessor() { 518 for secondInstanceData := range secondInstanceBuffer { 519 if f.frontendOptions.SingleInstanceLock != nil && 520 f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { 521 f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) 522 } 523 } 524 }