Mercurial > hg > pub > prymula > com
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, ¤t_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 ¤t_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 |