* 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 buildpull/351/head
| @@ -30,6 +30,7 @@ jobs: | |||||
| liblo-dev \ | liblo-dev \ | ||||
| libgl-dev \ | libgl-dev \ | ||||
| libcairo2-dev \ | libcairo2-dev \ | ||||
| libdbus-1-dev \ | |||||
| libx11-dev | libx11-dev | ||||
| - name: Create Build Environment | - name: Create Build Environment | ||||
| shell: bash | shell: bash | ||||
| @@ -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-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 | 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 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 | # 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 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 | 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-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 | 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 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 | # 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 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 | sudo ln -s /usr/lib/arm-linux-gnueabihf/liblo.so.7 /usr/lib/arm-linux-gnueabihf/liblo.so | ||||
| @@ -96,7 +96,7 @@ jobs: | |||||
| run: | | run: | | ||||
| sudo dpkg --add-architecture i386 | sudo dpkg --add-architecture i386 | ||||
| sudo apt-get update -qq | 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 | - name: Build linux x86 | ||||
| env: | env: | ||||
| CFLAGS: -m32 | CFLAGS: -m32 | ||||
| @@ -124,7 +124,7 @@ jobs: | |||||
| - name: Set up dependencies | - name: Set up dependencies | ||||
| run: | | run: | | ||||
| sudo apt-get update -qq | 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 | - name: Build linux x86_64 | ||||
| env: | env: | ||||
| LDFLAGS: -static-libgcc -static-libstdc++ | LDFLAGS: -static-libgcc -static-libstdc++ | ||||
| @@ -250,7 +250,7 @@ jobs: | |||||
| sudo dpkg -i kxstudio-repos_10.0.3_all.deb | sudo dpkg -i kxstudio-repos_10.0.3_all.deb | ||||
| sudo apt-get update -qq | sudo apt-get update -qq | ||||
| # build-deps | # 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 | # runtime testing | ||||
| sudo apt-get install -yq carla-git lilv-utils lv2-dev lv2lint valgrind | sudo apt-get install -yq carla-git lilv-utils lv2-dev lv2lint valgrind | ||||
| - name: Build plugins | - name: Build plugins | ||||
| @@ -20,7 +20,7 @@ jobs: | |||||
| - name: Set up dependencies | - name: Set up dependencies | ||||
| run: | | run: | | ||||
| sudo apt-get update -qq | 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 | - name: Without any warnings | ||||
| env: | env: | ||||
| CFLAGS: -Werror | CFLAGS: -Werror | ||||
| @@ -238,6 +238,7 @@ HAVE_OPENGL = true | |||||
| else | else | ||||
| HAVE_OPENGL = $(shell $(PKG_CONFIG) --exists gl && echo true) | HAVE_OPENGL = $(shell $(PKG_CONFIG) --exists gl && echo true) | ||||
| ifneq ($(HAIKU),true) | ifneq ($(HAIKU),true) | ||||
| HAVE_DBUS = $(shell $(PKG_CONFIG) --exists dbus-1 && echo true) | |||||
| HAVE_X11 = $(shell $(PKG_CONFIG) --exists x11 && echo true) | HAVE_X11 = $(shell $(PKG_CONFIG) --exists x11 && echo true) | ||||
| HAVE_XCURSOR = $(shell $(PKG_CONFIG) --exists xcursor && echo true) | HAVE_XCURSOR = $(shell $(PKG_CONFIG) --exists xcursor && echo true) | ||||
| HAVE_XEXT = $(shell $(PKG_CONFIG) --exists xext && echo true) | HAVE_XEXT = $(shell $(PKG_CONFIG) --exists xext && echo true) | ||||
| @@ -284,6 +285,10 @@ DGL_SYSTEM_LIBS += -lgdi32 -lcomdlg32 | |||||
| endif | endif | ||||
| ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true) | 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) | ifeq ($(HAVE_X11),true) | ||||
| DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags x11) -DHAVE_X11 | DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags x11) -DHAVE_X11 | ||||
| DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs x11) | DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs x11) | ||||
| @@ -476,6 +481,7 @@ features: | |||||
| $(call print_available,UNIX) | $(call print_available,UNIX) | ||||
| @echo === Detected features | @echo === Detected features | ||||
| $(call print_available,HAVE_ALSA) | $(call print_available,HAVE_ALSA) | ||||
| $(call print_available,HAVE_DBUS) | |||||
| $(call print_available,HAVE_CAIRO) | $(call print_available,HAVE_CAIRO) | ||||
| $(call print_available,HAVE_DGL) | $(call print_available,HAVE_DGL) | ||||
| $(call print_available,HAVE_LIBLO) | $(call print_available,HAVE_LIBLO) | ||||
| @@ -19,9 +19,14 @@ | |||||
| #include "Geometry.hpp" | #include "Geometry.hpp" | ||||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||||
| # include "../distrho/extra/FileBrowserDialog.hpp" | |||||
| #endif | |||||
| START_NAMESPACE_DGL | START_NAMESPACE_DGL | ||||
| class Application; | class Application; | ||||
| class PluginWindow; | |||||
| class TopLevelWidget; | class TopLevelWidget; | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| @@ -53,53 +58,9 @@ class Window | |||||
| public: | public: | ||||
| #ifndef DGL_FILE_BROWSER_DISABLED | #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. | Window graphics context as a scoped struct. | ||||
| @@ -361,7 +322,7 @@ public: | |||||
| #ifndef DGL_FILE_BROWSER_DISABLED | #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. | 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 a path is selected, onFileSelected() will be called with the user chosen path. | ||||
| @@ -19,18 +19,6 @@ | |||||
| #include "pugl.hpp" | #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 | // #define DGL_DEBUG_EVENTS | ||||
| #if defined(DEBUG) && defined(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) | static double getDesktopScaleFactor(const PuglView* const view) | ||||
| { | { | ||||
| // allow custom scale for testing | // 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) | Window::PrivateData::PrivateData(Application& a, Window* const s) | ||||
| : app(a), | : app(a), | ||||
| appData(a.pData), | appData(a.pData), | ||||
| @@ -249,9 +84,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s) | |||||
| keepAspectRatio(false), | keepAspectRatio(false), | ||||
| ignoreIdleCallbacks(false), | ignoreIdleCallbacks(false), | ||||
| filenameToRenderInto(nullptr), | filenameToRenderInto(nullptr), | ||||
| #ifdef DISTRHO_OS_WINDOWS | |||||
| win32SelectedFile(nullptr), | |||||
| win32FileThread(false, win32SelectedFile), | |||||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||||
| fileBrowserHandle(nullptr), | |||||
| #endif | #endif | ||||
| modal() | modal() | ||||
| { | { | ||||
| @@ -276,9 +110,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c | |||||
| keepAspectRatio(false), | keepAspectRatio(false), | ||||
| ignoreIdleCallbacks(false), | ignoreIdleCallbacks(false), | ||||
| filenameToRenderInto(nullptr), | filenameToRenderInto(nullptr), | ||||
| #ifdef DISTRHO_OS_WINDOWS | |||||
| win32SelectedFile(nullptr), | |||||
| win32FileThread(false, win32SelectedFile), | |||||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||||
| fileBrowserHandle(nullptr), | |||||
| #endif | #endif | ||||
| modal(ppData) | modal(ppData) | ||||
| { | { | ||||
| @@ -307,9 +140,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||||
| keepAspectRatio(false), | keepAspectRatio(false), | ||||
| ignoreIdleCallbacks(false), | ignoreIdleCallbacks(false), | ||||
| filenameToRenderInto(nullptr), | filenameToRenderInto(nullptr), | ||||
| #ifdef DISTRHO_OS_WINDOWS | |||||
| win32SelectedFile(nullptr), | |||||
| win32FileThread(isEmbed, win32SelectedFile), | |||||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||||
| fileBrowserHandle(nullptr), | |||||
| #endif | #endif | ||||
| modal() | modal() | ||||
| { | { | ||||
| @@ -340,9 +172,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||||
| keepAspectRatio(false), | keepAspectRatio(false), | ||||
| ignoreIdleCallbacks(false), | ignoreIdleCallbacks(false), | ||||
| filenameToRenderInto(nullptr), | filenameToRenderInto(nullptr), | ||||
| #ifdef DISTRHO_OS_WINDOWS | |||||
| win32SelectedFile(nullptr), | |||||
| win32FileThread(isEmbed, win32SelectedFile), | |||||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||||
| fileBrowserHandle(nullptr), | |||||
| #endif | #endif | ||||
| modal() | modal() | ||||
| { | { | ||||
| @@ -363,8 +194,9 @@ Window::PrivateData::~PrivateData() | |||||
| if (isEmbed) | if (isEmbed) | ||||
| { | { | ||||
| #ifdef HAVE_X11 | |||||
| sofdFileDialogClose(); | |||||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||||
| if (fileBrowserHandle != nullptr) | |||||
| fileBrowserClose(fileBrowserHandle); | |||||
| #endif | #endif | ||||
| puglHide(view); | puglHide(view); | ||||
| appData->oneWindowClosed(); | appData->oneWindowClosed(); | ||||
| @@ -372,13 +204,6 @@ Window::PrivateData::~PrivateData() | |||||
| isVisible = false; | isVisible = false; | ||||
| } | } | ||||
| #ifdef DISTRHO_OS_WINDOWS | |||||
| win32FileThread.stop(); | |||||
| if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled) | |||||
| std::free(const_cast<char*>(win32SelectedFile)); | |||||
| #endif | |||||
| puglFreeView(view); | puglFreeView(view); | ||||
| } | } | ||||
| @@ -522,9 +347,14 @@ void Window::PrivateData::hide() | |||||
| if (modal.enabled) | if (modal.enabled) | ||||
| stopModal(); | stopModal(); | ||||
| #ifdef HAVE_X11 | |||||
| sofdFileDialogClose(); | |||||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||||
| if (fileBrowserHandle != nullptr) | |||||
| { | |||||
| fileBrowserClose(fileBrowserHandle); | |||||
| fileBrowserHandle = nullptr; | |||||
| } | |||||
| #endif | #endif | ||||
| puglHide(view); | puglHide(view); | ||||
| isVisible = false; | isVisible = false; | ||||
| @@ -566,20 +396,12 @@ void Window::PrivateData::setResizable(const bool resizable) | |||||
| void Window::PrivateData::idleCallback() | void Window::PrivateData::idleCallback() | ||||
| { | { | ||||
| #ifndef DGL_FILE_BROWSER_DISABLED | #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 | #endif | ||||
| } | } | ||||
| @@ -619,120 +441,23 @@ bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback) | |||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| // file handling | // 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 | #endif // ! DGL_FILE_BROWSER_DISABLED | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| @@ -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 { | struct Window::PrivateData : IdleCallback { | ||||
| /** Reference to the DGL Application class this (private data) window associates with. */ | /** Reference to the DGL Application class this (private data) window associates with. */ | ||||
| Application& app; | Application& app; | ||||
| @@ -95,11 +80,9 @@ struct Window::PrivateData : IdleCallback { | |||||
| /** Render to a picture file when non-null, automatically free+unset after saving. */ | /** Render to a picture file when non-null, automatically free+unset after saving. */ | ||||
| char* filenameToRenderInto; | 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 | #endif | ||||
| /** Modal window setup. */ | /** Modal window setup. */ | ||||
| @@ -176,10 +159,7 @@ struct Window::PrivateData : IdleCallback { | |||||
| #ifndef DGL_FILE_BROWSER_DISABLED | #ifndef DGL_FILE_BROWSER_DISABLED | ||||
| // file handling | // 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 | #endif | ||||
| static void renderToPicture(const char* filename, const GraphicsContext& context, uint width, uint height); | static void renderToPicture(const char* filename, const GraphicsContext& context, uint width, uint height); | ||||
| @@ -42,6 +42,7 @@ | |||||
| # endif | # endif | ||||
| #elif defined(DISTRHO_OS_WINDOWS) | #elif defined(DISTRHO_OS_WINDOWS) | ||||
| # include <wctype.h> | # include <wctype.h> | ||||
| # include <winsock2.h> | |||||
| # include <windows.h> | # include <windows.h> | ||||
| # include <windowsx.h> | # include <windowsx.h> | ||||
| # ifdef DGL_CAIRO | # ifdef DGL_CAIRO | ||||
| @@ -57,6 +58,7 @@ | |||||
| # endif | # endif | ||||
| #else | #else | ||||
| # include <dlfcn.h> | # include <dlfcn.h> | ||||
| # include <unistd.h> | |||||
| # include <sys/select.h> | # include <sys/select.h> | ||||
| # include <sys/time.h> | # include <sys/time.h> | ||||
| # include <X11/X.h> | # include <X11/X.h> | ||||
| @@ -90,10 +92,12 @@ | |||||
| # endif | # endif | ||||
| #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 | #endif | ||||
| #ifndef DISTRHO_OS_MAC | #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 | #endif | ||||
| #ifdef DISTRHO_OS_WINDOWS | #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 | #ifndef DISTRHO_OS_MAC | ||||
| END_NAMESPACE_DGL | END_NAMESPACE_DGL | ||||
| @@ -111,10 +111,6 @@ puglMacOSRemoveChildWindow(PuglView* view, PuglView* child); | |||||
| // macOS specific, center view based on parent coordinates (if there is one) | // macOS specific, center view based on parent coordinates (if there is one) | ||||
| PUGL_API void | PUGL_API void | ||||
| puglMacOSShowCentered(PuglView* view); | 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 | #endif | ||||
| #ifdef DISTRHO_OS_WINDOWS | #ifdef DISTRHO_OS_WINDOWS | ||||
| @@ -139,22 +135,6 @@ puglX11GrabFocus(const PuglView* view); | |||||
| // X11 specific, set dialog window type and pid hints | // X11 specific, set dialog window type and pid hints | ||||
| PUGL_API void | PUGL_API void | ||||
| puglX11SetWindowTypeAndPID(const PuglView* view); | 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 | #endif | ||||
| PUGL_END_DECLS | PUGL_END_DECLS | ||||
| @@ -48,6 +48,10 @@ typedef DGL_NAMESPACE::NanoTopLevelWidget UIWidget; | |||||
| typedef DGL_NAMESPACE::TopLevelWidget UIWidget; | typedef DGL_NAMESPACE::TopLevelWidget UIWidget; | ||||
| #endif | #endif | ||||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||||
| # include "extra/FileBrowserDialog.hpp" | |||||
| #endif | |||||
| START_NAMESPACE_DGL | START_NAMESPACE_DGL | ||||
| class PluginWindow; | class PluginWindow; | ||||
| END_NAMESPACE_DGL | END_NAMESPACE_DGL | ||||
| @@ -183,6 +187,22 @@ public: | |||||
| void sendNote(uint8_t channel, uint8_t note, uint8_t velocity); | void sendNote(uint8_t channel, uint8_t note, uint8_t velocity); | ||||
| #endif | #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 | #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS | ||||
| /* -------------------------------------------------------------------------------------------------------- | /* -------------------------------------------------------------------------------------------------------- | ||||
| * Direct DSP access - DO NOT USE THIS UNLESS STRICTLY NECESSARY!! */ | * 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. | The most common exception is custom OpenGL setup, but only really needed for custom OpenGL drawing code. | ||||
| */ | */ | ||||
| virtual void uiReshape(uint width, uint height); | 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(). | 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*). | 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. | If you need to use files as plugin state, please setup and use DISTRHO_PLUGIN_WANT_STATEFILES instead. | ||||
| */ | */ | ||||
| virtual void uiFileBrowserSelected(const char* filename); | virtual void uiFileBrowserSelected(const char* filename); | ||||
| # endif | |||||
| #endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||||
| #endif | |||||
| /* -------------------------------------------------------------------------------------------------------- | /* -------------------------------------------------------------------------------------------------------- | ||||
| * UI Resize Handling, internal */ | * UI Resize Handling, internal */ | ||||
| @@ -22,10 +22,15 @@ | |||||
| #include "src/DistrhoDefines.h" | #include "src/DistrhoDefines.h" | ||||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | #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 | START_NAMESPACE_DISTRHO | ||||
| double getDesktopScaleFactor(const uintptr_t parentWindowHandle) | double getDesktopScaleFactor(const uintptr_t parentWindowHandle) | ||||
| { | { | ||||
| @@ -40,19 +45,19 @@ double getDesktopScaleFactor(const uintptr_t parentWindowHandle) | |||||
| return [NSScreen mainScreen].backingScaleFactor; | return [NSScreen mainScreen].backingScaleFactor; | ||||
| } | } | ||||
| END_NAMESPACE_DISTRHO | 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 | #endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI | ||||
| @@ -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 | |||||
| @@ -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 | |||||
| @@ -207,6 +207,7 @@ typedef unsigned char uchar; | |||||
| typedef unsigned short int ushort; | typedef unsigned short int ushort; | ||||
| typedef unsigned int uint; | typedef unsigned int uint; | ||||
| typedef unsigned long int ulong; | typedef unsigned long int ulong; | ||||
| typedef unsigned long long int ulonglong; | |||||
| /* Deprecated macros */ | /* Deprecated macros */ | ||||
| #define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) DISTRHO_DECLARE_NON_COPYABLE(ClassName) | #define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) DISTRHO_DECLARE_NON_COPYABLE(ClassName) | ||||
| @@ -15,6 +15,29 @@ | |||||
| */ | */ | ||||
| #include "src/DistrhoPluginChecks.h" | #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 DISTRHO_PLUGIN_HAS_EXTERNAL_UI | ||||
| # if defined(DISTRHO_OS_WINDOWS) | # if defined(DISTRHO_OS_WINDOWS) | ||||
| @@ -255,6 +278,19 @@ void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity) | |||||
| } | } | ||||
| #endif | #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 | #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS | ||||
| /* ------------------------------------------------------------------------------------------------------------ | /* ------------------------------------------------------------------------------------------------------------ | ||||
| * Direct DSP access */ | * Direct DSP access */ | ||||
| @@ -311,13 +347,13 @@ void UI::uiReshape(uint, uint) | |||||
| // NOTE this must be the same as Window::onReshape | // NOTE this must be the same as Window::onReshape | ||||
| pData->fallbackOnResize(); | pData->fallbackOnResize(); | ||||
| } | } | ||||
| #endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||||
| # ifndef DGL_FILE_BROWSER_DISABLED | |||||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||||
| void UI::uiFileBrowserSelected(const char*) | void UI::uiFileBrowserSelected(const char*) | ||||
| { | { | ||||
| } | } | ||||
| # endif | |||||
| #endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||||
| #endif | |||||
| /* ------------------------------------------------------------------------------------------------------------ | /* ------------------------------------------------------------------------------------------------------------ | ||||
| * UI Resize Handling, internal */ | * UI Resize Handling, internal */ | ||||
| @@ -433,7 +433,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key) | |||||
| snprintf(title, sizeof(title)-1u, DISTRHO_PLUGIN_NAME ": %s", key); | snprintf(title, sizeof(title)-1u, DISTRHO_PLUGIN_NAME ": %s", key); | ||||
| title[sizeof(title)-1u] = '\0'; | title[sizeof(title)-1u] = '\0'; | ||||
| DGL_NAMESPACE::Window::FileBrowserOptions opts; | |||||
| FileBrowserOptions opts; | |||||
| opts.title = title; | opts.title = title; | ||||
| return window->openFileBrowser(opts); | return window->openFileBrowser(opts); | ||||
| #endif | #endif | ||||
| @@ -125,6 +125,7 @@ protected: | |||||
| repaint(); | repaint(); | ||||
| FileBrowserOptions opts; | FileBrowserOptions opts; | ||||
| // opts.saving = true; | |||||
| opts.title = "Look at me"; | opts.title = "Look at me"; | ||||
| if (! openFileBrowser(opts)) | if (! openFileBrowser(opts)) | ||||
| { | { | ||||