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.

854 lines
29KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2023 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_WASM
  28. # include <emscripten/emscripten.h>
  29. # include <sys/stat.h>
  30. #endif
  31. #ifdef DISTRHO_OS_WINDOWS
  32. # include <direct.h>
  33. # include <process.h>
  34. # include <winsock2.h>
  35. # include <windows.h>
  36. # include <commdlg.h>
  37. # include <vector>
  38. #else
  39. # include <unistd.h>
  40. #endif
  41. #ifdef HAVE_DBUS
  42. # include <dbus/dbus.h>
  43. #endif
  44. #ifdef HAVE_X11
  45. # define DBLCLKTME 400
  46. # include "sofd/libsofd.h"
  47. # include "sofd/libsofd.c"
  48. #endif
  49. #ifdef FILE_BROWSER_DIALOG_DGL_NAMESPACE
  50. START_NAMESPACE_DGL
  51. using DISTRHO_NAMESPACE::ScopedPointer;
  52. using DISTRHO_NAMESPACE::String;
  53. #else
  54. START_NAMESPACE_DISTRHO
  55. #endif
  56. // --------------------------------------------------------------------------------------------------------------------
  57. // static pointer used for signal null/none action taken
  58. static const char* const kSelectedFileCancelled = "__dpf_cancelled__";
  59. #ifdef HAVE_DBUS
  60. static constexpr bool isHexChar(const char c) noexcept
  61. {
  62. return c >= '0' && c <= 'f' && (c <= '9' || (c >= 'A' && c <= 'F') || c >= 'a');
  63. }
  64. static constexpr int toHexChar(const char c) noexcept
  65. {
  66. return c >= '0' && c <= '9' ? c - '0' : (c >= 'A' && c <= 'F' ? c - 'A' : c - 'a') + 10;
  67. }
  68. #endif
  69. // --------------------------------------------------------------------------------------------------------------------
  70. #ifdef DISTRHO_OS_WASM
  71. # define DISTRHO_WASM_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION
  72. # define DISTRHO_WASM_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_WASM_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION)
  73. # define DISTRHO_WASM_NAMESPACE_HELPER(NS) #NS
  74. # define DISTRHO_WASM_NAMESPACE(NS) DISTRHO_WASM_NAMESPACE_HELPER(NS)
  75. # define fileBrowserSetPathNamespaced DISTRHO_WASM_NAMESPACE_MACRO(FILE_BROWSER_DIALOG_NAMESPACE, fileBrowserSetPath)
  76. # define fileBrowserSetPathFuncName DISTRHO_WASM_NAMESPACE(FILE_BROWSER_DIALOG_NAMESPACE) "_fileBrowserSetPath"
  77. // FIXME use world class name as prefix
  78. static bool openWebBrowserFileDialog(const char* const funcname, void* const handle)
  79. {
  80. const char* const nameprefix = DISTRHO_WASM_NAMESPACE(FILE_BROWSER_DIALOG_NAMESPACE);
  81. return EM_ASM_INT({
  82. var canvasFileObjName = UTF8ToString($0) + "_file_open";
  83. var canvasFileOpenElem = document.getElementById(canvasFileObjName);
  84. var jsfuncname = UTF8ToString($1);
  85. var jsfunc = Module.cwrap(jsfuncname, 'null', ['number', 'string']);
  86. if (canvasFileOpenElem) {
  87. document.body.removeChild(canvasFileOpenElem);
  88. }
  89. canvasFileOpenElem = document.createElement('input');
  90. canvasFileOpenElem.type = 'file';
  91. canvasFileOpenElem.id = canvasFileObjName;
  92. canvasFileOpenElem.style.display = 'none';
  93. document.body.appendChild(canvasFileOpenElem);
  94. canvasFileOpenElem.onchange = function(e) {
  95. if (!canvasFileOpenElem.files) {
  96. jsfunc($2, "");
  97. return;
  98. }
  99. // store uploaded files inside a specific dir
  100. try {
  101. Module.FS.mkdir('/userfiles');
  102. } catch (e) {}
  103. var file = canvasFileOpenElem.files[0];
  104. var filename = '/userfiles/' + file.name;
  105. var reader = new FileReader();
  106. reader.onloadend = function(e) {
  107. var content = new Uint8Array(reader.result);
  108. Module.FS.writeFile(filename, content);
  109. jsfunc($2, filename);
  110. };
  111. reader.readAsArrayBuffer(file);
  112. };
  113. canvasFileOpenElem.click();
  114. return 1;
  115. }, nameprefix, funcname, handle) != 0;
  116. }
  117. static bool downloadWebBrowserFile(const char* const filename)
  118. {
  119. const char* const nameprefix = DISTRHO_WASM_NAMESPACE(FILE_BROWSER_DIALOG_NAMESPACE);
  120. return EM_ASM_INT({
  121. var canvasFileObjName = UTF8ToString($0) + "_file_save";
  122. var jsfilename = UTF8ToString($1);
  123. var canvasFileSaveElem = document.getElementById(canvasFileObjName);
  124. if (canvasFileSaveElem) {
  125. // only 1 file save allowed at once
  126. console.warn("One file save operation already in progress, refusing to open another");
  127. return 0;
  128. }
  129. canvasFileSaveElem = document.createElement('a');
  130. canvasFileSaveElem.download = jsfilename;
  131. canvasFileSaveElem.id = canvasFileObjName;
  132. canvasFileSaveElem.style.display = 'none';
  133. document.body.appendChild(canvasFileSaveElem);
  134. var content = Module.FS.readFile('/userfiles/' + jsfilename);
  135. canvasFileSaveElem.href = URL.createObjectURL(new Blob([content]));
  136. canvasFileSaveElem.click();
  137. setTimeout(function() {
  138. URL.revokeObjectURL(canvasFileSaveElem.href);
  139. document.body.removeChild(canvasFileSaveElem);
  140. }, 2000);
  141. return 1;
  142. }, nameprefix, filename) != 0;
  143. }
  144. #endif
  145. // --------------------------------------------------------------------------------------------------------------------
  146. struct FileBrowserData {
  147. const char* selectedFile;
  148. #ifdef DISTRHO_OS_MAC
  149. NSSavePanel* nsBasePanel;
  150. NSOpenPanel* nsOpenPanel;
  151. #endif
  152. #ifdef HAVE_DBUS
  153. DBusConnection* dbuscon;
  154. #endif
  155. #ifdef HAVE_X11
  156. Display* x11display;
  157. #endif
  158. #ifdef DISTRHO_OS_WASM
  159. char* defaultName;
  160. bool saving;
  161. #endif
  162. #ifdef DISTRHO_OS_WINDOWS
  163. OPENFILENAMEW ofn;
  164. volatile bool threadCancelled;
  165. uintptr_t threadHandle;
  166. std::vector<WCHAR> fileNameW;
  167. std::vector<WCHAR> startDirW;
  168. std::vector<WCHAR> titleW;
  169. const bool saving;
  170. bool isEmbed;
  171. FileBrowserData(const bool save)
  172. : selectedFile(nullptr),
  173. threadCancelled(false),
  174. threadHandle(0),
  175. fileNameW(32768),
  176. saving(save),
  177. isEmbed(false)
  178. {
  179. std::memset(&ofn, 0, sizeof(ofn));
  180. ofn.lStructSize = sizeof(ofn);
  181. ofn.lpstrFile = fileNameW.data();
  182. ofn.nMaxFile = (DWORD)fileNameW.size();
  183. }
  184. ~FileBrowserData()
  185. {
  186. if (cancelAndStop())
  187. free();
  188. }
  189. void setupAndStart(const bool embed,
  190. const char* const startDir,
  191. const char* const windowTitle,
  192. const uintptr_t winId,
  193. const FileBrowserOptions options)
  194. {
  195. isEmbed = embed;
  196. ofn.hwndOwner = (HWND)winId;
  197. ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
  198. if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
  199. ofn.Flags |= OFN_FORCESHOWHIDDEN;
  200. ofn.FlagsEx = 0x0;
  201. if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible)
  202. ofn.FlagsEx |= OFN_EX_NOPLACESBAR;
  203. startDirW.resize(std::strlen(startDir) + 1);
  204. if (MultiByteToWideChar(CP_UTF8, 0, startDir, -1, startDirW.data(), static_cast<int>(startDirW.size())))
  205. ofn.lpstrInitialDir = startDirW.data();
  206. titleW.resize(std::strlen(windowTitle) + 1);
  207. if (MultiByteToWideChar(CP_UTF8, 0, windowTitle, -1, titleW.data(), static_cast<int>(titleW.size())))
  208. ofn.lpstrTitle = titleW.data();
  209. uint threadId;
  210. threadCancelled = false;
  211. threadHandle = _beginthreadex(nullptr, 0, _run, this, 0, &threadId);
  212. }
  213. bool cancelAndStop()
  214. {
  215. threadCancelled = true;
  216. if (threadHandle == 0)
  217. return true;
  218. // if previous dialog running, carefully close its window
  219. const HWND owner = isEmbed ? GetParent(ofn.hwndOwner) : ofn.hwndOwner;
  220. if (owner != nullptr && owner != INVALID_HANDLE_VALUE)
  221. {
  222. const HWND window = GetWindow(owner, GW_HWNDFIRST);
  223. if (window != nullptr && window != INVALID_HANDLE_VALUE)
  224. {
  225. SendMessage(window, WM_SYSCOMMAND, SC_CLOSE, 0);
  226. SendMessage(window, WM_CLOSE, 0, 0);
  227. WaitForSingleObject((HANDLE)threadHandle, 5000);
  228. }
  229. }
  230. if (threadHandle == 0)
  231. return true;
  232. // not good if thread still running, but let's close the handle anyway
  233. CloseHandle((HANDLE)threadHandle);
  234. threadHandle = 0;
  235. return false;
  236. }
  237. void run()
  238. {
  239. const char* nextFile = nullptr;
  240. if (saving ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn))
  241. {
  242. if (threadCancelled)
  243. {
  244. threadHandle = 0;
  245. return;
  246. }
  247. // back to UTF-8
  248. std::vector<char> fileNameA(4 * 32768);
  249. if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1,
  250. fileNameA.data(), (int)fileNameA.size(),
  251. nullptr, nullptr))
  252. {
  253. nextFile = strdup(fileNameA.data());
  254. }
  255. }
  256. if (threadCancelled)
  257. {
  258. threadHandle = 0;
  259. return;
  260. }
  261. if (nextFile == nullptr)
  262. nextFile = kSelectedFileCancelled;
  263. selectedFile = nextFile;
  264. threadHandle = 0;
  265. }
  266. static unsigned __stdcall _run(void* const arg)
  267. {
  268. // CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  269. static_cast<FileBrowserData*>(arg)->run();
  270. // CoUninitialize();
  271. _endthreadex(0);
  272. return 0;
  273. }
  274. #else // DISTRHO_OS_WINDOWS
  275. FileBrowserData(const bool save)
  276. : selectedFile(nullptr)
  277. {
  278. #ifdef DISTRHO_OS_MAC
  279. if (save)
  280. {
  281. nsOpenPanel = nullptr;
  282. nsBasePanel = [[NSSavePanel savePanel]retain];
  283. }
  284. else
  285. {
  286. nsOpenPanel = [[NSOpenPanel openPanel]retain];
  287. nsBasePanel = nsOpenPanel;
  288. }
  289. #endif
  290. #ifdef DISTRHO_OS_WASM
  291. defaultName = nullptr;
  292. saving = save;
  293. #endif
  294. #ifdef HAVE_DBUS
  295. if ((dbuscon = dbus_bus_get(DBUS_BUS_SESSION, nullptr)) != nullptr)
  296. dbus_connection_set_exit_on_disconnect(dbuscon, false);
  297. #endif
  298. #ifdef HAVE_X11
  299. x11display = XOpenDisplay(nullptr);
  300. #endif
  301. // maybe unused
  302. return; (void)save;
  303. }
  304. ~FileBrowserData()
  305. {
  306. #ifdef DISTRHO_OS_MAC
  307. [nsBasePanel release];
  308. #endif
  309. #ifdef DISTRHO_OS_WASM
  310. std::free(defaultName);
  311. #endif
  312. #ifdef HAVE_DBUS
  313. if (dbuscon != nullptr)
  314. dbus_connection_unref(dbuscon);
  315. #endif
  316. #ifdef HAVE_X11
  317. if (x11display != nullptr)
  318. XCloseDisplay(x11display);
  319. #endif
  320. free();
  321. }
  322. #endif
  323. void free()
  324. {
  325. if (selectedFile == nullptr)
  326. return;
  327. if (selectedFile == kSelectedFileCancelled || std::strcmp(selectedFile, kSelectedFileCancelled) == 0)
  328. {
  329. selectedFile = nullptr;
  330. return;
  331. }
  332. std::free(const_cast<char*>(selectedFile));
  333. selectedFile = nullptr;
  334. }
  335. };
  336. // --------------------------------------------------------------------------------------------------------------------
  337. #ifdef DISTRHO_OS_WASM
  338. extern "C" {
  339. EMSCRIPTEN_KEEPALIVE
  340. void fileBrowserSetPathNamespaced(FileBrowserHandle handle, const char* filename)
  341. {
  342. handle->free();
  343. if (filename != nullptr && filename[0] != '\0')
  344. handle->selectedFile = strdup(filename);
  345. else
  346. handle->selectedFile = kSelectedFileCancelled;
  347. }
  348. }
  349. #endif
  350. FileBrowserHandle fileBrowserCreate(const bool isEmbed,
  351. const uintptr_t windowId,
  352. const double scaleFactor,
  353. const FileBrowserOptions& options)
  354. {
  355. String startDir(options.startDir);
  356. if (startDir.isEmpty())
  357. {
  358. #ifdef DISTRHO_OS_WINDOWS
  359. if (char* const cwd = _getcwd(nullptr, 0))
  360. #else
  361. if (char* const cwd = getcwd(nullptr, 0))
  362. #endif
  363. {
  364. startDir = cwd;
  365. std::free(cwd);
  366. }
  367. }
  368. DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), nullptr);
  369. if (! startDir.endsWith(DISTRHO_OS_SEP))
  370. startDir += DISTRHO_OS_SEP_STR;
  371. String windowTitle(options.title);
  372. if (windowTitle.isEmpty())
  373. windowTitle = "FileBrowser";
  374. ScopedPointer<FileBrowserData> handle(new FileBrowserData(options.saving));
  375. #ifdef DISTRHO_OS_MAC
  376. # if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
  377. // unsupported
  378. d_stderr2("fileBrowserCreate is unsupported on macos < 10.8");
  379. return nullptr;
  380. # else
  381. NSSavePanel* const nsBasePanel = handle->nsBasePanel;
  382. DISTRHO_SAFE_ASSERT_RETURN(nsBasePanel != nullptr, nullptr);
  383. if (! options.saving)
  384. {
  385. NSOpenPanel* const nsOpenPanel = handle->nsOpenPanel;
  386. DISTRHO_SAFE_ASSERT_RETURN(nsOpenPanel != nullptr, nullptr);
  387. [nsOpenPanel setAllowsMultipleSelection:NO];
  388. [nsOpenPanel setCanChooseDirectories:NO];
  389. [nsOpenPanel setCanChooseFiles:YES];
  390. }
  391. [nsBasePanel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]];
  392. // TODO file filter using allowedContentTypes: [UTType]
  393. if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
  394. [nsBasePanel setAllowsOtherFileTypes:YES];
  395. if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
  396. [nsBasePanel setShowsHiddenFiles:YES];
  397. NSString* const titleString = [[NSString alloc]
  398. initWithBytes:windowTitle
  399. length:strlen(windowTitle)
  400. encoding:NSUTF8StringEncoding];
  401. [nsBasePanel setTitle:titleString];
  402. FileBrowserData* const handleptr = handle.get();
  403. dispatch_async(dispatch_get_main_queue(), ^
  404. {
  405. [nsBasePanel beginSheetModalForWindow:[(NSView*)windowId window]
  406. completionHandler:^(NSModalResponse result)
  407. {
  408. if (result == NSModalResponseOK && [[nsBasePanel URL] isFileURL])
  409. {
  410. NSString* const path = [[nsBasePanel URL] path];
  411. handleptr->selectedFile = strdup([path UTF8String]);
  412. }
  413. else
  414. {
  415. handleptr->selectedFile = kSelectedFileCancelled;
  416. }
  417. }];
  418. });
  419. # endif
  420. #endif
  421. #ifdef DISTRHO_OS_WASM
  422. if (options.saving)
  423. {
  424. const size_t len = options.defaultName != nullptr ? strlen(options.defaultName) : 0;
  425. DISTRHO_SAFE_ASSERT_RETURN(len != 0, nullptr);
  426. // store uploaded files inside a specific dir
  427. mkdir("/userfiles", 0777);
  428. char* const filename = static_cast<char*>(malloc(len + 12));
  429. std::strncpy(filename, "/userfiles/", 12);
  430. std::memcpy(filename + 11, options.defaultName, len + 1);
  431. handle->defaultName = strdup(options.defaultName);
  432. handle->selectedFile = filename;
  433. return handle.release();
  434. }
  435. const char* const funcname = fileBrowserSetPathFuncName;
  436. if (openWebBrowserFileDialog(funcname, handle.get()))
  437. return handle.release();
  438. return nullptr;
  439. #endif
  440. #ifdef DISTRHO_OS_WINDOWS
  441. handle->setupAndStart(isEmbed, startDir, windowTitle, windowId, options);
  442. #endif
  443. #ifdef HAVE_DBUS
  444. // optional, can be null
  445. DBusConnection* const dbuscon = handle->dbuscon;
  446. // https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.portal.FileChooser
  447. if (dbuscon != nullptr)
  448. {
  449. // if this is the first time we are calling into DBus, check if things are working
  450. static bool checkAvailable = !dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr);
  451. if (checkAvailable)
  452. {
  453. checkAvailable = false;
  454. if (DBusMessage* const msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
  455. "/org/freedesktop/portal/desktop",
  456. "org.freedesktop.portal.FileChooser",
  457. "version"))
  458. {
  459. if (DBusMessage* const reply = dbus_connection_send_with_reply_and_block(dbuscon, msg, 250, nullptr))
  460. dbus_message_unref(reply);
  461. dbus_message_unref(msg);
  462. }
  463. }
  464. // Any subsquent calls should have this DBus service active
  465. if (dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr))
  466. {
  467. if (DBusMessage* const msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
  468. "/org/freedesktop/portal/desktop",
  469. "org.freedesktop.portal.FileChooser",
  470. options.saving ? "SaveFile" : "OpenFile"))
  471. {
  472. #ifdef HAVE_X11
  473. char windowIdStr[32];
  474. memset(windowIdStr, 0, sizeof(windowIdStr));
  475. snprintf(windowIdStr, sizeof(windowIdStr)-1, "x11:%llx", (ulonglong)windowId);
  476. const char* windowIdStrPtr = windowIdStr;
  477. #endif
  478. dbus_message_append_args(msg,
  479. #ifdef HAVE_X11
  480. DBUS_TYPE_STRING, &windowIdStrPtr,
  481. #endif
  482. DBUS_TYPE_STRING, &windowTitle,
  483. DBUS_TYPE_INVALID);
  484. DBusMessageIter iter, array;
  485. dbus_message_iter_init_append(msg, &iter);
  486. dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array);
  487. {
  488. DBusMessageIter dict, variant, variantArray;
  489. const char* const current_folder_key = "current_folder";
  490. const char* const current_folder_val = startDir.buffer();
  491. dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, nullptr, &dict);
  492. dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &current_folder_key);
  493. dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "ay", &variant);
  494. dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "y", &variantArray);
  495. dbus_message_iter_append_fixed_array(&variantArray, DBUS_TYPE_BYTE,
  496. &current_folder_val, startDir.length()+1);
  497. dbus_message_iter_close_container(&variant, &variantArray);
  498. dbus_message_iter_close_container(&dict, &variant);
  499. dbus_message_iter_close_container(&array, &dict);
  500. }
  501. dbus_message_iter_close_container(&iter, &array);
  502. dbus_connection_send(dbuscon, msg, nullptr);
  503. dbus_message_unref(msg);
  504. return handle.release();
  505. }
  506. }
  507. }
  508. #endif
  509. #ifdef HAVE_X11
  510. Display* const x11display = handle->x11display;
  511. DISTRHO_SAFE_ASSERT_RETURN(x11display != nullptr, nullptr);
  512. // unsupported at the moment
  513. if (options.saving)
  514. return nullptr;
  515. DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, nullptr);
  516. DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, windowTitle) == 0, nullptr);
  517. const int button1 = options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked ? 1
  518. : options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
  519. const int button2 = options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked ? 1
  520. : options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
  521. const int button3 = options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked ? 1
  522. : options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
  523. x_fib_cfg_buttons(1, button1);
  524. x_fib_cfg_buttons(2, button2);
  525. x_fib_cfg_buttons(3, button3);
  526. if (x_fib_show(x11display, windowId, 0, 0, scaleFactor + 0.5) != 0)
  527. return nullptr;
  528. #endif
  529. return handle.release();
  530. // might be unused
  531. (void)isEmbed;
  532. (void)windowId;
  533. (void)scaleFactor;
  534. }
  535. // --------------------------------------------------------------------------------------------------------------------
  536. // returns true if dialog was closed (with or without a file selection)
  537. bool fileBrowserIdle(const FileBrowserHandle handle)
  538. {
  539. #ifdef HAVE_DBUS
  540. if (DBusConnection* dbuscon = handle->dbuscon)
  541. {
  542. while (dbus_connection_dispatch(dbuscon) == DBUS_DISPATCH_DATA_REMAINS) {}
  543. dbus_connection_read_write_dispatch(dbuscon, 0);
  544. if (DBusMessage* const message = dbus_connection_pop_message(dbuscon))
  545. {
  546. const char* const interface = dbus_message_get_interface(message);
  547. const char* const member = dbus_message_get_member(message);
  548. if (interface != nullptr && std::strcmp(interface, "org.freedesktop.portal.Request") == 0
  549. && member != nullptr && std::strcmp(member, "Response") == 0)
  550. {
  551. do {
  552. DBusMessageIter iter;
  553. dbus_message_iter_init(message, &iter);
  554. // starts with uint32 for return/exit code
  555. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32);
  556. uint32_t ret = 1;
  557. dbus_message_iter_get_basic(&iter, &ret);
  558. if (ret != 0)
  559. break;
  560. // next must be array
  561. dbus_message_iter_next(&iter);
  562. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY);
  563. // open dict array
  564. DBusMessageIter dictArray;
  565. dbus_message_iter_recurse(&iter, &dictArray);
  566. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY);
  567. // open containing dict
  568. DBusMessageIter dict;
  569. dbus_message_iter_recurse(&dictArray, &dict);
  570. // look for dict with string "uris"
  571. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING);
  572. const char* key = nullptr;
  573. dbus_message_iter_get_basic(&dict, &key);
  574. DISTRHO_SAFE_ASSERT_BREAK(key != nullptr);
  575. // keep going until we find it
  576. while (std::strcmp(key, "uris") != 0)
  577. {
  578. key = nullptr;
  579. dbus_message_iter_next(&dictArray);
  580. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY);
  581. dbus_message_iter_recurse(&dictArray, &dict);
  582. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING);
  583. dbus_message_iter_get_basic(&dict, &key);
  584. DISTRHO_SAFE_ASSERT_BREAK(key != nullptr);
  585. }
  586. if (key == nullptr)
  587. break;
  588. // then comes variant
  589. dbus_message_iter_next(&dict);
  590. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_VARIANT);
  591. DBusMessageIter variant;
  592. dbus_message_iter_recurse(&dict, &variant);
  593. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_ARRAY);
  594. // open variant array (variant type is string)
  595. DBusMessageIter variantArray;
  596. dbus_message_iter_recurse(&variant, &variantArray);
  597. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variantArray) == DBUS_TYPE_STRING);
  598. const char* value = nullptr;
  599. dbus_message_iter_get_basic(&variantArray, &value);
  600. // and finally we have our dear value, just make sure it is local
  601. DISTRHO_SAFE_ASSERT_BREAK(value != nullptr);
  602. if (const char* const localvalue = std::strstr(value, "file:///"))
  603. {
  604. if (char* const decodedvalue = strdup(localvalue + 7))
  605. {
  606. for (char* s = decodedvalue; (s = std::strchr(s, '%')) != nullptr; ++s)
  607. {
  608. if (! isHexChar(s[1]) || ! isHexChar(s[2]))
  609. continue;
  610. const int decodedNum = toHexChar(s[1]) * 0x10 + toHexChar(s[2]);
  611. char replacementChar;
  612. switch (decodedNum)
  613. {
  614. case 0x20: replacementChar = ' '; break;
  615. case 0x22: replacementChar = '\"'; break;
  616. case 0x23: replacementChar = '#'; break;
  617. case 0x25: replacementChar = '%'; break;
  618. case 0x3c: replacementChar = '<'; break;
  619. case 0x3e: replacementChar = '>'; break;
  620. case 0x5b: replacementChar = '['; break;
  621. case 0x5c: replacementChar = '\\'; break;
  622. case 0x5d: replacementChar = ']'; break;
  623. case 0x5e: replacementChar = '^'; break;
  624. case 0x60: replacementChar = '`'; break;
  625. case 0x7b: replacementChar = '{'; break;
  626. case 0x7c: replacementChar = '|'; break;
  627. case 0x7d: replacementChar = '}'; break;
  628. case 0x7e: replacementChar = '~'; break;
  629. default: continue;
  630. }
  631. s[0] = replacementChar;
  632. std::memmove(s + 1, s + 3, std::strlen(s) - 2);
  633. }
  634. handle->selectedFile = decodedvalue;
  635. }
  636. }
  637. } while(false);
  638. if (handle->selectedFile == nullptr)
  639. handle->selectedFile = kSelectedFileCancelled;
  640. }
  641. }
  642. }
  643. #endif
  644. #ifdef HAVE_X11
  645. Display* const x11display = handle->x11display;
  646. if (x11display == nullptr)
  647. return false;
  648. XEvent event;
  649. while (XPending(x11display) > 0)
  650. {
  651. XNextEvent(x11display, &event);
  652. if (x_fib_handle_events(x11display, &event) == 0)
  653. continue;
  654. if (x_fib_status() > 0)
  655. handle->selectedFile = x_fib_filename();
  656. else
  657. handle->selectedFile = kSelectedFileCancelled;
  658. x_fib_close(x11display);
  659. XCloseDisplay(x11display);
  660. handle->x11display = nullptr;
  661. break;
  662. }
  663. #endif
  664. return handle->selectedFile != nullptr;
  665. }
  666. // --------------------------------------------------------------------------------------------------------------------
  667. // close sofd file dialog
  668. void fileBrowserClose(const FileBrowserHandle handle)
  669. {
  670. #ifdef DISTRHO_OS_WASM
  671. if (handle->saving && fileBrowserGetPath(handle) != nullptr)
  672. downloadWebBrowserFile(handle->defaultName);
  673. #endif
  674. #ifdef HAVE_X11
  675. if (Display* const x11display = handle->x11display)
  676. x_fib_close(x11display);
  677. #endif
  678. delete handle;
  679. }
  680. // --------------------------------------------------------------------------------------------------------------------
  681. // get path chosen via sofd file dialog
  682. const char* fileBrowserGetPath(const FileBrowserHandle handle)
  683. {
  684. if (const char* const selectedFile = handle->selectedFile)
  685. if (selectedFile != kSelectedFileCancelled && std::strcmp(selectedFile, kSelectedFileCancelled) != 0)
  686. return selectedFile;
  687. return nullptr;
  688. }
  689. // --------------------------------------------------------------------------------------------------------------------
  690. #ifdef FILE_BROWSER_DIALOG_DGL_NAMESPACE
  691. END_NAMESPACE_DGL
  692. #else
  693. END_NAMESPACE_DISTRHO
  694. #endif
  695. #undef FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE
  696. #undef FILE_BROWSER_DIALOG_DGL_NAMESPACE
  697. #undef FILE_BROWSER_DIALOG_NAMESPACE
  698. #undef fileBrowserSetPathNamespaced
  699. #undef fileBrowserSetPathFuncName