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.

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