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.

826 lines
28KB

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