Browse Source

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
pull/351/head
Filipe Coelho GitHub 3 years ago
parent
commit
29709cbe4e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 856 additions and 569 deletions
  1. +1
    -0
      .github/workflows/cmake.yml
  2. +5
    -5
      .github/workflows/example-plugins.yml
  3. +1
    -1
      .github/workflows/makefile.yml
  4. +6
    -0
      Makefile.base.mk
  5. +9
    -48
      dgl/Window.hpp
  6. +33
    -308
      dgl/src/WindowPrivateData.cpp
  7. +4
    -24
      dgl/src/WindowPrivateData.hpp
  8. +9
    -141
      dgl/src/pugl.cpp
  9. +0
    -20
      dgl/src/pugl.hpp
  10. +23
    -3
      distrho/DistrhoUI.hpp
  11. +20
    -15
      distrho/DistrhoUI_macOS.mm
  12. +569
    -0
      distrho/extra/FileBrowserDialog.cpp
  13. +134
    -0
      distrho/extra/FileBrowserDialog.hpp
  14. +0
    -0
      distrho/extra/sofd/libsofd.c
  15. +0
    -0
      distrho/extra/sofd/libsofd.h
  16. +1
    -0
      distrho/src/DistrhoDefines.h
  17. +39
    -3
      distrho/src/DistrhoUI.cpp
  18. +1
    -1
      distrho/src/DistrhoUIPrivateData.hpp
  19. +1
    -0
      tests/FileBrowserDialog.cpp

+ 1
- 0
.github/workflows/cmake.yml View File

@@ -30,6 +30,7 @@ jobs:
liblo-dev \
libgl-dev \
libcairo2-dev \
libdbus-1-dev \
libx11-dev
- name: Create Build Environment
shell: bash


+ 5
- 5
.github/workflows/example-plugins.yml View File

@@ -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


+ 1
- 1
.github/workflows/makefile.yml View File

@@ -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


+ 6
- 0
Makefile.base.mk View File

@@ -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)


+ 9
- 48
dgl/Window.hpp View File

@@ -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.


+ 33
- 308
dgl/src/WindowPrivateData.cpp View File

@@ -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

// -----------------------------------------------------------------------


+ 4
- 24
dgl/src/WindowPrivateData.hpp View File

@@ -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);


+ 9
- 141
dgl/src/pugl.cpp View File

@@ -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


+ 0
- 20
dgl/src/pugl.hpp View File

@@ -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


+ 23
- 3
distrho/DistrhoUI.hpp View File

@@ -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 */


+ 20
- 15
distrho/DistrhoUI_macOS.mm View File

@@ -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

+ 569
- 0
distrho/extra/FileBrowserDialog.cpp View File

@@ -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

+ 134
- 0
distrho/extra/FileBrowserDialog.hpp View File

@@ -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

dgl/src/sofd/libsofd.c → distrho/extra/sofd/libsofd.c View File


dgl/src/sofd/libsofd.h → distrho/extra/sofd/libsofd.h View File


+ 1
- 0
distrho/src/DistrhoDefines.h View File

@@ -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)


+ 39
- 3
distrho/src/DistrhoUI.cpp View File

@@ -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 */


+ 1
- 1
distrho/src/DistrhoUIPrivateData.hpp View File

@@ -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


+ 1
- 0
tests/FileBrowserDialog.cpp View File

@@ -125,6 +125,7 @@ protected:
repaint();

FileBrowserOptions opts;
// opts.saving = true;
opts.title = "Look at me";
if (! openFileBrowser(opts))
{


Loading…
Cancel
Save