Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 lines
15KB

  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. #include "CarlaBridgeFormat.hpp"
  18. #include "CarlaBridgeToolkit.hpp"
  19. #include "CarlaLibUtils.hpp"
  20. #include <glib.h>
  21. #include <glib-object.h>
  22. #ifdef HAVE_X11
  23. # include <X11/Xlib.h>
  24. #endif
  25. // #include <gtk/gtk.h>
  26. // #ifdef HAVE_X11
  27. // # include <gdk/gdkx.h>
  28. // #endif
  29. // #undef GTK_TYPE_CONTAINER
  30. // #define GTK_TYPE_CONTAINER (gtk.container_get_type())
  31. //
  32. // #undef GTK_TYPE_WINDOW
  33. // #define GTK_TYPE_WINDOW (gtk.window_get_type())
  34. #define GTK_CONTAINER(G) ((GtkContainer*)G)
  35. #define GTK_WINDOW(G) ((GtkWindow*)G)
  36. struct GdkDisplay;
  37. struct GdkDrawable;
  38. struct GdkWindow;
  39. struct GtkContainer;
  40. struct GtkWidget;
  41. struct GtkWindow;
  42. enum GtkWindowType {
  43. GTK_WINDOW_TOPLEVEL
  44. };
  45. typedef void (*gtksym_init)(int* argc, char*** argv);
  46. typedef void (*gtksym_main)(void);
  47. typedef uint (*gtksym_main_level)(void);
  48. typedef void (*gtksym_main_quit)(void);
  49. typedef GType (*gtksym_container_get_type)(void) G_GNUC_CONST;
  50. typedef void (*gtksym_container_add)(GtkContainer* container, GtkWidget* widget);
  51. typedef void (*gtksym_widget_destroy)(GtkWidget* widget);
  52. typedef void (*gtksym_widget_hide)(GtkWidget* widget);
  53. typedef void (*gtksym_widget_show_all)(GtkWidget* widget);
  54. typedef GType (*gtksym_window_get_type)(void) G_GNUC_CONST;
  55. typedef GtkWidget* (*gtksym_window_new)(GtkWindowType type);
  56. typedef void (*gtksym_window_get_position)(GtkWindow* window, int* root_x, int* root_y);
  57. typedef void (*gtksym_window_get_size)(GtkWindow* window, int* width, int* height);
  58. typedef void (*gtksym_window_resize)(GtkWindow* window, int width, int height);
  59. typedef void (*gtksym_window_set_resizable)(GtkWindow* window, int resizable);
  60. typedef void (*gtksym_window_set_title)(GtkWindow* window, const char* title);
  61. #ifdef HAVE_X11
  62. typedef GdkWindow* (*gtksym_widget_get_window)(GtkWidget* widget);
  63. # ifdef BRIDGE_GTK3
  64. typedef GdkDisplay* (*gdksym_window_get_display)(GdkWindow* window);
  65. typedef Display* (*gdksym_x11_display_get_xdisplay)(GdkDisplay* display);
  66. typedef Window (*gdksym_x11_window_get_xid)(GdkWindow* window);
  67. # else
  68. typedef Display* (*gdksym_x11_drawable_get_xdisplay)(GdkDrawable* drawable);
  69. typedef XID (*gdksym_x11_drawable_get_xid)(GdkDrawable* drawable);
  70. # endif
  71. #endif
  72. CARLA_BRIDGE_UI_START_NAMESPACE
  73. // -------------------------------------------------------------------------
  74. struct GtkLoader {
  75. lib_t lib;
  76. gtksym_init init;
  77. gtksym_main main;
  78. gtksym_main_level main_level;
  79. gtksym_main_quit main_quit;
  80. gtksym_container_get_type container_get_type;
  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_get_type window_get_type;
  86. gtksym_window_new window_new;
  87. gtksym_window_get_position window_get_position;
  88. gtksym_window_get_size window_get_size;
  89. gtksym_window_resize window_resize;
  90. gtksym_window_set_resizable window_set_resizable;
  91. gtksym_window_set_title window_set_title;
  92. bool ok;
  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. GtkLoader()
  105. : lib(nullptr),
  106. init(nullptr),
  107. main(nullptr),
  108. main_level(nullptr),
  109. main_quit(nullptr),
  110. container_get_type(nullptr),
  111. container_add(nullptr),
  112. widget_destroy(nullptr),
  113. widget_hide(nullptr),
  114. widget_show_all(nullptr),
  115. window_get_type(nullptr),
  116. window_new(nullptr),
  117. window_get_position(nullptr),
  118. window_get_size(nullptr),
  119. window_resize(nullptr),
  120. window_set_resizable(nullptr),
  121. window_set_title(nullptr),
  122. ok(false)
  123. #ifdef HAVE_X11
  124. , widget_get_window(nullptr),
  125. # ifdef BRIDGE_GTK3
  126. window_get_display(nullptr),
  127. x11_display_get_xdisplay(nullptr),
  128. x11_window_get_xid(nullptr)
  129. # else
  130. x11_drawable_get_xdisplay(nullptr),
  131. x11_drawable_get_xid(nullptr)
  132. # endif
  133. #endif
  134. {
  135. const char* filename;
  136. const char* const filenames[] = {
  137. #ifdef BRIDGE_GTK3
  138. # if defined(CARLA_OS_MAC)
  139. "libgtk-3.0.dylib",
  140. # else
  141. "libgtk-3.so.0",
  142. # endif
  143. #else
  144. # if defined(CARLA_OS_MAC)
  145. "libgtk-quartz-2.0.dylib",
  146. "libgtk-x11-2.0.dylib",
  147. "/opt/homebrew/opt/gtk+/lib/libgtk-quartz-2.0.0.dylib",
  148. "/opt/local/lib/libgtk-quartz-2.0.dylib",
  149. "/opt/local/lib/libgtk-x11-2.0.dylib",
  150. # else
  151. "libgtk-x11-2.0.so.0",
  152. # endif
  153. #endif
  154. };
  155. for (size_t i=0; i<sizeof(filenames)/sizeof(filenames[0]); ++i)
  156. {
  157. filename = filenames[i];
  158. if ((lib = lib_open(filename, true)) != nullptr)
  159. break;
  160. }
  161. if (lib == nullptr)
  162. {
  163. fprintf(stderr, "Failed to load Gtk, reason:\n%s\n", lib_error(filename));
  164. return;
  165. }
  166. else
  167. {
  168. fprintf(stdout, "%s loaded successfully!\n", filename);
  169. }
  170. #define GTK_LIB_SYMBOL(NAME) \
  171. NAME = lib_symbol<gtksym_##NAME>(lib, "gtk_" #NAME); \
  172. CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,);
  173. #define GDK_LIB_SYMBOL(NAME) \
  174. NAME = lib_symbol<gdksym_##NAME>(lib, "gdk_" #NAME);
  175. GTK_LIB_SYMBOL(init)
  176. GTK_LIB_SYMBOL(main)
  177. GTK_LIB_SYMBOL(main_level)
  178. GTK_LIB_SYMBOL(main_quit)
  179. GTK_LIB_SYMBOL(container_get_type)
  180. GTK_LIB_SYMBOL(container_add)
  181. GTK_LIB_SYMBOL(widget_destroy)
  182. GTK_LIB_SYMBOL(widget_hide)
  183. GTK_LIB_SYMBOL(widget_show_all)
  184. GTK_LIB_SYMBOL(window_get_type)
  185. GTK_LIB_SYMBOL(window_new)
  186. GTK_LIB_SYMBOL(window_get_position)
  187. GTK_LIB_SYMBOL(window_get_size)
  188. GTK_LIB_SYMBOL(window_resize)
  189. GTK_LIB_SYMBOL(window_set_resizable)
  190. GTK_LIB_SYMBOL(window_set_title)
  191. ok = true;
  192. #ifdef HAVE_X11
  193. GTK_LIB_SYMBOL(widget_get_window)
  194. # ifdef BRIDGE_GTK3
  195. GDK_LIB_SYMBOL(window_get_display)
  196. GDK_LIB_SYMBOL(x11_display_get_xdisplay)
  197. GDK_LIB_SYMBOL(x11_window_get_xid)
  198. # else
  199. GDK_LIB_SYMBOL(x11_drawable_get_xdisplay)
  200. GDK_LIB_SYMBOL(x11_drawable_get_xid)
  201. # endif
  202. #endif
  203. #undef GDK_LIB_SYMBOL
  204. #undef GTK_LIB_SYMBOL
  205. }
  206. ~GtkLoader()
  207. {
  208. if (lib != nullptr)
  209. lib_close(lib);
  210. }
  211. void main_quit_if_needed()
  212. {
  213. if (main_level() != 0)
  214. main_quit();
  215. }
  216. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GtkLoader)
  217. };
  218. // -------------------------------------------------------------------------
  219. static const bool gHideShowTesting = std::getenv("CARLA_UI_TESTING") != nullptr;
  220. // -------------------------------------------------------------------------
  221. class CarlaBridgeToolkitGtk : public CarlaBridgeToolkit
  222. {
  223. public:
  224. CarlaBridgeToolkitGtk(CarlaBridgeFormat* const format)
  225. : CarlaBridgeToolkit(format),
  226. gtk(),
  227. fNeedsShow(false),
  228. fWindow(nullptr),
  229. fLastX(0),
  230. fLastY(0),
  231. fLastWidth(0),
  232. fLastHeight(0)
  233. {
  234. carla_debug("CarlaBridgeToolkitGtk::CarlaBridgeToolkitGtk(%p)", format);
  235. }
  236. ~CarlaBridgeToolkitGtk() override
  237. {
  238. CARLA_SAFE_ASSERT(fWindow == nullptr);
  239. carla_debug("CarlaBridgeToolkitGtk::~CarlaBridgeToolkitGtk()");
  240. }
  241. bool init(const int /*argc*/, const char** /*argv[]*/) override
  242. {
  243. CARLA_SAFE_ASSERT_RETURN(fWindow == nullptr, false);
  244. carla_debug("CarlaBridgeToolkitGtk::init()");
  245. if (! gtk.ok)
  246. return false;
  247. static int gargc = 0;
  248. static char** gargv = nullptr;
  249. gtk.init(&gargc, &gargv);
  250. fWindow = gtk.window_new(GTK_WINDOW_TOPLEVEL);
  251. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr, false);
  252. gtk.window_resize(GTK_WINDOW(fWindow), 30, 30);
  253. gtk.widget_hide(fWindow);
  254. return true;
  255. }
  256. void exec(const bool showUI) override
  257. {
  258. CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
  259. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  260. carla_debug("CarlaBridgeToolkitGtk::exec(%s)", bool2str(showUI));
  261. const CarlaBridgeFormat::Options& options(fPlugin->getOptions());
  262. GtkWindow* const gtkWindow(GTK_WINDOW(fWindow));
  263. CARLA_SAFE_ASSERT_RETURN(gtkWindow != nullptr,);
  264. GtkWidget* const widget((GtkWidget*)fPlugin->getWidget());
  265. CARLA_SAFE_ASSERT_RETURN(widget != nullptr,);
  266. gtk.container_add(GTK_CONTAINER(fWindow), widget);
  267. gtk.window_set_resizable(gtkWindow, options.isResizable);
  268. gtk.window_set_title(gtkWindow, options.windowTitle.buffer());
  269. if (showUI || fNeedsShow)
  270. {
  271. show();
  272. fNeedsShow = false;
  273. }
  274. g_timeout_add(30, gtk_ui_timeout, this);
  275. g_signal_connect(fWindow, "destroy", G_CALLBACK(gtk_ui_destroy), this);
  276. g_signal_connect(fWindow, "realize", G_CALLBACK(gtk_ui_realize), this);
  277. // First idle
  278. handleTimeout();
  279. // Main loop
  280. gtk.main();
  281. }
  282. void quit() override
  283. {
  284. carla_debug("CarlaBridgeToolkitGtk::quit()");
  285. if (fWindow != nullptr)
  286. {
  287. gtk.widget_destroy(fWindow);
  288. fWindow = nullptr;
  289. gtk.main_quit_if_needed();
  290. }
  291. }
  292. void show() override
  293. {
  294. carla_debug("CarlaBridgeToolkitGtk::show()");
  295. fNeedsShow = true;
  296. if (fWindow != nullptr)
  297. gtk.widget_show_all(fWindow);
  298. }
  299. void focus() override
  300. {
  301. carla_debug("CarlaBridgeToolkitGtk::focus()");
  302. }
  303. void hide() override
  304. {
  305. carla_debug("CarlaBridgeToolkitGtk::hide()");
  306. fNeedsShow = false;
  307. if (fWindow != nullptr)
  308. gtk.widget_hide(fWindow);
  309. }
  310. void setChildWindow(void* const) override {}
  311. void setSize(const uint width, const uint height) override
  312. {
  313. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  314. carla_debug("CarlaBridgeToolkitGtk::resize(%i, %i)", width, height);
  315. gtk.window_resize(GTK_WINDOW(fWindow), static_cast<int>(width), static_cast<int>(height));
  316. }
  317. void setTitle(const char* const title) override
  318. {
  319. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  320. carla_debug("CarlaBridgeToolkitGtk::setTitle(\"%s\")", title);
  321. gtk.window_set_title(GTK_WINDOW(fWindow), title);
  322. }
  323. // ---------------------------------------------------------------------
  324. protected:
  325. GtkLoader gtk;
  326. bool fNeedsShow;
  327. GtkWidget* fWindow;
  328. int fLastX;
  329. int fLastY;
  330. int fLastWidth;
  331. int fLastHeight;
  332. void handleDestroy()
  333. {
  334. carla_debug("CarlaBridgeToolkitGtk::handleDestroy()");
  335. fWindow = nullptr;
  336. gtk.main_quit_if_needed();
  337. }
  338. void handleRealize()
  339. {
  340. carla_debug("CarlaBridgeToolkitGtk::handleRealize()");
  341. #ifdef HAVE_X11
  342. const CarlaBridgeFormat::Options& options(fPlugin->getOptions());
  343. if (options.transientWindowId != 0)
  344. setTransient(options.transientWindowId);
  345. #endif
  346. }
  347. int handleTimeout()
  348. {
  349. if (fWindow != nullptr)
  350. {
  351. gtk.window_get_position(GTK_WINDOW(fWindow), &fLastX, &fLastY);
  352. gtk.window_get_size(GTK_WINDOW(fWindow), &fLastWidth, &fLastHeight);
  353. }
  354. if (fPlugin->isPipeRunning())
  355. fPlugin->idlePipe();
  356. fPlugin->idleUI();
  357. if (gHideShowTesting)
  358. {
  359. static int counter = 0;
  360. ++counter;
  361. if (counter == 100)
  362. {
  363. hide();
  364. }
  365. else if (counter == 200)
  366. {
  367. show();
  368. counter = 0;
  369. }
  370. }
  371. return 1;
  372. }
  373. #ifdef HAVE_X11
  374. void setTransient(const uintptr_t winId)
  375. {
  376. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  377. carla_debug("CarlaBridgeToolkitGtk::setTransient(0x" P_UINTPTR ")", winId);
  378. if (gtk.widget_get_window == nullptr)
  379. return;
  380. # ifdef BRIDGE_GTK3
  381. if (gtk.window_get_display == nullptr)
  382. return;
  383. if (gtk.x11_display_get_xdisplay == nullptr)
  384. return;
  385. if (gtk.x11_window_get_xid == nullptr)
  386. return;
  387. # else
  388. if (gtk.x11_drawable_get_xdisplay == nullptr)
  389. return;
  390. if (gtk.x11_drawable_get_xid == nullptr)
  391. return;
  392. # endif
  393. GdkWindow* const gdkWindow = gtk.widget_get_window(fWindow);
  394. CARLA_SAFE_ASSERT_RETURN(gdkWindow != nullptr,);
  395. # ifdef BRIDGE_GTK3
  396. GdkDisplay* const gdkDisplay = gtk.window_get_display(gdkWindow);
  397. CARLA_SAFE_ASSERT_RETURN(gdkDisplay != nullptr,);
  398. ::Display* const display = gtk.x11_display_get_xdisplay(gdkDisplay);
  399. CARLA_SAFE_ASSERT_RETURN(display != nullptr,);
  400. const ::XID xid = gtk.x11_window_get_xid(gdkWindow);
  401. CARLA_SAFE_ASSERT_RETURN(xid != 0,);
  402. # else
  403. ::Display* const display = gtk.x11_drawable_get_xdisplay((GdkDrawable*)gdkWindow);
  404. CARLA_SAFE_ASSERT_RETURN(display != nullptr,);
  405. const ::XID xid = gtk.x11_drawable_get_xid((GdkDrawable*)gdkWindow);
  406. CARLA_SAFE_ASSERT_RETURN(xid != 0,);
  407. # endif
  408. XSetTransientForHint(display, xid, static_cast< ::Window>(winId));
  409. }
  410. #endif
  411. // ---------------------------------------------------------------------
  412. private:
  413. static void gtk_ui_destroy(GtkWidget*, void* data)
  414. {
  415. CARLA_SAFE_ASSERT_RETURN(data != nullptr,);
  416. ((CarlaBridgeToolkitGtk*)data)->handleDestroy();
  417. }
  418. static void gtk_ui_realize(GtkWidget*, void* data)
  419. {
  420. CARLA_SAFE_ASSERT_RETURN(data != nullptr,);
  421. ((CarlaBridgeToolkitGtk*)data)->handleRealize();
  422. }
  423. static int gtk_ui_timeout(void* data)
  424. {
  425. CARLA_SAFE_ASSERT_RETURN(data != nullptr, false);
  426. return ((CarlaBridgeToolkitGtk*)data)->handleTimeout();
  427. }
  428. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaBridgeToolkitGtk)
  429. };
  430. // -------------------------------------------------------------------------
  431. CarlaBridgeToolkit* CarlaBridgeToolkit::createNew(CarlaBridgeFormat* const format)
  432. {
  433. return new CarlaBridgeToolkitGtk(format);
  434. }
  435. // -------------------------------------------------------------------------
  436. CARLA_BRIDGE_UI_END_NAMESPACE