Collection of DPF-based plugins for packaging
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.

845 lines
29KB

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