/* * Carla Bridge UI * Copyright (C) 2011-2021 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ #include "CarlaBridgeFormat.hpp" #include "CarlaBridgeToolkit.hpp" #include "CarlaLibUtils.hpp" #ifdef HAVE_X11 # include #endif struct GtkHandle; enum GtkWidgetType { GTK_WINDOW_TOPLEVEL }; typedef ulong (*gsym_signal_connect_data)(void* instance, const char* detailed_signal, void (*c_handler)(GtkHandle*, void* data), void* data, void* destroy_data, int connect_flags); typedef uint (*gsym_timeout_add)(uint interval, int (*function)(void* user_data), void* data); typedef void (*gtksym_init)(int* argc, char*** argv); typedef void (*gtksym_main)(void); typedef uint (*gtksym_main_level)(void); typedef void (*gtksym_main_quit)(void); typedef void (*gtksym_container_add)(GtkHandle* container, GtkHandle* widget); typedef void (*gtksym_widget_destroy)(GtkHandle* widget); typedef void (*gtksym_widget_hide)(GtkHandle* widget); typedef void (*gtksym_widget_show_all)(GtkHandle* widget); typedef GtkHandle* (*gtksym_window_new)(GtkWidgetType type); typedef void (*gtksym_window_get_position)(GtkHandle* window, int* root_x, int* root_y); typedef void (*gtksym_window_get_size)(GtkHandle* window, int* width, int* height); typedef void (*gtksym_window_resize)(GtkHandle* window, int width, int height); typedef void (*gtksym_window_set_resizable)(GtkHandle* window, int resizable); typedef void (*gtksym_window_set_title)(GtkHandle* window, const char* title); #ifdef HAVE_X11 typedef GtkHandle* (*gtksym_widget_get_window)(GtkHandle* widget); # ifdef BRIDGE_GTK3 typedef GtkHandle* (*gdksym_window_get_display)(GtkHandle* window); typedef Display* (*gdksym_x11_display_get_xdisplay)(GtkHandle* display); typedef Window (*gdksym_x11_window_get_xid)(GtkHandle* window); # else typedef Display* (*gdksym_x11_drawable_get_xdisplay)(GtkHandle* drawable); typedef XID (*gdksym_x11_drawable_get_xid)(GtkHandle* drawable); # endif #endif CARLA_BRIDGE_UI_START_NAMESPACE // ------------------------------------------------------------------------- struct GtkLoader { lib_t lib; #ifdef CARLA_OS_WIN lib_t glib; lib_t golib; #endif gsym_timeout_add timeout_add; gsym_signal_connect_data signal_connect_data; gtksym_init init; gtksym_main main; gtksym_main_level main_level; gtksym_main_quit main_quit; gtksym_container_add container_add; gtksym_widget_destroy widget_destroy; gtksym_widget_hide widget_hide; gtksym_widget_show_all widget_show_all; gtksym_window_new window_new; gtksym_window_get_position window_get_position; gtksym_window_get_size window_get_size; gtksym_window_resize window_resize; gtksym_window_set_resizable window_set_resizable; gtksym_window_set_title window_set_title; bool ok; #ifdef HAVE_X11 gtksym_widget_get_window widget_get_window; # ifdef BRIDGE_GTK3 gdksym_window_get_display window_get_display; gdksym_x11_display_get_xdisplay x11_display_get_xdisplay; gdksym_x11_window_get_xid x11_window_get_xid; # else gdksym_x11_drawable_get_xdisplay x11_drawable_get_xdisplay; gdksym_x11_drawable_get_xid x11_drawable_get_xid; # endif #endif GtkLoader() : lib(nullptr), #ifdef CARLA_OS_WIN glib(nullptr), golib(nullptr), #endif timeout_add(nullptr), signal_connect_data(nullptr), init(nullptr), main(nullptr), main_level(nullptr), main_quit(nullptr), container_add(nullptr), widget_destroy(nullptr), widget_hide(nullptr), widget_show_all(nullptr), window_new(nullptr), window_get_position(nullptr), window_get_size(nullptr), window_resize(nullptr), window_set_resizable(nullptr), window_set_title(nullptr), ok(false) #ifdef HAVE_X11 , widget_get_window(nullptr), # ifdef BRIDGE_GTK3 window_get_display(nullptr), x11_display_get_xdisplay(nullptr), x11_window_get_xid(nullptr) # else x11_drawable_get_xdisplay(nullptr), x11_drawable_get_xid(nullptr) # endif #endif { const char* filename; const char* const filenames[] = { #ifdef BRIDGE_GTK3 # if defined(CARLA_OS_MAC) "libgtk-3.0.dylib", # elif defined(CARLA_OS_WIN) "libgtk-3-0.dll", # else "libgtk-3.so.0", # endif #else # if defined(CARLA_OS_MAC) "libgtk-quartz-2.0.dylib", "libgtk-x11-2.0.dylib", "/opt/homebrew/opt/gtk+/lib/libgtk-quartz-2.0.0.dylib", "/opt/local/lib/libgtk-quartz-2.0.dylib", "/opt/local/lib/libgtk-x11-2.0.dylib", # elif defined(CARLA_OS_WIN) "libgtk-win32-2.0-0.dll", # ifdef CARLA_OS_WIN64 "C:\\msys64\\mingw64\\bin\\libgtk-win32-2.0-0.dll", # else "C:\\msys64\\mingw32\\bin\\libgtk-win32-2.0-0.dll", # endif # else "libgtk-x11-2.0.so.0", # endif #endif }; for (size_t i=0; i(glib, "g_" #NAME); \ CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,); #define GO_LIB_SYMBOL(NAME) \ NAME = lib_symbol(golib, "g_" #NAME); \ CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,); #else #define G_LIB_SYMBOL(NAME) \ NAME = lib_symbol(lib, "g_" #NAME); \ CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,); #define GO_LIB_SYMBOL G_LIB_SYMBOL #endif #define GTK_LIB_SYMBOL(NAME) \ NAME = lib_symbol(lib, "gtk_" #NAME); \ CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,); #define GDK_LIB_SYMBOL(NAME) \ NAME = lib_symbol(lib, "gdk_" #NAME); \ CARLA_SAFE_ASSERT(NAME != nullptr); G_LIB_SYMBOL(timeout_add) GO_LIB_SYMBOL(signal_connect_data) GTK_LIB_SYMBOL(init) GTK_LIB_SYMBOL(main) GTK_LIB_SYMBOL(main_level) GTK_LIB_SYMBOL(main_quit) GTK_LIB_SYMBOL(container_add) GTK_LIB_SYMBOL(widget_destroy) GTK_LIB_SYMBOL(widget_hide) GTK_LIB_SYMBOL(widget_show_all) GTK_LIB_SYMBOL(window_new) GTK_LIB_SYMBOL(window_get_position) GTK_LIB_SYMBOL(window_get_size) GTK_LIB_SYMBOL(window_resize) GTK_LIB_SYMBOL(window_set_resizable) GTK_LIB_SYMBOL(window_set_title) ok = true; #ifdef HAVE_X11 GTK_LIB_SYMBOL(widget_get_window) # ifdef BRIDGE_GTK3 GDK_LIB_SYMBOL(window_get_display) GDK_LIB_SYMBOL(x11_display_get_xdisplay) GDK_LIB_SYMBOL(x11_window_get_xid) # else GDK_LIB_SYMBOL(x11_drawable_get_xdisplay) GDK_LIB_SYMBOL(x11_drawable_get_xid) # endif #endif #undef G_LIB_SYMBOL #undef GO_LIB_SYMBOL #undef GDK_LIB_SYMBOL #undef GTK_LIB_SYMBOL } ~GtkLoader() { if (lib != nullptr) lib_close(lib); #ifdef CARLA_OS_WIN if (golib != nullptr) lib_close(golib); if (glib != nullptr) lib_close(glib); #endif } void main_quit_if_needed() { if (main_level() != 0) main_quit(); } CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GtkLoader) }; // ------------------------------------------------------------------------- static const bool gHideShowTesting = std::getenv("CARLA_UI_TESTING") != nullptr; // ------------------------------------------------------------------------- class CarlaBridgeToolkitGtk : public CarlaBridgeToolkit { public: CarlaBridgeToolkitGtk(CarlaBridgeFormat* const format) : CarlaBridgeToolkit(format), gtk(), fNeedsShow(false), fWindow(nullptr), fLastX(0), fLastY(0), fLastWidth(0), fLastHeight(0) { carla_debug("CarlaBridgeToolkitGtk::CarlaBridgeToolkitGtk(%p)", format); } ~CarlaBridgeToolkitGtk() override { CARLA_SAFE_ASSERT(fWindow == nullptr); carla_debug("CarlaBridgeToolkitGtk::~CarlaBridgeToolkitGtk()"); } bool init(const int /*argc*/, const char** /*argv[]*/) override { CARLA_SAFE_ASSERT_RETURN(fWindow == nullptr, false); carla_debug("CarlaBridgeToolkitGtk::init()"); if (! gtk.ok) return false; static int gargc = 0; static char** gargv = nullptr; gtk.init(&gargc, &gargv); fWindow = gtk.window_new(GTK_WINDOW_TOPLEVEL); CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr, false); gtk.window_resize(fWindow, 30, 30); gtk.widget_hide(fWindow); return true; } void exec(const bool showUI) override { CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,); CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,); carla_debug("CarlaBridgeToolkitGtk::exec(%s)", bool2str(showUI)); const CarlaBridgeFormat::Options& options(fPlugin->getOptions()); GtkHandle* const widget((GtkHandle*)fPlugin->getWidget()); CARLA_SAFE_ASSERT_RETURN(widget != nullptr,); gtk.container_add(fWindow, widget); gtk.window_set_resizable(fWindow, options.isResizable); gtk.window_set_title(fWindow, options.windowTitle.buffer()); if (showUI || fNeedsShow) { show(); fNeedsShow = false; } gtk.timeout_add(30, gtk_ui_timeout, this); gtk.signal_connect_data(fWindow, "destroy", gtk_ui_destroy, this, nullptr, 0); gtk.signal_connect_data(fWindow, "realize", gtk_ui_realize, this, nullptr, 0); // First idle handleTimeout(); // Main loop gtk.main(); } void quit() override { carla_debug("CarlaBridgeToolkitGtk::quit()"); if (fWindow != nullptr) { gtk.widget_destroy(fWindow); fWindow = nullptr; gtk.main_quit_if_needed(); } } void show() override { carla_debug("CarlaBridgeToolkitGtk::show()"); fNeedsShow = true; if (fWindow != nullptr) gtk.widget_show_all(fWindow); } void focus() override { carla_debug("CarlaBridgeToolkitGtk::focus()"); } void hide() override { carla_debug("CarlaBridgeToolkitGtk::hide()"); fNeedsShow = false; if (fWindow != nullptr) gtk.widget_hide(fWindow); } void setChildWindow(void* const) override {} void setSize(const uint width, const uint height) override { CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,); carla_debug("CarlaBridgeToolkitGtk::resize(%i, %i)", width, height); gtk.window_resize(fWindow, static_cast(width), static_cast(height)); } void setTitle(const char* const title) override { CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,); carla_debug("CarlaBridgeToolkitGtk::setTitle(\"%s\")", title); gtk.window_set_title(fWindow, title); } // --------------------------------------------------------------------- protected: GtkLoader gtk; bool fNeedsShow; GtkHandle* fWindow; int fLastX; int fLastY; int fLastWidth; int fLastHeight; void handleDestroy() { carla_debug("CarlaBridgeToolkitGtk::handleDestroy()"); fWindow = nullptr; gtk.main_quit_if_needed(); } void handleRealize() { carla_debug("CarlaBridgeToolkitGtk::handleRealize()"); #ifdef HAVE_X11 const CarlaBridgeFormat::Options& options(fPlugin->getOptions()); if (options.transientWindowId != 0) setTransient(options.transientWindowId); #endif } int handleTimeout() { if (fWindow != nullptr) { gtk.window_get_position(fWindow, &fLastX, &fLastY); gtk.window_get_size(fWindow, &fLastWidth, &fLastHeight); } if (fPlugin->isPipeRunning()) fPlugin->idlePipe(); fPlugin->idleUI(); if (gHideShowTesting) { static int counter = 0; ++counter; if (counter == 100) { hide(); } else if (counter == 200) { show(); counter = 0; } } return 1; } #ifdef HAVE_X11 void setTransient(const uintptr_t winId) { CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,); carla_debug("CarlaBridgeToolkitGtk::setTransient(0x" P_UINTPTR ")", winId); if (gtk.widget_get_window == nullptr) return; # ifdef BRIDGE_GTK3 if (gtk.window_get_display == nullptr) return; if (gtk.x11_display_get_xdisplay == nullptr) return; if (gtk.x11_window_get_xid == nullptr) return; # else if (gtk.x11_drawable_get_xdisplay == nullptr) return; if (gtk.x11_drawable_get_xid == nullptr) return; # endif GtkHandle* const gdkWindow = gtk.widget_get_window(fWindow); CARLA_SAFE_ASSERT_RETURN(gdkWindow != nullptr,); # ifdef BRIDGE_GTK3 GtkHandle* const gdkDisplay = gtk.window_get_display(gdkWindow); CARLA_SAFE_ASSERT_RETURN(gdkDisplay != nullptr,); ::Display* const display = gtk.x11_display_get_xdisplay(gdkDisplay); CARLA_SAFE_ASSERT_RETURN(display != nullptr,); const ::XID xid = gtk.x11_window_get_xid(gdkWindow); CARLA_SAFE_ASSERT_RETURN(xid != 0,); # else ::Display* const display = gtk.x11_drawable_get_xdisplay((GtkHandle*)gdkWindow); CARLA_SAFE_ASSERT_RETURN(display != nullptr,); const ::XID xid = gtk.x11_drawable_get_xid((GtkHandle*)gdkWindow); CARLA_SAFE_ASSERT_RETURN(xid != 0,); # endif XSetTransientForHint(display, xid, static_cast< ::Window>(winId)); } #endif // --------------------------------------------------------------------- private: static void gtk_ui_destroy(GtkHandle*, void* data) { CARLA_SAFE_ASSERT_RETURN(data != nullptr,); ((CarlaBridgeToolkitGtk*)data)->handleDestroy(); } static void gtk_ui_realize(GtkHandle*, void* data) { CARLA_SAFE_ASSERT_RETURN(data != nullptr,); ((CarlaBridgeToolkitGtk*)data)->handleRealize(); } static int gtk_ui_timeout(void* data) { CARLA_SAFE_ASSERT_RETURN(data != nullptr, false); return ((CarlaBridgeToolkitGtk*)data)->handleTimeout(); } CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaBridgeToolkitGtk) }; // ------------------------------------------------------------------------- CarlaBridgeToolkit* CarlaBridgeToolkit::createNew(CarlaBridgeFormat* const format) { return new CarlaBridgeToolkitGtk(format); } // ------------------------------------------------------------------------- CARLA_BRIDGE_UI_END_NAMESPACE