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.

644 lines
22KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2022 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. #if !defined(DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED) && !defined(DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED)
  17. # error bad include
  18. #endif
  19. #if !defined(FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE) && !defined(FILE_BROWSER_DIALOG_DGL_NAMESPACE)
  20. # error bad usage
  21. #endif
  22. #include "ScopedPointer.hpp"
  23. #include "String.hpp"
  24. #ifdef DISTRHO_OS_MAC
  25. # import <Cocoa/Cocoa.h>
  26. #endif
  27. #ifdef DISTRHO_OS_WINDOWS
  28. # include <direct.h>
  29. # include <process.h>
  30. # include <winsock2.h>
  31. # include <windows.h>
  32. # include <commdlg.h>
  33. # include <vector>
  34. #else
  35. # include <unistd.h>
  36. #endif
  37. #ifdef HAVE_DBUS
  38. # include <dbus/dbus.h>
  39. #endif
  40. #ifdef HAVE_X11
  41. # define DBLCLKTME 400
  42. # include "sofd/libsofd.h"
  43. # include "sofd/libsofd.c"
  44. #endif
  45. #ifdef FILE_BROWSER_DIALOG_DGL_NAMESPACE
  46. START_NAMESPACE_DGL
  47. #else
  48. START_NAMESPACE_DISTRHO
  49. #endif
  50. // --------------------------------------------------------------------------------------------------------------------
  51. // static pointer used for signal null/none action taken
  52. static const char* const kSelectedFileCancelled = "__dpf_cancelled__";
  53. struct FileBrowserData {
  54. const char* selectedFile;
  55. #ifdef DISTRHO_OS_MAC
  56. NSSavePanel* nsBasePanel;
  57. NSOpenPanel* nsOpenPanel;
  58. #endif
  59. #ifdef HAVE_DBUS
  60. DBusConnection* dbuscon;
  61. #endif
  62. #ifdef HAVE_X11
  63. Display* x11display;
  64. #endif
  65. #ifdef DISTRHO_OS_WINDOWS
  66. OPENFILENAMEW ofn;
  67. volatile bool threadCancelled;
  68. uintptr_t threadHandle;
  69. std::vector<WCHAR> fileNameW;
  70. std::vector<WCHAR> startDirW;
  71. std::vector<WCHAR> titleW;
  72. const bool saving;
  73. bool isEmbed;
  74. FileBrowserData(const bool save)
  75. : selectedFile(nullptr),
  76. threadCancelled(false),
  77. threadHandle(0),
  78. fileNameW(32768),
  79. saving(save),
  80. isEmbed(false)
  81. {
  82. std::memset(&ofn, 0, sizeof(ofn));
  83. ofn.lStructSize = sizeof(ofn);
  84. ofn.lpstrFile = fileNameW.data();
  85. ofn.nMaxFile = (DWORD)fileNameW.size();
  86. }
  87. ~FileBrowserData()
  88. {
  89. if (cancelAndStop())
  90. free();
  91. }
  92. void setupAndStart(const bool embed,
  93. const char* const startDir,
  94. const char* const windowTitle,
  95. const uintptr_t winId,
  96. const FileBrowserOptions options)
  97. {
  98. isEmbed = embed;
  99. ofn.hwndOwner = (HWND)winId;
  100. ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
  101. if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
  102. ofn.Flags |= OFN_FORCESHOWHIDDEN;
  103. ofn.FlagsEx = 0x0;
  104. if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible)
  105. ofn.FlagsEx |= OFN_EX_NOPLACESBAR;
  106. startDirW.resize(std::strlen(startDir) + 1);
  107. if (MultiByteToWideChar(CP_UTF8, 0, startDir, -1, startDirW.data(), static_cast<int>(startDirW.size())))
  108. ofn.lpstrInitialDir = startDirW.data();
  109. titleW.resize(std::strlen(windowTitle) + 1);
  110. if (MultiByteToWideChar(CP_UTF8, 0, windowTitle, -1, titleW.data(), static_cast<int>(titleW.size())))
  111. ofn.lpstrTitle = titleW.data();
  112. uint threadId;
  113. threadCancelled = false;
  114. threadHandle = _beginthreadex(nullptr, 0, _run, this, 0, &threadId);
  115. }
  116. bool cancelAndStop()
  117. {
  118. threadCancelled = true;
  119. if (threadHandle == 0)
  120. return true;
  121. // if previous dialog running, carefully close its window
  122. const HWND owner = isEmbed ? GetParent(ofn.hwndOwner) : ofn.hwndOwner;
  123. if (owner != nullptr && owner != INVALID_HANDLE_VALUE)
  124. {
  125. const HWND window = GetWindow(owner, GW_HWNDFIRST);
  126. if (window != nullptr && window != INVALID_HANDLE_VALUE)
  127. {
  128. SendMessage(window, WM_SYSCOMMAND, SC_CLOSE, 0);
  129. SendMessage(window, WM_CLOSE, 0, 0);
  130. WaitForSingleObject((HANDLE)threadHandle, 5000);
  131. }
  132. }
  133. if (threadHandle == 0)
  134. return true;
  135. // not good if thread still running, but let's close the handle anyway
  136. CloseHandle((HANDLE)threadHandle);
  137. threadHandle = 0;
  138. return false;
  139. }
  140. void run()
  141. {
  142. const char* nextFile = nullptr;
  143. if (saving ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn))
  144. {
  145. if (threadCancelled)
  146. {
  147. threadHandle = 0;
  148. return;
  149. }
  150. // back to UTF-8
  151. std::vector<char> fileNameA(4 * 32768);
  152. if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1,
  153. fileNameA.data(), (int)fileNameA.size(),
  154. nullptr, nullptr))
  155. {
  156. nextFile = strdup(fileNameA.data());
  157. }
  158. }
  159. if (threadCancelled)
  160. {
  161. threadHandle = 0;
  162. return;
  163. }
  164. if (nextFile == nullptr)
  165. nextFile = kSelectedFileCancelled;
  166. selectedFile = nextFile;
  167. threadHandle = 0;
  168. }
  169. static unsigned __stdcall _run(void* const arg)
  170. {
  171. // CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  172. static_cast<FileBrowserData*>(arg)->run();
  173. // CoUninitialize();
  174. _endthreadex(0);
  175. return 0;
  176. }
  177. #else // DISTRHO_OS_WINDOWS
  178. FileBrowserData(const bool saving)
  179. : selectedFile(nullptr)
  180. {
  181. #ifdef DISTRHO_OS_MAC
  182. if (saving)
  183. {
  184. nsOpenPanel = nullptr;
  185. nsBasePanel = [[NSSavePanel savePanel]retain];
  186. }
  187. else
  188. {
  189. nsOpenPanel = [[NSOpenPanel openPanel]retain];
  190. nsBasePanel = nsOpenPanel;
  191. }
  192. #endif
  193. #ifdef HAVE_DBUS
  194. if ((dbuscon = dbus_bus_get(DBUS_BUS_SESSION, nullptr)) != nullptr)
  195. dbus_connection_set_exit_on_disconnect(dbuscon, false);
  196. #endif
  197. #ifdef HAVE_X11
  198. x11display = XOpenDisplay(nullptr);
  199. #endif
  200. // maybe unused
  201. return; (void)saving;
  202. }
  203. ~FileBrowserData()
  204. {
  205. #ifdef DISTRHO_OS_MAC
  206. [nsBasePanel release];
  207. #endif
  208. #ifdef HAVE_DBUS
  209. if (dbuscon != nullptr)
  210. dbus_connection_unref(dbuscon);
  211. #endif
  212. #ifdef HAVE_X11
  213. if (x11display != nullptr)
  214. XCloseDisplay(x11display);
  215. #endif
  216. free();
  217. }
  218. #endif
  219. void free()
  220. {
  221. if (selectedFile == nullptr)
  222. return;
  223. if (selectedFile == kSelectedFileCancelled || std::strcmp(selectedFile, kSelectedFileCancelled) == 0)
  224. {
  225. selectedFile = nullptr;
  226. return;
  227. }
  228. std::free(const_cast<char*>(selectedFile));
  229. selectedFile = nullptr;
  230. }
  231. };
  232. // --------------------------------------------------------------------------------------------------------------------
  233. FileBrowserHandle fileBrowserCreate(const bool isEmbed,
  234. const uintptr_t windowId,
  235. const double scaleFactor,
  236. const FileBrowserOptions& options)
  237. {
  238. String startDir(options.startDir);
  239. if (startDir.isEmpty())
  240. {
  241. #ifdef DISTRHO_OS_WINDOWS
  242. if (char* const cwd = _getcwd(nullptr, 0))
  243. {
  244. startDir = cwd;
  245. std::free(cwd);
  246. }
  247. #else
  248. if (char* const cwd = getcwd(nullptr, 0))
  249. {
  250. startDir = cwd;
  251. std::free(cwd);
  252. }
  253. #endif
  254. }
  255. DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), nullptr);
  256. if (! startDir.endsWith(DISTRHO_OS_SEP))
  257. startDir += DISTRHO_OS_SEP_STR;
  258. String windowTitle(options.title);
  259. if (windowTitle.isEmpty())
  260. windowTitle = "FileBrowser";
  261. ScopedPointer<FileBrowserData> handle(new FileBrowserData(options.saving));
  262. #ifdef DISTRHO_OS_MAC
  263. # if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_8
  264. // unsupported
  265. return nullptr;
  266. # else
  267. NSSavePanel* const nsBasePanel = handle->nsBasePanel;
  268. DISTRHO_SAFE_ASSERT_RETURN(nsBasePanel != nullptr, nullptr);
  269. if (! options.saving)
  270. {
  271. NSOpenPanel* const nsOpenPanel = handle->nsOpenPanel;
  272. DISTRHO_SAFE_ASSERT_RETURN(nsOpenPanel != nullptr, nullptr);
  273. [nsOpenPanel setAllowsMultipleSelection:NO];
  274. [nsOpenPanel setCanChooseDirectories:NO];
  275. [nsOpenPanel setCanChooseFiles:YES];
  276. }
  277. [nsBasePanel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]];
  278. // TODO file filter using allowedContentTypes: [UTType]
  279. if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
  280. [nsBasePanel setAllowsOtherFileTypes:YES];
  281. if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
  282. [nsBasePanel setShowsHiddenFiles:YES];
  283. NSString* const titleString = [[NSString alloc]
  284. initWithBytes:windowTitle
  285. length:strlen(windowTitle)
  286. encoding:NSUTF8StringEncoding];
  287. [nsBasePanel setTitle:titleString];
  288. FileBrowserData* const handleptr = handle.get();
  289. dispatch_async(dispatch_get_main_queue(), ^
  290. {
  291. [nsBasePanel beginSheetModalForWindow:[(NSView*)windowId window]
  292. completionHandler:^(NSModalResponse result)
  293. {
  294. if (result == NSModalResponseOK && [[nsBasePanel URL] isFileURL])
  295. {
  296. NSString* const path = [[nsBasePanel URL] path];
  297. handleptr->selectedFile = strdup([path UTF8String]);
  298. }
  299. else
  300. {
  301. handleptr->selectedFile = kSelectedFileCancelled;
  302. }
  303. }];
  304. });
  305. # endif
  306. #endif
  307. #ifdef DISTRHO_OS_WINDOWS
  308. handle->setupAndStart(isEmbed, startDir, windowTitle, windowId, options);
  309. #endif
  310. #ifdef HAVE_DBUS
  311. // optional, can be null
  312. DBusConnection* const dbuscon = handle->dbuscon;
  313. // https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.portal.FileChooser
  314. if (dbuscon != nullptr)
  315. {
  316. // if this is the first time we are calling into DBus, check if things are working
  317. static bool checkAvailable = !dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr);
  318. if (checkAvailable)
  319. {
  320. checkAvailable = false;
  321. if (DBusMessage* const msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
  322. "/org/freedesktop/portal/desktop",
  323. "org.freedesktop.portal.FileChooser",
  324. "version"))
  325. {
  326. if (DBusMessage* const reply = dbus_connection_send_with_reply_and_block(dbuscon, msg, 250, nullptr))
  327. dbus_message_unref(reply);
  328. dbus_message_unref(msg);
  329. }
  330. }
  331. // Any subsquent calls should have this DBus service active
  332. if (dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr))
  333. {
  334. if (DBusMessage* const msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
  335. "/org/freedesktop/portal/desktop",
  336. "org.freedesktop.portal.FileChooser",
  337. options.saving ? "SaveFile" : "OpenFile"))
  338. {
  339. #ifdef HAVE_X11
  340. char windowIdStr[32];
  341. memset(windowIdStr, 0, sizeof(windowIdStr));
  342. snprintf(windowIdStr, sizeof(windowIdStr)-1, "x11:%llx", (ulonglong)windowId);
  343. const char* windowIdStrPtr = windowIdStr;
  344. #endif
  345. dbus_message_append_args(msg,
  346. #ifdef HAVE_X11
  347. DBUS_TYPE_STRING, &windowIdStrPtr,
  348. #endif
  349. DBUS_TYPE_STRING, &windowTitle,
  350. DBUS_TYPE_INVALID);
  351. DBusMessageIter iter, array;
  352. dbus_message_iter_init_append(msg, &iter);
  353. dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array);
  354. {
  355. DBusMessageIter dict, variant, variantArray;
  356. const char* const current_folder_key = "current_folder";
  357. const char* const current_folder_val = startDir.buffer();
  358. dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, nullptr, &dict);
  359. dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &current_folder_key);
  360. dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "ay", &variant);
  361. dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "y", &variantArray);
  362. dbus_message_iter_append_fixed_array(&variantArray, DBUS_TYPE_BYTE,
  363. &current_folder_val, startDir.length()+1);
  364. dbus_message_iter_close_container(&variant, &variantArray);
  365. dbus_message_iter_close_container(&dict, &variant);
  366. dbus_message_iter_close_container(&array, &dict);
  367. }
  368. dbus_message_iter_close_container(&iter, &array);
  369. dbus_connection_send(dbuscon, msg, nullptr);
  370. dbus_message_unref(msg);
  371. return handle.release();
  372. }
  373. }
  374. }
  375. #endif
  376. #ifdef HAVE_X11
  377. Display* const x11display = handle->x11display;
  378. DISTRHO_SAFE_ASSERT_RETURN(x11display != nullptr, nullptr);
  379. // unsupported at the moment
  380. if (options.saving)
  381. return nullptr;
  382. DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, nullptr);
  383. DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, windowTitle) == 0, nullptr);
  384. const int button1 = options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked ? 1
  385. : options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
  386. const int button2 = options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked ? 1
  387. : options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
  388. const int button3 = options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked ? 1
  389. : options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
  390. x_fib_cfg_buttons(1, button1);
  391. x_fib_cfg_buttons(2, button2);
  392. x_fib_cfg_buttons(3, button3);
  393. if (x_fib_show(x11display, windowId, 0, 0, scaleFactor + 0.5) != 0)
  394. return nullptr;
  395. #endif
  396. return handle.release();
  397. // might be unused
  398. (void)isEmbed;
  399. (void)windowId;
  400. (void)scaleFactor;
  401. }
  402. // --------------------------------------------------------------------------------------------------------------------
  403. // returns true if dialog was closed (with or without a file selection)
  404. bool fileBrowserIdle(const FileBrowserHandle handle)
  405. {
  406. #ifdef HAVE_DBUS
  407. if (DBusConnection* dbuscon = handle->dbuscon)
  408. {
  409. while (dbus_connection_dispatch(dbuscon) == DBUS_DISPATCH_DATA_REMAINS) {}
  410. dbus_connection_read_write_dispatch(dbuscon, 0);
  411. if (DBusMessage* const message = dbus_connection_pop_message(dbuscon))
  412. {
  413. const char* const interface = dbus_message_get_interface(message);
  414. const char* const member = dbus_message_get_member(message);
  415. if (interface != nullptr && std::strcmp(interface, "org.freedesktop.portal.Request") == 0
  416. && member != nullptr && std::strcmp(member, "Response") == 0)
  417. {
  418. do {
  419. DBusMessageIter iter;
  420. dbus_message_iter_init(message, &iter);
  421. // starts with uint32 for return/exit code
  422. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32);
  423. uint32_t ret = 1;
  424. dbus_message_iter_get_basic(&iter, &ret);
  425. if (ret != 0)
  426. break;
  427. // next must be array
  428. dbus_message_iter_next(&iter);
  429. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY);
  430. // open dict array
  431. DBusMessageIter dictArray;
  432. dbus_message_iter_recurse(&iter, &dictArray);
  433. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY);
  434. // open containing dict
  435. DBusMessageIter dict;
  436. dbus_message_iter_recurse(&dictArray, &dict);
  437. // look for dict with string "uris"
  438. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING);
  439. const char* key = nullptr;
  440. dbus_message_iter_get_basic(&dict, &key);
  441. DISTRHO_SAFE_ASSERT_BREAK(key != nullptr);
  442. // keep going until we find it
  443. while (std::strcmp(key, "uris") != 0)
  444. {
  445. key = nullptr;
  446. dbus_message_iter_next(&dictArray);
  447. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY);
  448. dbus_message_iter_recurse(&dictArray, &dict);
  449. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING);
  450. dbus_message_iter_get_basic(&dict, &key);
  451. DISTRHO_SAFE_ASSERT_BREAK(key != nullptr);
  452. }
  453. if (key == nullptr)
  454. break;
  455. // then comes variant
  456. dbus_message_iter_next(&dict);
  457. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_VARIANT);
  458. DBusMessageIter variant;
  459. dbus_message_iter_recurse(&dict, &variant);
  460. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_ARRAY);
  461. // open variant array (variant type is string)
  462. DBusMessageIter variantArray;
  463. dbus_message_iter_recurse(&variant, &variantArray);
  464. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variantArray) == DBUS_TYPE_STRING);
  465. const char* value = nullptr;
  466. dbus_message_iter_get_basic(&variantArray, &value);
  467. // and finally we have our dear value, just make sure it is local
  468. DISTRHO_SAFE_ASSERT_BREAK(value != nullptr);
  469. if (const char* const localvalue = std::strstr(value, "file:///"))
  470. handle->selectedFile = strdup(localvalue + 7);
  471. } while(false);
  472. if (handle->selectedFile == nullptr)
  473. handle->selectedFile = kSelectedFileCancelled;
  474. }
  475. }
  476. }
  477. #endif
  478. #ifdef HAVE_X11
  479. Display* const x11display = handle->x11display;
  480. if (x11display == nullptr)
  481. return false;
  482. XEvent event;
  483. while (XPending(x11display) > 0)
  484. {
  485. XNextEvent(x11display, &event);
  486. if (x_fib_handle_events(x11display, &event) == 0)
  487. continue;
  488. if (x_fib_status() > 0)
  489. handle->selectedFile = x_fib_filename();
  490. else
  491. handle->selectedFile = kSelectedFileCancelled;
  492. x_fib_close(x11display);
  493. XCloseDisplay(x11display);
  494. handle->x11display = nullptr;
  495. break;
  496. }
  497. #endif
  498. return handle->selectedFile != nullptr;
  499. }
  500. // --------------------------------------------------------------------------------------------------------------------
  501. // close sofd file dialog
  502. void fileBrowserClose(const FileBrowserHandle handle)
  503. {
  504. #ifdef HAVE_X11
  505. if (Display* const x11display = handle->x11display)
  506. x_fib_close(x11display);
  507. #endif
  508. delete handle;
  509. }
  510. // --------------------------------------------------------------------------------------------------------------------
  511. // get path chosen via sofd file dialog
  512. const char* fileBrowserGetPath(const FileBrowserHandle handle)
  513. {
  514. if (const char* const selectedFile = handle->selectedFile)
  515. if (selectedFile != kSelectedFileCancelled && std::strcmp(selectedFile, kSelectedFileCancelled) != 0)
  516. return selectedFile;
  517. return nullptr;
  518. }
  519. // --------------------------------------------------------------------------------------------------------------------
  520. #ifdef FILE_BROWSER_DIALOG_DGL_NAMESPACE
  521. END_NAMESPACE_DGL
  522. #else
  523. END_NAMESPACE_DISTRHO
  524. #endif
  525. #undef FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE
  526. #undef FILE_BROWSER_DIALOG_DGL_NAMESPACE