comparison DPF-Prymula-audioplugins/dpf/distrho/extra/FileBrowserDialogImpl.cpp @ 3:84e66ea83026

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