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.

895 lines
30KB

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