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