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.

696 lines
24KB

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