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.

608 lines
17KB

  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. #ifdef CARLA_OS_WIN
  64. lib_t glib;
  65. lib_t golib;
  66. #endif
  67. gsym_timeout_add timeout_add;
  68. gsym_signal_connect_data signal_connect_data;
  69. gtksym_init init;
  70. gtksym_main main;
  71. gtksym_main_level main_level;
  72. gtksym_main_quit main_quit;
  73. gtksym_container_add container_add;
  74. gtksym_widget_destroy widget_destroy;
  75. gtksym_widget_hide widget_hide;
  76. gtksym_widget_show_all widget_show_all;
  77. gtksym_window_new window_new;
  78. gtksym_window_get_position window_get_position;
  79. gtksym_window_get_size window_get_size;
  80. gtksym_window_resize window_resize;
  81. gtksym_window_set_resizable window_set_resizable;
  82. gtksym_window_set_title window_set_title;
  83. bool ok;
  84. #ifdef HAVE_X11
  85. gtksym_widget_get_window widget_get_window;
  86. # ifdef BRIDGE_GTK3
  87. gdksym_window_get_display window_get_display;
  88. gdksym_x11_display_get_xdisplay x11_display_get_xdisplay;
  89. gdksym_x11_window_get_xid x11_window_get_xid;
  90. # else
  91. gdksym_x11_drawable_get_xdisplay x11_drawable_get_xdisplay;
  92. gdksym_x11_drawable_get_xid x11_drawable_get_xid;
  93. # endif
  94. #endif
  95. GtkLoader()
  96. : lib(nullptr),
  97. #ifdef CARLA_OS_WIN
  98. glib(nullptr),
  99. golib(nullptr),
  100. #endif
  101. timeout_add(nullptr),
  102. signal_connect_data(nullptr),
  103. init(nullptr),
  104. main(nullptr),
  105. main_level(nullptr),
  106. main_quit(nullptr),
  107. container_add(nullptr),
  108. widget_destroy(nullptr),
  109. widget_hide(nullptr),
  110. widget_show_all(nullptr),
  111. window_new(nullptr),
  112. window_get_position(nullptr),
  113. window_get_size(nullptr),
  114. window_resize(nullptr),
  115. window_set_resizable(nullptr),
  116. window_set_title(nullptr),
  117. ok(false)
  118. #ifdef HAVE_X11
  119. , widget_get_window(nullptr),
  120. # ifdef BRIDGE_GTK3
  121. window_get_display(nullptr),
  122. x11_display_get_xdisplay(nullptr),
  123. x11_window_get_xid(nullptr)
  124. # else
  125. x11_drawable_get_xdisplay(nullptr),
  126. x11_drawable_get_xid(nullptr)
  127. # endif
  128. #endif
  129. {
  130. const char* filename;
  131. const char* const filenames[] = {
  132. #ifdef BRIDGE_GTK3
  133. # if defined(CARLA_OS_MAC)
  134. "libgtk-3.0.dylib",
  135. # elif defined(CARLA_OS_WIN)
  136. "libgtk-3-0.dll",
  137. # else
  138. "libgtk-3.so.0",
  139. # endif
  140. #else
  141. # if defined(CARLA_OS_MAC)
  142. "libgtk-quartz-2.0.dylib",
  143. "libgtk-x11-2.0.dylib",
  144. "/opt/homebrew/opt/gtk+/lib/libgtk-quartz-2.0.0.dylib",
  145. "/opt/local/lib/libgtk-quartz-2.0.dylib",
  146. "/opt/local/lib/libgtk-x11-2.0.dylib",
  147. # elif defined(CARLA_OS_WIN)
  148. "libgtk-win32-2.0-0.dll",
  149. # ifdef CARLA_OS_WIN64
  150. "C:\\msys64\\mingw64\\bin\\libgtk-win32-2.0-0.dll",
  151. # else
  152. "C:\\msys64\\mingw32\\bin\\libgtk-win32-2.0-0.dll",
  153. # endif
  154. # else
  155. "libgtk-x11-2.0.so.0",
  156. # endif
  157. #endif
  158. };
  159. for (size_t i=0; i<sizeof(filenames)/sizeof(filenames[0]); ++i)
  160. {
  161. filename = filenames[i];
  162. if ((lib = lib_open(filename, true)) != nullptr)
  163. break;
  164. }
  165. if (lib == nullptr)
  166. {
  167. fprintf(stderr, "Failed to load Gtk, reason:\n%s\n", lib_error(filename));
  168. return;
  169. }
  170. else
  171. {
  172. fprintf(stdout, "%s loaded successfully!\n", filename);
  173. }
  174. #ifdef CARLA_OS_WIN
  175. const char* gfilename;
  176. const char* const gfilenames[] = {
  177. "libglib-2.0-0.dll",
  178. # ifdef CARLA_OS_WIN64
  179. "C:\\msys64\\mingw64\\bin\\libglib-2.0-0.dll",
  180. # else
  181. "C:\\msys64\\mingw32\\bin\\libglib-2.0-0.dll",
  182. # endif
  183. };
  184. for (size_t i=0; i<sizeof(gfilenames)/sizeof(gfilenames[0]); ++i)
  185. {
  186. gfilename = gfilenames[i];
  187. if ((glib = lib_open(gfilename, true)) != nullptr)
  188. break;
  189. }
  190. if (glib == nullptr)
  191. {
  192. fprintf(stderr, "Failed to load glib, reason:\n%s\n", lib_error(gfilename));
  193. return;
  194. }
  195. else
  196. {
  197. fprintf(stdout, "%s loaded successfully!\n", gfilename);
  198. }
  199. const char* gofilename;
  200. const char* const gofilenames[] = {
  201. "libgobject-2.0-0.dll",
  202. # ifdef CARLA_OS_WIN64
  203. "C:\\msys64\\mingw64\\bin\\libgobject-2.0-0.dll",
  204. # else
  205. "C:\\msys64\\mingw32\\bin\\libgobject-2.0-0.dll",
  206. # endif
  207. };
  208. for (size_t i=0; i<sizeof(gofilenames)/sizeof(gofilenames[0]); ++i)
  209. {
  210. gofilename = gofilenames[i];
  211. if ((golib = lib_open(gofilename, true)) != nullptr)
  212. break;
  213. }
  214. if (golib == nullptr)
  215. {
  216. fprintf(stderr, "Failed to load gobject, reason:\n%s\n", lib_error(gofilename));
  217. return;
  218. }
  219. else
  220. {
  221. fprintf(stdout, "%s loaded successfully!\n", gofilename);
  222. }
  223. #define G_LIB_SYMBOL(NAME) \
  224. NAME = lib_symbol<gsym_##NAME>(glib, "g_" #NAME); \
  225. CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,);
  226. #define GO_LIB_SYMBOL(NAME) \
  227. NAME = lib_symbol<gsym_##NAME>(golib, "g_" #NAME); \
  228. CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,);
  229. #else
  230. #define G_LIB_SYMBOL(NAME) \
  231. NAME = lib_symbol<gsym_##NAME>(lib, "g_" #NAME); \
  232. CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,);
  233. #define GO_LIB_SYMBOL G_LIB_SYMBOL
  234. #endif
  235. #define GTK_LIB_SYMBOL(NAME) \
  236. NAME = lib_symbol<gtksym_##NAME>(lib, "gtk_" #NAME); \
  237. CARLA_SAFE_ASSERT_RETURN(NAME != nullptr,);
  238. #define GDK_LIB_SYMBOL(NAME) \
  239. NAME = lib_symbol<gdksym_##NAME>(lib, "gdk_" #NAME); \
  240. CARLA_SAFE_ASSERT(NAME != nullptr);
  241. G_LIB_SYMBOL(timeout_add)
  242. GO_LIB_SYMBOL(signal_connect_data)
  243. GTK_LIB_SYMBOL(init)
  244. GTK_LIB_SYMBOL(main)
  245. GTK_LIB_SYMBOL(main_level)
  246. GTK_LIB_SYMBOL(main_quit)
  247. GTK_LIB_SYMBOL(container_add)
  248. GTK_LIB_SYMBOL(widget_destroy)
  249. GTK_LIB_SYMBOL(widget_hide)
  250. GTK_LIB_SYMBOL(widget_show_all)
  251. GTK_LIB_SYMBOL(window_new)
  252. GTK_LIB_SYMBOL(window_get_position)
  253. GTK_LIB_SYMBOL(window_get_size)
  254. GTK_LIB_SYMBOL(window_resize)
  255. GTK_LIB_SYMBOL(window_set_resizable)
  256. GTK_LIB_SYMBOL(window_set_title)
  257. ok = true;
  258. #ifdef HAVE_X11
  259. GTK_LIB_SYMBOL(widget_get_window)
  260. # ifdef BRIDGE_GTK3
  261. GDK_LIB_SYMBOL(window_get_display)
  262. GDK_LIB_SYMBOL(x11_display_get_xdisplay)
  263. GDK_LIB_SYMBOL(x11_window_get_xid)
  264. # else
  265. GDK_LIB_SYMBOL(x11_drawable_get_xdisplay)
  266. GDK_LIB_SYMBOL(x11_drawable_get_xid)
  267. # endif
  268. #endif
  269. #undef G_LIB_SYMBOL
  270. #undef GO_LIB_SYMBOL
  271. #undef GDK_LIB_SYMBOL
  272. #undef GTK_LIB_SYMBOL
  273. }
  274. ~GtkLoader()
  275. {
  276. if (lib != nullptr)
  277. lib_close(lib);
  278. #ifdef CARLA_OS_WIN
  279. if (golib != nullptr)
  280. lib_close(golib);
  281. if (glib != nullptr)
  282. lib_close(glib);
  283. #endif
  284. }
  285. void main_quit_if_needed()
  286. {
  287. if (main_level() != 0)
  288. main_quit();
  289. }
  290. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GtkLoader)
  291. };
  292. // -------------------------------------------------------------------------
  293. static const bool gHideShowTesting = std::getenv("CARLA_UI_TESTING") != nullptr;
  294. // -------------------------------------------------------------------------
  295. class CarlaBridgeToolkitGtk : public CarlaBridgeToolkit
  296. {
  297. public:
  298. CarlaBridgeToolkitGtk(CarlaBridgeFormat* const format)
  299. : CarlaBridgeToolkit(format),
  300. gtk(),
  301. fNeedsShow(false),
  302. fWindow(nullptr),
  303. fLastX(0),
  304. fLastY(0),
  305. fLastWidth(0),
  306. fLastHeight(0)
  307. {
  308. carla_debug("CarlaBridgeToolkitGtk::CarlaBridgeToolkitGtk(%p)", format);
  309. }
  310. ~CarlaBridgeToolkitGtk() override
  311. {
  312. CARLA_SAFE_ASSERT(fWindow == nullptr);
  313. carla_debug("CarlaBridgeToolkitGtk::~CarlaBridgeToolkitGtk()");
  314. }
  315. bool init(const int /*argc*/, const char** /*argv[]*/) override
  316. {
  317. CARLA_SAFE_ASSERT_RETURN(fWindow == nullptr, false);
  318. carla_debug("CarlaBridgeToolkitGtk::init()");
  319. if (! gtk.ok)
  320. return false;
  321. static int gargc = 0;
  322. static char** gargv = nullptr;
  323. gtk.init(&gargc, &gargv);
  324. fWindow = gtk.window_new(GTK_WINDOW_TOPLEVEL);
  325. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr, false);
  326. gtk.window_resize(fWindow, 30, 30);
  327. gtk.widget_hide(fWindow);
  328. return true;
  329. }
  330. void exec(const bool showUI) override
  331. {
  332. CARLA_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
  333. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  334. carla_debug("CarlaBridgeToolkitGtk::exec(%s)", bool2str(showUI));
  335. const CarlaBridgeFormat::Options& options(fPlugin->getOptions());
  336. GtkHandle* const widget((GtkHandle*)fPlugin->getWidget());
  337. CARLA_SAFE_ASSERT_RETURN(widget != nullptr,);
  338. gtk.container_add(fWindow, widget);
  339. gtk.window_set_resizable(fWindow, options.isResizable);
  340. gtk.window_set_title(fWindow, options.windowTitle.buffer());
  341. if (showUI || fNeedsShow)
  342. {
  343. show();
  344. fNeedsShow = false;
  345. }
  346. gtk.timeout_add(30, gtk_ui_timeout, this);
  347. gtk.signal_connect_data(fWindow, "destroy", gtk_ui_destroy, this, nullptr, 0);
  348. gtk.signal_connect_data(fWindow, "realize", gtk_ui_realize, this, nullptr, 0);
  349. // First idle
  350. handleTimeout();
  351. // Main loop
  352. gtk.main();
  353. }
  354. void quit() override
  355. {
  356. carla_debug("CarlaBridgeToolkitGtk::quit()");
  357. if (fWindow != nullptr)
  358. {
  359. gtk.widget_destroy(fWindow);
  360. fWindow = nullptr;
  361. gtk.main_quit_if_needed();
  362. }
  363. }
  364. void show() override
  365. {
  366. carla_debug("CarlaBridgeToolkitGtk::show()");
  367. fNeedsShow = true;
  368. if (fWindow != nullptr)
  369. gtk.widget_show_all(fWindow);
  370. }
  371. void focus() override
  372. {
  373. carla_debug("CarlaBridgeToolkitGtk::focus()");
  374. }
  375. void hide() override
  376. {
  377. carla_debug("CarlaBridgeToolkitGtk::hide()");
  378. fNeedsShow = false;
  379. if (fWindow != nullptr)
  380. gtk.widget_hide(fWindow);
  381. }
  382. void setChildWindow(void* const) override {}
  383. void setSize(const uint width, const uint height) override
  384. {
  385. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  386. carla_debug("CarlaBridgeToolkitGtk::resize(%i, %i)", width, height);
  387. gtk.window_resize(fWindow, static_cast<int>(width), static_cast<int>(height));
  388. }
  389. void setTitle(const char* const title) override
  390. {
  391. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  392. carla_debug("CarlaBridgeToolkitGtk::setTitle(\"%s\")", title);
  393. gtk.window_set_title(fWindow, title);
  394. }
  395. // ---------------------------------------------------------------------
  396. protected:
  397. GtkLoader gtk;
  398. bool fNeedsShow;
  399. GtkHandle* fWindow;
  400. int fLastX;
  401. int fLastY;
  402. int fLastWidth;
  403. int fLastHeight;
  404. void handleDestroy()
  405. {
  406. carla_debug("CarlaBridgeToolkitGtk::handleDestroy()");
  407. fWindow = nullptr;
  408. gtk.main_quit_if_needed();
  409. }
  410. void handleRealize()
  411. {
  412. carla_debug("CarlaBridgeToolkitGtk::handleRealize()");
  413. #ifdef HAVE_X11
  414. const CarlaBridgeFormat::Options& options(fPlugin->getOptions());
  415. if (options.transientWindowId != 0)
  416. setTransient(options.transientWindowId);
  417. #endif
  418. }
  419. int handleTimeout()
  420. {
  421. if (fWindow != nullptr)
  422. {
  423. gtk.window_get_position(fWindow, &fLastX, &fLastY);
  424. gtk.window_get_size(fWindow, &fLastWidth, &fLastHeight);
  425. }
  426. if (fPlugin->isPipeRunning())
  427. fPlugin->idlePipe();
  428. fPlugin->idleUI();
  429. if (gHideShowTesting)
  430. {
  431. static int counter = 0;
  432. ++counter;
  433. if (counter == 100)
  434. {
  435. hide();
  436. }
  437. else if (counter == 200)
  438. {
  439. show();
  440. counter = 0;
  441. }
  442. }
  443. return 1;
  444. }
  445. #ifdef HAVE_X11
  446. void setTransient(const uintptr_t winId)
  447. {
  448. CARLA_SAFE_ASSERT_RETURN(fWindow != nullptr,);
  449. carla_debug("CarlaBridgeToolkitGtk::setTransient(0x" P_UINTPTR ")", winId);
  450. if (gtk.widget_get_window == nullptr)
  451. return;
  452. # ifdef BRIDGE_GTK3
  453. if (gtk.window_get_display == nullptr)
  454. return;
  455. if (gtk.x11_display_get_xdisplay == nullptr)
  456. return;
  457. if (gtk.x11_window_get_xid == nullptr)
  458. return;
  459. # else
  460. if (gtk.x11_drawable_get_xdisplay == nullptr)
  461. return;
  462. if (gtk.x11_drawable_get_xid == nullptr)
  463. return;
  464. # endif
  465. GtkHandle* const gdkWindow = gtk.widget_get_window(fWindow);
  466. CARLA_SAFE_ASSERT_RETURN(gdkWindow != nullptr,);
  467. # ifdef BRIDGE_GTK3
  468. GtkHandle* const gdkDisplay = gtk.window_get_display(gdkWindow);
  469. CARLA_SAFE_ASSERT_RETURN(gdkDisplay != nullptr,);
  470. ::Display* const display = gtk.x11_display_get_xdisplay(gdkDisplay);
  471. CARLA_SAFE_ASSERT_RETURN(display != nullptr,);
  472. const ::XID xid = gtk.x11_window_get_xid(gdkWindow);
  473. CARLA_SAFE_ASSERT_RETURN(xid != 0,);
  474. # else
  475. ::Display* const display = gtk.x11_drawable_get_xdisplay((GtkHandle*)gdkWindow);
  476. CARLA_SAFE_ASSERT_RETURN(display != nullptr,);
  477. const ::XID xid = gtk.x11_drawable_get_xid((GtkHandle*)gdkWindow);
  478. CARLA_SAFE_ASSERT_RETURN(xid != 0,);
  479. # endif
  480. XSetTransientForHint(display, xid, static_cast< ::Window>(winId));
  481. }
  482. #endif
  483. // ---------------------------------------------------------------------
  484. private:
  485. static void gtk_ui_destroy(GtkHandle*, void* data)
  486. {
  487. CARLA_SAFE_ASSERT_RETURN(data != nullptr,);
  488. ((CarlaBridgeToolkitGtk*)data)->handleDestroy();
  489. }
  490. static void gtk_ui_realize(GtkHandle*, void* data)
  491. {
  492. CARLA_SAFE_ASSERT_RETURN(data != nullptr,);
  493. ((CarlaBridgeToolkitGtk*)data)->handleRealize();
  494. }
  495. static int gtk_ui_timeout(void* data)
  496. {
  497. CARLA_SAFE_ASSERT_RETURN(data != nullptr, false);
  498. return ((CarlaBridgeToolkitGtk*)data)->handleTimeout();
  499. }
  500. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaBridgeToolkitGtk)
  501. };
  502. // -------------------------------------------------------------------------
  503. CarlaBridgeToolkit* CarlaBridgeToolkit::createNew(CarlaBridgeFormat* const format)
  504. {
  505. return new CarlaBridgeToolkitGtk(format);
  506. }
  507. // -------------------------------------------------------------------------
  508. CARLA_BRIDGE_UI_END_NAMESPACE