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.

332 lines
11KB

  1. /*
  2. * DISTRHO Ildaeil Plugin
  3. * Copyright (C) 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 3 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 LICENSE file.
  16. */
  17. #include "CarlaNativePlugin.h"
  18. #include <X11/Xlib.h>
  19. #include <X11/Xutil.h>
  20. #include "../FX/DistrhoPluginInfo.h"
  21. #include "DistrhoUI.hpp"
  22. #include "DistrhoPlugin.hpp"
  23. #include "ResizeHandle.hpp"
  24. START_NAMESPACE_DISTRHO
  25. class IldaeilPlugin : public Plugin
  26. {
  27. public:
  28. const NativePluginDescriptor* fCarlaPluginDescriptor;
  29. NativePluginHandle fCarlaPluginHandle;
  30. NativeHostDescriptor fCarlaHostDescriptor;
  31. CarlaHostHandle fCarlaHostHandle;
  32. UI* fUI;
  33. // ...
  34. };
  35. // -----------------------------------------------------------------------------------------------------------
  36. // shared resource pointer
  37. // carla_juce_init();
  38. class IldaeilUI : public UI
  39. {
  40. IldaeilPlugin* const fPlugin;
  41. // ResizeHandle fResizeHandle;
  42. uint fPluginCount;
  43. uint fPluginSelected;
  44. ::Window fHostWindowLookingToResize;
  45. public:
  46. IldaeilUI()
  47. : UI(1280, 720),
  48. fPlugin((IldaeilPlugin*)getPluginInstancePointer()),
  49. // fResizeHandle(this),
  50. fPluginCount(0),
  51. fPluginSelected(0),
  52. fHostWindowLookingToResize(0)
  53. {
  54. using namespace CarlaBackend;
  55. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  56. return;
  57. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  58. if (carla_get_current_plugin_count(handle) != 0)
  59. {
  60. const CarlaPluginInfo* const info = carla_get_plugin_info(handle, 0);
  61. if (info->hints & PLUGIN_HAS_CUSTOM_UI) // FIXME use PLUGIN_HAS_CUSTOM_EMBED_UI
  62. {
  63. const uintptr_t winId = getWindow().getNativeWindowHandle();
  64. carla_embed_custom_ui(handle, 0, (void*)winId);
  65. fHostWindowLookingToResize = (::Window)winId;
  66. tryResizingToChildWindowContent();
  67. }
  68. }
  69. // start cache/lookup, maybe spawn thread for this?
  70. fPluginCount = carla_get_cached_plugin_count(PLUGIN_LV2, nullptr);
  71. for (uint i=0; i<fPluginCount; ++i)
  72. carla_get_cached_plugin_info(PLUGIN_LV2, i);
  73. }
  74. ~IldaeilUI() override
  75. {
  76. if (fPlugin != nullptr)
  77. {
  78. fPlugin->fUI = nullptr;
  79. if (fPlugin->fCarlaHostHandle != nullptr)
  80. carla_show_custom_ui(fPlugin->fCarlaHostHandle, 0, false);
  81. }
  82. }
  83. void onImGuiDisplay() override
  84. {
  85. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  86. return;
  87. const CarlaHostHandle handle = fPlugin->fCarlaHostHandle;
  88. if (carla_get_current_plugin_count(handle) != 0)
  89. return;
  90. float width = getWidth();
  91. float height = getHeight();
  92. float margin = 20.0f;
  93. ImGui::SetNextWindowPos(ImVec2(margin, margin));
  94. ImGui::SetNextWindowSize(ImVec2(width - 2 * margin, height - 2 * margin));
  95. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoResize))
  96. {
  97. static char searchBuf[0xff] = "Search...";
  98. ImGui::InputText("", searchBuf, sizeof(searchBuf)-1, ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_AutoSelectAll);
  99. using namespace CarlaBackend;
  100. if (ImGui::Button("Load Plugin"))
  101. {
  102. do {
  103. const CarlaCachedPluginInfo* info = carla_get_cached_plugin_info(PLUGIN_LV2, fPluginSelected);
  104. DISTRHO_SAFE_ASSERT_BREAK(info != nullptr);
  105. const char* const slash = std::strchr(info->label, DISTRHO_OS_SEP);
  106. DISTRHO_SAFE_ASSERT_BREAK(slash != nullptr);
  107. d_stdout("Loading %s...", info->name);
  108. if (carla_add_plugin(handle, BINARY_NATIVE, PLUGIN_LV2, nullptr, nullptr,
  109. slash+1, 0, 0x0, PLUGIN_OPTIONS_NULL))
  110. {
  111. const CarlaPluginInfo* const info = carla_get_plugin_info(handle, 0);
  112. if (info->hints & PLUGIN_HAS_CUSTOM_UI) // FIXME use PLUGIN_HAS_CUSTOM_EMBED_UI
  113. {
  114. const uintptr_t winId = getWindow().getNativeWindowHandle();
  115. carla_embed_custom_ui(handle, 0, (void*)winId);
  116. fHostWindowLookingToResize = (::Window)winId;
  117. tryResizingToChildWindowContent();
  118. }
  119. repaint();
  120. }
  121. } while (false);
  122. }
  123. if (ImGui::BeginChild("pluginlistwindow"))
  124. {
  125. if (ImGui::BeginTable("pluginlist", 3, ImGuiTableFlags_NoSavedSettings|ImGuiTableFlags_NoClip))
  126. {
  127. ImGui::TableSetupColumn("Name");
  128. ImGui::TableSetupColumn("Bundle");
  129. ImGui::TableSetupColumn("URI");
  130. ImGui::TableHeadersRow();
  131. const char* const search = searchBuf[0] != 0 && std::strcmp(searchBuf, "Search...") != 0 ? searchBuf : nullptr;
  132. if (fPluginCount != 0)
  133. {
  134. for (uint i=0; i<fPluginCount; ++i)
  135. {
  136. const CarlaCachedPluginInfo* info = carla_get_cached_plugin_info(PLUGIN_LV2, i);
  137. DISTRHO_SAFE_ASSERT_CONTINUE(info != nullptr);
  138. #if DISTRHO_PLUGIN_IS_SYNTH
  139. if (info->midiIns != 1 || info->audioOuts != 2)
  140. continue;
  141. #elif DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
  142. if (info->midiIns != 1 || info->midiOuts != 1)
  143. continue;
  144. if (info->audioIns != 0 || info->audioOuts != 0)
  145. continue;
  146. #else
  147. if (info->audioIns != 2 || info->audioOuts != 2)
  148. continue;
  149. #endif
  150. const char* const slash = std::strchr(info->label, DISTRHO_OS_SEP);
  151. DISTRHO_SAFE_ASSERT_CONTINUE(slash != nullptr);
  152. if (search != nullptr && strcasestr(info->name, search) == nullptr)
  153. continue;
  154. bool selected = fPluginSelected == i;
  155. ImGui::TableNextRow();
  156. ImGui::TableSetColumnIndex(0);
  157. ImGui::Selectable(info->name, &selected);
  158. ImGui::TableSetColumnIndex(1);
  159. ImGui::Selectable(slash+1, &selected);
  160. ImGui::TableSetColumnIndex(2);
  161. ImGui::TextUnformatted(info->label, slash);
  162. if (selected)
  163. fPluginSelected = i;
  164. }
  165. }
  166. ImGui::EndTable();
  167. }
  168. ImGui::EndChild();
  169. }
  170. }
  171. ImGui::End();
  172. }
  173. void uiIdle() override
  174. {
  175. if (fPlugin == nullptr || fPlugin->fCarlaHostHandle == nullptr)
  176. return;
  177. fPlugin->fCarlaPluginDescriptor->ui_idle(fPlugin->fCarlaPluginHandle);
  178. if (fHostWindowLookingToResize == 0)
  179. return;
  180. tryResizingToChildWindowContent();
  181. }
  182. private:
  183. void tryResizingToChildWindowContent()
  184. {
  185. if (::Display* const display = XOpenDisplay(nullptr))
  186. {
  187. if (const ::Window childWindow = getChildWindow(display, fHostWindowLookingToResize))
  188. {
  189. d_stdout("found child window");
  190. XSizeHints sizeHints;
  191. memset(&sizeHints, 0, sizeof(sizeHints));
  192. if (XGetNormalHints(display, childWindow, &sizeHints))
  193. {
  194. int width = 0;
  195. int height = 0;
  196. if (sizeHints.flags & PSize)
  197. {
  198. width = sizeHints.width;
  199. height = sizeHints.height;
  200. }
  201. else if (sizeHints.flags & PBaseSize)
  202. {
  203. width = sizeHints.base_width;
  204. height = sizeHints.base_height;
  205. }
  206. else if (sizeHints.flags & PMinSize)
  207. {
  208. width = sizeHints.min_width;
  209. height = sizeHints.min_height;
  210. }
  211. d_stdout("child window bounds %u %u", width, height);
  212. if (width > 1 && height > 1)
  213. {
  214. fHostWindowLookingToResize = 0;
  215. setSize(static_cast<uint>(width), static_cast<uint>(height));
  216. }
  217. }
  218. else
  219. d_stdout("child window without bounds");
  220. }
  221. XCloseDisplay(display);
  222. }
  223. }
  224. ::Window getChildWindow(::Display* const display, const ::Window hostWindow) const
  225. {
  226. ::Window rootWindow, parentWindow, ret = 0;
  227. ::Window* childWindows = nullptr;
  228. uint numChildren = 0;
  229. XQueryTree(display, hostWindow, &rootWindow, &parentWindow, &childWindows, &numChildren);
  230. if (numChildren > 0 && childWindows != nullptr)
  231. {
  232. ret = childWindows[0];
  233. XFree(childWindows);
  234. }
  235. return ret;
  236. }
  237. protected:
  238. /* --------------------------------------------------------------------------------------------------------
  239. * DSP/Plugin Callbacks */
  240. /**
  241. A parameter has changed on the plugin side.
  242. This is called by the host to inform the UI about parameter changes.
  243. */
  244. void parameterChanged(uint32_t index, float value) override
  245. {
  246. }
  247. // -------------------------------------------------------------------------------------------------------
  248. private:
  249. /**
  250. Set our UI class as non-copyable and add a leak detector just in case.
  251. */
  252. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(IldaeilUI)
  253. };
  254. /* ------------------------------------------------------------------------------------------------------------
  255. * UI entry point, called by DPF to create a new UI instance. */
  256. UI* createUI()
  257. {
  258. return new IldaeilUI();
  259. }
  260. // -----------------------------------------------------------------------------------------------------------
  261. END_NAMESPACE_DISTRHO