1 /*
2 * Carla Bridge UI
3 * Copyright (C) 2011-2021 Filipe Coelho <falktx@falktx.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
16 */
17
18 #include "CarlaBridgeFormat.hpp"
19 #include "CarlaBridgeToolkit.hpp"
20 #include "CarlaLibUtils.hpp"
21
22 #ifdef HAVE_X11
23 # include <X11/Xlib.h>
24 #endif
25
26 struct GtkHandle;
27
28 enum GtkWidgetType {
29 GTK_WINDOW_TOPLEVEL
30 };
31
32 typedef ulong (*gsym_signal_connect_data)(void* instance,
33 const char* detailed_signal,
34 void (*c_handler)(GtkHandle*, void* data),
35 void* data,
36 void* destroy_data,
37 int connect_flags);
38 typedef uint (*gsym_timeout_add)(uint interval, int (*function)(void* user_data), void* data);
39 typedef void (*gtksym_init)(int* argc, char*** argv);
40 typedef void (*gtksym_main)(void);
41 typedef uint (*gtksym_main_level)(void);
42 typedef void (*gtksym_main_quit)(void);
43 typedef void (*gtksym_container_add)(GtkHandle* container, GtkHandle* widget);
44 typedef void (*gtksym_widget_destroy)(GtkHandle* widget);
45 typedef void (*gtksym_widget_hide)(GtkHandle* widget);
46 typedef void (*gtksym_widget_show_all)(GtkHandle* widget);
47 typedef GtkHandle* (*gtksym_window_new)(GtkWidgetType type);
48 typedef void (*gtksym_window_get_position)(GtkHandle* window, int* root_x, int* root_y);
49 typedef void (*gtksym_window_get_size)(GtkHandle* window, int* width, int* height);
50 typedef void (*gtksym_window_resize)(GtkHandle* window, int width, int height);
51 typedef void (*gtksym_window_set_resizable)(GtkHandle* window, int resizable);
52 typedef void (*gtksym_window_set_title)(GtkHandle* window, const char* title);
53 #ifdef HAVE_X11
54 typedef GtkHandle* (*gtksym_widget_get_window)(GtkHandle* widget);
55 # ifdef BRIDGE_GTK3
56 typedef GtkHandle* (*gdksym_window_get_display)(GtkHandle* window);
57 typedef Display* (*gdksym_x11_display_get_xdisplay)(GtkHandle* display);
58 typedef Window (*gdksym_x11_window_get_xid)(GtkHandle* window);
59 # else
60 typedef Display* (*gdksym_x11_drawable_get_xdisplay)(GtkHandle* drawable);
61 typedef XID (*gdksym_x11_drawable_get_xid)(GtkHandle* drawable);
62 # endif
63 #endif
64
65 CARLA_BRIDGE_UI_START_NAMESPACE
66
67 // -------------------------------------------------------------------------
68
69 struct GtkLoader {
70 lib_t lib;
71 #ifdef CARLA_OS_WIN
72 lib_t glib;
73 lib_t golib;
74 #endif
75 gsym_timeout_add timeout_add;
76 gsym_signal_connect_data signal_connect_data;
77 gtksym_init init;
78 gtksym_main main;
79 gtksym_main_level main_level;
80 gtksym_main_quit main_quit;
81 gtksym_container_add container_add;
82 gtksym_widget_destroy widget_destroy;
83 gtksym_widget_hide widget_hide;
84 gtksym_widget_show_all widget_show_all;
85 gtksym_window_new window_new;
86 gtksym_window_get_position window_get_position;
87 gtksym_window_get_size window_get_size;
88 gtksym_window_resize window_resize;
89 gtksym_window_set_resizable window_set_resizable;
90 gtksym_window_set_title window_set_title;
91 bool ok;
92
93 #ifdef HAVE_X11
94 gtksym_widget_get_window widget_get_window;
95 # ifdef BRIDGE_GTK3
96 gdksym_window_get_display window_get_display;
97 gdksym_x11_display_get_xdisplay x11_display_get_xdisplay;
98 gdksym_x11_window_get_xid x11_window_get_xid;
99 # else
100 gdksym_x11_drawable_get_xdisplay x11_drawable_get_xdisplay;
101 gdksym_x11_drawable_get_xid x11_drawable_get_xid;
102 # endif
103 #endif
104
GtkLoaderGtkLoader105 GtkLoader()
106 : lib(nullptr),
107 #ifdef CARLA_OS_WIN
108 glib(nullptr),
109 golib(nullptr),
110 #endif
111 timeout_add(nullptr),
112 signal_connect_data(nullptr),
113 init(nullptr),
114 main(nullptr),
115 main_level(nullptr),
116 main_quit(nullptr),
117 container_add(nullptr),
118 widget_destroy(nullptr),
119 widget_hide(nullptr),
120 widget_show_all(nullptr),
121 window_new(nullptr),
122 window_get_position(nullptr),
123 window_get_size(nullptr),
124 window_resize(nullptr),
125 window_set_resizable(nullptr),
126 window_set_title(nullptr),
127 ok(false)
128 #ifdef HAVE_X11
129 , widget_get_window(nullptr),
130 # ifdef BRIDGE_GTK3
131 window_get_display(nullptr),
132 x11_display_get_xdisplay(nullptr),
133 x11_window_get_xid(nullptr)
134 # else
135 x11_drawable_get_xdisplay(nullptr),
136 x11_drawable_get_xid(nullptr)
137 # endif
138 #endif
139 {
140 const char* filename;
141 const char* const filenames[] = {
142 #ifdef BRIDGE_GTK3
143 # if defined(CARLA_OS_MAC)
144 "libgtk-3.0.dylib",
145 # elif defined(CARLA_OS_WIN)
146 "libgtk-3-0.dll",
147 # else
148 "libgtk-3.so.0",
149 # endif
150 #else
151 # if defined(CARLA_OS_MAC)
152 "libgtk-quartz-2.0.dylib",
153 "libgtk-x11-2.0.dylib",
154 "/opt/homebrew/opt/gtk+/lib/libgtk-quartz-2.0.0.dylib",
155 "/opt/local/lib/libgtk-quartz-2.0.dylib",
156 "/opt/local/lib/libgtk-x11-2.0.dylib",
157 # elif defined(CARLA_OS_WIN)
158 "libgtk-win32-2.0-0.dll",
159 # ifdef CARLA_OS_WIN64
160 "C:\\msys64\\mingw64\\bin\\libgtk-win32-2.0-0.dll",
161 # else
162 "C:\\msys64\\mingw32\\bin\\libgtk-win32-2.0-0.dll",
163 # endif
164 # else
165 "libgtk-x11-2.0.so.0",
166 # endif
167 #endif
168 };
169
170 for (size_t i=0; i<sizeof(filenames)/sizeof(filenames[0]); ++i)
171 {
172 filename = filenames[i];
173 if ((lib = lib_open(filename, true)) != nullptr)
174 break;
175 }
176
177 if (lib == nullptr)
178 {
179 fprintf(stderr, "Failed to load Gtk, reason:\n%s\n", lib_error(filename));
180 return;
181 }
182 else
183 {
184 fprintf(stdout, "%s loaded successfully!\n", filename);
185 }
186
187 #ifdef CARLA_OS_WIN
188 const char* gfilename;
189 const char* const gfilenames[] = {
190 "libglib-2.0-0.dll",
191 # ifdef CARLA_OS_WIN64
192 "C:\\msys64\\mingw64\\bin\\libglib-2.0-0.dll",
193 # else
194 "C:\\msys64\\mingw32\\bin\\libglib-2.0-0.dll",
195 # endif
196 };
197
198 for (size_t i=0; i<sizeof(gfilenames)/sizeof(gfilenames[0]); ++i)
199 {
200 gfilename = gfilenames[i];
201 if ((glib = lib_open(gfilename, true)) != nullptr)
202 break;
203 }
204
205 if (glib == nullptr)
206 {
207 fprintf(stderr, "Failed to load glib, reason:\n%s\n", lib_error(gfilename));
208 return;
209 }
210 else
211 {
212 fprintf(stdout, "%s loaded successfully!\n", gfilename);
213 }
214
215 const char* gofilename;
216 const char* const gofilenames[] = {
217 "libgobject-2.0-0.dll",
218 # ifdef CARLA_OS_WIN64
219 "C:\\msys64\\mingw64\\bin\\libgobject-2.0-0.dll",
220 # else
221 "C:\\msys64\\mingw32\\bin\\libgobject-2.0-0.dll",
222 # endif
223 };
224
225 for (size_t i=0; i<sizeof(gofilenames)/sizeof(gofilenames[0]); ++i)
226 {
227 gofilename = gofilenames[i];
228 if ((golib = lib_open(gofilename, true)) != nullptr)
229 break;
230 }
231
232 if (golib == nullptr)
233 {
234 fprintf(stderr, "Failed to load gobject, reason:\n%s\n", lib_error(gofilename));
235 return;
236 }
237 else
238 {
239 fprintf(stdout, "%s loaded successfully!\n", gofilename);
240 }
241
242 #define G_LIB_SYMBOL(NAME) \
243 NAME = lib_symbol<gsym_##NAME>(glib, "g_" #NAME); \
244 CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,);
245
246 #define GO_LIB_SYMBOL(NAME) \
247 NAME = lib_symbol<gsym_##NAME>(golib, "g_" #NAME); \
248 CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,);
249 #else
250 #define G_LIB_SYMBOL(NAME) \
251 NAME = lib_symbol<gsym_##NAME>(lib, "g_" #NAME); \
252 CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,);
253
254 #define GO_LIB_SYMBOL G_LIB_SYMBOL
255 #endif
256
257 #define GTK_LIB_SYMBOL(NAME) \
258 NAME = lib_symbol<gtksym_##NAME>(lib, "gtk_" #NAME); \
259 CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,);
260
261 #define GDK_LIB_SYMBOL(NAME) \
262 NAME = lib_symbol<gdksym_##NAME>(lib, "gdk_" #NAME); \
263 CARLA_SAFE_ASSERT(NAME != nullptr);
264
265 G_LIB_SYMBOL(timeout_add)
266 GO_LIB_SYMBOL(signal_connect_data)
267
268 GTK_LIB_SYMBOL(init)
269 GTK_LIB_SYMBOL(main)
270 GTK_LIB_SYMBOL(main_level)
271 GTK_LIB_SYMBOL(main_quit)
272 GTK_LIB_SYMBOL(container_add)
273 GTK_LIB_SYMBOL(widget_destroy)
274 GTK_LIB_SYMBOL(widget_hide)
275 GTK_LIB_SYMBOL(widget_show_all)
276 GTK_LIB_SYMBOL(window_new)
277 GTK_LIB_SYMBOL(window_get_position)
278 GTK_LIB_SYMBOL(window_get_size)
279 GTK_LIB_SYMBOL(window_resize)
280 GTK_LIB_SYMBOL(window_set_resizable)
281 GTK_LIB_SYMBOL(window_set_title)
282
283 ok = true;
284
285 #ifdef HAVE_X11
286 GTK_LIB_SYMBOL(widget_get_window)
287 # ifdef BRIDGE_GTK3
288 GDK_LIB_SYMBOL(window_get_display)
289 GDK_LIB_SYMBOL(x11_display_get_xdisplay)
290 GDK_LIB_SYMBOL(x11_window_get_xid)
291 # else
292 GDK_LIB_SYMBOL(x11_drawable_get_xdisplay)
293 GDK_LIB_SYMBOL(x11_drawable_get_xid)
294 # endif
295 #endif
296
297 #undef G_LIB_SYMBOL
298 #undef GO_LIB_SYMBOL
299 #undef GDK_LIB_SYMBOL
300 #undef GTK_LIB_SYMBOL
301 }
302
~GtkLoaderGtkLoader303 ~GtkLoader()
304 {
305 if (lib != nullptr)
306 lib_close(lib);
307 #ifdef CARLA_OS_WIN
308 if (golib != nullptr)
309 lib_close(golib);
310 if (glib != nullptr)
311 lib_close(glib);
312 #endif
313 }
314
main_quit_if_neededGtkLoader315 void main_quit_if_needed()
316 {
317 if (main_level() != 0)
318 main_quit();
319 }
320
321 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GtkLoader)
322 };
323
324 // -------------------------------------------------------------------------
325
326 static const bool gHideShowTesting = std::getenv("CARLA_UI_TESTING") != nullptr;
327
328 // -------------------------------------------------------------------------
329
330 class CarlaBridgeToolkitGtk : public CarlaBridgeToolkit
331 {
332 public:
CarlaBridgeToolkitGtk(CarlaBridgeFormat * const format)333 CarlaBridgeToolkitGtk(CarlaBridgeFormat* const format)
334 : CarlaBridgeToolkit(format),
335 gtk(),
336 fNeedsShow(false),
337 fWindow(nullptr),
338 fLastX(0),
339 fLastY(0),
340 fLastWidth(0),
341 fLastHeight(0)
342 {
343 carla_debug("CarlaBridgeToolkitGtk::CarlaBridgeToolkitGtk(%p)", format);
344 }
345
~CarlaBridgeToolkitGtk()346 ~CarlaBridgeToolkitGtk() override
347 {
348 CARLA_SAFE_ASSERT(fWindow == nullptr);
349 carla_debug("CarlaBridgeToolkitGtk::~CarlaBridgeToolkitGtk()");
350 }
351
init(const int,const char **)352 bool init(const int /*argc*/, const char** /*argv[]*/) override
353 {
354 CARLA_SAFE_ASSERT_RETURN(fWindow == nullptr, false);
355 carla_debug("CarlaBridgeToolkitGtk::init()");
356
357 if (! gtk.ok)
358 return false;
359
360 static int gargc = 0;
361 static char** gargv = nullptr;
362 gtk.init(&gargc, &gargv);
363
364 fWindow = gtk.window_new(GTK_WINDOW_TOPLEVEL);
365 CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr, false);
366
367 gtk.window_resize(fWindow, 30, 30);
368 gtk.widget_hide(fWindow);
369
370 return true;
371 }
372
exec(const bool showUI)373 void exec(const bool showUI) override
374 {
375 CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
376 CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
377 carla_debug("CarlaBridgeToolkitGtk::exec(%s)", bool2str(showUI));
378
379 const CarlaBridgeFormat::Options& options(fPlugin->getOptions());
380
381 GtkHandle* const widget((GtkHandle*)fPlugin->getWidget());
382 CARLA_SAFE_ASSERT_RETURN(widget != nullptr,);
383
384 gtk.container_add(fWindow, widget);
385 gtk.window_set_resizable(fWindow, options.isResizable);
386 gtk.window_set_title(fWindow, options.windowTitle.buffer());
387
388 if (showUI || fNeedsShow)
389 {
390 show();
391 fNeedsShow = false;
392 }
393
394 gtk.timeout_add(30, gtk_ui_timeout, this);
395 gtk.signal_connect_data(fWindow, "destroy", gtk_ui_destroy, this, nullptr, 0);
396 gtk.signal_connect_data(fWindow, "realize", gtk_ui_realize, this, nullptr, 0);
397
398 // First idle
399 handleTimeout();
400
401 // Main loop
402 gtk.main();
403 }
404
quit()405 void quit() override
406 {
407 carla_debug("CarlaBridgeToolkitGtk::quit()");
408
409 if (fWindow != nullptr)
410 {
411 gtk.widget_destroy(fWindow);
412 fWindow = nullptr;
413
414 gtk.main_quit_if_needed();
415 }
416 }
417
show()418 void show() override
419 {
420 carla_debug("CarlaBridgeToolkitGtk::show()");
421
422 fNeedsShow = true;
423
424 if (fWindow != nullptr)
425 gtk.widget_show_all(fWindow);
426 }
427
focus()428 void focus() override
429 {
430 carla_debug("CarlaBridgeToolkitGtk::focus()");
431 }
432
hide()433 void hide() override
434 {
435 carla_debug("CarlaBridgeToolkitGtk::hide()");
436
437 fNeedsShow = false;
438
439 if (fWindow != nullptr)
440 gtk.widget_hide(fWindow);
441 }
442
setChildWindow(void * const)443 void setChildWindow(void* const) override {}
444
setSize(const uint width,const uint height)445 void setSize(const uint width, const uint height) override
446 {
447 CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
448 carla_debug("CarlaBridgeToolkitGtk::resize(%i, %i)", width, height);
449
450 gtk.window_resize(fWindow, static_cast<int>(width), static_cast<int>(height));
451 }
452
setTitle(const char * const title)453 void setTitle(const char* const title) override
454 {
455 CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
456 carla_debug("CarlaBridgeToolkitGtk::setTitle(\"%s\")", title);
457
458 gtk.window_set_title(fWindow, title);
459 }
460
461 // ---------------------------------------------------------------------
462
463 protected:
464 GtkLoader gtk;
465 bool fNeedsShow;
466 GtkHandle* fWindow;
467
468 int fLastX;
469 int fLastY;
470 int fLastWidth;
471 int fLastHeight;
472
handleDestroy()473 void handleDestroy()
474 {
475 carla_debug("CarlaBridgeToolkitGtk::handleDestroy()");
476
477 fWindow = nullptr;
478 gtk.main_quit_if_needed();
479 }
480
handleRealize()481 void handleRealize()
482 {
483 carla_debug("CarlaBridgeToolkitGtk::handleRealize()");
484
485 #ifdef HAVE_X11
486 const CarlaBridgeFormat::Options& options(fPlugin->getOptions());
487
488 if (options.transientWindowId != 0)
489 setTransient(options.transientWindowId);
490 #endif
491 }
492
handleTimeout()493 int handleTimeout()
494 {
495 if (fWindow != nullptr)
496 {
497 gtk.window_get_position(fWindow, &fLastX, &fLastY);
498 gtk.window_get_size(fWindow, &fLastWidth, &fLastHeight);
499 }
500
501 if (fPlugin->isPipeRunning())
502 fPlugin->idlePipe();
503
504 fPlugin->idleUI();
505
506 if (gHideShowTesting)
507 {
508 static int counter = 0;
509 ++counter;
510
511 if (counter == 100)
512 {
513 hide();
514 }
515 else if (counter == 200)
516 {
517 show();
518 counter = 0;
519 }
520 }
521
522 return 1;
523 }
524
525 #ifdef HAVE_X11
setTransient(const uintptr_t winId)526 void setTransient(const uintptr_t winId)
527 {
528 CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
529 carla_debug("CarlaBridgeToolkitGtk::setTransient(0x" P_UINTPTR ")", winId);
530
531 if (gtk.widget_get_window == nullptr)
532 return;
533 # ifdef BRIDGE_GTK3
534 if (gtk.window_get_display == nullptr)
535 return;
536 if (gtk.x11_display_get_xdisplay == nullptr)
537 return;
538 if (gtk.x11_window_get_xid == nullptr)
539 return;
540 # else
541 if (gtk.x11_drawable_get_xdisplay == nullptr)
542 return;
543 if (gtk.x11_drawable_get_xid == nullptr)
544 return;
545 # endif
546
547 GtkHandle* const gdkWindow = gtk.widget_get_window(fWindow);
548 CARLA_SAFE_ASSERT_RETURN(gdkWindow != nullptr,);
549
550 # ifdef BRIDGE_GTK3
551 GtkHandle* const gdkDisplay = gtk.window_get_display(gdkWindow);
552 CARLA_SAFE_ASSERT_RETURN(gdkDisplay != nullptr,);
553
554 ::Display* const display = gtk.x11_display_get_xdisplay(gdkDisplay);
555 CARLA_SAFE_ASSERT_RETURN(display != nullptr,);
556
557 const ::XID xid = gtk.x11_window_get_xid(gdkWindow);
558 CARLA_SAFE_ASSERT_RETURN(xid != 0,);
559 # else
560 ::Display* const display = gtk.x11_drawable_get_xdisplay((GtkHandle*)gdkWindow);
561 CARLA_SAFE_ASSERT_RETURN(display != nullptr,);
562
563 const ::XID xid = gtk.x11_drawable_get_xid((GtkHandle*)gdkWindow);
564 CARLA_SAFE_ASSERT_RETURN(xid != 0,);
565 # endif
566
567 XSetTransientForHint(display, xid, static_cast< ::Window>(winId));
568 }
569 #endif
570
571 // ---------------------------------------------------------------------
572
573 private:
gtk_ui_destroy(GtkHandle *,void * data)574 static void gtk_ui_destroy(GtkHandle*, void* data)
575 {
576 CARLA_SAFE_ASSERT_RETURN(data != nullptr,);
577
578 ((CarlaBridgeToolkitGtk*)data)->handleDestroy();
579 }
580
gtk_ui_realize(GtkHandle *,void * data)581 static void gtk_ui_realize(GtkHandle*, void* data)
582 {
583 CARLA_SAFE_ASSERT_RETURN(data != nullptr,);
584
585 ((CarlaBridgeToolkitGtk*)data)->handleRealize();
586 }
587
gtk_ui_timeout(void * data)588 static int gtk_ui_timeout(void* data)
589 {
590 CARLA_SAFE_ASSERT_RETURN(data != nullptr, false);
591
592 return ((CarlaBridgeToolkitGtk*)data)->handleTimeout();
593 }
594
595 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaBridgeToolkitGtk)
596 };
597
598 // -------------------------------------------------------------------------
599
createNew(CarlaBridgeFormat * const format)600 CarlaBridgeToolkit* CarlaBridgeToolkit::createNew(CarlaBridgeFormat* const format)
601 {
602 return new CarlaBridgeToolkitGtk(format);
603 }
604
605 // -------------------------------------------------------------------------
606
607 CARLA_BRIDGE_UI_END_NAMESPACE
608