commit 29709cbe4e1f5d97b7630fd1664e2eb1210bb82f
parent fbbfe11a5bb1a9d02f9fa7a341caae7e7d532e7e
Author: Filipe Coelho <falktx@falktx.com>
Date: Fri, 12 Nov 2021 15:11:21 +0000
UI filebrowser saving mode, separate from pugl/DGL/Window (#349)
* Add UI::openFileBrowser that matches Window::openFileBrowser
* Add empty implementation so it builds
* Move file browser dialog implementation into its own file
Signed-off-by: falkTX <falktx@falktx.com>
* Fix warnings
Signed-off-by: falkTX <falktx@falktx.com>
* Fix tests; Add non-implemented saving flag
Signed-off-by: falkTX <falktx@falktx.com>
* Initial DBus/freedesktop file browser implementation
Signed-off-by: falkTX <falktx@falktx.com>
* Build fixes
Signed-off-by: falkTX <falktx@falktx.com>
* Fix window id
Signed-off-by: falkTX <falktx@falktx.com>
* More build fixes
Signed-off-by: falkTX <falktx@falktx.com>
* More file dialog tweaks
Signed-off-by: falkTX <falktx@falktx.com>
* Attempted fixes
Signed-off-by: falkTX <falktx@falktx.com>
* Fix C++98 build
Signed-off-by: falkTX <falktx@falktx.com>
* Fix windows build
Signed-off-by: falkTX <falktx@falktx.com>
* Really fix windows builds
Signed-off-by: falkTX <falktx@falktx.com>
* Fix for MSVC
Signed-off-by: falkTX <falktx@falktx.com>
* Yet another fix attempt
Signed-off-by: falkTX <falktx@falktx.com>
* Also fix macOS side
Signed-off-by: falkTX <falktx@falktx.com>
* More attempted fixes, this is getting annoying...
Signed-off-by: falkTX <falktx@falktx.com>
* FileBrowserDialog: Implement saving in Windows
Signed-off-by: falkTX <falktx@falktx.com>
* FileBrowserDialog: Implement saving on macOS
Signed-off-by: falkTX <falktx@falktx.com>
* Rework last commit
Signed-off-by: falkTX <falktx@falktx.com>
* One more macOS fix needed
Signed-off-by: falkTX <falktx@falktx.com>
* unref dbus connection on close
Signed-off-by: falkTX <falktx@falktx.com>
* More build fixes
Signed-off-by: falkTX <falktx@falktx.com>
* Hopefully final macOS fix
Signed-off-by: falkTX <falktx@falktx.com>
* Add libdbus-1-dev to CI
Signed-off-by: falkTX <falktx@falktx.com>
* Check that org.freedesktop.portal.Desktop exists before connecting
Signed-off-by: falkTX <falktx@falktx.com>
* Less indentation
Signed-off-by: falkTX <falktx@falktx.com>
* Fix macOS build
Diffstat:
19 files changed, 856 insertions(+), 569 deletions(-)
diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
@@ -30,6 +30,7 @@ jobs:
liblo-dev \
libgl-dev \
libcairo2-dev \
+ libdbus-1-dev \
libx11-dev
- name: Create Build Environment
shell: bash
diff --git a/.github/workflows/example-plugins.yml b/.github/workflows/example-plugins.yml
@@ -26,7 +26,7 @@ jobs:
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list
sudo apt-get update -qq
- sudo apt-get install -yq g++-aarch64-linux-gnu libasound2-dev:arm64 libcairo2-dev:arm64 libgl1-mesa-dev:arm64 liblo-dev:arm64 libpulse-dev:arm64 libx11-dev:arm64 libxcursor-dev:arm64 libxext-dev:arm64 libxrandr-dev:arm64 qemu-user-static
+ sudo apt-get install -yq g++-aarch64-linux-gnu libasound2-dev:arm64 libcairo2-dev:arm64 libdbus-1-dev:arm64 libgl1-mesa-dev:arm64 liblo-dev:arm64 libpulse-dev:arm64 libx11-dev:arm64 libxcursor-dev:arm64 libxext-dev:arm64 libxrandr-dev:arm64 qemu-user-static
# fix broken Ubuntu packages missing pkg-config file in multi-arch package
sudo apt-get install -yq libasound2-dev libgl1-mesa-dev liblo-dev libpulse-dev libxcursor-dev libxrandr-dev
sudo ln -s /usr/lib/aarch64-linux-gnu/liblo.so.7 /usr/lib/aarch64-linux-gnu/liblo.so
@@ -63,7 +63,7 @@ jobs:
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list
sudo apt-get update -qq
- sudo apt-get install -yq g++-arm-linux-gnueabihf libasound2-dev:armhf libcairo2-dev:armhf libgl1-mesa-dev:armhf liblo-dev:armhf libpulse-dev:armhf libx11-dev:armhf libxcursor-dev:armhf libxext-dev:armhf libxrandr-dev:armhf qemu-user-static
+ sudo apt-get install -yq g++-arm-linux-gnueabihf libasound2-dev:armhf libcairo2-dev:armhf libdbus-1-dev:armhf libgl1-mesa-dev:armhf liblo-dev:armhf libpulse-dev:armhf libx11-dev:armhf libxcursor-dev:armhf libxext-dev:armhf libxrandr-dev:armhf qemu-user-static
# fix broken Ubuntu packages missing pkg-config file in multi-arch package
sudo apt-get install -yq libasound2-dev libgl1-mesa-dev liblo-dev libpulse-dev libxcursor-dev libxrandr-dev
sudo ln -s /usr/lib/arm-linux-gnueabihf/liblo.so.7 /usr/lib/arm-linux-gnueabihf/liblo.so
@@ -96,7 +96,7 @@ jobs:
run: |
sudo dpkg --add-architecture i386
sudo apt-get update -qq
- sudo apt-get install -yq g++-multilib libasound2-dev:i386 libcairo2-dev:i386 libgl1-mesa-dev:i386 liblo-dev:i386 libpulse-dev:i386 libx11-dev:i386 libxcursor-dev:i386 libxext-dev:i386 libxrandr-dev:i386
+ sudo apt-get install -yq g++-multilib libasound2-dev:i386 libcairo2-dev:i386 libdbus-1-dev:i386 libgl1-mesa-dev:i386 liblo-dev:i386 libpulse-dev:i386 libx11-dev:i386 libxcursor-dev:i386 libxext-dev:i386 libxrandr-dev:i386
- name: Build linux x86
env:
CFLAGS: -m32
@@ -124,7 +124,7 @@ jobs:
- name: Set up dependencies
run: |
sudo apt-get update -qq
- sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev
+ sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev
- name: Build linux x86_64
env:
LDFLAGS: -static-libgcc -static-libstdc++
@@ -250,7 +250,7 @@ jobs:
sudo dpkg -i kxstudio-repos_10.0.3_all.deb
sudo apt-get update -qq
# build-deps
- sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev
+ sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev
# runtime testing
sudo apt-get install -yq carla-git lilv-utils lv2-dev lv2lint valgrind
- name: Build plugins
diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml
@@ -20,7 +20,7 @@ jobs:
- name: Set up dependencies
run: |
sudo apt-get update -qq
- sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev xvfb
+ sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev xvfb
- name: Without any warnings
env:
CFLAGS: -Werror
diff --git a/Makefile.base.mk b/Makefile.base.mk
@@ -238,6 +238,7 @@ HAVE_OPENGL = true
else
HAVE_OPENGL = $(shell $(PKG_CONFIG) --exists gl && echo true)
ifneq ($(HAIKU),true)
+HAVE_DBUS = $(shell $(PKG_CONFIG) --exists dbus-1 && echo true)
HAVE_X11 = $(shell $(PKG_CONFIG) --exists x11 && echo true)
HAVE_XCURSOR = $(shell $(PKG_CONFIG) --exists xcursor && echo true)
HAVE_XEXT = $(shell $(PKG_CONFIG) --exists xext && echo true)
@@ -284,6 +285,10 @@ DGL_SYSTEM_LIBS += -lgdi32 -lcomdlg32
endif
ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true)
+ifeq ($(HAVE_DBUS),true)
+DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags dbus-1) -DHAVE_DBUS
+DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs dbus-1)
+endif
ifeq ($(HAVE_X11),true)
DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags x11) -DHAVE_X11
DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs x11)
@@ -476,6 +481,7 @@ features:
$(call print_available,UNIX)
@echo === Detected features
$(call print_available,HAVE_ALSA)
+ $(call print_available,HAVE_DBUS)
$(call print_available,HAVE_CAIRO)
$(call print_available,HAVE_DGL)
$(call print_available,HAVE_LIBLO)
diff --git a/dgl/Window.hpp b/dgl/Window.hpp
@@ -19,9 +19,14 @@
#include "Geometry.hpp"
+#ifndef DGL_FILE_BROWSER_DISABLED
+# include "../distrho/extra/FileBrowserDialog.hpp"
+#endif
+
START_NAMESPACE_DGL
class Application;
+class PluginWindow;
class TopLevelWidget;
// -----------------------------------------------------------------------
@@ -53,53 +58,9 @@ class Window
public:
#ifndef DGL_FILE_BROWSER_DISABLED
- /**
- File browser options.
- @see Window::openFileBrowser
- */
- struct FileBrowserOptions {
- /**
- File browser button state.
- This allows to customize the behaviour of the file browse dialog buttons.
- Note these are merely hints, not all systems support them.
- */
- enum ButtonState {
- kButtonInvisible,
- kButtonVisibleUnchecked,
- kButtonVisibleChecked,
- };
-
- /** Start directory, uses current working directory if null */
- const char* startDir;
- /** File browser dialog window title, uses "FileBrowser" if null */
- const char* title;
- // TODO file filter
-
- /**
- File browser buttons.
- */
- struct Buttons {
- /** Whether to list all files vs only those with matching file extension */
- ButtonState listAllFiles;
- /** Whether to show hidden files */
- ButtonState showHidden;
- /** Whether to show list of places (bookmarks) */
- ButtonState showPlaces;
-
- /** Constructor for default values */
- Buttons()
- : listAllFiles(kButtonVisibleChecked),
- showHidden(kButtonVisibleUnchecked),
- showPlaces(kButtonVisibleChecked) {}
- } buttons;
-
- /** Constructor for default values */
- FileBrowserOptions()
- : startDir(nullptr),
- title(nullptr),
- buttons() {}
- };
-#endif // DGL_FILE_BROWSER_DISABLED
+ typedef DISTRHO_NAMESPACE::FileBrowserHandle FileBrowserHandle;
+ typedef DISTRHO_NAMESPACE::FileBrowserOptions FileBrowserOptions;
+#endif
/**
Window graphics context as a scoped struct.
@@ -361,7 +322,7 @@ public:
#ifndef DGL_FILE_BROWSER_DISABLED
/**
- Open a file browser dialog with this window as parent.
+ Open a file browser dialog with this window as transient parent.
A few options can be specified to setup the dialog.
If a path is selected, onFileSelected() will be called with the user chosen path.
diff --git a/dgl/src/WindowPrivateData.cpp b/dgl/src/WindowPrivateData.cpp
@@ -19,18 +19,6 @@
#include "pugl.hpp"
-#include "../../distrho/extra/String.hpp"
-
-#ifdef DISTRHO_OS_WINDOWS
-# include <direct.h>
-# include <process.h>
-# include <winsock2.h>
-# include <windows.h>
-# include <vector>
-#else
-# include <unistd.h>
-#endif
-
// #define DGL_DEBUG_EVENTS
#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
@@ -64,11 +52,6 @@ START_NAMESPACE_DGL
// -----------------------------------------------------------------------
-#ifdef DISTRHO_OS_WINDOWS
-// static pointer used for direct comparisons
-static const char* const kWin32SelectedFileCancelled = "__dpf_cancelled__";
-#endif
-
static double getDesktopScaleFactor(const PuglView* const view)
{
// allow custom scale for testing
@@ -83,154 +66,6 @@ static double getDesktopScaleFactor(const PuglView* const view)
// -----------------------------------------------------------------------
-#ifdef DISTRHO_OS_WINDOWS
-struct FileBrowserThread::PrivateData {
- OPENFILENAMEW ofn;
- volatile bool threadCancelled;
- uintptr_t threadHandle;
- std::vector<WCHAR> fileNameW;
- std::vector<WCHAR> startDirW;
- std::vector<WCHAR> titleW;
- const bool isEmbed;
- const char*& win32SelectedFile;
-
- PrivateData(const bool embed, const char*& file)
- : threadCancelled(false),
- threadHandle(0),
- fileNameW(32768),
- isEmbed(embed),
- win32SelectedFile(file)
- {
- std::memset(&ofn, 0, sizeof(ofn));
- ofn.lStructSize = sizeof(ofn);
- ofn.lpstrFile = fileNameW.data();
- ofn.nMaxFile = (DWORD)fileNameW.size();
- }
-
- void setup(const char* const startDir,
- const char* const title,
- const uintptr_t winId,
- const Window::FileBrowserOptions options)
- {
- ofn.hwndOwner = (HWND)winId;
-
- ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
- if (options.buttons.showHidden == Window::FileBrowserOptions::kButtonVisibleChecked)
- ofn.Flags |= OFN_FORCESHOWHIDDEN;
-
- ofn.FlagsEx = 0x0;
- if (options.buttons.showPlaces == Window::FileBrowserOptions::kButtonInvisible)
- ofn.FlagsEx |= OFN_EX_NOPLACESBAR;
-
- startDirW.resize(std::strlen(startDir) + 1);
- if (MultiByteToWideChar(CP_UTF8, 0, startDir, -1, startDirW.data(), static_cast<int>(startDirW.size())))
- ofn.lpstrInitialDir = startDirW.data();
-
- titleW.resize(std::strlen(title) + 1);
- if (MultiByteToWideChar(CP_UTF8, 0, title, -1, titleW.data(), static_cast<int>(titleW.size())))
- ofn.lpstrTitle = titleW.data();
- }
-
- void run()
- {
- const char* nextFile = nullptr;
-
- if (GetOpenFileNameW(&ofn))
- {
- if (threadCancelled)
- {
- threadHandle = 0;
- return;
- }
-
- // back to UTF-8
- std::vector<char> fileNameA(4 * 32768);
- if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1,
- fileNameA.data(), (int)fileNameA.size(),
- nullptr, nullptr))
- {
- nextFile = strdup(fileNameA.data());
- }
- }
-
- if (threadCancelled)
- {
- threadHandle = 0;
- return;
- }
-
- if (nextFile == nullptr)
- nextFile = kWin32SelectedFileCancelled;
-
- win32SelectedFile = nextFile;
- threadHandle = 0;
- }
-};
-
-FileBrowserThread::FileBrowserThread(const bool isEmbed, const char*& file)
- : pData(new PrivateData(isEmbed, file)) {}
-
-FileBrowserThread::~FileBrowserThread()
-{
- stop();
- delete pData;
-}
-
-unsigned __stdcall FileBrowserThread__run(void* const arg)
-{
- // CoInitializeEx(nullptr, COINIT_MULTITHREADED);
- static_cast<FileBrowserThread*>(arg)->pData->run();
- // CoUninitialize();
- _endthreadex(0);
- return 0;
-}
-
-void FileBrowserThread::start(const char* const startDir,
- const char* const title,
- const uintptr_t winId,
- const Window::FileBrowserOptions options)
-{
- pData->setup(startDir, title, winId, options);
-
- uint threadId;
- pData->threadCancelled = false;
- pData->threadHandle = _beginthreadex(nullptr, 0, FileBrowserThread__run, this, 0, &threadId);
-}
-
-void FileBrowserThread::stop()
-{
- pData->threadCancelled = true;
-
- if (pData->threadHandle == 0)
- return;
-
- // if previous dialog running, carefully close its window
- const HWND owner = pData->isEmbed ? GetParent(pData->ofn.hwndOwner) : pData->ofn.hwndOwner;
-
- if (owner != nullptr && owner != INVALID_HANDLE_VALUE)
- {
- const HWND window = GetWindow(owner, GW_HWNDFIRST);
-
- if (window != nullptr && window != INVALID_HANDLE_VALUE)
- {
- SendMessage(window, WM_SYSCOMMAND, SC_CLOSE, 0);
- SendMessage(window, WM_CLOSE, 0, 0);
- WaitForSingleObject((HANDLE)pData->threadHandle, 5000);
- }
- }
-
- // not good if thread still running, but let's close the handle anyway
- if (pData->threadHandle != 0)
- {
- CloseHandle((HANDLE)pData->threadHandle);
- pData->threadHandle = 0;
- }
-}
-
-#endif // DISTRHO_OS_WINDOWS && !_MSC_VER
-
-// -----------------------------------------------------------------------
-
Window::PrivateData::PrivateData(Application& a, Window* const s)
: app(a),
appData(a.pData),
@@ -249,9 +84,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s)
keepAspectRatio(false),
ignoreIdleCallbacks(false),
filenameToRenderInto(nullptr),
-#ifdef DISTRHO_OS_WINDOWS
- win32SelectedFile(nullptr),
- win32FileThread(false, win32SelectedFile),
+#ifndef DGL_FILE_BROWSER_DISABLED
+ fileBrowserHandle(nullptr),
#endif
modal()
{
@@ -276,9 +110,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c
keepAspectRatio(false),
ignoreIdleCallbacks(false),
filenameToRenderInto(nullptr),
-#ifdef DISTRHO_OS_WINDOWS
- win32SelectedFile(nullptr),
- win32FileThread(false, win32SelectedFile),
+#ifndef DGL_FILE_BROWSER_DISABLED
+ fileBrowserHandle(nullptr),
#endif
modal(ppData)
{
@@ -307,9 +140,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s,
keepAspectRatio(false),
ignoreIdleCallbacks(false),
filenameToRenderInto(nullptr),
-#ifdef DISTRHO_OS_WINDOWS
- win32SelectedFile(nullptr),
- win32FileThread(isEmbed, win32SelectedFile),
+#ifndef DGL_FILE_BROWSER_DISABLED
+ fileBrowserHandle(nullptr),
#endif
modal()
{
@@ -340,9 +172,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s,
keepAspectRatio(false),
ignoreIdleCallbacks(false),
filenameToRenderInto(nullptr),
-#ifdef DISTRHO_OS_WINDOWS
- win32SelectedFile(nullptr),
- win32FileThread(isEmbed, win32SelectedFile),
+#ifndef DGL_FILE_BROWSER_DISABLED
+ fileBrowserHandle(nullptr),
#endif
modal()
{
@@ -363,8 +194,9 @@ Window::PrivateData::~PrivateData()
if (isEmbed)
{
-#ifdef HAVE_X11
- sofdFileDialogClose();
+#ifndef DGL_FILE_BROWSER_DISABLED
+ if (fileBrowserHandle != nullptr)
+ fileBrowserClose(fileBrowserHandle);
#endif
puglHide(view);
appData->oneWindowClosed();
@@ -372,13 +204,6 @@ Window::PrivateData::~PrivateData()
isVisible = false;
}
-#ifdef DISTRHO_OS_WINDOWS
- win32FileThread.stop();
-
- if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled)
- std::free(const_cast<char*>(win32SelectedFile));
-#endif
-
puglFreeView(view);
}
@@ -522,9 +347,14 @@ void Window::PrivateData::hide()
if (modal.enabled)
stopModal();
-#ifdef HAVE_X11
- sofdFileDialogClose();
+#ifndef DGL_FILE_BROWSER_DISABLED
+ if (fileBrowserHandle != nullptr)
+ {
+ fileBrowserClose(fileBrowserHandle);
+ fileBrowserHandle = nullptr;
+ }
#endif
+
puglHide(view);
isVisible = false;
@@ -566,20 +396,12 @@ void Window::PrivateData::setResizable(const bool resizable)
void Window::PrivateData::idleCallback()
{
#ifndef DGL_FILE_BROWSER_DISABLED
-# ifdef DISTRHO_OS_WINDOWS
- if (const char* path = win32SelectedFile)
+ if (fileBrowserHandle != nullptr && fileBrowserIdle(fileBrowserHandle))
{
- win32SelectedFile = nullptr;
- if (path == kWin32SelectedFileCancelled)
- path = nullptr;
- self->onFileSelected(path);
- std::free(const_cast<char*>(path));
+ self->onFileSelected(fileBrowserGetPath(fileBrowserHandle));
+ fileBrowserClose(fileBrowserHandle);
+ fileBrowserHandle = nullptr;
}
-# endif
-# ifdef HAVE_X11
- if (sofdFileDialogIdle(view))
- self->onFileSelected(sofdFileDialogGetPath());
-# endif
#endif
}
@@ -619,120 +441,23 @@ bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback)
// -----------------------------------------------------------------------
// file handling
-bool Window::PrivateData::openFileBrowser(const Window::FileBrowserOptions& options)
+bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options)
{
- using DISTRHO_NAMESPACE::String;
-
- // --------------------------------------------------------------------------
- // configure start dir
-
- // TODO: get abspath if needed
- // TODO: cross-platform
-
- String startDir(options.startDir);
-
- if (startDir.isEmpty())
- {
- // TESTING verify this whole thing...
-# ifdef DISTRHO_OS_WINDOWS
- if (char* const cwd = _getcwd(nullptr, 0))
- {
- startDir = cwd;
- std::free(cwd);
- }
-# else
- if (char* const cwd = getcwd(nullptr, 0))
- {
- startDir = cwd;
- std::free(cwd);
- }
-# endif
- }
-
- DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), false);
-
- if (! startDir.endsWith(DISTRHO_OS_SEP))
- startDir += DISTRHO_OS_SEP_STR;
-
- // --------------------------------------------------------------------------
- // configure title
-
- String title(options.title);
-
- if (title.isEmpty())
- {
- title = puglGetWindowTitle(view);
-
- if (title.isEmpty())
- title = "FileBrowser";
- }
-
- // --------------------------------------------------------------------------
- // show
-
-# ifdef DISTRHO_OS_MAC
- uint flags = 0x0;
-
- if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
- flags |= 0x001;
- else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked)
- flags |= 0x002;
-
- if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
- flags |= 0x010;
- else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked)
- flags |= 0x020;
+ if (fileBrowserHandle != nullptr)
+ fileBrowserClose(fileBrowserHandle);
- if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked)
- flags |= 0x100;
- else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked)
- flags |= 0x200;
+ FileBrowserOptions options2 = options;
- return puglMacOSFilePanelOpen(view, startDir, title, flags, openPanelCallback);
-# endif
-
-# ifdef DISTRHO_OS_WINDOWS
- // only one possible at a time
- DISTRHO_SAFE_ASSERT_RETURN(win32FileThread.pData->threadHandle == 0, false);
-
- if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled)
- std::free(const_cast<char*>(win32SelectedFile));
- win32SelectedFile = nullptr;
-
- win32FileThread.start(startDir, title, puglGetNativeWindow(view), options);
- return true;
-# endif
-
-# ifdef HAVE_X11
- uint flags = 0x0;
-
- if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
- flags |= 0x001;
- else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked)
- flags |= 0x002;
+ if (options2.title == nullptr)
+ options2.title = puglGetWindowTitle(view);
- if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
- flags |= 0x010;
- else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked)
- flags |= 0x020;
+ fileBrowserHandle = fileBrowserCreate(isEmbed,
+ puglGetNativeWindow(view),
+ autoScaling ? autoScaleFactor : scaleFactor,
+ options2);
- if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked)
- flags |= 0x100;
- else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked)
- flags |= 0x200;
-
- return sofdFileDialogShow(view, startDir, title, flags, autoScaling ? autoScaleFactor : scaleFactor);
-# endif
-
- return false;
+ return fileBrowserHandle != nullptr;
}
-
-# ifdef DISTRHO_OS_MAC
-void Window::PrivateData::openPanelCallback(PuglView* const view, const char* const path)
-{
- ((Window::PrivateData*)puglGetHandle(view))->self->onFileSelected(path);
-}
-# endif
#endif // ! DGL_FILE_BROWSER_DISABLED
// -----------------------------------------------------------------------
diff --git a/dgl/src/WindowPrivateData.hpp b/dgl/src/WindowPrivateData.hpp
@@ -31,21 +31,6 @@ class TopLevelWidget;
// -----------------------------------------------------------------------
-#ifdef DISTRHO_OS_WINDOWS
-struct FileBrowserThread
-{
- struct PrivateData;
- PrivateData* const pData;
-
- FileBrowserThread(bool isEmbed, const char*& win32SelectedFile);
- ~FileBrowserThread();
- void start(const char* startDir, const char* title, uintptr_t winId, Window::FileBrowserOptions options);
- void stop();
-};
-#endif
-
-// -----------------------------------------------------------------------
-
struct Window::PrivateData : IdleCallback {
/** Reference to the DGL Application class this (private data) window associates with. */
Application& app;
@@ -95,11 +80,9 @@ struct Window::PrivateData : IdleCallback {
/** Render to a picture file when non-null, automatically free+unset after saving. */
char* filenameToRenderInto;
-#ifdef DISTRHO_OS_WINDOWS
- /** Selected file for openFileBrowser on windows, stored for fake async operation. */
- const char* win32SelectedFile;
- /** Thread where the openFileBrowser runs. */
- FileBrowserThread win32FileThread;
+#ifndef DGL_FILE_BROWSER_DISABLED
+ /** Handle for file browser dialog operations. */
+ FileBrowserHandle fileBrowserHandle;
#endif
/** Modal window setup. */
@@ -176,10 +159,7 @@ struct Window::PrivateData : IdleCallback {
#ifndef DGL_FILE_BROWSER_DISABLED
// file handling
- bool openFileBrowser(const Window::FileBrowserOptions& options);
-# ifdef DISTRHO_OS_MAC
- static void openPanelCallback(PuglView* view, const char* path);
-# endif
+ bool openFileBrowser(const FileBrowserOptions& options);
#endif
static void renderToPicture(const char* filename, const GraphicsContext& context, uint width, uint height);
diff --git a/dgl/src/pugl.cpp b/dgl/src/pugl.cpp
@@ -42,6 +42,7 @@
# endif
#elif defined(DISTRHO_OS_WINDOWS)
# include <wctype.h>
+# include <winsock2.h>
# include <windows.h>
# include <windowsx.h>
# ifdef DGL_CAIRO
@@ -57,6 +58,7 @@
# endif
#else
# include <dlfcn.h>
+# include <unistd.h>
# include <sys/select.h>
# include <sys/time.h>
# include <X11/X.h>
@@ -90,10 +92,12 @@
# endif
#endif
-#ifdef HAVE_X11
-# define DBLCLKTME 400
-# include "sofd/libsofd.h"
-# include "sofd/libsofd.c"
+#ifndef DGL_FILE_BROWSER_DISABLED
+# ifdef DISTRHO_OS_MAC
+# import "../../distrho/extra/FileBrowserDialog.cpp"
+# else
+# include "../../distrho/extra/FileBrowserDialog.cpp"
+# endif
#endif
#ifndef DISTRHO_OS_MAC
@@ -528,53 +532,6 @@ void puglMacOSShowCentered(PuglView* const view)
}
// --------------------------------------------------------------------------------------------------------------------
-// macOS specific, setup file browser dialog
-
-bool puglMacOSFilePanelOpen(PuglView* const view,
- const char* const startDir, const char* const title, const uint flags,
- openPanelCallback callback)
-{
- PuglInternals* impl = view->impl;
-
- NSOpenPanel* const panel = [NSOpenPanel openPanel];
-
- [panel setAllowsMultipleSelection:NO];
- [panel setCanChooseDirectories:NO];
- [panel setCanChooseFiles:YES];
- [panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]];
-
- // TODO file filter using allowedContentTypes: [UTType]
-
- if (flags & 0x001)
- [panel setAllowsOtherFileTypes:YES];
- if (flags & 0x010)
- [panel setShowsHiddenFiles:YES];
-
- NSString* titleString = [[NSString alloc]
- initWithBytes:title
- length:strlen(title)
- encoding:NSUTF8StringEncoding];
- [panel setTitle:titleString];
-
- dispatch_async(dispatch_get_main_queue(), ^
- {
- [panel beginSheetModalForWindow:(impl->window ? impl->window : [view->impl->wrapperView window])
- completionHandler:^(NSModalResponse result)
- {
- if (result == NSModalResponseOK && [[panel URL] isFileURL])
- {
- NSString* const path = [[panel URL] path];
- callback(view, [path UTF8String]);
- }
- else
- {
- callback(view, nullptr);
- }
- }];
- });
-
- return true;
-}
#endif
#ifdef DISTRHO_OS_WINDOWS
@@ -680,96 +637,7 @@ void puglX11SetWindowTypeAndPID(const PuglView* const view)
}
// --------------------------------------------------------------------------------------------------------------------
-// X11 specific stuff for sofd
-
-static Display* sofd_display;
-static char* sofd_filename;
-
-// --------------------------------------------------------------------------------------------------------------------
-// X11 specific, show file dialog via sofd
-
-bool sofdFileDialogShow(PuglView* const view,
- const char* const startDir, const char* const title,
- const uint flags, const double scaleFactor)
-{
- // only one possible at a time
- DISTRHO_SAFE_ASSERT_RETURN(sofd_display == nullptr, false);
-
- sofd_display = XOpenDisplay(nullptr);
- DISTRHO_SAFE_ASSERT_RETURN(sofd_display != nullptr, false);
-
- DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, false);
- DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, title) == 0, false);
-
- x_fib_cfg_buttons(3, flags & 0x001 ? 1 : flags & 0x002 ? 0 : -1);
- x_fib_cfg_buttons(1, flags & 0x010 ? 1 : flags & 0x020 ? 0 : -1);
- x_fib_cfg_buttons(2, flags & 0x100 ? 1 : flags & 0x200 ? 0 : -1);
-
- return (x_fib_show(sofd_display, view->impl->win, 0, 0, scaleFactor + 0.5) == 0);
-}
-
-// --------------------------------------------------------------------------------------------------------------------
-// X11 specific, idle sofd file dialog, returns true if dialog was closed (with or without a file selection)
-
-bool sofdFileDialogIdle(PuglView* const view)
-{
- if (sofd_display == nullptr)
- return false;
-
- XEvent event;
- while (XPending(sofd_display) > 0)
- {
- XNextEvent(sofd_display, &event);
-
- if (x_fib_handle_events(sofd_display, &event) == 0)
- continue;
-
- if (sofd_filename != nullptr)
- std::free(sofd_filename);
-
- if (x_fib_status() > 0)
- sofd_filename = x_fib_filename();
- else
- sofd_filename = nullptr;
-
- x_fib_close(sofd_display);
- XCloseDisplay(sofd_display);
- sofd_display = nullptr;
- return true;
- }
-
- return false;
-}
-
-// --------------------------------------------------------------------------------------------------------------------
-// X11 specific, close sofd file dialog
-
-void sofdFileDialogClose()
-{
- if (sofd_display != nullptr)
- {
- x_fib_close(sofd_display);
- XCloseDisplay(sofd_display);
- sofd_display = nullptr;
- }
-
- if (sofd_filename != nullptr)
- {
- std::free(sofd_filename);
- sofd_filename = nullptr;
- }
-}
-
-// --------------------------------------------------------------------------------------------------------------------
-// X11 specific, get path chosen via sofd file dialog
-
-const char* sofdFileDialogGetPath()
-{
- return sofd_filename;
-}
-#endif
-
-// --------------------------------------------------------------------------------------------------------------------
+#endif // HAVE_X11
#ifndef DISTRHO_OS_MAC
END_NAMESPACE_DGL
diff --git a/dgl/src/pugl.hpp b/dgl/src/pugl.hpp
@@ -111,10 +111,6 @@ puglMacOSRemoveChildWindow(PuglView* view, PuglView* child);
// macOS specific, center view based on parent coordinates (if there is one)
PUGL_API void
puglMacOSShowCentered(PuglView* view);
-
-// macOS specific, setup file browser dialog
-typedef void (*openPanelCallback)(PuglView* view, const char* path);
-bool puglMacOSFilePanelOpen(PuglView* view, const char* startDir, const char* title, uint flags, openPanelCallback callback);
#endif
#ifdef DISTRHO_OS_WINDOWS
@@ -139,22 +135,6 @@ puglX11GrabFocus(const PuglView* view);
// X11 specific, set dialog window type and pid hints
PUGL_API void
puglX11SetWindowTypeAndPID(const PuglView* view);
-
-// X11 specific, show file dialog via sofd
-PUGL_API bool
-sofdFileDialogShow(PuglView* view, const char* startDir, const char* title, uint flags, double scaleFactor);
-
-// X11 specific, idle sofd file dialog, returns true if dialog was closed (with or without a file selection)
-PUGL_API bool
-sofdFileDialogIdle(PuglView* const view);
-
-// X11 specific, close sofd file dialog
-PUGL_API void
-sofdFileDialogClose();
-
-// X11 specific, get path chosen via sofd file dialog
-PUGL_API const char*
-sofdFileDialogGetPath();
#endif
PUGL_END_DECLS
diff --git a/distrho/DistrhoUI.hpp b/distrho/DistrhoUI.hpp
@@ -48,6 +48,10 @@ typedef DGL_NAMESPACE::NanoTopLevelWidget UIWidget;
typedef DGL_NAMESPACE::TopLevelWidget UIWidget;
#endif
+#ifndef DGL_FILE_BROWSER_DISABLED
+# include "extra/FileBrowserDialog.hpp"
+#endif
+
START_NAMESPACE_DGL
class PluginWindow;
END_NAMESPACE_DGL
@@ -183,6 +187,22 @@ public:
void sendNote(uint8_t channel, uint8_t note, uint8_t velocity);
#endif
+#ifndef DGL_FILE_BROWSER_DISABLED
+ /**
+ Open a file browser dialog with this window as transient parent.@n
+ A few options can be specified to setup the dialog.
+
+ If a path is selected, onFileSelected() will be called with the user chosen path.
+ If the user cancels or does not pick a file, onFileSelected() will be called with nullptr as filename.
+
+ This function does not block the event loop.
+
+ @note This is exactly the same API as provided by the Window class,
+ but redeclared here so that non-embed/DGL based UIs can still use file browser related functions.
+ */
+ bool openFileBrowser(const FileBrowserOptions& options = FileBrowserOptions());
+#endif
+
#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
/* --------------------------------------------------------------------------------------------------------
* Direct DSP access - DO NOT USE THIS UNLESS STRICTLY NECESSARY!! */
@@ -297,8 +317,9 @@ protected:
The most common exception is custom OpenGL setup, but only really needed for custom OpenGL drawing code.
*/
virtual void uiReshape(uint width, uint height);
+#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
-# ifndef DGL_FILE_BROWSER_DISABLED
+#ifndef DGL_FILE_BROWSER_DISABLED
/**
Window file selected function, called when a path is selected by the user, as triggered by openFileBrowser().
This function is for plugin UIs to be able to override Window::onFileSelected(const char*).
@@ -309,8 +330,7 @@ protected:
If you need to use files as plugin state, please setup and use DISTRHO_PLUGIN_WANT_STATEFILES instead.
*/
virtual void uiFileBrowserSelected(const char* filename);
-# endif
-#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+#endif
/* --------------------------------------------------------------------------------------------------------
* UI Resize Handling, internal */
diff --git a/distrho/DistrhoUI_macOS.mm b/distrho/DistrhoUI_macOS.mm
@@ -22,10 +22,15 @@
#include "src/DistrhoDefines.h"
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
-#import <Cocoa/Cocoa.h>
-#include <algorithm>
-#include <cmath>
+# import <Cocoa/Cocoa.h>
+# include <algorithm>
+# include <cmath>
+# ifndef DGL_FILE_BROWSER_DISABLED
+# import "extra/FileBrowserDialog.cpp"
+# endif
+
+// Declared in DistrhoUI.cpp but defined here because it uses Obj-C
START_NAMESPACE_DISTRHO
double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
{
@@ -40,19 +45,19 @@ double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
return [NSScreen mainScreen].backingScaleFactor;
}
END_NAMESPACE_DISTRHO
-#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI
-#include "../dgl/Base.hpp"
-#define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, SEP, PUGL_NS, INTERFACE) DGL_NS ## SEP ## PUGL_NS ## SEP ## INTERFACE
-#define DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NS, PUGL_NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, _, PUGL_NS, INTERFACE)
+#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI
-#define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, CairoView)
-#define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, OpenGLView)
-#define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, StubView)
-#define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, VulkanView)
-#define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, Window)
-#define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WindowDelegate)
-#define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WrapperView)
+# include "../dgl/Base.hpp"
+# define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, SEP, PUGL_NS, INTERFACE) DGL_NS ## SEP ## PUGL_NS ## SEP ## INTERFACE
+# define DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NS, PUGL_NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, _, PUGL_NS, INTERFACE)
+# define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, CairoView)
+# define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, OpenGLView)
+# define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, StubView)
+# define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, VulkanView)
+# define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, Window)
+# define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WindowDelegate)
+# define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WrapperView)
+# import "src/pugl.mm"
-#import "src/pugl.mm"
#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI
diff --git a/distrho/extra/FileBrowserDialog.cpp b/distrho/extra/FileBrowserDialog.cpp
@@ -0,0 +1,569 @@
+/*
+ * DISTRHO Plugin Framework (DPF)
+ * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any purpose with
+ * or without fee is hereby granted, provided that the above copyright notice and this
+ * permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+ * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "FileBrowserDialog.hpp"
+#include "ScopedPointer.hpp"
+#include "String.hpp"
+
+#ifdef DISTRHO_OS_MAC
+# import <Cocoa/Cocoa.h>
+#endif
+#ifdef DISTRHO_OS_WINDOWS
+# include <direct.h>
+# include <process.h>
+# include <winsock2.h>
+# include <windows.h>
+# include <vector>
+#else
+# include <unistd.h>
+#endif
+#ifdef HAVE_DBUS
+# include <dbus/dbus.h>
+#endif
+#ifdef HAVE_X11
+# define DBLCLKTME 400
+# include "sofd/libsofd.h"
+# include "sofd/libsofd.c"
+#endif
+
+START_NAMESPACE_DISTRHO
+
+// --------------------------------------------------------------------------------------------------------------------
+
+// static pointer used for signal null/none action taken
+static const char* const kSelectedFileCancelled = "__dpf_cancelled__";
+
+struct FileBrowserData {
+ const char* selectedFile;
+
+#ifdef DISTRHO_OS_MAC
+ NSSavePanel* nsBasePanel;
+ NSOpenPanel* nsOpenPanel;
+#endif
+#ifdef HAVE_DBUS
+ DBusConnection* dbuscon;
+#endif
+#ifdef HAVE_X11
+ Display* x11display;
+#endif
+
+#ifdef DISTRHO_OS_WINDOWS
+ OPENFILENAMEW ofn;
+ volatile bool threadCancelled;
+ uintptr_t threadHandle;
+ std::vector<WCHAR> fileNameW;
+ std::vector<WCHAR> startDirW;
+ std::vector<WCHAR> titleW;
+ const bool saving;
+ bool isEmbed;
+
+ FileBrowserData(const bool save)
+ : selectedFile(nullptr),
+ threadCancelled(false),
+ threadHandle(0),
+ fileNameW(32768),
+ saving(save),
+ isEmbed(false)
+ {
+ std::memset(&ofn, 0, sizeof(ofn));
+ ofn.lStructSize = sizeof(ofn);
+ ofn.lpstrFile = fileNameW.data();
+ ofn.nMaxFile = (DWORD)fileNameW.size();
+ }
+
+ ~FileBrowserData()
+ {
+ if (cancelAndStop() && selectedFile != nullptr && selectedFile != kSelectedFileCancelled)
+ std::free(const_cast<char*>(selectedFile));
+ }
+
+ void setupAndStart(const bool embed,
+ const char* const startDir,
+ const char* const windowTitle,
+ const uintptr_t winId,
+ const FileBrowserOptions options)
+ {
+ isEmbed = embed;
+
+ ofn.hwndOwner = (HWND)winId;
+
+ ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
+ if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
+ ofn.Flags |= OFN_FORCESHOWHIDDEN;
+
+ ofn.FlagsEx = 0x0;
+ if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible)
+ ofn.FlagsEx |= OFN_EX_NOPLACESBAR;
+
+ startDirW.resize(std::strlen(startDir) + 1);
+ if (MultiByteToWideChar(CP_UTF8, 0, startDir, -1, startDirW.data(), static_cast<int>(startDirW.size())))
+ ofn.lpstrInitialDir = startDirW.data();
+
+ titleW.resize(std::strlen(windowTitle) + 1);
+ if (MultiByteToWideChar(CP_UTF8, 0, windowTitle, -1, titleW.data(), static_cast<int>(titleW.size())))
+ ofn.lpstrTitle = titleW.data();
+
+ uint threadId;
+ threadCancelled = false;
+ threadHandle = _beginthreadex(nullptr, 0, _run, this, 0, &threadId);
+ }
+
+ bool cancelAndStop()
+ {
+ threadCancelled = true;
+
+ if (threadHandle == 0)
+ return true;
+
+ // if previous dialog running, carefully close its window
+ const HWND owner = isEmbed ? GetParent(ofn.hwndOwner) : ofn.hwndOwner;
+
+ if (owner != nullptr && owner != INVALID_HANDLE_VALUE)
+ {
+ const HWND window = GetWindow(owner, GW_HWNDFIRST);
+
+ if (window != nullptr && window != INVALID_HANDLE_VALUE)
+ {
+ SendMessage(window, WM_SYSCOMMAND, SC_CLOSE, 0);
+ SendMessage(window, WM_CLOSE, 0, 0);
+ WaitForSingleObject((HANDLE)threadHandle, 5000);
+ }
+ }
+
+ if (threadHandle == 0)
+ return true;
+
+ // not good if thread still running, but let's close the handle anyway
+ CloseHandle((HANDLE)threadHandle);
+ threadHandle = 0;
+ return false;
+ }
+
+ void run()
+ {
+ const char* nextFile = nullptr;
+
+ if (saving ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn))
+ {
+ if (threadCancelled)
+ {
+ threadHandle = 0;
+ return;
+ }
+
+ // back to UTF-8
+ std::vector<char> fileNameA(4 * 32768);
+ if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1,
+ fileNameA.data(), (int)fileNameA.size(),
+ nullptr, nullptr))
+ {
+ nextFile = strdup(fileNameA.data());
+ }
+ }
+
+ if (threadCancelled)
+ {
+ threadHandle = 0;
+ return;
+ }
+
+ if (nextFile == nullptr)
+ nextFile = kSelectedFileCancelled;
+
+ selectedFile = nextFile;
+ threadHandle = 0;
+ }
+
+ static unsigned __stdcall _run(void* const arg)
+ {
+ // CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ static_cast<FileBrowserData*>(arg)->run();
+ // CoUninitialize();
+ _endthreadex(0);
+ return 0;
+ }
+#else // DISTRHO_OS_WINDOWS
+ FileBrowserData(const bool saving)
+ : selectedFile(nullptr)
+ {
+#ifdef DISTRHO_OS_MAC
+ if (saving)
+ {
+ nsOpenPanel = nullptr;
+ nsBasePanel = [[NSSavePanel savePanel]retain];
+ }
+ else
+ {
+ nsOpenPanel = [[NSOpenPanel openPanel]retain];
+ nsBasePanel = nsOpenPanel;
+ }
+#endif
+#ifdef HAVE_DBUS
+ if ((dbuscon = dbus_bus_get(DBUS_BUS_SESSION, nullptr)) != nullptr)
+ dbus_connection_set_exit_on_disconnect(dbuscon, false);
+#endif
+#ifdef HAVE_X11
+ x11display = XOpenDisplay(nullptr);
+#endif
+
+ // maybe unused
+ return; (void)saving;
+ }
+
+ ~FileBrowserData()
+ {
+#ifdef DISTRHO_OS_MAC
+ [nsBasePanel release];
+#endif
+#ifdef HAVE_DBUS
+ if (dbuscon != nullptr)
+ dbus_connection_unref(dbuscon);
+#endif
+#ifdef HAVE_X11
+ if (x11display != nullptr)
+ XCloseDisplay(x11display);
+#endif
+
+ if (selectedFile != nullptr && selectedFile != kSelectedFileCancelled)
+ std::free(const_cast<char*>(selectedFile));
+ }
+#endif
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE
+namespace DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE {
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+
+FileBrowserHandle fileBrowserCreate(const bool isEmbed,
+ const uintptr_t windowId,
+ const double scaleFactor,
+ const FileBrowserOptions& options)
+{
+ String startDir(options.startDir);
+
+ if (startDir.isEmpty())
+ {
+#ifdef DISTRHO_OS_WINDOWS
+ if (char* const cwd = _getcwd(nullptr, 0))
+ {
+ startDir = cwd;
+ std::free(cwd);
+ }
+#else
+ if (char* const cwd = getcwd(nullptr, 0))
+ {
+ startDir = cwd;
+ std::free(cwd);
+ }
+#endif
+ }
+
+ DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), nullptr);
+
+ if (! startDir.endsWith(DISTRHO_OS_SEP))
+ startDir += DISTRHO_OS_SEP_STR;
+
+ String windowTitle(options.title);
+
+ if (windowTitle.isEmpty())
+ windowTitle = "FileBrowser";
+
+ ScopedPointer<FileBrowserData> handle(new FileBrowserData(options.saving));
+
+#ifdef DISTRHO_OS_MAC
+ NSSavePanel* const nsBasePanel = handle->nsBasePanel;
+ DISTRHO_SAFE_ASSERT_RETURN(nsBasePanel != nullptr, nullptr);
+
+ if (! options.saving)
+ {
+ NSOpenPanel* const nsOpenPanel = handle->nsOpenPanel;
+ DISTRHO_SAFE_ASSERT_RETURN(nsOpenPanel != nullptr, nullptr);
+
+ [nsOpenPanel setAllowsMultipleSelection:NO];
+ [nsOpenPanel setCanChooseDirectories:NO];
+ [nsOpenPanel setCanChooseFiles:YES];
+ }
+
+ [nsBasePanel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]];
+
+ // TODO file filter using allowedContentTypes: [UTType]
+
+ if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
+ [nsBasePanel setAllowsOtherFileTypes:YES];
+ if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
+ [nsBasePanel setShowsHiddenFiles:YES];
+
+ NSString* const titleString = [[NSString alloc]
+ initWithBytes:windowTitle
+ length:strlen(windowTitle)
+ encoding:NSUTF8StringEncoding];
+ [nsBasePanel setTitle:titleString];
+
+ FileBrowserData* const handleptr = handle.get();
+
+ dispatch_async(dispatch_get_main_queue(), ^
+ {
+ [nsBasePanel beginSheetModalForWindow:[(NSView*)windowId window]
+ completionHandler:^(NSModalResponse result)
+ {
+ if (result == NSModalResponseOK && [[nsBasePanel URL] isFileURL])
+ {
+ NSString* const path = [[nsBasePanel URL] path];
+ handleptr->selectedFile = strdup([path UTF8String]);
+ }
+ else
+ {
+ handleptr->selectedFile = kSelectedFileCancelled;
+ }
+ }];
+ });
+#endif
+
+#ifdef DISTRHO_OS_WINDOWS
+ handle->setupAndStart(isEmbed, startDir, windowTitle, windowId, options);
+#endif
+
+#ifdef HAVE_DBUS
+ // optional, can be null
+ DBusConnection* const dbuscon = handle->dbuscon;
+
+ if (dbuscon != nullptr && dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr))
+ {
+ // https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.portal.FileChooser
+ if (DBusMessage* const message = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.FileChooser",
+ options.saving ? "SaveFile" : "OpenFile"))
+ {
+ char windowIdStr[32];
+ memset(windowIdStr, 0, sizeof(windowIdStr));
+# ifdef HAVE_X11
+ snprintf(windowIdStr, sizeof(windowIdStr)-1, "x11:%llx", (ulonglong)windowId);
+# endif
+ const char* windowIdStrPtr = windowIdStr;
+
+ dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &windowIdStrPtr,
+ DBUS_TYPE_STRING, &windowTitle,
+ DBUS_TYPE_INVALID);
+
+ DBusMessageIter iter, array;
+ dbus_message_iter_init_append(message, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array);
+
+ /* here merely as example, in case we need to configure it
+ {
+ DBusMessageIter dict, variant;
+ const char* const property = "property";
+ const char* const value = "value";
+
+ dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, nullptr, &dict);
+ dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &property);
+ dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "s", &variant);
+ dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &value);
+ dbus_message_iter_close_container(&dict, &variant);
+ dbus_message_iter_close_container(&array, &dict);
+ }
+ */
+
+ dbus_message_iter_close_container(&iter, &array);
+
+ dbus_connection_send(dbuscon, message, nullptr);
+
+ dbus_message_unref(message);
+ return handle.release();
+ }
+ }
+#endif
+
+#ifdef HAVE_X11
+ Display* const x11display = handle->x11display;
+ DISTRHO_SAFE_ASSERT_RETURN(x11display != nullptr, nullptr);
+
+ // unsupported at the moment
+ if (options.saving)
+ return nullptr;
+
+ DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, nullptr);
+ DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, windowTitle) == 0, nullptr);
+
+ const int button1 = options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked ? 1
+ : options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
+ const int button2 = options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked ? 1
+ : options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
+ const int button3 = options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked ? 1
+ : options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
+
+ x_fib_cfg_buttons(1, button1);
+ x_fib_cfg_buttons(2, button2);
+ x_fib_cfg_buttons(3, button3);
+
+ if (x_fib_show(x11display, windowId, 0, 0, scaleFactor + 0.5) != 0)
+ return nullptr;
+#endif
+
+ return handle.release();
+
+ // might be unused
+ (void)isEmbed;
+ (void)windowId;
+ (void)scaleFactor;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// returns true if dialog was closed (with or without a file selection)
+
+bool fileBrowserIdle(const FileBrowserHandle handle)
+{
+#ifdef HAVE_DBUS
+ if (DBusConnection* dbuscon = handle->dbuscon)
+ {
+ while (dbus_connection_dispatch(dbuscon) == DBUS_DISPATCH_DATA_REMAINS) {}
+ dbus_connection_read_write_dispatch(dbuscon, 0);
+
+ if (DBusMessage* const message = dbus_connection_pop_message(dbuscon))
+ {
+ const char* const interface = dbus_message_get_interface(message);
+ const char* const member = dbus_message_get_member(message);
+
+ if (interface != nullptr && std::strcmp(interface, "org.freedesktop.portal.Request") == 0
+ && member != nullptr && std::strcmp(member, "Response") == 0)
+ {
+ do {
+ DBusMessageIter iter;
+ dbus_message_iter_init(message, &iter);
+
+ // starts with uint32 for return/exit code
+ DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32);
+
+ uint32_t ret = 1;
+ dbus_message_iter_get_basic(&iter, &ret);
+
+ if (ret != 0)
+ break;
+
+ // next must be array
+ dbus_message_iter_next(&iter);
+ DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY);
+
+ // open dict array
+ DBusMessageIter dictArray;
+ dbus_message_iter_recurse(&iter, &dictArray);
+ DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY);
+
+ // open containing dict
+ DBusMessageIter dict;
+ dbus_message_iter_recurse(&dictArray, &dict);
+
+ // start with the string "uris"
+ DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING);
+
+ const char* key = nullptr;
+ dbus_message_iter_get_basic(&dict, &key);
+ DISTRHO_SAFE_ASSERT_BREAK(key != nullptr && std::strcmp(key, "uris") == 0);
+
+ // then comes variant
+ dbus_message_iter_next(&dict);
+ DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_VARIANT);
+
+ DBusMessageIter variant;
+ dbus_message_iter_recurse(&dict, &variant);
+ DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_ARRAY);
+
+ // open variant array (variant type is string)
+ DBusMessageIter variantArray;
+ dbus_message_iter_recurse(&variant, &variantArray);
+ DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variantArray) == DBUS_TYPE_STRING);
+
+ const char* value = nullptr;
+ dbus_message_iter_get_basic(&variantArray, &value);
+
+ // and finally we have our dear value, just make sure it is local
+ DISTRHO_SAFE_ASSERT_BREAK(value != nullptr);
+
+ if (const char* const localvalue = std::strstr(value, "file:///"))
+ handle->selectedFile = strdup(localvalue + 7);
+
+ } while(false);
+
+ if (handle->selectedFile == nullptr)
+ handle->selectedFile = kSelectedFileCancelled;
+ }
+ }
+ }
+#endif
+
+#ifdef HAVE_X11
+ Display* const x11display = handle->x11display;
+
+ if (x11display == nullptr)
+ return false;
+
+ XEvent event;
+ while (XPending(x11display) > 0)
+ {
+ XNextEvent(x11display, &event);
+
+ if (x_fib_handle_events(x11display, &event) == 0)
+ continue;
+
+ if (x_fib_status() > 0)
+ handle->selectedFile = x_fib_filename();
+ else
+ handle->selectedFile = kSelectedFileCancelled;
+
+ x_fib_close(x11display);
+ XCloseDisplay(x11display);
+ handle->x11display = nullptr;
+ break;
+ }
+#endif
+
+ return handle->selectedFile != nullptr;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// close sofd file dialog
+
+void fileBrowserClose(const FileBrowserHandle handle)
+{
+#ifdef HAVE_X11
+ if (Display* const x11display = handle->x11display)
+ x_fib_close(x11display);
+#endif
+
+ delete handle;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// get path chosen via sofd file dialog
+
+const char* fileBrowserGetPath(const FileBrowserHandle handle)
+{
+ return handle->selectedFile != kSelectedFileCancelled ? handle->selectedFile : nullptr;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE
+}
+#endif
+
+END_NAMESPACE_DISTRHO
diff --git a/distrho/extra/FileBrowserDialog.hpp b/distrho/extra/FileBrowserDialog.hpp
@@ -0,0 +1,134 @@
+/*
+ * DISTRHO Plugin Framework (DPF)
+ * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any purpose with
+ * or without fee is hereby granted, provided that the above copyright notice and this
+ * permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+ * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED
+#define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED
+
+#include "../DistrhoUtils.hpp"
+
+START_NAMESPACE_DISTRHO
+
+// --------------------------------------------------------------------------------------------------------------------
+// File Browser Dialog stuff
+
+struct FileBrowserData;
+typedef FileBrowserData* FileBrowserHandle;
+
+// --------------------------------------------------------------------------------------------------------------------
+
+/**
+ File browser options, for customizing the file browser dialog.@n
+ By default the file browser dialog will be work as "open file" in the current working directory.
+*/
+struct FileBrowserOptions {
+ /** Whether we are saving, opening files otherwise (default) */
+ bool saving;
+
+ /** Start directory, uses current working directory if null */
+ const char* startDir;
+
+ /** File browser dialog window title, uses "FileBrowser" if null */
+ const char* title;
+
+ // TODO file filter
+
+ /**
+ File browser button state.
+ This allows to customize the behaviour of the file browse dialog buttons.
+ Note these are merely hints, not all systems support them.
+ */
+ enum ButtonState {
+ kButtonInvisible,
+ kButtonVisibleUnchecked,
+ kButtonVisibleChecked,
+ };
+
+ /**
+ File browser buttons.
+ */
+ struct Buttons {
+ /** Whether to list all files vs only those with matching file extension */
+ ButtonState listAllFiles;
+ /** Whether to show hidden files */
+ ButtonState showHidden;
+ /** Whether to show list of places (bookmarks) */
+ ButtonState showPlaces;
+
+ /** Constructor for default values */
+ Buttons()
+ : listAllFiles(kButtonVisibleChecked),
+ showHidden(kButtonVisibleUnchecked),
+ showPlaces(kButtonVisibleChecked) {}
+ } buttons;
+
+ /** Constructor for default values */
+ FileBrowserOptions()
+ : saving(false),
+ startDir(nullptr),
+ title(nullptr),
+ buttons() {}
+};
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE
+namespace DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE {
+#endif
+
+/**
+ Create a new file browser dialog.
+
+ @p isEmbed: Whether the window this dialog belongs to is an embed/child window (needed to close dialog on Windows)
+ @p windowId: The native window id to attach this dialog to as transient parent (X11 Window, HWND or NSView*)
+ @p scaleFactor: Scale factor to use (only used on X11)
+ @p options: Extra options, optional
+ By default the file browser dialog will be work as "open file" in the current working directory.
+*/
+FileBrowserHandle fileBrowserCreate(bool isEmbed,
+ uintptr_t windowId,
+ double scaleFactor,
+ const FileBrowserOptions& options = FileBrowserOptions());
+
+/**
+ Idle the file browser dialog handle.@n
+ Returns true if dialog was closed (with or without a file selection),
+ in which case the handle must not be used afterwards.
+ You can then call fileBrowserGetPath to know the selected file (or null if cancelled).
+*/
+bool fileBrowserIdle(const FileBrowserHandle handle);
+
+/**
+ Close the file browser dialog, handle must not be used afterwards.
+*/
+void fileBrowserClose(const FileBrowserHandle handle);
+
+/**
+ Get the path chosen by the user or null.@n
+ Should only be called after fileBrowserIdle returns true.
+*/
+const char* fileBrowserGetPath(const FileBrowserHandle handle);
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE
+}
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+
+END_NAMESPACE_DISTRHO
+
+#endif // DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED
diff --git a/dgl/src/sofd/libsofd.c b/distrho/extra/sofd/libsofd.c
diff --git a/dgl/src/sofd/libsofd.h b/distrho/extra/sofd/libsofd.h
diff --git a/distrho/src/DistrhoDefines.h b/distrho/src/DistrhoDefines.h
@@ -207,6 +207,7 @@ typedef unsigned char uchar;
typedef unsigned short int ushort;
typedef unsigned int uint;
typedef unsigned long int ulong;
+typedef unsigned long long int ulonglong;
/* Deprecated macros */
#define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) DISTRHO_DECLARE_NON_COPYABLE(ClassName)
diff --git a/distrho/src/DistrhoUI.cpp b/distrho/src/DistrhoUI.cpp
@@ -15,6 +15,29 @@
*/
#include "src/DistrhoPluginChecks.h"
+#include "src/DistrhoDefines.h"
+
+#if !defined(DGL_FILE_BROWSER_DISABLED) && !defined(DISTRHO_OS_MAC)
+# define DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION
+# define DISTRHO_PUGL_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION)
+# define DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE Plugin
+# define x_fib_add_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_add_recent)
+# define x_fib_cfg_buttons DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_cfg_buttons)
+# define x_fib_cfg_filter_callback DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_cfg_filter_callback)
+# define x_fib_close DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_close)
+# define x_fib_configure DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_configure)
+# define x_fib_filename DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_filename)
+# define x_fib_free_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_free_recent)
+# define x_fib_handle_events DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_handle_events)
+# define x_fib_load_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_load_recent)
+# define x_fib_recent_at DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_recent_at)
+# define x_fib_recent_count DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_recent_count)
+# define x_fib_recent_file DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_recent_file)
+# define x_fib_save_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_save_recent)
+# define x_fib_show DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_show)
+# define x_fib_status DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_status)
+# include "../extra/FileBrowserDialog.cpp"
+#endif
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# if defined(DISTRHO_OS_WINDOWS)
@@ -255,6 +278,19 @@ void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity)
}
#endif
+#ifndef DGL_FILE_BROWSER_DISABLED
+bool UI::openFileBrowser(const FileBrowserOptions& options)
+{
+# if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+ // TODO
+ return false;
+ (void)options;
+# else
+ return getWindow().openFileBrowser(options);
+# endif
+}
+#endif
+
#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
/* ------------------------------------------------------------------------------------------------------------
* Direct DSP access */
@@ -311,13 +347,13 @@ void UI::uiReshape(uint, uint)
// NOTE this must be the same as Window::onReshape
pData->fallbackOnResize();
}
+#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
-# ifndef DGL_FILE_BROWSER_DISABLED
+#ifndef DGL_FILE_BROWSER_DISABLED
void UI::uiFileBrowserSelected(const char*)
{
}
-# endif
-#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
+#endif
/* ------------------------------------------------------------------------------------------------------------
* UI Resize Handling, internal */
diff --git a/distrho/src/DistrhoUIPrivateData.hpp b/distrho/src/DistrhoUIPrivateData.hpp
@@ -433,7 +433,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key)
snprintf(title, sizeof(title)-1u, DISTRHO_PLUGIN_NAME ": %s", key);
title[sizeof(title)-1u] = '\0';
- DGL_NAMESPACE::Window::FileBrowserOptions opts;
+ FileBrowserOptions opts;
opts.title = title;
return window->openFileBrowser(opts);
#endif
diff --git a/tests/FileBrowserDialog.cpp b/tests/FileBrowserDialog.cpp
@@ -125,6 +125,7 @@ protected:
repaint();
FileBrowserOptions opts;
+ // opts.saving = true;
opts.title = "Look at me";
if (! openFileBrowser(opts))
{