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.

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