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.

437 lines
12KB

  1. // SPDX-FileCopyrightText: 2011-2025 Filipe Coelho <falktx@falktx.com>
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #ifndef HAVE_PYQT
  4. # error This file should not be built
  5. #endif
  6. #include "CarlaLv2Utils.hpp"
  7. #include "CarlaPipeUtils.hpp"
  8. #include "distrho/extra/ScopedSafeLocale.hpp"
  9. // --------------------------------------------------------------------------------------------------------------------
  10. class NativePluginUI : public LV2_External_UI_Widget_Compat
  11. {
  12. public:
  13. NativePluginUI(LV2UI_Write_Function writeFunction, LV2UI_Controller controller,
  14. LV2UI_Widget* widget, const LV2_Feature* const* features)
  15. : fUridMap(nullptr),
  16. fUridUnmap(nullptr),
  17. fUridTranser(0),
  18. fUridTranser2(0),
  19. fUI()
  20. {
  21. run = extui_run;
  22. show = extui_show;
  23. hide = extui_hide;
  24. fUI.writeFunction = writeFunction;
  25. fUI.controller = controller;
  26. const LV2_URID_Map* uridMap = nullptr;
  27. const LV2_URID_Unmap* uridUnmap = nullptr;
  28. for (int i=0; features[i] != nullptr; ++i)
  29. {
  30. /**/ if (std::strcmp(features[i]->URI, LV2_URID__map) == 0)
  31. uridMap = (const LV2_URID_Map*)features[i]->data;
  32. else if (std::strcmp(features[i]->URI, LV2_URID__unmap) == 0)
  33. uridUnmap = (const LV2_URID_Unmap*)features[i]->data;
  34. }
  35. if (uridMap == nullptr)
  36. {
  37. carla_stderr("Host doesn't provide urid-map feature");
  38. return;
  39. }
  40. fUridMap = uridMap;
  41. fUridUnmap = uridUnmap;
  42. fUridTranser = uridMap->map(uridMap->handle, LV2_ATOM__eventTransfer);
  43. fUridTranser2 = uridMap->map(uridMap->handle, "urn:carla:transmitEv");
  44. // ---------------------------------------------------------------
  45. // see if the host supports external-ui
  46. for (int i=0; features[i] != nullptr; ++i)
  47. {
  48. if (std::strcmp(features[i]->URI, LV2_EXTERNAL_UI__Host) == 0 ||
  49. std::strcmp(features[i]->URI, LV2_EXTERNAL_UI_DEPRECATED_URI) == 0)
  50. {
  51. fUI.host = (const LV2_External_UI_Host*)features[i]->data;
  52. break;
  53. }
  54. }
  55. if (fUI.host != nullptr)
  56. {
  57. fUI.name = carla_strdup(fUI.host->plugin_human_id);
  58. *widget = (LV2_External_UI_Widget_Compat*)this;
  59. return;
  60. }
  61. // ---------------------------------------------------------------
  62. // no external-ui support, use showInterface
  63. for (int i=0; features[i] != nullptr; ++i)
  64. {
  65. if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) != 0)
  66. continue;
  67. const LV2_Options_Option* const options((const LV2_Options_Option*)features[i]->data);
  68. CARLA_SAFE_ASSERT_BREAK(options != nullptr);
  69. for (int j=0; options[j].key != 0; ++j)
  70. {
  71. if (options[j].key != uridMap->map(uridMap->handle, LV2_UI__windowTitle))
  72. continue;
  73. const char* const title((const char*)options[j].value);
  74. CARLA_SAFE_ASSERT_BREAK(title != nullptr && title[0] != '\0');
  75. fUI.name = carla_strdup(title);
  76. break;
  77. }
  78. break;
  79. }
  80. if (fUI.name == nullptr)
  81. fUI.name = carla_strdup("Carla");
  82. *widget = nullptr;
  83. }
  84. ~NativePluginUI()
  85. {
  86. if (fUI.isVisible)
  87. writeAtomMessage("quit");
  88. fUI.host = nullptr;
  89. fUI.writeFunction = nullptr;
  90. fUI.controller = nullptr;
  91. }
  92. // ----------------------------------------------------------------------------------------------------------------
  93. void lv2ui_port_event(uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
  94. {
  95. CARLA_SAFE_ASSERT_RETURN(buffer != nullptr,);
  96. if (format == 0)
  97. {
  98. char msg[128];
  99. const float* const valuePtr = (const float*)buffer;
  100. {
  101. const ScopedSafeLocale ssl;
  102. std::snprintf(msg, 127, "control %u %.12g", portIndex, static_cast<double>(*valuePtr));
  103. }
  104. msg[127] = '\0';
  105. writeAtomMessage(msg);
  106. return;
  107. }
  108. if (format == fUridTranser)
  109. {
  110. CARLA_SAFE_ASSERT_RETURN(bufferSize > sizeof(LV2_Atom),);
  111. const LV2_Atom* const atom = (const LV2_Atom*)buffer;
  112. if (atom->type == fUridTranser2)
  113. {
  114. const char* const msg = (const char*)(atom + 1);
  115. if (std::strcmp(msg, "quit") == 0)
  116. {
  117. handleUiClosed();
  118. }
  119. return;
  120. }
  121. }
  122. if (fUridUnmap != nullptr)
  123. {
  124. carla_stdout("lv2ui_port_event %u %u %u:%s %p",
  125. portIndex, bufferSize, format, fUridUnmap->unmap(fUridUnmap->handle, format), buffer);
  126. }
  127. }
  128. // ----------------------------------------------------------------------------------------------------------------
  129. void lv2ui_select_program(uint32_t bank, uint32_t program) const
  130. {
  131. char msg[128];
  132. std::snprintf(msg, 127, "program %u %u", bank, program);
  133. msg[127] = '\0';
  134. writeAtomMessage(msg);
  135. }
  136. // ----------------------------------------------------------------------------------------------------------------
  137. int lv2ui_idle() const
  138. {
  139. if (! fUI.isVisible)
  140. return 1;
  141. handleUiRun();
  142. return 0;
  143. }
  144. int lv2ui_show()
  145. {
  146. handleUiShow();
  147. return 0;
  148. }
  149. int lv2ui_hide()
  150. {
  151. handleUiHide();
  152. return 0;
  153. }
  154. // ----------------------------------------------------------------------------------------------------------------
  155. protected:
  156. void handleUiShow()
  157. {
  158. writeAtomMessage("show");
  159. fUI.isVisible = true;
  160. }
  161. void handleUiHide()
  162. {
  163. if (fUI.isVisible)
  164. {
  165. fUI.isVisible = false;
  166. writeAtomMessage("hide");
  167. }
  168. }
  169. void handleUiRun() const
  170. {
  171. if (fUI.isVisible)
  172. writeAtomMessage("idle");
  173. }
  174. void handleUiClosed()
  175. {
  176. fUI.isVisible = false;
  177. if (fUI.host != nullptr && fUI.host->ui_closed != nullptr && fUI.controller != nullptr)
  178. fUI.host->ui_closed(fUI.controller);
  179. fUI.host = nullptr;
  180. fUI.writeFunction = nullptr;
  181. fUI.controller = nullptr;
  182. }
  183. bool writeAtomMessage(const char* const msg) const
  184. {
  185. CARLA_SAFE_ASSERT_RETURN(fUI.writeFunction != nullptr, false);
  186. CARLA_SAFE_ASSERT_RETURN(fUridTranser != 0, false);
  187. CARLA_SAFE_ASSERT_RETURN(fUridTranser2 != 0, false);
  188. #ifdef DEBUG
  189. if (std::strcmp(msg, "idle") != 0) {
  190. carla_debug("writeAtomMessage(%s)", msg);
  191. }
  192. #endif
  193. const size_t msgSize = std::strlen(msg)+1;
  194. const size_t atomSize = sizeof(LV2_Atom) + msgSize;
  195. if (atomSize <= 128)
  196. {
  197. char atomBuf[atomSize];
  198. carla_zeroChars(atomBuf, atomSize);
  199. LV2_Atom* const atom = (LV2_Atom*)atomBuf;
  200. atom->size = static_cast<uint32_t>(msgSize);
  201. atom->type = fUridTranser2;
  202. std::memcpy(atomBuf+sizeof(LV2_Atom), msg, msgSize);
  203. fUI.writeFunction(fUI.controller, 0, static_cast<uint32_t>(atomSize), fUridTranser, atomBuf);
  204. }
  205. else
  206. {
  207. char* const atomBuf = new char[atomSize];
  208. carla_zeroChars(atomBuf, atomSize);
  209. LV2_Atom* const atom = (LV2_Atom*)atomBuf;
  210. atom->size = static_cast<uint32_t>(msgSize);
  211. atom->type = fUridTranser2;
  212. std::memcpy(atomBuf+sizeof(LV2_Atom), msg, msgSize);
  213. fUI.writeFunction(fUI.controller, 0, static_cast<uint32_t>(atomSize), fUridTranser, atomBuf);
  214. delete[] atomBuf;
  215. }
  216. return true;
  217. }
  218. // ----------------------------------------------------------------------------------------------------------------
  219. private:
  220. const LV2_URID_Map* fUridMap;
  221. const LV2_URID_Unmap* fUridUnmap;
  222. LV2_URID fUridTranser, fUridTranser2;
  223. struct UI {
  224. const LV2_External_UI_Host* host;
  225. LV2UI_Write_Function writeFunction;
  226. LV2UI_Controller controller;
  227. const char* name;
  228. bool isVisible;
  229. UI()
  230. : host(nullptr),
  231. writeFunction(nullptr),
  232. controller(nullptr),
  233. name(nullptr),
  234. isVisible(false) {}
  235. ~UI()
  236. {
  237. if (name != nullptr)
  238. {
  239. delete[] name;
  240. name = nullptr;
  241. }
  242. }
  243. CARLA_DECLARE_NON_COPYABLE(UI);
  244. } fUI;
  245. // ----------------------------------------------------------------------------------------------------------------
  246. #define handlePtr ((NativePluginUI*)handle)
  247. static void extui_run(LV2_External_UI_Widget_Compat* handle)
  248. {
  249. handlePtr->handleUiRun();
  250. }
  251. static void extui_show(LV2_External_UI_Widget_Compat* handle)
  252. {
  253. carla_debug("extui_show(%p)", handle);
  254. handlePtr->handleUiShow();
  255. }
  256. static void extui_hide(LV2_External_UI_Widget_Compat* handle)
  257. {
  258. carla_debug("extui_hide(%p)", handle);
  259. handlePtr->handleUiHide();
  260. }
  261. #undef handlePtr
  262. // -------------------------------------------------------------------
  263. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NativePluginUI)
  264. };
  265. // -----------------------------------------------------------------------
  266. // LV2 UI descriptor functions
  267. static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char*, const char*,
  268. LV2UI_Write_Function writeFunction, LV2UI_Controller controller,
  269. LV2UI_Widget* widget, const LV2_Feature* const* features)
  270. {
  271. carla_debug("lv2ui_instantiate(..., %p, %p, %p)", writeFunction, controller, widget, features);
  272. NativePluginUI* const ui = new NativePluginUI(writeFunction, controller, widget, features);
  273. // TODO: check ok
  274. return (LV2UI_Handle)ui;
  275. }
  276. #define uiPtr ((NativePluginUI*)ui)
  277. static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
  278. {
  279. carla_debug("lv2ui_port_event(%p, %i, %i, %i, %p)", ui, portIndex, bufferSize, format, buffer);
  280. uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer);
  281. }
  282. static void lv2ui_cleanup(LV2UI_Handle ui)
  283. {
  284. carla_debug("lv2ui_cleanup(%p)", ui);
  285. delete uiPtr;
  286. }
  287. static void lv2ui_select_program(LV2UI_Handle ui, uint32_t bank, uint32_t program)
  288. {
  289. carla_debug("lv2ui_select_program(%p, %i, %i)", ui, bank, program);
  290. uiPtr->lv2ui_select_program(bank, program);
  291. }
  292. static int lv2ui_idle(LV2UI_Handle ui)
  293. {
  294. return uiPtr->lv2ui_idle();
  295. }
  296. static int lv2ui_show(LV2UI_Handle ui)
  297. {
  298. carla_debug("lv2ui_show(%p)", ui);
  299. return uiPtr->lv2ui_show();
  300. }
  301. static int lv2ui_hide(LV2UI_Handle ui)
  302. {
  303. carla_debug("lv2ui_hide(%p)", ui);
  304. return uiPtr->lv2ui_hide();
  305. }
  306. static const void* lv2ui_extension_data(const char* uri)
  307. {
  308. carla_stdout("lv2ui_extension_data(\"%s\")", uri);
  309. static const LV2UI_Idle_Interface uiidle = { lv2ui_idle };
  310. static const LV2UI_Show_Interface uishow = { lv2ui_show, lv2ui_hide };
  311. static const LV2_Programs_UI_Interface uiprograms = { lv2ui_select_program };
  312. if (std::strcmp(uri, LV2_UI__idleInterface) == 0)
  313. return &uiidle;
  314. if (std::strcmp(uri, LV2_UI__showInterface) == 0)
  315. return &uishow;
  316. if (std::strcmp(uri, LV2_PROGRAMS__UIInterface) == 0)
  317. return &uiprograms;
  318. return nullptr;
  319. }
  320. #undef uiPtr
  321. // -----------------------------------------------------------------------
  322. // Startup code
  323. CARLA_PLUGIN_EXPORT
  324. const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index)
  325. {
  326. carla_debug("lv2ui_descriptor(%i)", index);
  327. static const LV2UI_Descriptor lv2UiExtDesc = {
  328. /* URI */ "http://kxstudio.sf.net/carla/ui-bridge-ext",
  329. /* instantiate */ lv2ui_instantiate,
  330. /* cleanup */ lv2ui_cleanup,
  331. /* port_event */ lv2ui_port_event,
  332. /* extension_data */ lv2ui_extension_data
  333. };
  334. return (index == 0) ? &lv2UiExtDesc : nullptr;
  335. }
  336. // -----------------------------------------------------------------------
  337. #include "CarlaPipeUtils.cpp"
  338. // -----------------------------------------------------------------------