DISTRHO Plugin Framework
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.

600 lines
18KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * Permission to use, copy, modify, and/or distribute this software for any purpose with
  6. * or without fee is hereby granted, provided that the above copyright notice and this
  7. * permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  10. * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
  11. * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
  12. * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
  13. * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  14. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #include "DistrhoDetails.hpp"
  17. #include "DistrhoPluginUtils.hpp"
  18. #include "src/DistrhoPluginChecks.h"
  19. #include <cstddef>
  20. #ifdef DISTRHO_PROPER_CPP11_SUPPORT
  21. # include <cstdint>
  22. #else
  23. # include <stdint.h>
  24. #endif
  25. #if defined(DISTRHO_OS_WINDOWS)
  26. # include <winsock2.h>
  27. # include <windows.h>
  28. #elif defined(HAVE_X11)
  29. # define Window X11Window
  30. # include <X11/Xresource.h>
  31. # undef Window
  32. #endif
  33. #if DISTRHO_UI_FILE_BROWSER && !defined(DISTRHO_OS_MAC)
  34. # define DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION
  35. # define DISTRHO_PUGL_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION)
  36. # define x_fib_add_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_add_recent)
  37. # define x_fib_cfg_buttons DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_cfg_buttons)
  38. # define x_fib_cfg_filter_callback DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_cfg_filter_callback)
  39. # define x_fib_close DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_close)
  40. # define x_fib_configure DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_configure)
  41. # define x_fib_filename DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_filename)
  42. # define x_fib_free_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_free_recent)
  43. # define x_fib_handle_events DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_handle_events)
  44. # define x_fib_load_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_load_recent)
  45. # define x_fib_recent_at DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_at)
  46. # define x_fib_recent_count DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_count)
  47. # define x_fib_recent_file DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_file)
  48. # define x_fib_save_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_save_recent)
  49. # define x_fib_show DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_show)
  50. # define x_fib_status DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_status)
  51. # define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED
  52. # define FILE_BROWSER_DIALOG_NAMESPACE DISTRHO_NAMESPACE
  53. # define FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE
  54. START_NAMESPACE_DISTRHO
  55. # include "../extra/FileBrowserDialogImpl.hpp"
  56. END_NAMESPACE_DISTRHO
  57. # define Window X11Window
  58. # include "../extra/FileBrowserDialogImpl.cpp"
  59. # undef Window
  60. #endif
  61. #if DISTRHO_UI_WEB_VIEW && !defined(DISTRHO_OS_MAC)
  62. # define DISTRHO_WEB_VIEW_HPP_INCLUDED
  63. # define WEB_VIEW_NAMESPACE DISTRHO_NAMESPACE
  64. # define WEB_VIEW_DISTRHO_NAMESPACE
  65. START_NAMESPACE_DISTRHO
  66. # include "../extra/WebViewImpl.hpp"
  67. END_NAMESPACE_DISTRHO
  68. # define Window X11Window
  69. # include "../extra/WebViewImpl.cpp"
  70. # undef Window
  71. #endif
  72. #include "src/TopLevelWidgetPrivateData.hpp"
  73. #include "src/WindowPrivateData.hpp"
  74. #include "DistrhoUIPrivateData.hpp"
  75. START_NAMESPACE_DISTRHO
  76. /* ------------------------------------------------------------------------------------------------------------
  77. * get global scale factor */
  78. #ifdef DISTRHO_OS_MAC
  79. double getDesktopScaleFactor(uintptr_t parentWindowHandle);
  80. #else
  81. static double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
  82. {
  83. // allow custom scale for testing
  84. if (const char* const scale = getenv("DPF_SCALE_FACTOR"))
  85. return std::max(1.0, std::atof(scale));
  86. #if defined(DISTRHO_OS_WINDOWS)
  87. if (const HMODULE Shcore = LoadLibraryA("Shcore.dll"))
  88. {
  89. typedef HRESULT(WINAPI* PFN_GetProcessDpiAwareness)(HANDLE, DWORD*);
  90. typedef HRESULT(WINAPI* PFN_GetScaleFactorForMonitor)(HMONITOR, DWORD*);
  91. #if defined(__GNUC__) && (__GNUC__ >= 9)
  92. #pragma GCC diagnostic push
  93. #pragma GCC diagnostic ignored "-Wcast-function-type"
  94. #endif
  95. const PFN_GetProcessDpiAwareness GetProcessDpiAwareness
  96. = (PFN_GetProcessDpiAwareness)GetProcAddress(Shcore, "GetProcessDpiAwareness");
  97. const PFN_GetScaleFactorForMonitor GetScaleFactorForMonitor
  98. = (PFN_GetScaleFactorForMonitor)GetProcAddress(Shcore, "GetScaleFactorForMonitor");
  99. #if defined(__GNUC__) && (__GNUC__ >= 9)
  100. #pragma GCC diagnostic pop
  101. #endif
  102. DWORD dpiAware = 0;
  103. DWORD scaleFactor = 100;
  104. if (GetProcessDpiAwareness && GetScaleFactorForMonitor
  105. && GetProcessDpiAwareness(nullptr, &dpiAware) == 0 && dpiAware != 0)
  106. {
  107. const HMONITOR hMon = parentWindowHandle != 0
  108. ? MonitorFromWindow((HWND)parentWindowHandle, MONITOR_DEFAULTTOPRIMARY)
  109. : MonitorFromPoint(POINT{0,0}, MONITOR_DEFAULTTOPRIMARY);
  110. GetScaleFactorForMonitor(hMon, &scaleFactor);
  111. }
  112. FreeLibrary(Shcore);
  113. return static_cast<double>(scaleFactor) / 100.0;
  114. }
  115. #elif defined(HAVE_X11)
  116. ::Display* const display = XOpenDisplay(nullptr);
  117. DISTRHO_SAFE_ASSERT_RETURN(display != nullptr, 1.0);
  118. XrmInitialize();
  119. double dpi = 96.0;
  120. if (char* const rms = XResourceManagerString(display))
  121. {
  122. if (const XrmDatabase db = XrmGetStringDatabase(rms))
  123. {
  124. char* type = nullptr;
  125. XrmValue value = {};
  126. if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)
  127. && type != nullptr
  128. && std::strcmp(type, "String") == 0
  129. && value.addr != nullptr)
  130. {
  131. char* end = nullptr;
  132. const double xftDpi = std::strtod(value.addr, &end);
  133. if (xftDpi > 0.0 && xftDpi < HUGE_VAL)
  134. dpi = xftDpi;
  135. }
  136. XrmDestroyDatabase(db);
  137. }
  138. }
  139. XCloseDisplay(display);
  140. return dpi / 96;
  141. #endif
  142. return 1.0;
  143. // might be unused
  144. (void)parentWindowHandle;
  145. }
  146. #endif // !DISTRHO_OS_MAC
  147. /* ------------------------------------------------------------------------------------------------------------
  148. * UI::PrivateData special handling */
  149. UI::PrivateData* UI::PrivateData::s_nextPrivateData = nullptr;
  150. PluginWindow& UI::PrivateData::createNextWindow(UI* const ui, uint width, uint height)
  151. {
  152. UI::PrivateData* const uiData = s_nextPrivateData;
  153. const double scaleFactor = d_isNotZero(uiData->scaleFactor) ? uiData->scaleFactor : getDesktopScaleFactor(uiData->winId);
  154. if (d_isNotZero(scaleFactor) && d_isNotEqual(scaleFactor, 1.0))
  155. {
  156. width *= scaleFactor;
  157. height *= scaleFactor;
  158. }
  159. d_stdout("createNextWindow %u %u %d", width, height, scaleFactor);
  160. uiData->window = new PluginWindow(ui, uiData->app, uiData->winId, width, height, scaleFactor);
  161. if (uiData->callbacksPtr != nullptr)
  162. {
  163. #if DISTRHO_UI_USE_WEB_VIEW
  164. String path;
  165. if (uiData->bundlePath != nullptr)
  166. {
  167. path = getResourcePath(uiData->bundlePath);
  168. }
  169. else
  170. {
  171. path = getBinaryFilename();
  172. path.truncate(path.rfind(DISTRHO_OS_SEP));
  173. path += "/resources";
  174. }
  175. // TODO convert win32 paths to web
  176. // TODO encode paths (e.g. %20 for space)
  177. WebViewOptions opts;
  178. opts.initialJS = ""
  179. "editParameter = function(index, started){ postMessage('editparam '+index+' '+(started ? 1 : 0)) };"
  180. "setParameterValue = function(index, value){ postMessage('setparam '+index+' '+value) };"
  181. #if DISTRHO_PLUGIN_WANT_STATE
  182. "setState = function(key, value){ postMessage('setstate '+key+' '+value) };"
  183. "requestStateFile = function(key){ postMessage('reqstatefile '+key) };"
  184. #endif
  185. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  186. "sendNote = function(channel, note, velocity){ postMessage('sendnote '+channel+' '+note+' '+velocity) };"
  187. #endif
  188. ;
  189. opts.callback = webViewMessageCallback;
  190. opts.callbackPtr = uiData;
  191. uiData->webview = webViewCreate("file://" + path + "/index.html", uiData->winId, width, height, scaleFactor, opts);
  192. #endif
  193. }
  194. // If there are no callbacks, this is most likely a temporary window, so ignore idle callbacks
  195. else
  196. {
  197. uiData->window->setIgnoreIdleCallbacks();
  198. }
  199. return uiData->window.getObject();
  200. }
  201. #if DISTRHO_UI_USE_WEB_VIEW
  202. void UI::PrivateData::webViewMessageCallback(void* const arg, char* const msg)
  203. {
  204. UI::PrivateData* const uiData = static_cast<UI::PrivateData*>(arg);
  205. if (std::strncmp(msg, "setparam ", 9) == 0)
  206. {
  207. const char* const strindex = msg + 9;
  208. char* strvalue = nullptr;
  209. const ulong index = std::strtoul(strindex, &strvalue, 10);
  210. DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,);
  211. float value;
  212. {
  213. const ScopedSafeLocale ssl;
  214. value = std::atof(strvalue);
  215. }
  216. uiData->setParamCallback(index + uiData->parameterOffset, value);
  217. return;
  218. }
  219. if (std::strncmp(msg, "editparam ", 10) == 0)
  220. {
  221. const char* const strindex = msg + 10;
  222. char* strvalue = nullptr;
  223. const ulong index = std::strtoul(strindex, &strvalue, 10);
  224. DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,);
  225. const bool started = strvalue[0] != '0';
  226. uiData->editParamCallback(index + uiData->parameterOffset, started);
  227. return;
  228. }
  229. #if DISTRHO_PLUGIN_WANT_STATE
  230. if (std::strncmp(msg, "setstate ", 9) == 0)
  231. {
  232. char* const key = msg + 9;
  233. char* const sep = std::strchr(key, ' ');
  234. DISTRHO_SAFE_ASSERT_RETURN(sep != nullptr,);
  235. *sep = 0;
  236. char* const value = sep + 1;
  237. uiData->setStateCallback(key, value);
  238. return;
  239. }
  240. if (std::strncmp(msg, "reqstatefile ", 13) == 0)
  241. {
  242. const char* const key = msg + 13;
  243. uiData->fileRequestCallback(key);
  244. return;
  245. }
  246. #endif
  247. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  248. if (std::strncmp(msg, "sendnote ", 9) == 0)
  249. {
  250. const char* const strchannel = msg + 9;
  251. char* strnote = nullptr;
  252. char* strvelocity = nullptr;
  253. char* end = nullptr;
  254. const ulong channel = std::strtoul(strchannel, &strnote, 10);
  255. DISTRHO_SAFE_ASSERT_RETURN(strnote != nullptr && strchannel != strnote,);
  256. const ulong note = std::strtoul(strnote, &strvelocity, 10);
  257. DISTRHO_SAFE_ASSERT_RETURN(strvelocity != nullptr && strchannel != strvelocity,);
  258. const ulong velocity = std::strtoul(strvelocity, &end, 10);
  259. DISTRHO_SAFE_ASSERT_RETURN(end != nullptr && strvelocity != end,);
  260. uiData->sendNoteCallback(channel, note, velocity);
  261. return;
  262. }
  263. #endif
  264. d_stderr("UI received unknown message '%s'", msg);
  265. }
  266. #endif
  267. /* ------------------------------------------------------------------------------------------------------------
  268. * UI */
  269. UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetAsMinimumSize)
  270. : UIWidget(UI::PrivateData::createNextWindow(this,
  271. // width
  272. #ifdef DISTRHO_UI_DEFAULT_WIDTH
  273. width == 0 ? DISTRHO_UI_DEFAULT_WIDTH :
  274. #endif
  275. width,
  276. // height
  277. #ifdef DISTRHO_UI_DEFAULT_HEIGHT
  278. height == 0 ? DISTRHO_UI_DEFAULT_HEIGHT :
  279. #endif
  280. height
  281. )),
  282. uiData(UI::PrivateData::s_nextPrivateData)
  283. {
  284. if (width != 0 && height != 0)
  285. {
  286. Widget::setSize(width, height);
  287. if (automaticallyScaleAndSetAsMinimumSize)
  288. setGeometryConstraints(width, height, true, true, true);
  289. }
  290. #ifdef DISTRHO_UI_DEFAULT_WIDTH
  291. else
  292. {
  293. Widget::setSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT);
  294. }
  295. #endif
  296. }
  297. UI::~UI()
  298. {
  299. #if DISTRHO_UI_USE_WEB_VIEW
  300. if (uiData->webview != nullptr)
  301. webViewDestroy(uiData->webview);
  302. #endif
  303. }
  304. /* ------------------------------------------------------------------------------------------------------------
  305. * Host state */
  306. bool UI::isResizable() const noexcept
  307. {
  308. #if DISTRHO_UI_USER_RESIZABLE
  309. return uiData->window->isResizable();
  310. #else
  311. return false;
  312. #endif
  313. }
  314. uint UI::getBackgroundColor() const noexcept
  315. {
  316. return uiData->bgColor;
  317. }
  318. uint UI::getForegroundColor() const noexcept
  319. {
  320. return uiData->fgColor;
  321. }
  322. double UI::getSampleRate() const noexcept
  323. {
  324. return uiData->sampleRate;
  325. }
  326. const char* UI::getBundlePath() const noexcept
  327. {
  328. return uiData->bundlePath;
  329. }
  330. void UI::editParameter(uint32_t index, bool started)
  331. {
  332. uiData->editParamCallback(index + uiData->parameterOffset, started);
  333. }
  334. void UI::setParameterValue(uint32_t index, float value)
  335. {
  336. uiData->setParamCallback(index + uiData->parameterOffset, value);
  337. }
  338. #if DISTRHO_PLUGIN_WANT_STATE
  339. void UI::setState(const char* key, const char* value)
  340. {
  341. uiData->setStateCallback(key, value);
  342. }
  343. #endif
  344. #if DISTRHO_PLUGIN_WANT_STATE
  345. bool UI::requestStateFile(const char* key)
  346. {
  347. return uiData->fileRequestCallback(key);
  348. }
  349. #endif
  350. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  351. void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity)
  352. {
  353. uiData->sendNoteCallback(channel, note, velocity);
  354. }
  355. #endif
  356. #if DISTRHO_UI_FILE_BROWSER
  357. bool UI::openFileBrowser(const FileBrowserOptions& options)
  358. {
  359. return getWindow().openFileBrowser((DGL_NAMESPACE::FileBrowserOptions&)options);
  360. }
  361. #endif
  362. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  363. /* ------------------------------------------------------------------------------------------------------------
  364. * Direct DSP access */
  365. void* UI::getPluginInstancePointer() const noexcept
  366. {
  367. return uiData->dspPtr;
  368. }
  369. #endif
  370. /* ------------------------------------------------------------------------------------------------------------
  371. * DSP/Plugin Callbacks */
  372. void UI::parameterChanged(const uint32_t index, const float value)
  373. {
  374. #if DISTRHO_UI_USE_WEB_VIEW
  375. if (uiData->webview != nullptr)
  376. {
  377. char msg[128];
  378. {
  379. const ScopedSafeLocale ssl;
  380. std::snprintf(msg, sizeof(msg) - 1,
  381. "typeof(parameterChanged) === 'function' && parameterChanged(%u,%f)", index, value);
  382. }
  383. webViewEvaluateJS(uiData->webview, msg);
  384. }
  385. #else
  386. // unused
  387. (void)index;
  388. (void)value;
  389. #endif
  390. }
  391. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  392. void UI::programLoaded(const uint32_t index)
  393. {
  394. #if DISTRHO_UI_USE_WEB_VIEW
  395. if (uiData->webview != nullptr)
  396. {
  397. char msg[128];
  398. std::snprintf(msg, sizeof(msg) - 1,
  399. "typeof(programLoaded) === 'function' && programLoaded(%u)", index);
  400. webViewEvaluateJS(uiData->webview, msg);
  401. }
  402. #else
  403. // unused
  404. (void)index;
  405. #endif
  406. }
  407. #endif
  408. #if DISTRHO_PLUGIN_WANT_STATE
  409. void UI::stateChanged(const char* const key, const char* const value)
  410. {
  411. #if DISTRHO_UI_USE_WEB_VIEW
  412. if (uiData->webview != nullptr)
  413. {
  414. const size_t keylen = std::strlen(key);
  415. const size_t valuelen = std::strlen(value);
  416. const size_t msglen = keylen + valuelen + 60;
  417. if (char* const msg = static_cast<char*>(std::malloc(msglen)))
  418. {
  419. // TODO escape \\'
  420. std::snprintf(msg, msglen - 1,
  421. "typeof(stateChanged) === 'function' && stateChanged('%s','%s')", key, value);
  422. msg[msglen - 1] = '\0';
  423. webViewEvaluateJS(uiData->webview, msg);
  424. std::free(msg);
  425. }
  426. }
  427. #else
  428. // unused
  429. (void)key;
  430. (void)value;
  431. #endif
  432. }
  433. #endif
  434. /* ------------------------------------------------------------------------------------------------------------
  435. * DSP/Plugin Callbacks (optional) */
  436. void UI::sampleRateChanged(const double sampleRate)
  437. {
  438. #if DISTRHO_UI_USE_WEB_VIEW
  439. if (uiData->webview != nullptr)
  440. {
  441. char msg[128];
  442. {
  443. const ScopedSafeLocale ssl;
  444. std::snprintf(msg, sizeof(msg) - 1,
  445. "typeof(sampleRateChanged) === 'function' && sampleRateChanged(%f)", sampleRate);
  446. }
  447. webViewEvaluateJS(uiData->webview, msg);
  448. }
  449. #else
  450. // unused
  451. (void)sampleRate;
  452. #endif
  453. }
  454. /* ------------------------------------------------------------------------------------------------------------
  455. * UI Callbacks (optional) */
  456. void UI::uiScaleFactorChanged(double)
  457. {
  458. }
  459. std::vector<DGL_NAMESPACE::ClipboardDataOffer> UI::getClipboardDataOfferTypes()
  460. {
  461. return uiData->window->getClipboardDataOfferTypes();
  462. }
  463. uint32_t UI::uiClipboardDataOffer()
  464. {
  465. std::vector<DGL_NAMESPACE::ClipboardDataOffer> offers(uiData->window->getClipboardDataOfferTypes());
  466. for (std::vector<DGL_NAMESPACE::ClipboardDataOffer>::iterator it=offers.begin(), end=offers.end(); it != end;++it)
  467. {
  468. const DGL_NAMESPACE::ClipboardDataOffer offer = *it;
  469. if (std::strcmp(offer.type, "text/plain") == 0)
  470. return offer.id;
  471. }
  472. return 0;
  473. }
  474. void UI::uiFocus(bool, DGL_NAMESPACE::CrossingMode)
  475. {
  476. }
  477. void UI::uiReshape(const uint width, const uint height)
  478. {
  479. // NOTE this must be the same as Window::onReshape
  480. pData->fallbackOnResize(width, height);
  481. }
  482. #if DISTRHO_UI_FILE_BROWSER
  483. void UI::uiFileBrowserSelected(const char*)
  484. {
  485. }
  486. #endif
  487. /* ------------------------------------------------------------------------------------------------------------
  488. * UI Resize Handling, internal */
  489. void UI::onResize(const ResizeEvent& ev)
  490. {
  491. UIWidget::onResize(ev);
  492. #if ! DISTRHO_UI_USES_SIZE_REQUEST
  493. if (uiData->initializing)
  494. return;
  495. const uint width = ev.size.getWidth();
  496. const uint height = ev.size.getHeight();
  497. uiData->setSizeCallback(width, height);
  498. #endif
  499. }
  500. // NOTE: only used for CLAP and VST3
  501. void UI::requestSizeChange(const uint width, const uint height)
  502. {
  503. #if DISTRHO_UI_USES_SIZE_REQUEST
  504. if (uiData->initializing)
  505. uiData->window->setSizeFromHost(width, height);
  506. else
  507. uiData->setSizeCallback(width, height);
  508. #else
  509. // unused
  510. (void)width;
  511. (void)height;
  512. #endif
  513. }
  514. // -----------------------------------------------------------------------------------------------------------
  515. END_NAMESPACE_DISTRHO