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.

507 lines
17KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2021 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. #include "FileBrowserDialog.hpp"
  17. #include "ScopedPointer.hpp"
  18. #ifdef DISTRHO_OS_MAC
  19. # import <Cocoa/Cocoa.h>
  20. #endif
  21. #ifdef DISTRHO_OS_WINDOWS
  22. # include <process.h>
  23. # include <vector>
  24. #endif
  25. #ifdef HAVE_DBUS
  26. # include <dbus/dbus.h>
  27. #endif
  28. #ifdef HAVE_X11
  29. # define DBLCLKTME 400
  30. # include "sofd/libsofd.h"
  31. # include "sofd/libsofd.c"
  32. #endif
  33. START_NAMESPACE_DISTRHO
  34. // --------------------------------------------------------------------------------------------------------------------
  35. // static pointer used for signal null/none action taken
  36. static const char* const kSelectedFileCancelled = "__dpf_cancelled__";
  37. struct FileBrowserData {
  38. const char* selectedFile;
  39. #ifdef DISTRHO_OS_MAC
  40. NSOpenPanel* nspanel;
  41. #endif
  42. #ifdef HAVE_DBUS
  43. DBusConnection* dbuscon;
  44. #endif
  45. #ifdef HAVE_X11
  46. Display* x11display;
  47. #endif
  48. #ifdef DISTRHO_OS_WINDOWS
  49. OPENFILENAMEW ofn;
  50. volatile bool threadCancelled;
  51. uintptr_t threadHandle;
  52. std::vector<WCHAR> fileNameW;
  53. std::vector<WCHAR> startDirW;
  54. std::vector<WCHAR> titleW;
  55. const bool isEmbed;
  56. FileBrowserData(const bool embed)
  57. : selectedFile(nullptr),
  58. threadCancelled(false),
  59. threadHandle(0),
  60. fileNameW(32768),
  61. isEmbed(embed)
  62. {
  63. std::memset(&ofn, 0, sizeof(ofn));
  64. ofn.lStructSize = sizeof(ofn);
  65. ofn.lpstrFile = fileNameW.data();
  66. ofn.nMaxFile = (DWORD)fileNameW.size();
  67. }
  68. ~FileBrowserData()
  69. {
  70. if (cancelAndStop() && selectedFile != nullptr && selectedFile != kSelectedFileCancelled)
  71. std::free(const_cast<char*>(selectedFile));
  72. }
  73. void setupAndStart(const char* const startDir,
  74. const char* const windowTitle,
  75. const uintptr_t winId,
  76. const FileBrowserOptions options)
  77. {
  78. ofn.hwndOwner = (HWND)winId;
  79. ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
  80. if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
  81. ofn.Flags |= OFN_FORCESHOWHIDDEN;
  82. ofn.FlagsEx = 0x0;
  83. if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible)
  84. ofn.FlagsEx |= OFN_EX_NOPLACESBAR;
  85. startDirW.resize(std::strlen(startDir) + 1);
  86. if (MultiByteToWideChar(CP_UTF8, 0, startDir, -1, startDirW.data(), static_cast<int>(startDirW.size())))
  87. ofn.lpstrInitialDir = startDirW.data();
  88. titleW.resize(std::strlen(windowTitle) + 1);
  89. if (MultiByteToWideChar(CP_UTF8, 0, windowTitle, -1, titleW.data(), static_cast<int>(titleW.size())))
  90. ofn.lpstrTitle = titleW.data();
  91. uint threadId;
  92. threadCancelled = false;
  93. threadHandle = _beginthreadex(nullptr, 0, _run, this, 0, &threadId);
  94. }
  95. bool cancelAndStop()
  96. {
  97. threadCancelled = true;
  98. if (threadHandle == 0)
  99. return true;
  100. // if previous dialog running, carefully close its window
  101. const HWND owner = isEmbed ? GetParent(ofn.hwndOwner) : ofn.hwndOwner;
  102. if (owner != nullptr && owner != INVALID_HANDLE_VALUE)
  103. {
  104. const HWND window = GetWindow(owner, GW_HWNDFIRST);
  105. if (window != nullptr && window != INVALID_HANDLE_VALUE)
  106. {
  107. SendMessage(window, WM_SYSCOMMAND, SC_CLOSE, 0);
  108. SendMessage(window, WM_CLOSE, 0, 0);
  109. WaitForSingleObject((HANDLE)threadHandle, 5000);
  110. }
  111. }
  112. if (threadHandle == 0)
  113. return true;
  114. // not good if thread still running, but let's close the handle anyway
  115. CloseHandle((HANDLE)threadHandle);
  116. threadHandle = 0;
  117. return false;
  118. }
  119. void run()
  120. {
  121. const char* nextFile = nullptr;
  122. if (GetOpenFileNameW(&ofn))
  123. {
  124. if (threadCancelled)
  125. {
  126. threadHandle = 0;
  127. return;
  128. }
  129. // back to UTF-8
  130. std::vector<char> fileNameA(4 * 32768);
  131. if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1,
  132. fileNameA.data(), (int)fileNameA.size(),
  133. nullptr, nullptr))
  134. {
  135. nextFile = strdup(fileNameA.data());
  136. }
  137. }
  138. if (threadCancelled)
  139. {
  140. threadHandle = 0;
  141. return;
  142. }
  143. if (nextFile == nullptr)
  144. nextFile = kSelectedFileCancelled;
  145. selectedFile = nextFile;
  146. threadHandle = 0;
  147. }
  148. static unsigned __stdcall _run(void* const arg)
  149. {
  150. // CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  151. static_cast<FileBrowserData*>(arg)->run();
  152. // CoUninitialize();
  153. _endthreadex(0);
  154. return 0;
  155. }
  156. #else // DISTRHO_OS_WINDOWS
  157. FileBrowserData(bool)
  158. : selectedFile(nullptr)
  159. {
  160. #ifdef DISTRHO_OS_MAC
  161. nspanel = [[NSOpenPanel openPanel]retain];
  162. #endif
  163. #ifdef HAVE_DBUS
  164. DBusError err;
  165. dbus_error_init(&err);
  166. if ((dbuscon = dbus_bus_get(DBUS_BUS_SESSION, &err)) != nullptr)
  167. dbus_connection_set_exit_on_disconnect(dbuscon, false);
  168. dbus_error_free(&err);
  169. #endif
  170. #ifdef HAVE_X11
  171. x11display = XOpenDisplay(nullptr);
  172. #endif
  173. }
  174. ~FileBrowserData()
  175. {
  176. #ifdef DISTRHO_OS_MAC
  177. [nspanel release];
  178. #endif
  179. #ifdef HAVE_X11
  180. if (x11display != nullptr)
  181. XCloseDisplay(x11display);
  182. #endif
  183. if (selectedFile != nullptr && selectedFile != kSelectedFileCancelled)
  184. std::free(const_cast<char*>(selectedFile));
  185. }
  186. #endif
  187. };
  188. // --------------------------------------------------------------------------------------------------------------------
  189. #ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE
  190. namespace DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE {
  191. #endif
  192. // --------------------------------------------------------------------------------------------------------------------
  193. FileBrowserHandle fileBrowserOpen(const bool isEmbed,
  194. const uintptr_t windowId,
  195. const double scaleFactor,
  196. const char* const startDir,
  197. const char* const windowTitle,
  198. const FileBrowserOptions& options)
  199. {
  200. ScopedPointer<FileBrowserData> handle;
  201. handle = new FileBrowserData(isEmbed);
  202. #ifdef DISTRHO_OS_MAC
  203. NSOpenPanel* const nspanel = handle->nspanel;
  204. DISTRHO_SAFE_ASSERT_RETURN(nspanel != nullptr, nullptr);
  205. [nspanel setAllowsMultipleSelection:NO];
  206. [nspanel setCanChooseDirectories:NO];
  207. [nspanel setCanChooseFiles:YES];
  208. [nspanel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]];
  209. // TODO file filter using allowedContentTypes: [UTType]
  210. if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
  211. [nspanel setAllowsOtherFileTypes:YES];
  212. if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
  213. [nspanel setShowsHiddenFiles:YES];
  214. NSString* const titleString = [[NSString alloc]
  215. initWithBytes:windowTitle
  216. length:strlen(windowTitle)
  217. encoding:NSUTF8StringEncoding];
  218. [nspanel setTitle:titleString];
  219. dispatch_async(dispatch_get_main_queue(), ^
  220. {
  221. [nspanel beginSheetModalForWindow:[(NSView*)windowId window]
  222. completionHandler:^(NSModalResponse result)
  223. {
  224. DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr,);
  225. if (result == NSModalResponseOK && [[nspanel URL] isFileURL])
  226. {
  227. NSString* const path = [[nspanel URL] path];
  228. handle->selectedFile = strdup([path UTF8String]);
  229. }
  230. else
  231. {
  232. handle->selectedFile = kSelectedFileCancelled;
  233. }
  234. }];
  235. });
  236. #endif
  237. #ifdef DISTRHO_OS_WINDOWS
  238. handle->setupAndStart(startDir, windowTitle, windowId, options);
  239. #endif
  240. #ifdef HAVE_DBUS
  241. // optional, can be null
  242. if (DBusConnection* dbuscon = handle->dbuscon)
  243. {
  244. if (DBusMessage* const message = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
  245. "/org/freedesktop/portal/desktop",
  246. "org.freedesktop.portal.FileChooser",
  247. options.saving ? "SaveFile" : "OpenFile"))
  248. {
  249. char windowIdStr[32];
  250. memset(windowIdStr, 0, sizeof(windowIdStr));
  251. snprintf(windowIdStr, sizeof(windowIdStr)-1, "x11:%llx", (ulonglong)windowId);
  252. const char* windowIdStrPtr = windowIdStr;
  253. dbus_message_append_args(message,
  254. DBUS_TYPE_STRING, &windowIdStrPtr,
  255. DBUS_TYPE_STRING, &windowTitle,
  256. DBUS_TYPE_INVALID);
  257. DBusMessageIter iter, array;
  258. dbus_message_iter_init_append(message, &iter);
  259. dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array);
  260. // here merely as example, does nothing yet
  261. {
  262. DBusMessageIter dict, variant;
  263. const char* const property = "property";
  264. const char* const value = "value";
  265. dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, nullptr, &dict);
  266. dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &property);
  267. dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "s", &variant);
  268. dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &value);
  269. dbus_message_iter_close_container(&dict, &variant);
  270. dbus_message_iter_close_container(&array, &dict);
  271. }
  272. dbus_message_iter_close_container(&iter, &array);
  273. dbus_connection_send(dbuscon, message, nullptr);
  274. dbus_message_unref(message);
  275. return handle.release();
  276. }
  277. }
  278. #endif
  279. #ifdef HAVE_X11
  280. Display* const x11display = handle->x11display;
  281. DISTRHO_SAFE_ASSERT_RETURN(x11display != nullptr, nullptr);
  282. // unsupported at the moment
  283. if (options.saving)
  284. return nullptr;
  285. DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, nullptr);
  286. DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, windowTitle) == 0, nullptr);
  287. const int button1 = options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked ? 1
  288. : options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
  289. const int button2 = options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked ? 1
  290. : options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
  291. const int button3 = options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked ? 1
  292. : options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
  293. x_fib_cfg_buttons(1, button1);
  294. x_fib_cfg_buttons(2, button2);
  295. x_fib_cfg_buttons(3, button3);
  296. if (x_fib_show(x11display, windowId, 0, 0, scaleFactor + 0.5) != 0)
  297. return nullptr;
  298. #endif
  299. return handle.release();
  300. // might be unused
  301. (void)windowId;
  302. (void)scaleFactor;
  303. (void)startDir;
  304. (void)windowTitle;
  305. (void)options;
  306. }
  307. // --------------------------------------------------------------------------------------------------------------------
  308. // returns true if dialog was closed (with or without a file selection)
  309. bool fileBrowserIdle(const FileBrowserHandle handle)
  310. {
  311. #ifdef HAVE_DBUS
  312. if (DBusConnection* dbuscon = handle->dbuscon)
  313. {
  314. while (dbus_connection_dispatch(dbuscon) == DBUS_DISPATCH_DATA_REMAINS) {}
  315. dbus_connection_read_write_dispatch(dbuscon, 0);
  316. if (DBusMessage * message = dbus_connection_pop_message(dbuscon))
  317. {
  318. const char* const interface = dbus_message_get_interface(message);
  319. const char* const member = dbus_message_get_member(message);
  320. if (interface != nullptr && std::strcmp(interface, "org.freedesktop.portal.Request") == 0
  321. && member != nullptr && std::strcmp(member, "Response") == 0)
  322. {
  323. do {
  324. DBusMessageIter iter;
  325. dbus_message_iter_init(message, &iter);
  326. // starts with uint32 for return/exit code
  327. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32);
  328. uint32_t ret = 1;
  329. dbus_message_iter_get_basic(&iter, &ret);
  330. if (ret != 0)
  331. break;
  332. // next must be array
  333. dbus_message_iter_next(&iter);
  334. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY);
  335. // open dict array
  336. DBusMessageIter dictArray;
  337. dbus_message_iter_recurse(&iter, &dictArray);
  338. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY);
  339. // open containing dict
  340. DBusMessageIter dict;
  341. dbus_message_iter_recurse(&dictArray, &dict);
  342. // start with the string "uris"
  343. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING);
  344. const char* key = nullptr;
  345. dbus_message_iter_get_basic(&dict, &key);
  346. DISTRHO_SAFE_ASSERT_BREAK(key != nullptr && std::strcmp(key, "uris") == 0);
  347. // then comes variant
  348. dbus_message_iter_next(&dict);
  349. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_VARIANT);
  350. DBusMessageIter variant;
  351. dbus_message_iter_recurse(&dict, &variant);
  352. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_ARRAY);
  353. // open variant array (variant type is string)
  354. DBusMessageIter variantArray;
  355. dbus_message_iter_recurse(&variant, &variantArray);
  356. DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variantArray) == DBUS_TYPE_STRING);
  357. const char* value = nullptr;
  358. dbus_message_iter_get_basic(&variantArray, &value);
  359. // and finally we have our dear value, just make sure it is local
  360. DISTRHO_SAFE_ASSERT_BREAK(value != nullptr);
  361. if (const char* const localvalue = std::strstr(value, "file:///"))
  362. handle->selectedFile = strdup(localvalue + 7);
  363. } while(false);
  364. if (handle->selectedFile == nullptr)
  365. handle->selectedFile = kSelectedFileCancelled;
  366. }
  367. }
  368. }
  369. #endif
  370. #ifdef HAVE_X11
  371. Display* const x11display = handle->x11display;
  372. if (x11display == nullptr)
  373. return false;
  374. XEvent event;
  375. while (XPending(x11display) > 0)
  376. {
  377. XNextEvent(x11display, &event);
  378. if (x_fib_handle_events(x11display, &event) == 0)
  379. continue;
  380. if (x_fib_status() > 0)
  381. handle->selectedFile = x_fib_filename();
  382. else
  383. handle->selectedFile = kSelectedFileCancelled;
  384. x_fib_close(x11display);
  385. XCloseDisplay(x11display);
  386. handle->x11display = nullptr;
  387. break;
  388. }
  389. #endif
  390. return handle->selectedFile != nullptr;
  391. }
  392. // --------------------------------------------------------------------------------------------------------------------
  393. // close sofd file dialog
  394. void fileBrowserClose(const FileBrowserHandle handle)
  395. {
  396. #ifdef HAVE_X11
  397. if (Display* const x11display = handle->x11display)
  398. x_fib_close(x11display);
  399. #endif
  400. delete handle;
  401. }
  402. // --------------------------------------------------------------------------------------------------------------------
  403. // get path chosen via sofd file dialog
  404. const char* fileBrowserGetPath(const FileBrowserHandle handle)
  405. {
  406. return handle->selectedFile != kSelectedFileCancelled ? handle->selectedFile : nullptr;
  407. }
  408. // --------------------------------------------------------------------------------------------------------------------
  409. #ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE
  410. }
  411. #endif
  412. END_NAMESPACE_DISTRHO