diff --git a/dpf/CMakeLists.txt b/dpf/CMakeLists.txt index 1cd8d96..02c7707 100644 --- a/dpf/CMakeLists.txt +++ b/dpf/CMakeLists.txt @@ -1,10 +1,10 @@ # DISTRHO Plugin Framework (DPF) # Copyright (C) 2021 Jean Pierre Cimalando -# Copyright (C) 2022-2024 Filipe Coelho +# Copyright (C) 2022-2025 Filipe Coelho # # SPDX-License-Identifier: ISC -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.8) project(DPF) @@ -34,11 +34,11 @@ if(DPF_LIBRARIES) if(PKG_CONFIG_FOUND) pkg_check_modules(CAIRO "cairo") if(CAIRO_FOUND AND (NOT HAIKU)) - dpf__add_dgl_cairo(TRUE, TRUE) + dpf__add_dgl_cairo(TRUE, TRUE, TRUE) endif() endif() - dpf__add_dgl_external(TRUE) - dpf__add_dgl_opengl(TRUE, TRUE) + dpf__add_dgl_external(TRUE, TRUE) + dpf__add_dgl_opengl(TRUE, TRUE, TRUE) endif() if(DPF_EXAMPLES) diff --git a/dpf/Makefile.plugins.mk b/dpf/Makefile.plugins.mk index 7280f78..62af718 100644 --- a/dpf/Makefile.plugins.mk +++ b/dpf/Makefile.plugins.mk @@ -287,7 +287,7 @@ HAVE_DGL = false endif endif -ifeq ($(HAVE_DGL)$(LINUX)$(USE_WEB_VIEW),truetruetrue) +ifeq ($(HAVE_DGL)$(LINUX)$(UI_TYPE),truetruewebview) DGL_LIB_SHARED = $(shell $(CC) -print-file-name=Scrt1.o) endif diff --git a/dpf/cmake/DPF-plugin.cmake b/dpf/cmake/DPF-plugin.cmake index d22dea7..9d5660e 100644 --- a/dpf/cmake/DPF-plugin.cmake +++ b/dpf/cmake/DPF-plugin.cmake @@ -121,23 +121,33 @@ function(dpf_add_plugin NAME) set(_dgl_library) if(_dpf_plugin_FILES_UI) if(_dpf_plugin_UI_TYPE STREQUAL "cairo") - dpf__add_dgl_cairo($> $) + dpf__add_dgl_cairo($> + $ + $) set(_dgl_library dgl-cairo) elseif(_dpf_plugin_UI_TYPE STREQUAL "external") - dpf__add_dgl_external($) + dpf__add_dgl_external($ + $) set(_dgl_library dgl-external) elseif(_dpf_plugin_UI_TYPE STREQUAL "opengl") - dpf__add_dgl_opengl($> $) + dpf__add_dgl_opengl($> + $ + $) set(_dgl_library dgl-opengl) elseif(_dpf_plugin_UI_TYPE STREQUAL "opengl3") - dpf__add_dgl_opengl3($> $) + dpf__add_dgl_opengl3($> + $ + $) set(_dgl_library dgl-opengl3) elseif(_dpf_plugin_UI_TYPE STREQUAL "vulkan") - dpf__add_dgl_vulkan($> $) + dpf__add_dgl_vulkan($> + $ + $) set(_dgl_library dgl-vulkan) elseif(_dpf_plugin_UI_TYPE STREQUAL "webview") set(_dpf_plugin_USE_WEB_VIEW TRUE) - dpf__add_dgl_external($) + dpf__add_dgl_external($ + $) set(_dgl_library dgl-external) else() message(FATAL_ERROR "Unrecognized UI type for plugin: ${_dpf_plugin_UI_TYPE}") @@ -234,6 +244,112 @@ function(dpf_add_plugin NAME) endforeach() endfunction() +# dpf_add_executable(target ) +# ------------------------------------------------------------------------------ +# +# Add a simple executable built using the DISTRHO Plugin Framework. +# +# ------------------------------------------------------------------------------ +# Arguments: +# +# `UI_TYPE` +# the user interface type, can be one of the following: +# - cairo +# - external +# - opengl (default) +# - opengl3 +# - vulkan +# - webview +# +# `NO_SHARED_RESOURCES` +# do not build DPF shared resources (fonts, etc) +# +# `USE_FILE_BROWSER` +# enable file browser dialog APIs +# +# `USE_WEB_VIEW` +# enable web browser view APIs +# +function(dpf_add_executable NAME) + set(options NO_SHARED_RESOURCES USE_FILE_BROWSER USE_WEB_VIEW) + set(oneValueArgs UI_TYPE) + cmake_parse_arguments(_dpf_plugin "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if("${_dpf_plugin_UI_TYPE}" STREQUAL "") + set(_dpf_plugin_UI_TYPE "opengl") + endif() + + set(_dgl_library) + if(_dpf_plugin_UI_TYPE STREQUAL "cairo") + dpf__add_dgl_cairo($> + $ + $) + set(_dgl_library dgl-cairo) + elseif(_dpf_plugin_UI_TYPE STREQUAL "external") + dpf__add_dgl_external($ + $) + set(_dgl_library dgl-external) + elseif(_dpf_plugin_UI_TYPE STREQUAL "opengl") + dpf__add_dgl_opengl($> + $ + $) + set(_dgl_library dgl-opengl) + elseif(_dpf_plugin_UI_TYPE STREQUAL "opengl3") + dpf__add_dgl_opengl3($> + $ + $) + set(_dgl_library dgl-opengl3) + elseif(_dpf_plugin_UI_TYPE STREQUAL "vulkan") + dpf__add_dgl_vulkan($> + $ + $) + set(_dgl_library dgl-vulkan) + elseif(_dpf_plugin_UI_TYPE STREQUAL "webview") + set(_dpf_plugin_USE_WEB_VIEW TRUE) + dpf__add_dgl_external($ + $) + set(_dgl_library dgl-external) + else() + message(FATAL_ERROR "Unrecognized UI type for executable: ${_dpf_plugin_UI_TYPE}") + endif() + + set(_dgl_has_ui OFF) + if(_dgl_library) + set(_dgl_has_ui ON) + endif() + + dpf__create_dummy_source_list(_no_srcs) + dpf__add_executable("${NAME}" ${_no_srcs}) + target_include_directories("${NAME}" PUBLIC "${DPF_ROOT_DIR}/distrho") + + if(_dpf_plugin_USE_FILE_BROWSER) + target_compile_definitions("${NAME}" PUBLIC "DGL_USE_FILE_BROWSER") + endif() + + if(_dpf_plugin_USE_WEB_VIEW) + target_compile_definitions("${NAME}" PUBLIC "DGL_USE_WEB_VIEW") + endif() + + if((NOT WIN32) AND (NOT APPLE) AND (NOT HAIKU)) + target_link_libraries("${NAME}" PRIVATE "dl") + endif() + + if(_dgl_library) + # make sure that all code will see DGL_* definitions + target_link_libraries("${NAME}" PUBLIC + "${_dgl_library}" + "${_dgl_library}-definitions" + dgl-system-libs-definitions + dgl-system-libs) + # extra linkage for linux web view + if(LINUX AND _dpf_plugin_USE_WEB_VIEW) + target_link_libraries("${NAME}" PRIVATE "rt") + endif() + # add the files containing C++17 or Objective-C classes + dpf__add_plugin_specific_ui_sources("${NAME}" "${_dpf_plugin_USE_WEB_VIEW}") + endif() +endfunction() + # ------------------------------------------------------------------------------ # DPF private functions (prefixed with `dpf__`) # ------------------------------------------------------------------------------ @@ -661,7 +777,7 @@ endfunction() # # Add the Cairo variant of DGL, if not already available. # -function(dpf__add_dgl_cairo SHARED_RESOURCES USE_FILE_BROWSER) +function(dpf__add_dgl_cairo SHARED_RESOURCES USE_FILE_BROWSER USE_WEB_VIEW) if(TARGET dgl-cairo) return() endif() @@ -710,6 +826,21 @@ function(dpf__add_dgl_cairo SHARED_RESOURCES USE_FILE_BROWSER) target_compile_definitions(dgl-cairo PUBLIC "DGL_USE_FILE_BROWSER") endif() + if(USE_WEB_VIEW) + target_compile_definitions(dgl-cairo PUBLIC "DGL_USE_FILE_BROWSER") + if(APPLE) + find_library(APPLE_WEBKIT_FRAMEWORK "WebKit") + target_link_libraries(dgl-cairo PRIVATE "${APPLE_WEBKIT_FRAMEWORK}") + elseif(WIN32) + target_sources(dgl-cairo PRIVATE + "${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp") + set_source_files_properties("${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp" + PROPERTIES + COMPILE_FLAGS + $,/std:c++17,-std=gnu++17>) + endif() + endif() + dpf__add_dgl_system_libs() target_link_libraries(dgl-cairo PRIVATE dgl-system-libs) @@ -730,7 +861,7 @@ endfunction() # # Add the external variant of DGL, if not already available. # -function(dpf__add_dgl_external USE_FILE_BROWSER) +function(dpf__add_dgl_external USE_FILE_BROWSER USE_WEB_VIEW) if(TARGET dgl-external) return() endif() @@ -770,6 +901,21 @@ function(dpf__add_dgl_external USE_FILE_BROWSER) target_compile_definitions(dgl-external PUBLIC "DGL_USE_FILE_BROWSER") endif() + if(USE_WEB_VIEW) + target_compile_definitions(dgl-external PUBLIC "DGL_USE_WEB_VIEW") + if(APPLE) + find_library(APPLE_WEBKIT_FRAMEWORK "WebKit") + target_link_libraries(dgl-external PRIVATE "${APPLE_WEBKIT_FRAMEWORK}") + elseif(WIN32) + target_sources(dgl-external PRIVATE + "${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp") + set_source_files_properties("${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp" + PROPERTIES + COMPILE_FLAGS + $,/std:c++17,-std=gnu++17>) + endif() + endif() + dpf__add_dgl_system_libs() target_compile_definitions(dgl-external PUBLIC "DGL_NO_SHARED_RESOURCES") target_link_libraries(dgl-external PRIVATE dgl-system-libs) @@ -786,7 +932,7 @@ endfunction() # # Add the OpenGL variant of DGL, if not already available. # -function(dpf__add_dgl_opengl SHARED_RESOURCES USE_FILE_BROWSER) +function(dpf__add_dgl_opengl SHARED_RESOURCES USE_FILE_BROWSER USE_WEB_VIEW) if(TARGET dgl-opengl) return() endif() @@ -841,6 +987,21 @@ function(dpf__add_dgl_opengl SHARED_RESOURCES USE_FILE_BROWSER) target_compile_definitions(dgl-opengl PUBLIC "DGL_USE_FILE_BROWSER") endif() + if(USE_WEB_VIEW) + target_compile_definitions(dgl-opengl PUBLIC "DGL_USE_WEB_VIEW") + if(APPLE) + find_library(APPLE_WEBKIT_FRAMEWORK "WebKit") + target_link_libraries(dgl-opengl PRIVATE "${APPLE_WEBKIT_FRAMEWORK}") + elseif(WIN32) + target_sources(dgl-opengl PRIVATE + "${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp") + set_source_files_properties("${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp" + PROPERTIES + COMPILE_FLAGS + $,/std:c++17,-std=gnu++17>) + endif() + endif() + dpf__add_dgl_system_libs() target_link_libraries(dgl-opengl PRIVATE dgl-system-libs) @@ -856,7 +1017,7 @@ endfunction() # # Add the OpenGL3 variant of DGL, if not already available. # -function(dpf__add_dgl_opengl3 SHARED_RESOURCES USE_FILE_BROWSER) +function(dpf__add_dgl_opengl3 SHARED_RESOURCES USE_FILE_BROWSER USE_WEB_VIEW) if(TARGET dgl-opengl3) return() endif() @@ -911,6 +1072,21 @@ function(dpf__add_dgl_opengl3 SHARED_RESOURCES USE_FILE_BROWSER) target_compile_definitions(dgl-opengl3 PUBLIC "DGL_USE_FILE_BROWSER") endif() + if(USE_WEB_VIEW) + target_compile_definitions(dgl-opengl3 PUBLIC "DGL_USE_WEB_VIEW") + if(APPLE) + find_library(APPLE_WEBKIT_FRAMEWORK "WebKit") + target_link_libraries(dgl-opengl3 PRIVATE "${APPLE_WEBKIT_FRAMEWORK}") + elseif(WIN32) + target_sources(dgl-opengl3 PRIVATE + "${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp") + set_source_files_properties("${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp" + PROPERTIES + COMPILE_FLAGS + $,/std:c++17,-std=gnu++17>) + endif() + endif() + dpf__add_dgl_system_libs() target_link_libraries(dgl-opengl3 PRIVATE dgl-system-libs) @@ -926,7 +1102,7 @@ endfunction() # # Add the Vulkan variant of DGL, if not already available. # -function(dpf__add_dgl_vulkan SHARED_RESOURCES USE_FILE_BROWSER) +function(dpf__add_dgl_vulkan SHARED_RESOURCES USE_FILE_BROWSER USE_WEB_VIEW) if(TARGET dgl-vulkan) return() endif() @@ -976,6 +1152,21 @@ function(dpf__add_dgl_vulkan SHARED_RESOURCES USE_FILE_BROWSER) target_compile_definitions(dgl-vulkan PUBLIC "DGL_USE_FILE_BROWSER") endif() + if(USE_WEB_VIEW) + target_compile_definitions(dgl-vulkan PUBLIC "DGL_USE_WEB_VIEW") + if(APPLE) + find_library(APPLE_WEBKIT_FRAMEWORK "WebKit") + target_link_libraries(dgl-vulkan PRIVATE "${APPLE_WEBKIT_FRAMEWORK}") + elseif(WIN32) + target_sources(dgl-vulkan PRIVATE + "${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp") + set_source_files_properties("${DPF_ROOT_DIR}/dgl/src/WebViewWin32.cpp" + PROPERTIES + COMPILE_FLAGS + $,/std:c++17,-std=gnu++17>) + endif() + endif() + dpf__add_dgl_system_libs() target_link_libraries(dgl-vulkan PRIVATE dgl-system-libs) @@ -1002,13 +1193,10 @@ function(dpf__add_plugin_specific_ui_sources NAME USE_WEB_VIEW) elseif(WIN32 AND USE_WEB_VIEW) target_sources("${NAME}" PRIVATE "${DPF_ROOT_DIR}/distrho/DistrhoUI_win32.cpp") - if (MSVC) - set_source_files_properties("${DPF_ROOT_DIR}/distrho/DistrhoUI_win32.cpp" - PROPERTIES COMPILE_FLAGS /std:c++17) - else() - set_source_files_properties("${DPF_ROOT_DIR}/distrho/DistrhoUI_win32.cpp" - PROPERTIES COMPILE_FLAGS -std=gnu++17) - endif() + set_source_files_properties("${DPF_ROOT_DIR}/distrho/DistrhoUI_win32.cpp" + PROPERTIES + COMPILE_FLAGS + $,/std:c++17,-std=gnu++17>) target_link_libraries("${NAME}" PRIVATE "ole32" "uuid") endif() endfunction() diff --git a/dpf/dgl/Application.hpp b/dpf/dgl/Application.hpp index da0278a..c028b53 100644 --- a/dpf/dgl/Application.hpp +++ b/dpf/dgl/Application.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -54,6 +54,12 @@ BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_on) BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_off) #endif +#ifdef DGL_USE_WEB_VIEW +BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_web_view_on) +#else +BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_web_view_off) +#endif + #ifdef DGL_NO_SHARED_RESOURCES BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_no_shared_resources_on) #else @@ -78,11 +84,16 @@ class DISTRHO_API Application { public: /** - Constructor. + Constructor for standalone or plugin application. */ - // NOTE: the default value is not yet passed, so we catch where we use this Application(bool isStandalone = true); + /** + Constructor for a standalone application. + This specific constructor is required if using web views in standalone applications. + */ + Application(int argc, char* argv[]); + /** Destructor. */ diff --git a/dpf/dgl/Base.hpp b/dpf/dgl/Base.hpp index 51e7704..c60a737 100644 --- a/dpf/dgl/Base.hpp +++ b/dpf/dgl/Base.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -41,8 +41,8 @@ # error typo detected use DGL_USE_FILE_BROWSER instead of DGL_USE_FILEBROWSER #endif -#ifdef DGL_UI_USE_WEBVIEW -# error typo detected use DGL_UI_USE_WEB_VIEW instead of DGL_UI_USE_WEBVIEW +#ifdef DGL_USE_WEBVIEW +# error typo detected use DGL_USE_WEB_VIEW instead of DGL_USE_WEBVIEW #endif #if defined(DGL_FILE_BROWSER_DISABLED) diff --git a/dpf/dgl/Color.hpp b/dpf/dgl/Color.hpp index f1b7bb3..0b8aaed 100644 --- a/dpf/dgl/Color.hpp +++ b/dpf/dgl/Color.hpp @@ -99,6 +99,11 @@ struct Color { */ Color invert() const noexcept; + /** + Create a new color based on this one but in grayscale (using weighted average). + */ + Color asGrayscale() const noexcept; + /** Create a color specified by hue, saturation and lightness. Values must in [0..1] range. diff --git a/dpf/dgl/EventHandlers.hpp b/dpf/dgl/EventHandlers.hpp index 443fa8d..f05f70d 100644 --- a/dpf/dgl/EventHandlers.hpp +++ b/dpf/dgl/EventHandlers.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -63,6 +63,9 @@ public: bool isCheckable() const noexcept; void setCheckable(bool checkable) noexcept; + bool isEnabled() const noexcept; + void setEnabled(bool enabled, bool appliesToEventInput = true) noexcept; + Point getLastClickPosition() const noexcept; Point getLastMotionPosition() const noexcept; @@ -121,6 +124,9 @@ public: KnobEventHandler& operator=(const KnobEventHandler& other); virtual ~KnobEventHandler(); + bool isEnabled() const noexcept; + void setEnabled(bool enabled, bool appliesToEventInput = true) noexcept; + // if setStep(1) has been called before, this returns true bool isInteger() const noexcept; @@ -138,6 +144,9 @@ public: // NOTE: value is assumed to be scaled if using log void setDefault(float def) noexcept; + float getMinimum() const noexcept; + float getMaximum() const noexcept; + // NOTE: value is assumed to be scaled if using log void setRange(float min, float max) noexcept; diff --git a/dpf/dgl/Makefile b/dpf/dgl/Makefile index da7a5e9..a58b90b 100644 --- a/dpf/dgl/Makefile +++ b/dpf/dgl/Makefile @@ -54,6 +54,10 @@ OBJS_common = \ $(BUILD_DIR)/dgl/Window.cpp.o \ $(BUILD_DIR)/dgl/WindowPrivateData.cpp.o +ifeq ($(WINDOWS)$(USE_WEB_VIEW),truetrue) +OBJS_common += $(BUILD_DIR)/dgl/WebViewWin32.cpp.o +endif + # --------------------------------------------------------------------------------------------------------------------- OBJS_cairo = $(OBJS_common) \ @@ -201,6 +205,11 @@ $(BUILD_DIR)/dgl/pugl.mm.o: src/pugl.mm @echo "Compiling $<" $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) -c -ObjC++ -o $@ +$(BUILD_DIR)/dgl/WebViewWin32.cpp.o: src/WebViewWin32.cpp + -@mkdir -p $(BUILD_DIR)/dgl + @echo "Compiling $<" + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -std=gnu++17 -c -o $@ + # --------------------------------------------------------------------------------------------------------------------- $(BUILD_DIR)/dgl/%.cpp.cairo.o: src/%.cpp diff --git a/dpf/dgl/NanoVG.hpp b/dpf/dgl/NanoVG.hpp index a1fab23..f9c5eb7 100644 --- a/dpf/dgl/NanoVG.hpp +++ b/dpf/dgl/NanoVG.hpp @@ -112,6 +112,11 @@ public: */ GLuint getTextureHandle() const; + /** + Update the image data in-place. + */ + void update(const uchar* data); + private: Handle fHandle; Size fSize; diff --git a/dpf/dgl/WebView.hpp b/dpf/dgl/WebView.hpp new file mode 100644 index 0000000..2c84faf --- /dev/null +++ b/dpf/dgl/WebView.hpp @@ -0,0 +1,28 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2025 Filipe Coelho + * + * 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 DGL_WEB_VIEW_HPP_INCLUDED +#define DGL_WEB_VIEW_HPP_INCLUDED + +#include "Base.hpp" + +START_NAMESPACE_DGL + +#include "../distrho/extra/WebViewImpl.hpp" + +END_NAMESPACE_DGL + +#endif // DGL_WEB_VIEW_HPP_INCLUDED diff --git a/dpf/dgl/Window.hpp b/dpf/dgl/Window.hpp index ac6b89c..64d0f56 100644 --- a/dpf/dgl/Window.hpp +++ b/dpf/dgl/Window.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -23,6 +23,10 @@ # include "FileBrowserDialog.hpp" #endif +#ifdef DGL_USE_WEB_VIEW +# include "WebView.hpp" +#endif + #include #ifdef DISTRHO_NAMESPACE @@ -407,6 +411,27 @@ public: bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions()); #endif + #ifdef DGL_USE_WEB_VIEW + /** + Create a new web view. + + The web view will be added on top of this window. + This means it will draw on top of whatever is below it, + something to take into consideration if mixing regular widgets with web views. + + Provided metrics in @p options must have scale factor pre-applied. + + @p url: The URL to open, assumed to be in encoded form (e.g spaces converted to %20) + @p options: Extra options, optional + */ + bool createWebView(const char* url, const DGL_NAMESPACE::WebViewOptions& options = WebViewOptions()); + + /** + Evaluate/run JavaScript on the web view. + */ + void evaluateJS(const char* js); + #endif + /** Request repaint of this window, for the entire area. */ diff --git a/dpf/dgl/src/Application.cpp b/dpf/dgl/src/Application.cpp index d69ef38..ad5b2be 100644 --- a/dpf/dgl/src/Application.cpp +++ b/dpf/dgl/src/Application.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -24,6 +24,11 @@ START_NAMESPACE_DGL +/* define webview start */ +#if defined(HAVE_X11) && defined(DISTRHO_OS_LINUX) && defined(DGL_USE_WEB_VIEW) +int dpf_webview_start(int argc, char* argv[]); +#endif + // -------------------------------------------------------------------------------------------------------------------- // build config sentinels @@ -42,6 +47,12 @@ BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_on) BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_off) #endif +#ifdef DGL_USE_WEB_VIEW +BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_web_view_on) +#else +BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_web_view_off) +#endif + #ifdef DGL_NO_SHARED_RESOURCES BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_no_shared_resources_on) #else @@ -64,6 +75,11 @@ bool dpf_check_build_status() noexcept #else fail_to_link_is_mismatch_dgl_use_file_browser_off.ok && #endif + #ifdef DGL_USE_WEB_VIEW + fail_to_link_is_mismatch_dgl_use_web_view_on.ok && + #else + fail_to_link_is_mismatch_dgl_use_web_view_off.ok && + #endif #ifdef DGL_NO_SHARED_RESOURCES fail_to_link_is_mismatch_dgl_no_shared_resources_on.ok && #else @@ -96,6 +112,43 @@ Application::Application(const bool isStandalone) #else fail_to_link_is_mismatch_dgl_use_file_browser_off.ok = true; #endif + #ifdef DGL_USE_WEB_VIEW + fail_to_link_is_mismatch_dgl_use_web_view_on.ok = true; + #else + fail_to_link_is_mismatch_dgl_use_web_view_off.ok = true; + #endif + #ifdef DGL_NO_SHARED_RESOURCES + fail_to_link_is_mismatch_dgl_no_shared_resources_on.ok = true; + #else + fail_to_link_is_mismatch_dgl_no_shared_resources_off.ok = true; + #endif + DISTRHO_SAFE_ASSERT(dpf_check_build_status()); +} + +Application::Application(int argc, char* argv[]) + : pData(new PrivateData(true)) +{ + #if defined(HAVE_X11) && defined(DISTRHO_OS_LINUX) && defined(DGL_USE_WEB_VIEW) + if (argc >= 2 && std::strcmp(argv[1], "dpf-ld-linux-webview") == 0) + std::exit(dpf_webview_start(argc, argv)); + #endif + + // build config sentinels + #ifdef DPF_DEBUG + fail_to_link_is_mismatch_dpf_debug_on.ok = true; + #else + fail_to_link_is_mismatch_dpf_debug_off.ok = true; + #endif + #ifdef DGL_USE_FILE_BROWSER + fail_to_link_is_mismatch_dgl_use_file_browser_on.ok = true; + #else + fail_to_link_is_mismatch_dgl_use_file_browser_off.ok = true; + #endif + #ifdef DGL_USE_WEB_VIEW + fail_to_link_is_mismatch_dgl_use_web_view_on.ok = true; + #else + fail_to_link_is_mismatch_dgl_use_web_view_off.ok = true; + #endif #ifdef DGL_NO_SHARED_RESOURCES fail_to_link_is_mismatch_dgl_no_shared_resources_on.ok = true; #else diff --git a/dpf/dgl/src/Color.cpp b/dpf/dgl/src/Color.cpp index df77968..525c651 100644 --- a/dpf/dgl/src/Color.cpp +++ b/dpf/dgl/src/Color.cpp @@ -172,6 +172,14 @@ Color Color::invert() const noexcept return color; } +Color Color::asGrayscale() const noexcept +{ + Color color(*this); + // values taken from https://goodcalculators.com/rgb-to-grayscale-conversion-calculator/ + color.red = color.green = color.blue = 0.299f * color.red + 0.587f * color.green + 0.114f * color.blue; + return color; +} + Color Color::fromHSL(float hue, float saturation, float lightness, float alpha) { float m1, m2; diff --git a/dpf/dgl/src/EventHandlers.cpp b/dpf/dgl/src/EventHandlers.cpp index a2721d2..9ae82ae 100644 --- a/dpf/dgl/src/EventHandlers.cpp +++ b/dpf/dgl/src/EventHandlers.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -31,6 +31,8 @@ struct ButtonEventHandler::PrivateData { int state; bool checkable; bool checked; + bool enabled; + bool enabledInput; Point lastClickPos; Point lastMotionPos; @@ -44,11 +46,16 @@ struct ButtonEventHandler::PrivateData { state(kButtonStateDefault), checkable(false), checked(false), + enabled(true), + enabledInput(true), lastClickPos(0, 0), lastMotionPos(0, 0) {} bool mouseEvent(const Widget::MouseEvent& ev) { + if (! enabledInput) + return false; + lastClickPos = ev.pos; // button was released, handle it now @@ -98,6 +105,9 @@ struct ButtonEventHandler::PrivateData { bool motionEvent(const Widget::MotionEvent& ev) { + if (! enabledInput) + return false; + // keep pressed if (button != -1) { @@ -171,6 +181,27 @@ struct ButtonEventHandler::PrivateData { } } + void setEnabled(const bool enabled2, const bool appliesToEventInput) noexcept + { + if (appliesToEventInput) + enabledInput = enabled2; + + if (enabled == enabled2) + return; + + // reset temp vars if disabling + if (! enabled2) + { + button = -1; + state = kButtonStateDefault; + lastClickPos = Point(); + lastMotionPos = Point(); + } + + enabled = enabled2; + widget->repaint(); + } + DISTRHO_DECLARE_NON_COPYABLE(PrivateData) }; @@ -217,6 +248,16 @@ void ButtonEventHandler::setCheckable(const bool checkable) noexcept pData->checkable = checkable; } +bool ButtonEventHandler::isEnabled() const noexcept +{ + return pData->enabled; +} + +void ButtonEventHandler::setEnabled(const bool enabled, const bool appliesToEventInput) noexcept +{ + pData->setEnabled(enabled, appliesToEventInput); +} + Point ButtonEventHandler::getLastClickPosition() const noexcept { return pData->lastClickPos; @@ -281,6 +322,8 @@ struct KnobEventHandler::PrivateData { float value; float valueDef; float valueTmp; + bool enabled; + bool enabledInput; bool usingDefault; bool usingLog; Orientation orientation; @@ -301,6 +344,8 @@ struct KnobEventHandler::PrivateData { value(0.5f), valueDef(value), valueTmp(value), + enabled(true), + enabledInput(true), usingDefault(false), usingLog(false), orientation(Vertical), @@ -320,6 +365,8 @@ struct KnobEventHandler::PrivateData { value(other->value), valueDef(other->valueDef), valueTmp(value), + enabled(other->enabled), + enabledInput(other->enabledInput), usingDefault(other->usingDefault), usingLog(other->usingLog), orientation(other->orientation), @@ -338,6 +385,8 @@ struct KnobEventHandler::PrivateData { value = other->value; valueDef = other->valueDef; valueTmp = value; + enabled = other->enabled; + enabledInput = other->enabledInput; usingDefault = other->usingDefault; usingLog = other->usingLog; orientation = other->orientation; @@ -363,6 +412,9 @@ struct KnobEventHandler::PrivateData { bool mouseEvent(const Widget::MouseEvent& ev, const double scaleFactor) { + if (! enabledInput) + return false; + if (ev.button != 1) return false; @@ -416,6 +468,9 @@ struct KnobEventHandler::PrivateData { bool motionEvent(const Widget::MotionEvent& ev, const double scaleFactor) { + if (! enabledInput) + return false; + if ((state & kKnobStateDragging) == 0x0) return false; @@ -501,6 +556,9 @@ struct KnobEventHandler::PrivateData { bool scrollEvent(const Widget::ScrollEvent& ev) { + if (! enabledInput) + return false; + if (! widget->contains(ev.pos)) return false; @@ -541,6 +599,28 @@ struct KnobEventHandler::PrivateData { return ((usingLog ? invlogscale(value) : value) - minimum) / diff; } + void setEnabled(const bool enabled2, const bool appliesToEventInput) noexcept + { + if (appliesToEventInput) + enabledInput = enabled2; + + if (enabled == enabled2) + return; + + // reset temp vars if disabling + if (! enabled2) + { + state = kKnobStateDefault; + lastX = 0.0; + lastY = 0.0; + lastClickTime = 0; + valueTmp = value; + } + + enabled = enabled2; + widget->repaint(); + } + void setRange(const float min, const float max) noexcept { DISTRHO_SAFE_ASSERT_RETURN(max > min,); @@ -598,6 +678,16 @@ KnobEventHandler::~KnobEventHandler() delete pData; } +bool KnobEventHandler::isEnabled() const noexcept +{ + return pData->enabled; +} + +void KnobEventHandler::setEnabled(const bool enabled, const bool appliesToEventInput) noexcept +{ + pData->setEnabled(enabled, appliesToEventInput); +} + bool KnobEventHandler::isInteger() const noexcept { return d_isEqual(pData->step, 1.f); @@ -629,6 +719,16 @@ void KnobEventHandler::setDefault(const float def) noexcept pData->usingDefault = true; } +float KnobEventHandler::getMinimum() const noexcept +{ + return pData->minimum; +} + +float KnobEventHandler::getMaximum() const noexcept +{ + return pData->maximum; +} + void KnobEventHandler::setRange(const float min, const float max) noexcept { pData->setRange(min, max); diff --git a/dpf/dgl/src/NanoVG.cpp b/dpf/dgl/src/NanoVG.cpp index 80e3f34..47781cc 100644 --- a/dpf/dgl/src/NanoVG.cpp +++ b/dpf/dgl/src/NanoVG.cpp @@ -279,6 +279,14 @@ GLuint NanoImage::getTextureHandle() const return nvglImageHandle(fHandle.context, fHandle.imageId); } +void NanoImage::update(const uchar* const data) +{ + DISTRHO_SAFE_ASSERT_RETURN(fHandle.context != nullptr && fHandle.imageId != 0,); + DISTRHO_SAFE_ASSERT_RETURN(data != nullptr,); + + nvgUpdateImage(fHandle.context, fHandle.imageId, data); +} + void NanoImage::_updateSize() { int w=0, h=0; diff --git a/dpf/dgl/src/WebViewWin32.cpp b/dpf/dgl/src/WebViewWin32.cpp new file mode 100644 index 0000000..9b3f380 --- /dev/null +++ b/dpf/dgl/src/WebViewWin32.cpp @@ -0,0 +1,23 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2025 Filipe Coelho + * + * 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 CHOC separately because it requires C++17 + +#define DISTRHO_WEB_VIEW_INCLUDE_IMPLEMENTATION +#define WEB_VIEW_NAMESPACE DGL_NAMESPACE +#define WEB_VIEW_DGL_NAMESPACE +#include "../WebView.hpp" +#include "../../distrho/extra/WebViewWin32.hpp" diff --git a/dpf/dgl/src/Window.cpp b/dpf/dgl/src/Window.cpp index b21e5c4..57ae257 100644 --- a/dpf/dgl/src/Window.cpp +++ b/dpf/dgl/src/Window.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -421,6 +421,20 @@ bool Window::openFileBrowser(const FileBrowserOptions& options) } #endif +#ifdef DGL_USE_WEB_VIEW +bool Window::createWebView(const char* const url, const DGL_NAMESPACE::WebViewOptions& options) +{ + return pData->createWebView(url, options); +} + +void Window::evaluateJS(const char* const js) +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->webViewHandle != nullptr,); + + webViewEvaluateJS(pData->webViewHandle, js); +} +#endif + void Window::repaint() noexcept { if (pData->view == nullptr) diff --git a/dpf/dgl/src/WindowPrivateData.cpp b/dpf/dgl/src/WindowPrivateData.cpp index a2afec6..be1a51d 100644 --- a/dpf/dgl/src/WindowPrivateData.cpp +++ b/dpf/dgl/src/WindowPrivateData.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -40,13 +40,11 @@ START_NAMESPACE_DGL #endif #ifdef DGL_DEBUG_EVENTS -# define DGL_DBG(msg) std::fprintf(stderr, "%s", msg); -# define DGL_DBGp(...) std::fprintf(stderr, __VA_ARGS__); -# define DGL_DBGF std::fflush(stderr); +# define DGL_DBG(msg) d_stdout("%s", msg); +# define DGL_DBGp(...) d_stdout(__VA_ARGS__); #else # define DGL_DBG(msg) # define DGL_DBGp(...) -# define DGL_DBGF #endif #define DEFAULT_WIDTH 640 @@ -130,6 +128,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s) filenameToRenderInto(nullptr), #ifdef DGL_USE_FILE_BROWSER fileBrowserHandle(nullptr), + #endif + #ifdef DGL_USE_WEB_VIEW + webViewHandle(nullptr), #endif modal() { @@ -160,6 +161,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c filenameToRenderInto(nullptr), #ifdef DGL_USE_FILE_BROWSER fileBrowserHandle(nullptr), + #endif + #ifdef DGL_USE_WEB_VIEW + webViewHandle(nullptr), #endif modal(ppData) { @@ -192,6 +196,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, filenameToRenderInto(nullptr), #ifdef DGL_USE_FILE_BROWSER fileBrowserHandle(nullptr), + #endif + #ifdef DGL_USE_WEB_VIEW + webViewHandle(nullptr), #endif modal() { @@ -227,6 +234,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, filenameToRenderInto(nullptr), #ifdef DGL_USE_FILE_BROWSER fileBrowserHandle(nullptr), + #endif + #ifdef DGL_USE_WEB_VIEW + webViewHandle(nullptr), #endif modal() { @@ -247,6 +257,10 @@ Window::PrivateData::~PrivateData() #ifdef DGL_USE_FILE_BROWSER if (fileBrowserHandle != nullptr) fileBrowserClose(fileBrowserHandle); + #endif + #ifdef DGL_USE_WEB_VIEW + if (webViewHandle != nullptr) + webViewDestroy(webViewHandle); #endif puglHide(view); appData->oneWindowClosed(); @@ -394,13 +408,21 @@ void Window::PrivateData::hide() if (modal.enabled) stopModal(); -#ifdef DGL_USE_FILE_BROWSER + #ifdef DGL_USE_FILE_BROWSER if (fileBrowserHandle != nullptr) { fileBrowserClose(fileBrowserHandle); fileBrowserHandle = nullptr; } -#endif + #endif + + #ifdef DGL_USE_WEB_VIEW + if (webViewHandle != nullptr) + { + webViewDestroy(webViewHandle); + webViewHandle = nullptr; + } + #endif puglHide(view); @@ -443,6 +465,11 @@ void Window::PrivateData::idleCallback() fileBrowserHandle = nullptr; } #endif + +#ifdef DGL_USE_WEB_VIEW + if (webViewHandle != nullptr) + webViewIdle(webViewHandle); +#endif } // ----------------------------------------------------------------------- @@ -479,7 +506,7 @@ bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback) #ifdef DGL_USE_FILE_BROWSER // ----------------------------------------------------------------------- -// file handling +// file browser dialog bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options) { @@ -491,6 +518,8 @@ bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options) if (options2.title == nullptr) options2.title = puglGetViewString(view, PUGL_WINDOW_TITLE); + options2.className = puglGetViewString(view, PUGL_CLASS_NAME); + fileBrowserHandle = fileBrowserCreate(isEmbed, puglGetNativeView(view), autoScaling ? autoScaleFactor : scaleFactor, @@ -500,12 +529,38 @@ bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options) } #endif // DGL_USE_FILE_BROWSER +#ifdef DGL_USE_WEB_VIEW +// ----------------------------------------------------------------------- +// file browser dialog + +bool Window::PrivateData::createWebView(const char* const url, const DGL_NAMESPACE::WebViewOptions& options) +{ + if (webViewHandle != nullptr) + webViewDestroy(webViewHandle); + + const PuglRect rect = puglGetFrame(view); + uint initialWidth = static_cast(rect.width) - options.offset.x; + uint initialHeight = static_cast(rect.height) - options.offset.y; + + webViewOffset = Point(options.offset.x, options.offset.y); + + webViewHandle = webViewCreate(url, + puglGetNativeView(view), + initialWidth, + initialHeight, + autoScaling ? autoScaleFactor : scaleFactor, + options); + + return webViewHandle != nullptr; +} +#endif // DGL_USE_WEB_VIEW + // ----------------------------------------------------------------------- // modal handling void Window::PrivateData::startModal() { - DGL_DBG("Window modal loop starting..."); DGL_DBGF; + DGL_DBG("Window modal loop starting..."); DISTRHO_SAFE_ASSERT_RETURN(modal.parent != nullptr, show()); // activate modal mode for this window @@ -527,7 +582,7 @@ void Window::PrivateData::startModal() void Window::PrivateData::stopModal() { - DGL_DBG("Window modal loop stopping..."); DGL_DBGF; + DGL_DBG("Window modal loop stopping..."); // deactivate modal mode modal.enabled = false; @@ -579,11 +634,11 @@ void Window::PrivateData::runAsModal(const bool blockWait) // ----------------------------------------------------------------------- // pugl events -void Window::PrivateData::onPuglConfigure(const double width, const double height) +void Window::PrivateData::onPuglConfigure(const uint width, const uint height) { DISTRHO_SAFE_ASSERT_INT2_RETURN(width > 1 && height > 1, width, height,); - DGL_DBGp("PUGL: onReshape : %f %f\n", width, height); + DGL_DBGp("PUGL: onReshape : %d %d\n", width, height); if (autoScaling) { @@ -596,8 +651,16 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh autoScaleFactor = 1.0; } - const uint uwidth = static_cast(width / autoScaleFactor + 0.5); - const uint uheight = static_cast(height / autoScaleFactor + 0.5); + const uint uwidth = d_roundToUnsignedInt(width / autoScaleFactor); + const uint uheight = d_roundToUnsignedInt(height / autoScaleFactor); + + #ifdef DGL_USE_WEB_VIEW + if (webViewHandle != nullptr) + webViewResize(webViewHandle, + uwidth - webViewOffset.getX(), + uheight - webViewOffset.getY(), + autoScaling ? autoScaleFactor : scaleFactor); + #endif self->onReshape(uwidth, uheight); @@ -860,7 +923,7 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu { Window::PrivateData* const pData = (Window::PrivateData*)puglGetHandle(view); #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) - if (event->type != PUGL_TIMER) { + if (event->type != PUGL_TIMER && event->type != PUGL_EXPOSE && event->type != PUGL_MOTION) { printEvent(event, "pugl event: ", true); } #endif @@ -962,7 +1025,7 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu Widget::KeyboardEvent ev; ev.mod = event->key.state; ev.flags = event->key.flags; - ev.time = static_cast(event->key.time * 1000.0 + 0.5); + ev.time = d_roundToUnsignedInt(event->key.time * 1000.0); ev.press = event->type == PUGL_KEY_PRESS; ev.key = event->key.key; ev.keycode = event->key.keycode; @@ -985,7 +1048,7 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu Widget::CharacterInputEvent ev; ev.mod = event->text.state; ev.flags = event->text.flags; - ev.time = static_cast(event->text.time * 1000.0 + 0.5); + ev.time = d_roundToUnsignedInt(event->text.time * 1000.0); ev.keycode = event->text.keycode; ev.character = event->text.character; std::strncpy(ev.string, event->text.string, sizeof(ev.string)); @@ -1008,7 +1071,7 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu Widget::MouseEvent ev; ev.mod = event->button.state; ev.flags = event->button.flags; - ev.time = static_cast(event->button.time * 1000.0 + 0.5); + ev.time = d_roundToUnsignedInt(event->button.time * 1000.0); ev.button = event->button.button + 1; ev.press = event->type == PUGL_BUTTON_PRESS; if (pData->autoScaling && 0) @@ -1031,7 +1094,7 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu Widget::MotionEvent ev; ev.mod = event->motion.state; ev.flags = event->motion.flags; - ev.time = static_cast(event->motion.time * 1000.0 + 0.5); + ev.time = d_roundToUnsignedInt(event->motion.time * 1000.0); if (pData->autoScaling && 0) { const double scaleFactor = pData->autoScaleFactor; @@ -1052,7 +1115,7 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu Widget::ScrollEvent ev; ev.mod = event->scroll.state; ev.flags = event->scroll.flags; - ev.time = static_cast(event->scroll.time * 1000.0 + 0.5); + ev.time = d_roundToUnsignedInt(event->scroll.time * 1000.0); if (pData->autoScaling && 0) { const double scaleFactor = pData->autoScaleFactor; @@ -1119,7 +1182,7 @@ static int printEvent(const PuglEvent* event, const char* prefix, const bool ver { #define FFMT "%6.1f" #define PFMT FFMT " " FFMT -#define PRINT(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) +#define PRINT(fmt, ...) d_stdout(fmt, __VA_ARGS__), 1 switch (event->type) { case PUGL_NOTHING: @@ -1188,25 +1251,21 @@ static int printEvent(const PuglEvent* event, const char* prefix, const bool ver if (verbose) { switch (event->type) { - case PUGL_CREATE: - return fprintf(stderr, "%sCreate\n", prefix); - case PUGL_DESTROY: - return fprintf(stderr, "%sDestroy\n", prefix); - case PUGL_MAP: - return fprintf(stderr, "%sMap\n", prefix); - case PUGL_UNMAP: - return fprintf(stderr, "%sUnmap\n", prefix); - case PUGL_UPDATE: - return 0; // fprintf(stderr, "%sUpdate\n", prefix); + case PUGL_REALIZE: + return PRINT("%sRealize\n", prefix); + case PUGL_UNREALIZE: + return PRINT("%sUnrealize\n", prefix); case PUGL_CONFIGURE: - return PRINT("%sConfigure " PFMT " " PFMT "\n", + return PRINT("%sConfigure %d %d %d %d\n", prefix, event->configure.x, event->configure.y, event->configure.width, event->configure.height); + case PUGL_UPDATE: + return 0; // fprintf(stderr, "%sUpdate\n", prefix); case PUGL_EXPOSE: - return PRINT("%sExpose " PFMT " " PFMT "\n", + return PRINT("%sExpose %d %d %d %d\n", prefix, event->expose.x, event->expose.y, diff --git a/dpf/dgl/src/WindowPrivateData.hpp b/dpf/dgl/src/WindowPrivateData.hpp index 0983a6d..31b1481 100644 --- a/dpf/dgl/src/WindowPrivateData.hpp +++ b/dpf/dgl/src/WindowPrivateData.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -95,6 +95,12 @@ struct Window::PrivateData : IdleCallback { DGL_NAMESPACE::FileBrowserHandle fileBrowserHandle; #endif + #ifdef DGL_USE_WEB_VIEW + /** Handle for web view operations. */ + DGL_NAMESPACE::WebViewHandle webViewHandle; + DGL_NAMESPACE::Point webViewOffset; + #endif + /** Modal window setup. */ struct Modal { PrivateData* parent; // parent of this window (so we can become modal) @@ -169,10 +175,15 @@ struct Window::PrivateData : IdleCallback { bool removeIdleCallback(IdleCallback* callback); #ifdef DGL_USE_FILE_BROWSER - // file handling + // file browser dialog bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options); #endif + #ifdef DGL_USE_WEB_VIEW + // web view + bool createWebView(const char* url, const DGL_NAMESPACE::WebViewOptions& options); + #endif + static void renderToPicture(const char* filename, const GraphicsContext& context, uint width, uint height); // modal handling @@ -181,7 +192,7 @@ struct Window::PrivateData : IdleCallback { void runAsModal(bool blockWait); // pugl events - void onPuglConfigure(double width, double height); + void onPuglConfigure(uint width, uint height); void onPuglExpose(); void onPuglClose(); void onPuglFocus(bool focus, CrossingMode mode); diff --git a/dpf/dgl/src/pugl-upstream/src/mac.m b/dpf/dgl/src/pugl-upstream/src/mac.m index 21469e4..205e695 100644 --- a/dpf/dgl/src/pugl-upstream/src/mac.m +++ b/dpf/dgl/src/pugl-upstream/src/mac.m @@ -188,7 +188,7 @@ getCurrentViewStyleFlags(PuglView* const view) } static PuglStatus -dispatchCurrentChildViewConfiguration(PuglView* const view) +dispatchCurrentChildViewConfiguration(PuglView* const view, bool drawViewResize) { const NSRect framePt = [view->impl->wrapperView frame]; const NSRect framePx = nsRectFromPoints(view, framePt); @@ -197,6 +197,11 @@ dispatchCurrentChildViewConfiguration(PuglView* const view) return PUGL_SUCCESS; } + if (drawViewResize) { + const NSSize sizePt = [view->impl->drawView convertSizeFromBacking:framePx.size]; + [view->impl->drawView setFrameSize:sizePt]; + } + const PuglConfigureEvent ev = { PUGL_CONFIGURE, 0, @@ -317,7 +322,7 @@ dispatchCurrentChildViewConfiguration(PuglView* const view) if (puglview->impl->window) { [puglview->impl->window dispatchCurrentConfiguration]; } else { - dispatchCurrentChildViewConfiguration(puglview); + dispatchCurrentChildViewConfiguration(puglview, true); } reshaped = false; } @@ -1767,7 +1772,7 @@ puglSetFrame(PuglView* view, const PuglRect frame) [impl->wrapperView setFrame:framePt]; [impl->drawView setFrame:sizePt]; - return dispatchCurrentChildViewConfiguration(view); + return dispatchCurrentChildViewConfiguration(view, false); } PuglStatus @@ -1806,7 +1811,7 @@ puglSetPosition(PuglView* const view, const int x, const int y) [impl->drawView setFrameOrigin:drawPt.origin]; // Dispatch new configuration - return dispatchCurrentChildViewConfiguration(view); + return dispatchCurrentChildViewConfiguration(view, false); } PuglStatus @@ -1843,7 +1848,7 @@ puglSetSize(PuglView* const view, const unsigned width, const unsigned height) [impl->drawView setFrameSize:drawPt.size]; // Dispatch new configuration - return dispatchCurrentChildViewConfiguration(view); + return dispatchCurrentChildViewConfiguration(view, false); } PuglStatus diff --git a/dpf/dgl/src/pugl.cpp b/dpf/dgl/src/pugl.cpp index 4acb533..a2585c7 100644 --- a/dpf/dgl/src/pugl.cpp +++ b/dpf/dgl/src/pugl.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -110,16 +110,26 @@ # endif #endif -#ifndef DGL_FILE_BROWSER_DISABLED +#ifdef DGL_USE_FILE_BROWSER +# define DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED # define FILE_BROWSER_DIALOG_DGL_NAMESPACE # define FILE_BROWSER_DIALOG_NAMESPACE DGL_NAMESPACE -# define DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED START_NAMESPACE_DGL # include "../../distrho/extra/FileBrowserDialogImpl.hpp" END_NAMESPACE_DGL # include "../../distrho/extra/FileBrowserDialogImpl.cpp" #endif +#ifdef DGL_USE_WEB_VIEW +# define DGL_WEB_VIEW_HPP_INCLUDED +# define WEB_VIEW_NAMESPACE DGL_NAMESPACE +# define WEB_VIEW_DGL_NAMESPACE +START_NAMESPACE_DGL +# include "../../distrho/extra/WebViewImpl.hpp" +END_NAMESPACE_DGL +# include "../../distrho/extra/WebViewImpl.cpp" +#endif + #if defined(DGL_USING_X11) && defined(DGL_X11_WINDOW_ICON_NAME) extern const ulong* DGL_X11_WINDOW_ICON_NAME; #endif @@ -363,7 +373,8 @@ PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height) #ifdef DGL_USING_X11 // workaround issues in fluxbox, see https://github.com/lv2/pugl/issues/118 - if (view->impl->win && !view->parent && !view->transientParent) + // NOTE troublesome if used under KDE + if (view->impl->win && !view->parent && !view->transientParent && std::getenv("KDE_SESSION_VERSION") == nullptr) { view->sizeHints[PUGL_DEFAULT_SIZE].width = view->sizeHints[PUGL_DEFAULT_SIZE].height = 0; } diff --git a/dpf/distrho/DistrhoDetails.hpp b/dpf/distrho/DistrhoDetails.hpp index f72608f..34affac 100644 --- a/dpf/distrho/DistrhoDetails.hpp +++ b/dpf/distrho/DistrhoDetails.hpp @@ -202,7 +202,7 @@ static constexpr const uint32_t kStateIsOnlyForUI = 0x20; /** Parameter designation.@n - Allows a parameter to be specially designated for a task, like bypass. + Allows a parameter to be specially designated for a task, like bypass and reset. Each designation is unique, there must be only one parameter that uses it.@n The use of designated parameters is completely optional. @@ -214,13 +214,20 @@ enum ParameterDesignation { /** Null or unset designation. */ - kParameterDesignationNull = 0, + kParameterDesignationNull, /** Bypass designation.@n When on (> 0.5f), it means the plugin must run in a bypassed state. */ - kParameterDesignationBypass = 1 + kParameterDesignationBypass, + + /** + Reset designation.@n + When on (> 0.5f), it means the plugin should reset its internal processing state + (like filters, oscillators, envelopes, lfos, etc) and kill all voices. + */ + kParameterDesignationReset, }; /** @@ -234,7 +241,12 @@ namespace ParameterDesignationSymbols { static constexpr const char bypass[] = "dpf_bypass"; /** - Bypass designation symbol, inverted for LV2 so it becomes "enabled". + Reset designation symbol. + */ + static constexpr const char reset[] = "dpf_reset"; + + /** + LV2 bypass designation symbol, inverted for LV2 so it becomes "enabled". */ static constexpr const char bypass_lv2[] = "lv2_enabled"; }; @@ -728,6 +740,18 @@ struct Parameter { ranges.min = 0.0f; ranges.max = 1.0f; break; + case kParameterDesignationReset: + hints = kParameterIsAutomatable|kParameterIsBoolean|kParameterIsInteger|kParameterIsTrigger; + name = "Reset"; + shortName = "Reset"; + symbol = ParameterDesignationSymbols::reset; + unit = ""; + midiCC = 0; + groupId = kPortGroupNone; + ranges.def = 0.0f; + ranges.min = 0.0f; + ranges.max = 1.0f; + break; } } diff --git a/dpf/distrho/DistrhoPluginUtils.hpp b/dpf/distrho/DistrhoPluginUtils.hpp index 414dcd3..aea47cc 100644 --- a/dpf/distrho/DistrhoPluginUtils.hpp +++ b/dpf/distrho/DistrhoPluginUtils.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -37,9 +37,29 @@ START_NAMESPACE_DISTRHO */ const char* getBinaryFilename(); +/** + Get an OS-specific directory intended to store persistent configuration data about the plugin.@n + Calling this function will ensure the dictory exists on the filesystem.@n + The returned path already includes DISTRHO_PLUGIN_NAME and final OS separator. +*/ +const char* getConfigDir(); + +/** + Get an OS-specific directory intended to store "documents" for the plugin.@n + Calling this function will ensure the dictory exists on the filesystem.@n + The returned path already includes DISTRHO_PLUGIN_NAME and final OS separator. +*/ +const char* getDocumentsDir(); + +/** + Get the user "home" directory.@n + This function is provided only for convenience, it should not be needed under normal circunstances. +*/ +const char* getHomeDir(); + /** Get a string representation of the current plugin format we are building against.@n - This can be "JACK/Standalone", "LADSPA", "DSSI", "LV2", "VST2" or "VST3".@n + This can be "AudioUnit", "JACK/Standalone", "LADSPA", "DSSI", "LV2", "VST2" or "VST3" or "CLAP".@n This string is purely informational and must not be used to tweak plugin behaviour. @note DO NOT CHANGE PLUGIN BEHAVIOUR BASED ON PLUGIN FORMAT. diff --git a/dpf/distrho/DistrhoUI.hpp b/dpf/distrho/DistrhoUI.hpp index f2e67f0..ca41257 100644 --- a/dpf/distrho/DistrhoUI.hpp +++ b/dpf/distrho/DistrhoUI.hpp @@ -185,7 +185,8 @@ public: #if DISTRHO_UI_FILE_BROWSER /** Open a file browser dialog with this window as transient parent.@n - A few options can be specified to setup the dialog. + A few options can be specified to setup the dialog.@n + The @a DISTRHO_NAMESPACE::FileBrowserOptions::className variable is automatically set in this call. 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. diff --git a/dpf/distrho/DistrhoUIMain.cpp b/dpf/distrho/DistrhoUIMain.cpp index 2236815..216e019 100644 --- a/dpf/distrho/DistrhoUIMain.cpp +++ b/dpf/distrho/DistrhoUIMain.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -66,7 +66,7 @@ # endif #endif -#if defined(DPF_USING_LD_LINUX_WEBVIEW) && !DISTRHO_IS_STANDALONE +#if defined(DISTRHO_UI_LINUX_WEBVIEW_START) && !DISTRHO_IS_STANDALONE int main(int argc, char* argv[]) { return DISTRHO_NAMESPACE::dpf_webview_start(argc, argv); diff --git a/dpf/distrho/DistrhoUI_win32.cpp b/dpf/distrho/DistrhoUI_win32.cpp index 562a414..74c09f4 100644 --- a/dpf/distrho/DistrhoUI_win32.cpp +++ b/dpf/distrho/DistrhoUI_win32.cpp @@ -22,6 +22,7 @@ #if DISTRHO_UI_WEB_VIEW # define DISTRHO_WEB_VIEW_INCLUDE_IMPLEMENTATION +# define WEB_VIEW_NAMESPACE DISTRHO_NAMESPACE # include "extra/WebView.hpp" # include "extra/WebViewWin32.hpp" #endif diff --git a/dpf/distrho/extra/Filesystem.hpp b/dpf/distrho/extra/Filesystem.hpp new file mode 100644 index 0000000..2fbc32a --- /dev/null +++ b/dpf/distrho/extra/Filesystem.hpp @@ -0,0 +1,106 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2025 Filipe Coelho + * + * 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_FILESYSTEM_UTILS_HPP_INCLUDED +#define DISTRHO_FILESYSTEM_UTILS_HPP_INCLUDED + +#include "String.hpp" + +#include + +#ifdef DISTRHO_OS_WINDOWS +# include +#endif + +START_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- +// filesystem related calls + +/* + * Wrapper around `fopen` call, needed on Windows because its C standard functions use ASCII instead of UTF-8. + */ +static inline +FILE* d_fopen(const char* const pathname, const char* const mode) +{ + #ifdef DISTRHO_OS_WINDOWS + WCHAR lpathname[MAX_PATH]; + WCHAR lmode[4]; + if (MultiByteToWideChar(CP_UTF8, 0, pathname, -1, lpathname, ARRAY_SIZE(lpathname)) != 0 && + MultiByteToWideChar(CP_UTF8, 0, mode, -1, lmode, ARRAY_SIZE(lmode)) != 0) + return _wfopen(lpathname, lmode); + #endif + + return fopen(pathname, mode); +} + +// -------------------------------------------------------------------------------------------------------------------- +// filesystem related classes + +/** + Handy class to help write files in a safe way, which does: + - open pathname + ".tmp" instead of opening a file directly (so partial writes are safe) + - on close, flush data to disk and rename file to remove ".tmp" + + To use it, create a local variable (on the stack) and call ok() or manually check @a fd variable. + @code + if (const SafeFileWriter file("/path/to/file.txt"); file.ok()) + file.write("Success!"); + @endcode + */ +struct SafeFileWriter +{ + String filename; + FILE* const fd; + + /** + Constructor, opening @a pathname + ".tmp" for writing. + */ + SafeFileWriter(const char* const pathname, const char* const mode = "w") + : filename(pathname), + fd(d_fopen(filename + ".tmp", mode)) {} + + /** + Destructor, will flush file data contents, close and rename file. + */ + ~SafeFileWriter() + { + if (fd == nullptr) + return; + + std::fflush(fd); + std::fclose(fd); + std::rename(filename + ".tmp", filename); + } + + /** Check if the file was opened successfully. */ + inline bool ok() const noexcept + { + return fd != nullptr; + } + + /** Wrapper around `fwrite`, purely for convenience. */ + inline size_t write(const void* const ptr, const size_t size, const size_t nmemb = 1) const + { + return std::fwrite(ptr, size, nmemb, fd); + } +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_FILESYSTEM_UTILS_HPP_INCLUDED diff --git a/dpf/distrho/extra/String.hpp b/dpf/distrho/extra/String.hpp index 0255bcd..8a34e98 100644 --- a/dpf/distrho/extra/String.hpp +++ b/dpf/distrho/extra/String.hpp @@ -22,6 +22,10 @@ #include +#if __cplusplus >= 201703L +# include +#endif + START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- @@ -49,10 +53,7 @@ public: fBufferLen(0), fBufferAlloc(false) { - char ch[2]; - ch[0] = c; - ch[1] = '\0'; - + const char ch[2] = { c, '\0' }; _dup(ch); } @@ -87,6 +88,19 @@ public: _dup(strBuf); } + #if __cplusplus >= 201703L + /* + * std::string_view compatible variant. + */ + explicit String(const std::string_view& strView) noexcept + : fBuffer(_null()), + fBufferLen(0), + fBufferAlloc(false) + { + _dup(strView.data(), strView.size()); + } + #endif + /* * Integer. */ diff --git a/dpf/distrho/extra/WebViewImpl.cpp b/dpf/distrho/extra/WebViewImpl.cpp index 7b6df4e..136375a 100644 --- a/dpf/distrho/extra/WebViewImpl.cpp +++ b/dpf/distrho/extra/WebViewImpl.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -99,14 +99,14 @@ #define MACRO_NAME(a, b, c) MACRO_NAME2(a, b, c) #define WEB_VIEW_DELEGATE_CLASS_NAME \ - MACRO_NAME(WebViewDelegate_, _, DISTRHO_NAMESPACE) + MACRO_NAME(WebViewDelegate_, _, WEB_VIEW_NAMESPACE) @interface WEB_VIEW_DELEGATE_CLASS_NAME : NSObject @end @implementation WEB_VIEW_DELEGATE_CLASS_NAME { @public - DISTRHO_NAMESPACE::WebViewMessageCallback callback; + WEB_VIEW_NAMESPACE::WebViewMessageCallback callback; void* callbackPtr; bool loaded; } @@ -242,6 +242,11 @@ START_NAMESPACE_DISTRHO #if WEB_VIEW_USING_X11_IPC +#ifdef WEB_VIEW_DGL_NAMESPACE +using DISTRHO_NAMESPACE::ChildProcess; +using DISTRHO_NAMESPACE::RingBufferControl; +#endif + #ifdef __linux__ typedef int32_t ipc_sem_t; #else @@ -773,8 +778,10 @@ void webViewIdle(const WebViewHandle handle) d_stderr("server ringbuffer data race, abort!"); handle->rbctrl2.flush(); - return; + break; } + + std::free(buffer); #else // unused (void)handle; @@ -941,7 +948,7 @@ struct QSize { S NAME = reinterpret_cast(dlsym(nullptr, #SN)); \ DISTRHO_SAFE_ASSERT_RETURN(NAME != nullptr, false); -static void web_wake_idle(void* const ptr) +static int web_wake_idle(void* const ptr) { WebViewRingBuffer* const shmptr = static_cast(ptr); @@ -989,6 +996,7 @@ static void web_wake_idle(void* const ptr) } free(buffer); + return 0; } // ----------------------------------------------------------------------------------------------------------- @@ -1039,7 +1047,9 @@ static bool gtk3(Display* const display, { void* lib; if ((lib = dlopen("libwebkit2gtk-4.0.so.37", RTLD_NOW|RTLD_GLOBAL)) == nullptr && - (lib = dlopen("libwebkit2gtk-4.0.so", RTLD_NOW|RTLD_GLOBAL)) == nullptr) + (lib = dlopen("libwebkit2gtk-4.1.so.0", RTLD_NOW|RTLD_GLOBAL)) == nullptr && + (lib = dlopen("libwebkit2gtk-4.0.so", RTLD_NOW|RTLD_GLOBAL)) == nullptr && + (lib = dlopen("libwebkit2gtk-4.1.so", RTLD_NOW|RTLD_GLOBAL)) == nullptr) { d_stdout("WebView gtk3 platform not available: %s", dlerror()); return false; @@ -1135,10 +1145,8 @@ static bool gtk3(Display* const display, GtkWidget* const window = gtk_plug_new(winId); DISTRHO_SAFE_ASSERT_RETURN(window != nullptr, false); - gtk_window_set_default_size(GTK_WINDOW(window), - (width - x) * scaleFactor, - (height - y) * scaleFactor); - gtk_window_move(GTK_WINDOW(window), x * scaleFactor, y * scaleFactor); + gtk_window_set_default_size(GTK_WINDOW(window), width, height); + gtk_window_move(GTK_WINDOW(window), x, y); WebKitSettings* const settings = webkit_settings_new(); DISTRHO_SAFE_ASSERT_RETURN(settings != nullptr, false); @@ -1774,7 +1782,7 @@ static bool qtwebengine(const int qtVersion, QWebEnginePage_setWebChannel(&page, &channel, 0); QWebEngineView_move(&webview, QPoint(x, y)); - QWebEngineView_resize(&webview, QSize(static_cast(width), static_cast(height))); + QWebEngineView_resize(&webview, QSize(static_cast(width / scaleFactor), static_cast(height / scaleFactor))); QWebEngineView_winId(&webview); QWindow_setParent(QWebEngineView_windowHandle(&webview), QWindow_fromWinId(winId)); @@ -2061,3 +2069,4 @@ END_NAMESPACE_DISTRHO #undef WEB_VIEW_DISTRHO_NAMESPACE #undef WEB_VIEW_DGL_NAMESPACE +#undef WEB_VIEW_NAMESPACE diff --git a/dpf/distrho/extra/WebViewImpl.hpp b/dpf/distrho/extra/WebViewImpl.hpp index cd18591..40a46dd 100644 --- a/dpf/distrho/extra/WebViewImpl.hpp +++ b/dpf/distrho/extra/WebViewImpl.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -18,7 +18,7 @@ # error bad include #endif -#if !defined(DGL_UI_USE_WEB_VIEW) && defined(DISTRHO_UI_WEB_VIEW) && DISTRHO_UI_WEB_VIEW == 0 +#if !defined(DGL_USE_WEB_VIEW) && defined(DISTRHO_UI_WEB_VIEW) && DISTRHO_UI_WEB_VIEW == 0 # error To use WebViews in DPF plugins please set DISTRHO_UI_WEB_VIEW to 1 #endif @@ -125,3 +125,12 @@ void webViewReload(WebViewHandle webview); void webViewResize(WebViewHandle webview, uint width, uint height, double scaleFactor); // -------------------------------------------------------------------------------------------------------------------- + +/** + Helper class for usage in std::shared_ptr and std::unique_ptr. +*/ +struct WebViewDestroy { + void operator()(WebViewHandle handle) { webViewDestroy(handle); } +}; + +// -------------------------------------------------------------------------------------------------------------------- diff --git a/dpf/distrho/extra/WebViewWin32.hpp b/dpf/distrho/extra/WebViewWin32.hpp index 4b005e2..03049cc 100644 --- a/dpf/distrho/extra/WebViewWin32.hpp +++ b/dpf/distrho/extra/WebViewWin32.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -26,13 +26,26 @@ START_NAMESPACE_DISTRHO class WebView; -WebView* webview_choc_create(const WebViewOptions& opts); +END_NAMESPACE_DISTRHO + +#ifdef WEB_VIEW_DGL_NAMESPACE +START_NAMESPACE_DGL +using DISTRHO_NAMESPACE::WebView; +#else +START_NAMESPACE_DISTRHO +#endif + +WebView* webview_choc_create(const WEB_VIEW_NAMESPACE::WebViewOptions& opts); void webview_choc_destroy(WebView*); void* webview_choc_handle(WebView*); void webview_choc_eval(WebView*, const char* js); void webview_choc_navigate(WebView*, const char* url); +#ifdef WEB_VIEW_DGL_NAMESPACE +END_NAMESPACE_DGL +#else END_NAMESPACE_DISTRHO +#endif // -------------------------------------------------------------------------------------------------------------------- @@ -41,9 +54,13 @@ END_NAMESPACE_DISTRHO # define WC_ERR_INVALID_CHARS 0 # include "choc/choc_WebView.h" +#ifdef WEB_VIEW_DGL_NAMESPACE +START_NAMESPACE_DGL +#else START_NAMESPACE_DISTRHO +#endif -WebView* webview_choc_create(const WebViewOptions& opts) +WebView* webview_choc_create(const WEB_VIEW_NAMESPACE::WebViewOptions& opts) { WebView::Options wopts; wopts.acceptsFirstMouseClick = true; @@ -52,7 +69,7 @@ WebView* webview_choc_create(const WebViewOptions& opts) std::unique_ptr webview = std::make_unique(wopts); DISTRHO_SAFE_ASSERT_RETURN(webview->loadedOK(), nullptr); - if (const WebViewMessageCallback callback = opts.callback) + if (const WEB_VIEW_NAMESPACE::WebViewMessageCallback callback = opts.callback) { webview->addInitScript("function postMessage(m){window.chrome.webview.postMessage(m);}"); @@ -94,7 +111,11 @@ void webview_choc_navigate(WebView* const webview, const char* const url) webview->navigate(url); } +#ifdef WEB_VIEW_DGL_NAMESPACE +END_NAMESPACE_DGL +#else END_NAMESPACE_DISTRHO +#endif #endif // DISTRHO_WEB_VIEW_INCLUDE_IMPLEMENTATION diff --git a/dpf/distrho/src/DistrhoPluginAU.cpp b/dpf/distrho/src/DistrhoPluginAU.cpp index c6ac59e..919faa7 100644 --- a/dpf/distrho/src/DistrhoPluginAU.cpp +++ b/dpf/distrho/src/DistrhoPluginAU.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -264,7 +264,7 @@ bool isNumChannelsComboValid(const uint16_t numInputs, const uint16_t numOutputs // -------------------------------------------------------------------------------------------------------------------- struct PropertyListener { - AudioUnitPropertyID prop; + AudioUnitPropertyID prop; AudioUnitPropertyListenerProc proc; void* userData; }; @@ -348,7 +348,8 @@ public: fUsingRenderListeners(false), fParameterCount(fPlugin.getParameterCount()), fLastParameterValues(nullptr), - fBypassParameterIndex(UINT32_MAX) + fBypassParameterIndex(UINT32_MAX), + fResetParameterIndex(UINT32_MAX) #if DISTRHO_PLUGIN_WANT_MIDI_INPUT , fMidiEventCount(0) #endif @@ -365,7 +366,7 @@ public: , fStateCount(fPlugin.getStateCount()) #endif { - if (fParameterCount != 0) + if (fParameterCount != 0) { fLastParameterValues = new float[fParameterCount]; std::memset(fLastParameterValues, 0, sizeof(float) * fParameterCount); @@ -374,8 +375,17 @@ public: { fLastParameterValues[i] = fPlugin.getParameterValue(i); - if (fPlugin.getParameterDesignation(i) == kParameterDesignationBypass) + switch (fPlugin.getParameterDesignation(i)) + { + case kParameterDesignationNull: + break; + case kParameterDesignationBypass: fBypassParameterIndex = i; + break; + case kParameterDesignationReset: + fResetParameterIndex = i; + break; + } } } @@ -923,13 +933,13 @@ public: case kAudioUnitProperty_FastDispatch: switch (inElement) { - case kAudioUnitGetParameterSelect: + case kAudioUnitGetParameterSelect: *static_cast(outData) = FastDispatchGetParameter; return noErr; - case kAudioUnitSetParameterSelect: + case kAudioUnitSetParameterSelect: *static_cast(outData) = FastDispatchSetParameter; return noErr; - case kAudioUnitRenderSelect: + case kAudioUnitRenderSelect: *static_cast(outData) = FastDispatchRender; return noErr; } @@ -1143,6 +1153,8 @@ public: const CFStringRef keyRef = CFStringCreateWithCString(nullptr, fPlugin.getStateKey(i), kCFStringEncodingASCII); + DISTRHO_SAFE_ASSERT_CONTINUE(keyRef != nullptr); + CFArrayAppendValue(keysRef, keyRef); CFRelease(keyRef); } @@ -1458,7 +1470,7 @@ public: const float value = bypass ? 1.f : 0.f; fLastParameterValues[fBypassParameterIndex] = value; fPlugin.setParameterValue(fBypassParameterIndex, value); - notifyPropertyListeners(inProp, inScope, inElement); + notifyPropertyListeners(inProp, inScope, inElement); } } return noErr; @@ -1480,12 +1492,12 @@ public: #if DISTRHO_PLUGIN_WANT_TIMEPOS { const UInt32 usableDataSize = std::min(inDataSize, static_cast(sizeof(HostCallbackInfo))); - const bool changed = std::memcmp(&fHostCallbackInfo, inData, usableDataSize) != 0; + const bool changed = std::memcmp(&fHostCallbackInfo, inData, usableDataSize) != 0; - std::memcpy(&fHostCallbackInfo, inData, usableDataSize); + std::memcpy(&fHostCallbackInfo, inData, usableDataSize); if (sizeof(HostCallbackInfo) > usableDataSize) - std::memset(&fHostCallbackInfo + usableDataSize, 0, sizeof(HostCallbackInfo) - usableDataSize); + std::memset(&fHostCallbackInfo + usableDataSize, 0, sizeof(HostCallbackInfo) - usableDataSize); if (changed) notifyPropertyListeners(inProp, inScope, inElement); @@ -1612,7 +1624,7 @@ public: AUEventListenerNotify(NULL, NULL, &event); if (fBypassParameterIndex == inElement) - notifyPropertyListeners(kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0); + notifyPropertyListeners(kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0); } return noErr; @@ -1637,28 +1649,44 @@ public: case 'DPFs': DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope); - DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(CFStringRef), inDataSize, kAudioUnitErr_InvalidPropertyValue); + DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(CFDictionaryRef), inDataSize, kAudioUnitErr_InvalidPropertyValue); #if DISTRHO_PLUGIN_WANT_STATE - DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement < fStateCount, inElement, kAudioUnitErr_InvalidElement); + DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement); { - const CFStringRef valueRef = *static_cast(inData); + const CFDictionaryRef dictRef = *static_cast(inData); + DISTRHO_SAFE_ASSERT_RETURN(CFGetTypeID(dictRef) == CFDictionaryGetTypeID(), + kAudioUnitErr_InvalidPropertyValue); + DISTRHO_SAFE_ASSERT_RETURN(CFDictionaryGetCount(dictRef) == 1, kAudioUnitErr_InvalidPropertyValue); + + CFStringRef keyRef = nullptr; + CFStringRef valueRef = nullptr; + CFDictionaryGetKeysAndValues(dictRef, + reinterpret_cast(&keyRef), + reinterpret_cast(&valueRef)); + DISTRHO_SAFE_ASSERT_RETURN(keyRef != nullptr && CFGetTypeID(keyRef) == CFStringGetTypeID(), + kAudioUnitErr_InvalidPropertyValue); DISTRHO_SAFE_ASSERT_RETURN(valueRef != nullptr && CFGetTypeID(valueRef) == CFStringGetTypeID(), kAudioUnitErr_InvalidPropertyValue); - const CFIndex valueLen = CFStringGetLength(valueRef); - char* const value = static_cast(std::malloc(valueLen + 1)); - DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, kAudio_ParamError); - DISTRHO_SAFE_ASSERT_RETURN(CFStringGetCString(valueRef, value, valueLen + 1, kCFStringEncodingUTF8), + const CFIndex keyRefLen = CFStringGetLength(keyRef); + char* key = static_cast(std::malloc(keyRefLen + 1)); + DISTRHO_SAFE_ASSERT_RETURN(CFStringGetCString(keyRef, key, keyRefLen + 1, kCFStringEncodingASCII), kAudioUnitErr_InvalidPropertyValue); - const String& key(fPlugin.getStateKey(inElement)); + const CFIndex valueRefLen = CFStringGetLength(valueRef); + char* value = static_cast(std::malloc(valueRefLen + 1)); + DISTRHO_SAFE_ASSERT_RETURN(CFStringGetCString(valueRef, value, valueRefLen + 1, kCFStringEncodingUTF8), + kAudioUnitErr_InvalidPropertyValue); + + const String dkey(key); // save this key as needed - if (fPlugin.wantStateKey(key)) - fStateMap[key] = value; + if (fPlugin.wantStateKey(dkey)) + fStateMap[dkey] = value; - fPlugin.setState(key, value); + fPlugin.setState(dkey, value); + std::free(key); std::free(value); } return noErr; @@ -1821,7 +1849,12 @@ public: DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Global || scope == kAudioUnitScope_Input || scope == kAudioUnitScope_Output, scope, kAudioUnitErr_InvalidScope); DISTRHO_SAFE_ASSERT_UINT_RETURN(elem == 0, elem, kAudioUnitErr_InvalidElement); - if (fPlugin.isActive()) + if (fResetParameterIndex != UINT32_MAX) + { + fPlugin.setParameterValue(fResetParameterIndex, 1.f); + fPlugin.setParameterValue(fResetParameterIndex, 0.f); + } + else if (fPlugin.isActive()) { fPlugin.deactivate(); fPlugin.activate(); @@ -2180,6 +2213,7 @@ private: const uint32_t fParameterCount; float* fLastParameterValues; uint32_t fBypassParameterIndex; + uint32_t fResetParameterIndex; #if DISTRHO_PLUGIN_WANT_MIDI_INPUT uint32_t fMidiEventCount; @@ -2220,7 +2254,7 @@ private: const PropertyListener& pl(*it); if (pl.prop == prop) - pl.proc(pl.userData, fComponent, prop, scope, elem); + pl.proc(pl.userData, fComponent, prop, scope, elem); } } @@ -2545,12 +2579,12 @@ private: CFStringRef keyRef = CFStringCreateWithCString(nullptr, key, kCFStringEncodingASCII); CFStringRef valueRef = CFStringCreateWithCString(nullptr, value, kCFStringEncodingUTF8); - if (CFDictionaryRef dictRef = CFDictionaryCreate(nullptr, - reinterpret_cast(&keyRef), - reinterpret_cast(&valueRef), - 1, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks)) + if (const CFDictionaryRef dictRef = CFDictionaryCreate(nullptr, + reinterpret_cast(&keyRef), + reinterpret_cast(&valueRef), + 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)) { CFArrayAppendValue(statesRef, dictRef); CFRelease(dictRef); @@ -2845,7 +2879,7 @@ private: // -------------------------------------------------------------------------------------------------------------------- struct AudioComponentPlugInInstance { - AudioComponentPlugInInterface acpi; + AudioComponentPlugInInterface acpi; PluginAU* plugin; AudioComponentPlugInInstance() noexcept @@ -2854,9 +2888,9 @@ struct AudioComponentPlugInInstance { { std::memset(&acpi, 0, sizeof(acpi)); acpi.Open = Open; - acpi.Close = Close; - acpi.Lookup = Lookup; - acpi.reserved = nullptr; + acpi.Close = Close; + acpi.Lookup = Lookup; + acpi.reserved = nullptr; } ~AudioComponentPlugInInstance() @@ -2864,7 +2898,7 @@ struct AudioComponentPlugInInstance { delete plugin; } - static OSStatus Open(void* const self, const AudioUnit component) + static OSStatus Open(void* const self, const AudioUnit component) { d_debug("AudioComponentPlugInInstance::Open(%p)", self); @@ -2872,7 +2906,7 @@ struct AudioComponentPlugInInstance { return noErr; } - static OSStatus Close(void* const self) + static OSStatus Close(void* const self) { d_debug("AudioComponentPlugInInstance::Close(%p)", self); @@ -2964,15 +2998,15 @@ struct AudioComponentPlugInInstance { d_debug("AudioComponentPlugInInstance::GetPropertyInfo(%p, %d:%x:%s, %d:%s, %d, ...)", self, inProp, inProp, AudioUnitPropertyID2Str(inProp), inScope, AudioUnitScope2Str(inScope), inElement); - UInt32 dataSize = 0; - Boolean writable = false; + UInt32 dataSize = 0; + Boolean writable = false; const OSStatus res = self->plugin->auGetPropertyInfo(inProp, inScope, inElement, dataSize, writable); - if (outDataSize != nullptr) - *outDataSize = dataSize; + if (outDataSize != nullptr) + *outDataSize = dataSize; - if (outWritable != nullptr) - *outWritable = writable; + if (outWritable != nullptr) + *outWritable = writable; return res; } @@ -3016,24 +3050,24 @@ struct AudioComponentPlugInInstance { if (res != noErr) return res; - void* outBuffer; + void* outBuffer; uint8_t* tmpBuffer; if (inDataSize < outDataSize) - { - tmpBuffer = new uint8_t[outDataSize]; - outBuffer = tmpBuffer; - } + { + tmpBuffer = new uint8_t[outDataSize]; + outBuffer = tmpBuffer; + } else { - tmpBuffer = nullptr; - outBuffer = outData; - } + tmpBuffer = nullptr; + outBuffer = outData; + } res = self->plugin->auGetProperty(inProp, inScope, inElement, outBuffer); - if (res != noErr) + if (res != noErr) { - *ioDataSize = 0; + *ioDataSize = 0; return res; } diff --git a/dpf/distrho/src/DistrhoPluginCLAP.cpp b/dpf/distrho/src/DistrhoPluginCLAP.cpp index b1487a9..68fb9e4 100644 --- a/dpf/distrho/src/DistrhoPluginCLAP.cpp +++ b/dpf/distrho/src/DistrhoPluginCLAP.cpp @@ -751,6 +751,7 @@ public: updateStateValueCallback), fHost(host), fOutputEvents(nullptr), + fResetParameterIndex(UINT32_MAX), #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0 fUsingCV(false), #endif @@ -763,7 +764,19 @@ public: #endif fHostExtensions(host) { - fCachedParameters.setup(fPlugin.getParameterCount()); + if (const uint32_t paramCount = fPlugin.getParameterCount()) + { + fCachedParameters.setup(paramCount); + + for (uint32_t i=0; irequest_restart(fHost); + if (fResetParameterIndex != UINT32_MAX) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + fMidiEventCount = 0; + #endif + fPlugin.setParameterValue(fResetParameterIndex, 1.f); + fPlugin.setParameterValue(fResetParameterIndex, 0.f); + } + else + { + fHost->request_restart(fHost); + } } bool process(const clap_process_t* const process) @@ -1119,14 +1143,19 @@ public: { const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); - if (fPlugin.getParameterDesignation(index) == kParameterDesignationBypass) + switch (fPlugin.getParameterDesignation(index)) { + case kParameterDesignationBypass: info->flags = CLAP_PARAM_IS_STEPPED|CLAP_PARAM_IS_BYPASS|CLAP_PARAM_IS_AUTOMATABLE; std::strcpy(info->name, "Bypass"); std::strcpy(info->module, "dpf_bypass"); - } - else - { + break; + case kParameterDesignationReset: + info->flags = CLAP_PARAM_IS_STEPPED|CLAP_PARAM_IS_READONLY; + std::strcpy(info->name, "Reset"); + std::strcpy(info->module, "dpf_reset"); + break; + default: const uint32_t hints = fPlugin.getParameterHints(index); const uint32_t groupId = fPlugin.getParameterGroupId(index); @@ -1156,6 +1185,7 @@ public: } d_strncpy(info->module + wrtn, fPlugin.getParameterSymbol(index), CLAP_PATH_SIZE - wrtn); + break; } info->id = index; @@ -1791,6 +1821,7 @@ private: const clap_host_t* const fHost; const clap_output_events_t* fOutputEvents; + uint32_t fResetParameterIndex; #if DISTRHO_PLUGIN_NUM_INPUTS != 0 const float* fAudioInputs[DISTRHO_PLUGIN_NUM_INPUTS]; #endif diff --git a/dpf/distrho/src/DistrhoPluginChecks.h b/dpf/distrho/src/DistrhoPluginChecks.h index a534900..52dc9d7 100644 --- a/dpf/distrho/src/DistrhoPluginChecks.h +++ b/dpf/distrho/src/DistrhoPluginChecks.h @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -286,15 +286,8 @@ static_assert(sizeof(STRINGIFY(DISTRHO_PLUGIN_UNIQUE_ID)) == 5, "The macro DISTR # error DISTRHO_UI_IS_STANDALONE must not be defined #endif -#ifdef DPF_USING_LD_LINUX_WEBVIEW -# error DPF_USING_LD_LINUX_WEBVIEW must not be defined -#endif - -// -------------------------------------------------------------------------------------------------------------------- -// Set DPF_USING_LD_LINUX_WEBVIEW for internal use - -#if DISTRHO_UI_WEB_VIEW && defined(DISTRHO_OS_LINUX) -# define DPF_USING_LD_LINUX_WEBVIEW +#ifdef DISTRHO_UI_LINUX_WEBVIEW_START +# error DISTRHO_UI_LINUX_WEBVIEW_START must not be defined #endif // -------------------------------------------------------------------------------------------------------------------- diff --git a/dpf/distrho/src/DistrhoPluginInternal.hpp b/dpf/distrho/src/DistrhoPluginInternal.hpp index e42fe86..d0bef80 100644 --- a/dpf/distrho/src/DistrhoPluginInternal.hpp +++ b/dpf/distrho/src/DistrhoPluginInternal.hpp @@ -338,6 +338,64 @@ public: DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); +#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + { + uint32_t j=0; +# if DISTRHO_PLUGIN_NUM_INPUTS > 0 + for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i, ++j) + fPlugin->initAudioPort(true, i, fData->audioPorts[j]); +# endif +# if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i, ++j) + fPlugin->initAudioPort(false, i, fData->audioPorts[j]); +# endif + } +#endif // DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + + for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) + fPlugin->initParameter(i, fData->parameters[i]); + + { + std::set portGroupIndices; + +#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + portGroupIndices.insert(fData->audioPorts[i].groupId); +#endif + for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) + portGroupIndices.insert(fData->parameters[i].groupId); + + portGroupIndices.erase(kPortGroupNone); + + if (const uint32_t portGroupSize = static_cast(portGroupIndices.size())) + { + fData->portGroups = new PortGroupWithId[portGroupSize]; + fData->portGroupCount = portGroupSize; + + uint32_t index = 0; + for (std::set::iterator it = portGroupIndices.begin(); it != portGroupIndices.end(); ++it, ++index) + { + PortGroupWithId& portGroup(fData->portGroups[index]); + portGroup.groupId = *it; + + if (portGroup.groupId < portGroupSize) + fPlugin->initPortGroup(portGroup.groupId, portGroup); + else + fillInPredefinedPortGroupData(portGroup.groupId, portGroup); + } + } + } + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + for (uint32_t i=0; i < fData->programCount; ++i) + fPlugin->initProgramName(i, fData->programNames[i]); +#endif + +#if DISTRHO_PLUGIN_WANT_STATE + for (uint32_t i=0; i < fData->stateCount; ++i) + fPlugin->initState(i, fData->states[i]); +#endif + #if defined(DPF_RUNTIME_TESTING) && defined(__GNUC__) && !defined(__clang__) /* Run-time testing build. * Verify that virtual functions are overriden if parameters, programs or states are in use. @@ -380,17 +438,30 @@ public: # if DISTRHO_PLUGIN_WANT_STATE if (fData->stateCount != 0) { - if ((void*)(fPlugin->*(static_cast(&Plugin::initState))) == - (void*)static_cast(&Plugin::initState)) + bool hasNonUiState = false; + for (uint32_t i=0; i < fData->stateCount; ++i) { - d_stderr2("DPF warning: Plugins with state must implement `initState`"); - abort(); + if ((fData->states[i].hints & kStateIsOnlyForUI) == 0) + { + hasNonUiState = true; + break; + } } - if ((void*)(fPlugin->*(&Plugin::setState)) == (void*)&Plugin::setState) + if (hasNonUiState) { - d_stderr2("DPF warning: Plugins with state must implement `setState`"); - abort(); + if ((void*)(fPlugin->*(static_cast(&Plugin::initState))) == + (void*)static_cast(&Plugin::initState)) + { + d_stderr2("DPF warning: Plugins with state must implement `initState`"); + abort(); + } + + if ((void*)(fPlugin->*(&Plugin::setState)) == (void*)&Plugin::setState) + { + d_stderr2("DPF warning: Plugins with state must implement `setState`"); + abort(); + } } } # endif @@ -412,64 +483,6 @@ public: # endif #endif -#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - { - uint32_t j=0; -# if DISTRHO_PLUGIN_NUM_INPUTS > 0 - for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i, ++j) - fPlugin->initAudioPort(true, i, fData->audioPorts[j]); -# endif -# if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i, ++j) - fPlugin->initAudioPort(false, i, fData->audioPorts[j]); -# endif - } -#endif // DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - - for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) - fPlugin->initParameter(i, fData->parameters[i]); - - { - std::set portGroupIndices; - -#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) - portGroupIndices.insert(fData->audioPorts[i].groupId); -#endif - for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) - portGroupIndices.insert(fData->parameters[i].groupId); - - portGroupIndices.erase(kPortGroupNone); - - if (const uint32_t portGroupSize = static_cast(portGroupIndices.size())) - { - fData->portGroups = new PortGroupWithId[portGroupSize]; - fData->portGroupCount = portGroupSize; - - uint32_t index = 0; - for (std::set::iterator it = portGroupIndices.begin(); it != portGroupIndices.end(); ++it, ++index) - { - PortGroupWithId& portGroup(fData->portGroups[index]); - portGroup.groupId = *it; - - if (portGroup.groupId < portGroupSize) - fPlugin->initPortGroup(portGroup.groupId, portGroup); - else - fillInPredefinedPortGroupData(portGroup.groupId, portGroup); - } - } - } - -#if DISTRHO_PLUGIN_WANT_PROGRAMS - for (uint32_t i=0, count=fData->programCount; i < count; ++i) - fPlugin->initProgramName(i, fData->programNames[i]); -#endif - -#if DISTRHO_PLUGIN_WANT_STATE - for (uint32_t i=0, count=fData->stateCount; i < count; ++i) - fPlugin->initState(i, fData->states[i]); -#endif - fData->callbacksPtr = callbacksPtr; fData->writeMidiCallbackFunc = writeMidiCall; fData->requestParameterValueChangeCallbackFunc = requestParameterValueChangeCall; diff --git a/dpf/distrho/src/DistrhoPluginJACK.cpp b/dpf/distrho/src/DistrhoPluginJACK.cpp index 5965191..8b8d295 100644 --- a/dpf/distrho/src/DistrhoPluginJACK.cpp +++ b/dpf/distrho/src/DistrhoPluginJACK.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -88,10 +88,6 @@ static const writeMidiFunc writeMidiCallback = nullptr; static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; #endif -#ifdef DPF_USING_LD_LINUX_WEBVIEW -int dpf_webview_start(int argc, char* argv[]); -#endif - // ----------------------------------------------------------------------- static volatile bool gCloseSignalReceived = false; @@ -1003,9 +999,9 @@ int main(int argc, char* argv[]) } #endif - #ifdef DPF_USING_LD_LINUX_WEBVIEW + #if defined(DISTRHO_UI_LINUX_WEBVIEW_START) if (argc >= 2 && std::strcmp(argv[1], "dpf-ld-linux-webview") == 0) - return dpf_webview_start(argc, argv); + return DISTRHO_NAMESPACE::dpf_webview_start(argc, argv); #endif if (argc == 2 && std::strcmp(argv[1], "selftest") == 0) diff --git a/dpf/distrho/src/DistrhoPluginLV2export.cpp b/dpf/distrho/src/DistrhoPluginLV2export.cpp index ba3cb19..3b0d22c 100644 --- a/dpf/distrho/src/DistrhoPluginLV2export.cpp +++ b/dpf/distrho/src/DistrhoPluginLV2export.cpp @@ -758,6 +758,16 @@ void lv2_generate_ttl(const char* const basename) pluginString += " lv2:portProperty lv2:toggled , lv2:integer ;\n"; pluginString += " lv2:designation lv2:enabled ;\n"; break; + case kParameterDesignationReset: + designated = true; + pluginString += " lv2:name \"Reset\" ;\n"; + pluginString += " lv2:symbol \"" + String(ParameterDesignationSymbols::reset) + "\" ;\n"; + pluginString += " lv2:default 0 ;\n"; + pluginString += " lv2:minimum 0 ;\n"; + pluginString += " lv2:maximum 1 ;\n"; + pluginString += " lv2:portProperty lv2:toggled , lv2:integer , <" LV2_PORT_PROPS__trigger "> ;\n"; + pluginString += " lv2:designation <" LV2_KXSTUDIO_PROPERTIES__Reset "> ;\n"; + break; } } diff --git a/dpf/distrho/src/DistrhoPluginVST3.cpp b/dpf/distrho/src/DistrhoPluginVST3.cpp index cb74392..7295c89 100644 --- a/dpf/distrho/src/DistrhoPluginVST3.cpp +++ b/dpf/distrho/src/DistrhoPluginVST3.cpp @@ -1747,6 +1747,12 @@ public: case kParameterDesignationBypass: flags |= V3_PARAM_IS_BYPASS; break; + case kParameterDesignationReset: + info->flags = V3_PARAM_READ_ONLY | V3_PARAM_IS_HIDDEN; + info->step_count = 1; + strncpy_utf16(info->title, "Reset", 128); + strncpy_utf16(info->short_title, "Reset", 128); + return V3_OK; } if (hints & kParameterIsOutput) diff --git a/dpf/distrho/src/DistrhoUIAU.mm b/dpf/distrho/src/DistrhoUIAU.mm index 1b3f77e..fb3b1cd 100644 --- a/dpf/distrho/src/DistrhoUIAU.mm +++ b/dpf/distrho/src/DistrhoUIAU.mm @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -125,6 +125,7 @@ public: CFRunLoopAddTimer(CFRunLoopGetCurrent(), fTimerRef, kCFRunLoopCommonModes); + // setup property listeners AudioUnitAddPropertyListener(fComponent, kAudioUnitProperty_SampleRate, auPropertyChangedCallback, this); AudioUnitAddPropertyListener(fComponent, 'DPFp', auPropertyChangedCallback, this); #if DISTRHO_PLUGIN_WANT_PROGRAMS @@ -322,15 +323,22 @@ private: #if DISTRHO_PLUGIN_WANT_STATE void setState(const char* const key, const char* const value) { - const std::vector::iterator it = std::find(fStateKeys.begin(), fStateKeys.end(), key); - DISTRHO_SAFE_ASSERT_RETURN(it != fStateKeys.end(),); - - if (const CFStringRef valueRef = CFStringCreateWithCString(nullptr, value, kCFStringEncodingUTF8)) + CFStringRef keyRef = CFStringCreateWithCString(nullptr, key, kCFStringEncodingASCII); + CFStringRef valueRef = CFStringCreateWithCString(nullptr, value, kCFStringEncodingUTF8); + + if (const CFDictionaryRef dictRef = CFDictionaryCreate(nullptr, + reinterpret_cast(&keyRef), + reinterpret_cast(&valueRef), + 1, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)) { - const uint32_t index = it - fStateKeys.begin(); - AudioUnitSetProperty(fComponent, 'DPFs', kAudioUnitScope_Global, index, &valueRef, sizeof(CFStringRef)); - CFRelease(valueRef); + AudioUnitSetProperty(fComponent, 'DPFs', kAudioUnitScope_Global, 0, &dictRef, sizeof(dictRef)); + CFRelease(dictRef); } + + CFRelease(keyRef); + CFRelease(valueRef); } static void setStateCallback(void* const ptr, const char* const key, const char* const value) diff --git a/dpf/distrho/src/DistrhoUIDSSI.cpp b/dpf/distrho/src/DistrhoUIDSSI.cpp index 17caf13..2fa65d3 100644 --- a/dpf/distrho/src/DistrhoUIDSSI.cpp +++ b/dpf/distrho/src/DistrhoUIDSSI.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -38,10 +38,6 @@ static constexpr const setSizeFunc setSizeCallback = nullptr; // unsupported in DSSI static constexpr const fileRequestFunc fileRequestCallback = nullptr; -#ifdef DPF_USING_LD_LINUX_WEBVIEW -int dpf_webview_start(int argc, char* argv[]); -#endif - // -------------------------------------------------------------------------------------------------------------------- @@ -391,9 +387,9 @@ int main(int argc, char* argv[]) { USE_NAMESPACE_DISTRHO - #ifdef DPF_USING_LD_LINUX_WEBVIEW + #if defined(DISTRHO_UI_LINUX_WEBVIEW_START) if (argc >= 2 && std::strcmp(argv[1], "dpf-ld-linux-webview") == 0) - return dpf_webview_start(argc - 1, argv + 1); + return DISTRHO_NAMESPACE::dpf_webview_start(argc, argv); #endif // dummy test mode diff --git a/dpf/distrho/src/DistrhoUIPrivateData.hpp b/dpf/distrho/src/DistrhoUIPrivateData.hpp index bcbcbfc..416f084 100644 --- a/dpf/distrho/src/DistrhoUIPrivateData.hpp +++ b/dpf/distrho/src/DistrhoUIPrivateData.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -61,6 +61,12 @@ START_NAMESPACE_DISTRHO +/* define webview start */ +#if defined(HAVE_X11) && defined(DISTRHO_OS_LINUX) && DISTRHO_UI_WEB_VIEW +# define DISTRHO_UI_LINUX_WEBVIEW_START +int dpf_webview_start(int argc, char* argv[]); +#endif + // ----------------------------------------------------------------------- // Plugin Application, will set class name based on plugin details diff --git a/dpf/distrho/src/DistrhoUtils.cpp b/dpf/distrho/src/DistrhoUtils.cpp index c7146fc..37083c9 100644 --- a/dpf/distrho/src/DistrhoUtils.cpp +++ b/dpf/distrho/src/DistrhoUtils.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * 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 @@ -23,13 +23,19 @@ #include "../DistrhoStandaloneUtils.hpp" #ifdef DISTRHO_OS_WINDOWS +# include +# include # include #else # ifndef STATIC_BUILD # include # endif +# include # include +# include # include +# include +# include #endif #ifdef DISTRHO_OS_WINDOWS @@ -76,6 +82,177 @@ const char* getBinaryFilename() return filename; } +const char* getConfigDir() +{ + #if defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WASM) || defined(DISTRHO_OS_WINDOWS) + return getDocumentsDir(); + #else + static String dir; + + if (dir.isEmpty()) + { + if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME")) + dir = xdgEnv; + + if (dir.isEmpty()) + { + dir = getHomeDir(); + dir += "/.config"; + } + + // ensure main config dir exists + if (access(dir, F_OK) != 0) + mkdir(dir, 0755); + + // and also our custom subdir + dir += "/" DISTRHO_PLUGIN_NAME "/"; + if (access(dir, F_OK) != 0) + mkdir(dir, 0755); + } + + return dir; + #endif +} + +const char* getDocumentsDir() +{ + static String dir; + + if (dir.isEmpty()) + { + #if defined(DISTRHO_OS_MAC) + dir = getHomeDir(); + dir += "/Documents/" DISTRHO_PLUGIN_NAME "/"; + #elif defined(DISTRHO_OS_WASM) + dir = getHomeDir(); + dir += "/"; + #elif defined(DISTRHO_OS_WINDOWS) + WCHAR wpath[MAX_PATH]; + if (SHGetFolderPathW(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, wpath) == S_OK) + { + CHAR apath[MAX_PATH]; + if (WideCharToMultiByte(CP_UTF8, 0, wpath, -1, apath, MAX_PATH, nullptr, nullptr) != 0) + { + dir = apath; + dir += "\\" DISTRHO_PLUGIN_NAME "\\"; + wcscat(wpath, L"\\" DISTRHO_PLUGIN_NAME "\\"); + } + } + #else + String xdgDirsConfigPath(getConfigDir()); + xdgDirsConfigPath += "/user-dirs.dirs"; + + if (FILE* const f = std::fopen(xdgDirsConfigPath, "r")) + { + std::fseek(f, 0, SEEK_END); + const long size = std::ftell(f); + std::fseek(f, 0, SEEK_SET); + + // something is wrong if config dirs file is longer than 1MiB! + if (size > 0 && size < 1024 * 1024) + { + if (char* filedata = static_cast(std::malloc(size))) + { + for (long r = 0, total = 0; total < size;) + { + r = std::fread(filedata + total, 1, size - total, f); + + if (r == 0) + { + std::free(filedata); + filedata = nullptr; + break; + } + + total += r; + } + + if (filedata != nullptr) + { + if (char* const xdgDocsDir = std::strstr(filedata, "XDG_DOCUMENTS_DIR=\"")) + { + if (char* const xdgDocsDirNL = std::strstr(xdgDocsDir, "\"\n")) + { + *xdgDocsDirNL = '\0'; + String sdir(xdgDocsDir + 19); + + if (sdir.startsWith("$HOME")) + { + dir = getHomeDir(); + dir += sdir.buffer() + 5; + } + else + { + dir = sdir; + } + + // ensure main config dir exists + if (access(dir, F_OK) != 0) + mkdir(dir, 0755); + } + } + + std::free(filedata); + } + } + } + + std::fclose(f); + } + + // ${XDG_CONFIG_HOME}/user-dirs.dirs does not exist or has bad data + if (dir.isEmpty()) + { + dir = getDocumentsDir(); + dir += DISTRHO_PLUGIN_NAME "/"; + } + #endif + + // ensure our custom subdir exists + if (dir.isNotEmpty()) + { + #ifdef DISTRHO_OS_WINDOWS + _wmkdir(wpath); + #else + if (access(dir, F_OK) != 0) + mkdir(dir, 0755); + #endif + } + } + + return dir; +} + +const char* getHomeDir() +{ + static String dir; + + if (dir.isEmpty()) + { + #ifdef DISTRHO_OS_WINDOWS + WCHAR wpath[MAX_PATH]; + if (SHGetFolderPathW(nullptr, CSIDL_PROFILE, nullptr, SHGFP_TYPE_CURRENT, wpath) == S_OK) + { + CHAR apath[MAX_PATH]; + if (WideCharToMultiByte(CP_UTF8, 0, wpath, -1, apath, MAX_PATH, nullptr, nullptr) != 0) + dir = apath; + } + #else + if (const char* const homeEnv = getenv("HOME")) + dir = homeEnv; + + if (dir.isEmpty()) + if (struct passwd* const pwd = getpwuid(getuid())) + dir = pwd->pw_dir; + + if (dir.isNotEmpty() && ! dir.endsWith('/')) + dir += "/"; + #endif + } + + return dir; +} + const char* getPluginFormatName() noexcept { #if defined(DISTRHO_PLUGIN_TARGET_AU) @@ -166,11 +343,6 @@ bool requestBufferSizeChange(uint) { return false; } bool requestMIDI() { return false; } #endif -/* define webview start */ -#ifdef DPF_USING_LD_LINUX_WEBVIEW -int dpf_webview_start(int argc, char* argv[]); -#endif - // -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DISTRHO diff --git a/dpf/distrho/src/lv2/lv2_kxstudio_properties.h b/dpf/distrho/src/lv2/lv2_kxstudio_properties.h index 2292265..68dbe66 100644 --- a/dpf/distrho/src/lv2/lv2_kxstudio_properties.h +++ b/dpf/distrho/src/lv2/lv2_kxstudio_properties.h @@ -1,6 +1,6 @@ /* LV2 KXStudio Properties Extension - Copyright 2014-2021 Filipe Coelho + Copyright 2014-2024 Filipe Coelho Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -27,6 +27,7 @@ #define LV2_KXSTUDIO_PROPERTIES_PREFIX LV2_KXSTUDIO_PROPERTIES_URI "#" #define LV2_KXSTUDIO_PROPERTIES__NonAutomatable LV2_KXSTUDIO_PROPERTIES_PREFIX "NonAutomatable" +#define LV2_KXSTUDIO_PROPERTIES__Reset LV2_KXSTUDIO_PROPERTIES_PREFIX "Reset" #define LV2_KXSTUDIO_PROPERTIES__TimePositionTicksPerBeat LV2_KXSTUDIO_PROPERTIES_PREFIX "TimePositionTicksPerBeat" #define LV2_KXSTUDIO_PROPERTIES__TransientWindowId LV2_KXSTUDIO_PROPERTIES_PREFIX "TransientWindowId" diff --git a/dpf/utils/install-plugins-symlinks.sh b/dpf/utils/install-plugins-symlinks.sh new file mode 100755 index 0000000..ac64f31 --- /dev/null +++ b/dpf/utils/install-plugins-symlinks.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# the realpath function is not available on some systems +if ! which realpath &>/dev/null; then + function realpath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" + } +fi + +set -e + +DPF_UTILS_DIR="$(dirname $(realpath ${0}))" + +if [ -d bin ]; then + cd bin +elif [ -d build/bin ]; then + cd build/bin +else + echo "Please run this script from the root folder" + exit +fi + +if [ "$(uname -s)" = "Darwin" ]; then + CLAP_PATH=~/Library/Audio/Plug-Ins/CLAP + LV2_PATH=~/Library/Audio/Plug-Ins/LV2 + VST2_PATH=~/Library/Audio/Plug-Ins/VST + VST3_PATH=~/Library/Audio/Plug-Ins/VST3 +else + CLAP_PATH=~/.clap + LV2_PATH=~/.lv2 + VST2_PATH=~/.vst + VST3_PATH=~/.vst3 +fi + +export IFS=$'\n' + +# NOTE macOS ignores AU plugins installed in ~/Library/Audio/Plug-Ins/Components/ +# we **MUST** install AU plugins system-wide, so we need sudo/root here +# they cannot be symlinks either, so we do a full copy +for p in $(find . -maxdepth 1 -name '*.component' -print | grep '.component'); do + basename=$(basename ${p}) + if [ ! -L /Library/Audio/Plug-Ins/Components/"${basename}" ]; then + sudo cp -r $(pwd)/"${basename}" /Library/Audio/Plug-Ins/Components/ + fi +done + +for p in $(find . -maxdepth 1 -name '*.clap' -print); do + basename=$(basename ${p}) + mkdir -p ${CLAP_PATH} + if [ ! -L ${CLAP_PATH}/"${basename}" ]; then + ln -s $(pwd)/"${basename}" ${CLAP_PATH}/"${basename}" + fi +done + +for p in $(find . -maxdepth 1 -name '*.lv2' -print); do + basename=$(basename ${p}) + mkdir -p ${LV2_PATH} + if [ ! -L ${LV2_PATH}/"${basename}" ]; then + ln -s $(pwd)/"${basename}" ${LV2_PATH}/"${basename}" + fi +done + +for p in $(find . -maxdepth 1 -name '*.vst' -print); do + basename=$(basename ${p}) + mkdir -p ${VST2_PATH} + if [ ! -L ${VST2_PATH}/"${basename}" ]; then + ln -s $(pwd)/"${basename}" ${VST2_PATH}/"${basename}" + fi +done + +for p in $(find . -maxdepth 1 -name '*.vst3' -print); do + basename=$(basename ${p}) + mkdir -p ${VST3_PATH} + if [ ! -L ${VST3_PATH}/"${basename}" ]; then + ln -s $(pwd)/"${basename}" ${VST3_PATH}/"${basename}" + fi +done