Browse Source

Update to latest DPF

Signed-off-by: falkTX <falktx@falktx.com>
pull/1996/head
falkTX 7 months ago
parent
commit
5627964316
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
95 changed files with 9868 additions and 4487 deletions
  1. +19
    -12
      data/update-dpf
  2. +0
    -33
      data/update-juce
  3. +0
    -1
      source/Makefile.mk
  4. +49
    -3
      source/modules/dgl/Application.hpp
  5. +134
    -64
      source/modules/dgl/Base.hpp
  6. +36
    -2
      source/modules/dgl/Color.hpp
  7. +40
    -6
      source/modules/dgl/EventHandlers.hpp
  8. +2
    -0
      source/modules/dgl/ImageBaseWidgets.hpp
  9. +27
    -13
      source/modules/dgl/NanoVG.hpp
  10. +5
    -21
      source/modules/dgl/OpenGL-include.hpp
  11. +2
    -1
      source/modules/dgl/OpenGL.hpp
  12. +11
    -1
      source/modules/dgl/StandaloneWindow.hpp
  13. +6
    -0
      source/modules/dgl/SubWidget.hpp
  14. +44
    -15
      source/modules/dgl/Widget.hpp
  15. +38
    -8
      source/modules/dgl/Window.hpp
  16. +141
    -4
      source/modules/dgl/src/Application.cpp
  17. +42
    -18
      source/modules/dgl/src/ApplicationPrivateData.cpp
  18. +6
    -0
      source/modules/dgl/src/ApplicationPrivateData.hpp
  19. +60
    -1
      source/modules/dgl/src/Color.cpp
  20. +202
    -45
      source/modules/dgl/src/EventHandlers.cpp
  21. +56
    -10
      source/modules/dgl/src/ImageBaseWidgets.cpp
  22. +107
    -9
      source/modules/dgl/src/NanoVG.cpp
  23. +49
    -58
      source/modules/dgl/src/OpenGL.cpp
  24. +12
    -3
      source/modules/dgl/src/SubWidget.cpp
  25. +13
    -2
      source/modules/dgl/src/TopLevelWidgetPrivateData.cpp
  26. +1
    -1
      source/modules/dgl/src/TopLevelWidgetPrivateData.hpp
  27. +17
    -1
      source/modules/dgl/src/Widget.cpp
  28. +4
    -1
      source/modules/dgl/src/WidgetPrivateData.cpp
  29. +2
    -1
      source/modules/dgl/src/WidgetPrivateData.hpp
  30. +109
    -41
      source/modules/dgl/src/Window.cpp
  31. +216
    -119
      source/modules/dgl/src/WindowPrivateData.cpp
  32. +24
    -9
      source/modules/dgl/src/WindowPrivateData.hpp
  33. +42
    -1
      source/modules/dgl/src/nanovg/nanovg.c
  34. +1
    -1
      source/modules/dgl/src/nanovg/nanovg.h
  35. +22
    -0
      source/modules/dgl/src/nanovg/nanovg_gl.h
  36. +1
    -1
      source/modules/dgl/src/pugl-upstream/COPYING
  37. +59
    -0
      source/modules/dgl/src/pugl-upstream/include/pugl/attributes.h
  38. +3
    -13
      source/modules/dgl/src/pugl-upstream/include/pugl/gl.h
  39. +22
    -0
      source/modules/dgl/src/pugl-upstream/include/pugl/glu.h
  40. +545
    -288
      source/modules/dgl/src/pugl-upstream/include/pugl/pugl.h
  41. +3
    -2
      source/modules/dgl/src/pugl-upstream/include/pugl/stub.h
  42. +59
    -0
      source/modules/dgl/src/pugl-upstream/pugl/attributes.h
  43. +34
    -0
      source/modules/dgl/src/pugl-upstream/pugl/cairo.h
  44. +84
    -0
      source/modules/dgl/src/pugl-upstream/pugl/gl.h
  45. +22
    -0
      source/modules/dgl/src/pugl-upstream/pugl/glu.h
  46. +2189
    -0
      source/modules/dgl/src/pugl-upstream/pugl/pugl.h
  47. +35
    -0
      source/modules/dgl/src/pugl-upstream/pugl/stub.h
  48. +153
    -0
      source/modules/dgl/src/pugl-upstream/pugl/vulkan.h
  49. +115
    -34
      source/modules/dgl/src/pugl-upstream/src/common.c
  50. +113
    -50
      source/modules/dgl/src/pugl-upstream/src/internal.c
  51. +24
    -6
      source/modules/dgl/src/pugl-upstream/src/internal.h
  52. +5
    -5
      source/modules/dgl/src/pugl-upstream/src/mac.h
  53. +570
    -226
      source/modules/dgl/src/pugl-upstream/src/mac.m
  54. +3
    -5
      source/modules/dgl/src/pugl-upstream/src/mac_cairo.m
  55. +19
    -18
      source/modules/dgl/src/pugl-upstream/src/mac_gl.m
  56. +5
    -2
      source/modules/dgl/src/pugl-upstream/src/mac_stub.m
  57. +6
    -3
      source/modules/dgl/src/pugl-upstream/src/mac_vulkan.m
  58. +15
    -0
      source/modules/dgl/src/pugl-upstream/src/macros.h
  59. +2
    -0
      source/modules/dgl/src/pugl-upstream/src/platform.h
  60. +20
    -4
      source/modules/dgl/src/pugl-upstream/src/types.h
  61. +0
    -734
      source/modules/dgl/src/pugl-upstream/src/wasm.c
  62. +0
    -33
      source/modules/dgl/src/pugl-upstream/src/wasm.h
  63. +0
    -255
      source/modules/dgl/src/pugl-upstream/src/wasm_gl.c
  64. +0
    -26
      source/modules/dgl/src/pugl-upstream/src/wasm_stub.c
  65. +481
    -249
      source/modules/dgl/src/pugl-upstream/src/win.c
  66. +16
    -10
      source/modules/dgl/src/pugl-upstream/src/win.h
  67. +6
    -10
      source/modules/dgl/src/pugl-upstream/src/win_cairo.c
  68. +24
    -32
      source/modules/dgl/src/pugl-upstream/src/win_gl.c
  69. +6
    -3
      source/modules/dgl/src/pugl-upstream/src/win_vulkan.c
  70. +743
    -339
      source/modules/dgl/src/pugl-upstream/src/x11.c
  71. +19
    -0
      source/modules/dgl/src/pugl-upstream/src/x11.h
  72. +39
    -16
      source/modules/dgl/src/pugl-upstream/src/x11_cairo.c
  73. +21
    -16
      source/modules/dgl/src/pugl-upstream/src/x11_gl.c
  74. +12
    -3
      source/modules/dgl/src/pugl-upstream/src/x11_vulkan.c
  75. +181
    -142
      source/modules/dgl/src/pugl.cpp
  76. +28
    -10
      source/modules/dgl/src/pugl.hpp
  77. +1084
    -0
      source/modules/distrho/DistrhoDetails.hpp
  78. +68
    -851
      source/modules/distrho/DistrhoPlugin.hpp
  79. +8
    -2
      source/modules/distrho/DistrhoPluginMain.cpp
  80. +27
    -5
      source/modules/distrho/DistrhoPluginUtils.hpp
  81. +11
    -53
      source/modules/distrho/DistrhoUI.hpp
  82. +43
    -7
      source/modules/distrho/DistrhoUIMain.cpp
  83. +149
    -33
      source/modules/distrho/DistrhoUtils.hpp
  84. +2
    -2
      source/modules/distrho/extra/LeakDetector.hpp
  85. +216
    -16
      source/modules/distrho/extra/String.hpp
  86. +10
    -1
      source/modules/distrho/src/DistrhoDefines.h
  87. +32
    -20
      source/modules/distrho/src/DistrhoPlugin.cpp
  88. +141
    -41
      source/modules/distrho/src/DistrhoPluginChecks.h
  89. +193
    -75
      source/modules/distrho/src/DistrhoPluginInternal.hpp
  90. +286
    -103
      source/modules/distrho/src/DistrhoUI.cpp
  91. +45
    -68
      source/modules/distrho/src/DistrhoUIInternal.hpp
  92. +112
    -146
      source/modules/distrho/src/DistrhoUIPrivateData.hpp
  93. +210
    -23
      source/modules/distrho/src/DistrhoUtils.cpp
  94. +1
    -0
      source/plugin/Makefile
  95. +9
    -1
      source/plugin/ui_launcher.cpp

+ 19
- 12
data/update-dpf View File

@@ -11,13 +11,15 @@ DPF_DIR=/tmp/dpf-carla
rm -rf ${DPF_DIR}
git clone git@github.com:DISTRHO/DPF.git ${DPF_DIR} --depth=1 --recursive -b develop

cp -v ${DPF_DIR}/dgl/*.hpp ${CARLA_DIR}/source/modules/dgl/
cp -v ${DPF_DIR}/dgl/src/*.cpp ${CARLA_DIR}/source/modules/dgl/src/
cp -v ${DPF_DIR}/dgl/src/*.hpp ${CARLA_DIR}/source/modules/dgl/src/
cp -v ${DPF_DIR}/distrho/*.cpp ${CARLA_DIR}/source/modules/distrho/
cp -v ${DPF_DIR}/distrho/*.hpp ${CARLA_DIR}/source/modules/distrho/
cp -v ${DPF_DIR}/distrho/src/*.cpp ${CARLA_DIR}/source/modules/distrho/src/
cp -v ${DPF_DIR}/distrho/src/*.hpp ${CARLA_DIR}/source/modules/distrho/src/
cp -v ${DPF_DIR}/dgl/*.hpp ${CARLA_DIR}/source/modules/dgl/
cp -v ${DPF_DIR}/dgl/src/*.cpp ${CARLA_DIR}/source/modules/dgl/src/
cp -v ${DPF_DIR}/dgl/src/*.hpp ${CARLA_DIR}/source/modules/dgl/src/
cp -v ${DPF_DIR}/dgl/src/nanovg/*.* ${CARLA_DIR}/source/modules/dgl/src/nanovg/
cp -v ${DPF_DIR}/distrho/*.cpp ${CARLA_DIR}/source/modules/distrho/
cp -v ${DPF_DIR}/distrho/*.hpp ${CARLA_DIR}/source/modules/distrho/
cp -v ${DPF_DIR}/distrho/src/*.cpp ${CARLA_DIR}/source/modules/distrho/src/
cp -v ${DPF_DIR}/distrho/src/*.h ${CARLA_DIR}/source/modules/distrho/src/
cp -v ${DPF_DIR}/distrho/src/*.hpp ${CARLA_DIR}/source/modules/distrho/src/

cp -v ${DPF_DIR}/distrho/extra/LeakDetector.hpp ${CARLA_DIR}/source/modules/distrho/extra/
cp -v ${DPF_DIR}/distrho/extra/ScopedPointer.hpp ${CARLA_DIR}/source/modules/distrho/extra/
@@ -31,20 +33,25 @@ cp -r -v ${DPF_DIR}/dgl/src/pugl-upstream/COPYING ${CARLA_DIR}/source/modules/dg

rm ${CARLA_DIR}/source/modules/dgl/Cairo.hpp
rm ${CARLA_DIR}/source/modules/dgl/FileBrowserDialog.hpp
rm ${CARLA_DIR}/source/modules/dgl/Layout.hpp
rm ${CARLA_DIR}/source/modules/dgl/Vulkan.hpp
rm ${CARLA_DIR}/source/modules/dgl/WebView.hpp
rm ${CARLA_DIR}/source/modules/dgl/src/Cairo.cpp
rm ${CARLA_DIR}/source/modules/dgl/src/Vulkan.cpp
rm ${CARLA_DIR}/source/modules/dgl/src/Layout.cpp
rm ${CARLA_DIR}/source/modules/dgl/src/Resources.{cpp,hpp}
rm ${CARLA_DIR}/source/modules/dgl/src/Stub.cpp
rm ${CARLA_DIR}/source/modules/dgl/src/Vulkan.cpp
rm ${CARLA_DIR}/source/modules/dgl/src/WebViewWin32.cpp

rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/src/.clang-tidy
rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/include/.clang-tidy
rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/include/meson.build
rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/include/pugl/cairo.h
rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/include/pugl/vulkan.h
rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/src/.clang-tidy

rm ${CARLA_DIR}/source/modules/distrho/DistrhoInfo.hpp
rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoPlugin{JACK,LADSPA+DSSI,LV2,LV2export,VST2,VST3}.cpp
rm ${CARLA_DIR}/source/modules/distrho/DistrhoUI_win32.cpp
rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoPlugin{AU,CLAP,Export,JACK,LADSPA+DSSI,LV2,LV2export,Stub,VST2,VST3}.cpp
rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoPluginVST.hpp
rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoUI{DSSI,LV2,VST3}.cpp
rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoUI{DSSI,LV2,Stub,VST3}.cpp

rm -rf ${DPF_DIR}

+ 0
- 33
data/update-juce View File

@@ -1,33 +0,0 @@
#!/bin/bash

set -e

cd $(dirname ${0})
cd ..

CARLA_DIR=$(pwd)
DISTRHO_PORTS_DIR=/tmp/distrho-ports-carla

rm -rf ${DISTRHO_PORTS_DIR}
git clone git@github.com:DISTRHO/DISTRHO-Ports.git ${DISTRHO_PORTS_DIR} --depth=1

CARLA_MODULES_DIR=${CARLA_DIR}/source/modules
JUCE_MODULES_DIR=${DISTRHO_PORTS_DIR}/libs/juce7/source/modules

MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_dsp juce_events juce_graphics juce_gui_basics juce_gui_extra")

for M in ${MODULES}; do
echo ${CARLA_MODULES_DIR}/${M};
rm -f ${CARLA_MODULES_DIR}/${M}/juce_*
rm -rf ${CARLA_MODULES_DIR}/${M}/{a..z}*
cp -r -v ${JUCE_MODULES_DIR}/${M}/* ${CARLA_MODULES_DIR}/${M}/
rm ${CARLA_MODULES_DIR}/${M}/juce_*.mm
done

find ${CARLA_MODULES_DIR} -name juce_module_info -delete

rm -rf ${CARLA_MODULES_DIR}/../includes/vst3sdk
mv ${CARLA_MODULES_DIR}/juce_audio_processors/format_types/VST3_SDK ${CARLA_MODULES_DIR}/../includes/vst3sdk
rm -rf ${CARLA_MODULES_DIR}/../includes/vst3sdk/*.pdf

# rm -rf ${DISTRHO_PORTS_DIR}

+ 0
- 1
source/Makefile.mk View File

@@ -186,7 +186,6 @@ endif
endif

ifneq ($(USING_CUSTOM_DPF),true)
BASE_FLAGS += -DDGL_FILE_BROWSER_DISABLED
BASE_FLAGS += -DDGL_NO_SHARED_RESOURCES
endif



+ 49
- 3
source/modules/dgl/Application.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -27,6 +27,47 @@ END_NAMESPACE_DISTRHO

START_NAMESPACE_DGL

// --------------------------------------------------------------------------------------------------------------------
// build config sentinels

/**
This set of static variables act as a build sentinel that detects a configuration error.

Usually this means the way DGL was built and how it is being used and linked into your program is different,
we want to avoid such combinations as memory layout would then also be different
leading to all sort of subtle but very nasty memory corruption issues.

Make sure the flags used to build DGL match the ones used by your program and the link errors should go away.
*/
#define BUILD_CONFIG_SENTINEL(NAME) \
static struct DISTRHO_JOIN_MACRO(_, NAME) { bool ok; DISTRHO_JOIN_MACRO(_, NAME)() noexcept; } NAME;

#ifdef DPF_DEBUG
BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dpf_debug_on)
#else
BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dpf_debug_off)
#endif

#ifdef DGL_USE_FILE_BROWSER
BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_on)
#else
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
BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_no_shared_resources_off)
#endif

#undef BUILD_CONFIG_SENTINEL

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

/**
@@ -43,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.
*/


+ 134
- 64
source/modules/dgl/Base.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -20,6 +20,35 @@
#include "../distrho/extra/LeakDetector.hpp"
#include "../distrho/extra/ScopedPointer.hpp"

// --------------------------------------------------------------------------------------------------------------------
// Compatibility checks

#if defined(DGL_CAIRO) && defined(DGL_EXTERNAL)
# error invalid build config: trying to build for both cairo and external at the same time
#elif defined(DGL_CAIRO) && defined(DGL_OPENGL)
# error invalid build config: trying to build for both cairo and opengl at the same time
#elif defined(DGL_CAIRO) && defined(DGL_VULKAN)
# error invalid build config: trying to build for both cairo and vulkan at the same time
#elif defined(DGL_EXTERNAL) && defined(DGL_OPENGL)
# error invalid build config: trying to build for both external and opengl at the same time
#elif defined(DGL_EXTERNAL) && defined(DGL_VULKAN)
# error invalid build config: trying to build for both external and vulkan at the same time
#elif defined(DGL_OPENGL) && defined(DGL_VULKAN)
# error invalid build config: trying to build for both opengl and vulkan at the same time
#endif

#ifdef DGL_USE_FILEBROWSER
# error typo detected use DGL_USE_FILE_BROWSER instead of DGL_USE_FILEBROWSER
#endif

#ifdef DGL_USE_WEBVIEW
# error typo detected use DGL_USE_WEB_VIEW instead of DGL_USE_WEBVIEW
#endif

#if defined(DGL_FILE_BROWSER_DISABLED)
# error DGL_FILE_BROWSER_DISABLED has been replaced by DGL_USE_FILE_BROWSER (opt-in vs opt-out)
#endif

// --------------------------------------------------------------------------------------------------------------------
// Define namespace

@@ -40,10 +69,13 @@ START_NAMESPACE_DGL
Keyboard modifier flags.
*/
enum Modifier {
kModifierShift = 1u << 0u, ///< Shift key
kModifierControl = 1u << 1u, ///< Control key
kModifierAlt = 1u << 2u, ///< Alt/Option key
kModifierSuper = 1u << 3u ///< Mod4/Command/Windows key
kModifierShift = 1U << 0U, ///< Shift key
kModifierControl = 1U << 1U, ///< Control key
kModifierAlt = 1U << 2U, ///< Alt/Option key
kModifierSuper = 1U << 3U, ///< Mod4/Command/Windows key
kModifierNumLock = 1U << 4U, ///< Num lock enabled
kModifierScrollLock = 1U << 5U, ///< Scroll lock enabled
kModifierCapsLock = 1U << 6U, ///< Caps lock enabled
};

/**
@@ -62,55 +94,88 @@ enum Modifier {
*/
enum Key {
// Convenience symbols for ASCII control characters
kKeyBackspace = 0x08,
kKeyEscape = 0x1B,
kKeyDelete = 0x7F,
kKeyBackspace = 0x00000008U, ///< Backspace
kKeyTab = 0x00000009U, ///< Tab
kKeyEnter = 0x0000000DU, ///< Enter
kKeyEscape = 0x0000001BU, ///< Escape
kKeyDelete = 0x0000007FU, ///< Delete
kKeySpace = 0x00000020U, ///< Space

// Unicode Private Use Area
kKeyF1 = 0xE000U, ///< F1
kKeyF2, ///< F2
kKeyF3, ///< F3
kKeyF4, ///< F4
kKeyF5, ///< F5
kKeyF6, ///< F6
kKeyF7, ///< F7
kKeyF8, ///< F8
kKeyF9, ///< F9
kKeyF10, ///< F10
kKeyF11, ///< F11
kKeyF12, ///< F12
kKeyPageUp = 0xE031U, ///< Page Up
kKeyPageDown, ///< Page Down
kKeyEnd, ///< End
kKeyHome, ///< Home
kKeyLeft, ///< Left
kKeyUp, ///< Up
kKeyRight, ///< Right
kKeyDown, ///< Down
kKeyPrintScreen = 0xE041U, ///< Print Screen
kKeyInsert, ///< Insert
kKeyPause, ///< Pause/Break
kKeyMenu, ///< Menu
kKeyNumLock, ///< Num Lock
kKeyScrollLock, ///< Scroll Lock
kKeyCapsLock, ///< Caps Lock
kKeyShiftL = 0xE051U, ///< Left Shift
kKeyShiftR, ///< Right Shift
kKeyControlL, ///< Left Control
kKeyControlR, ///< Right Control
kKeyAltL, ///< Left Alt
kKeyAltR, ///< Right Alt / AltGr
kKeySuperL, ///< Left Super
kKeySuperR, ///< Right Super
kKeyPad0 = 0xE060U, ///< Keypad 0
kKeyPad1, ///< Keypad 1
kKeyPad2, ///< Keypad 2
kKeyPad3, ///< Keypad 3
kKeyPad4, ///< Keypad 4
kKeyPad5, ///< Keypad 5
kKeyPad6, ///< Keypad 6
kKeyPad7, ///< Keypad 7
kKeyPad8, ///< Keypad 8
kKeyPad9, ///< Keypad 9
kKeyPadEnter, ///< Keypad Enter
kKeyPadPageUp = 0xE071U, ///< Keypad Page Up
kKeyPadPageDown, ///< Keypad Page Down
kKeyPadEnd, ///< Keypad End
kKeyPadHome, ///< Keypad Home
kKeyPadLeft, ///< Keypad Left
kKeyPadUp, ///< Keypad Up
kKeyPadRight, ///< Keypad Right
kKeyPadDown, ///< Keypad Down
kKeyPadClear = 0xE09DU, ///< Keypad Clear/Begin
kKeyPadInsert, ///< Keypad Insert
kKeyPadDelete, ///< Keypad Delete
kKeyPadEqual, ///< Keypad Equal
kKeyPadMultiply = 0xE0AAU, ///< Keypad Multiply
kKeyPadAdd, ///< Keypad Add
kKeyPadSeparator, ///< Keypad Separator
kKeyPadSubtract, ///< Keypad Subtract
kKeyPadDecimal, ///< Keypad Decimal
kKeyPadDivide, ///< Keypad Divide

// Backwards compatibility with old DPF
kCharBackspace DISTRHO_DEPRECATED_BY("kKeyBackspace") = kKeyBackspace,
kCharEscape DISTRHO_DEPRECATED_BY("kKeyEscape") = kKeyEscape,
kCharDelete DISTRHO_DEPRECATED_BY("kKeyDelete") = kKeyDelete,

// Unicode Private Use Area
kKeyF1 = 0xE000,
kKeyF2,
kKeyF3,
kKeyF4,
kKeyF5,
kKeyF6,
kKeyF7,
kKeyF8,
kKeyF9,
kKeyF10,
kKeyF11,
kKeyF12,
kKeyLeft,
kKeyUp,
kKeyRight,
kKeyDown,
kKeyPageUp,
kKeyPageDown,
kKeyHome,
kKeyEnd,
kKeyInsert,
kKeyShift,
kKeyShiftL = kKeyShift,
kKeyShiftR,
kKeyControl,
kKeyControlL = kKeyControl,
kKeyControlR,
kKeyAlt,
kKeyAltL = kKeyAlt,
kKeyAltR,
kKeySuper,
kKeySuperL = kKeySuper,
kKeySuperR,
kKeyMenu,
kKeyCapsLock,
kKeyScrollLock,
kKeyNumLock,
kKeyPrintScreen,
kKeyPause
kKeyShift DISTRHO_DEPRECATED_BY("kKeyShiftL") = kKeyShiftL,
kKeyControl DISTRHO_DEPRECATED_BY("kKeyControlL") = kKeyControlL,
kKeyAlt DISTRHO_DEPRECATED_BY("kKeyAltL") = kKeyAltL,
kKeySuper DISTRHO_DEPRECATED_BY("kKeySuperL") = kKeySuperL,
};

/**
@@ -118,7 +183,7 @@ enum Key {
*/
enum EventFlag {
kFlagSendEvent = 1, ///< Event is synthetic
kFlagIsHint = 2 ///< Event is a hint (not direct user input)
kFlagIsHint = 2, ///< Event is a hint (not direct user input)
};

/**
@@ -127,7 +192,7 @@ enum EventFlag {
enum CrossingMode {
kCrossingNormal, ///< Crossing due to pointer motion
kCrossingGrab, ///< Crossing due to a grab
kCrossingUngrab ///< Crossing due to a grab release
kCrossingUngrab, ///< Crossing due to a grab release
};

/**
@@ -159,15 +224,20 @@ enum MouseButton {
This is a portable subset of mouse cursors that exist on X11, MacOS, and Windows.
*/
enum MouseCursor {
kMouseCursorArrow, ///< Default pointing arrow
kMouseCursorCaret, ///< Caret (I-Beam) for text entry
kMouseCursorCrosshair, ///< Cross-hair
kMouseCursorHand, ///< Hand with a pointing finger
kMouseCursorNotAllowed, ///< Operation not allowed
kMouseCursorLeftRight, ///< Left/right arrow for horizontal resize
kMouseCursorUpDown, ///< Up/down arrow for vertical resize
kMouseCursorDiagonal, ///< Top-left to bottom-right arrow for diagonal resize
kMouseCursorAntiDiagonal ///< Bottom-left to top-right arrow for diagonal resize
kMouseCursorArrow, ///< Default pointing arrow
kMouseCursorCaret, ///< Caret (I-Beam) for text entry
kMouseCursorCrosshair, ///< Cross-hair
kMouseCursorHand, ///< Hand with a pointing finger
kMouseCursorNotAllowed, ///< Operation not allowed
kMouseCursorLeftRight, ///< Left/right arrow for horizontal resize
kMouseCursorUpDown, ///< Up/down arrow for vertical resize
kMouseCursorUpLeftDownRight, ///< Diagonal arrow for down/right resize
kMouseCursorUpRightDownLeft, ///< Diagonal arrow for down/left resize
kMouseCursorAllScroll, ///< Omnidirectional "arrow" for scrolling

// Backwards compatibility with old DPF
kMouseCursorDiagonal DISTRHO_DEPRECATED_BY("kMouseCursorUpLeftDownRight") = kMouseCursorUpLeftDownRight,
kMouseCursorAntiDiagonal DISTRHO_DEPRECATED_BY("kMouseCursorUpRightDownLeft") = kMouseCursorUpRightDownLeft,
};

/**
@@ -178,11 +248,11 @@ enum MouseCursor {
while a smooth scroll is for those with arbitrary scroll direction freedom, like some touchpads.
*/
enum ScrollDirection {
kScrollUp, ///< Scroll up
kScrollDown, ///< Scroll down
kScrollLeft, ///< Scroll left
kScrollRight, ///< Scroll right
kScrollSmooth ///< Smooth scroll in any direction
kScrollUp, ///< Scroll up
kScrollDown, ///< Scroll down
kScrollLeft, ///< Scroll left
kScrollRight, ///< Scroll right
kScrollSmooth, ///< Smooth scroll in any direction
};

/**


+ 36
- 2
source/modules/dgl/Color.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -68,7 +68,41 @@ struct Color {
/**
Create a new color based on this one but with a different alpha value.
*/
Color withAlpha(float alpha) noexcept;
Color withAlpha(float alpha) const noexcept;

/**
Create a new color based on this one but with subtracted numeric value on all elements.
Value must be in [0..255] range.
*/
Color minus(int value) const noexcept;

/**
Create a new color based on this one but with subtracted floating-point value on all elements.
Value must be in [0..1] range.
*/
Color minus(float value) const noexcept;

/**
Create a new color based on this one but with added numeric value on all elements.
Value must be in [0..255] range.
*/
Color plus(int value) const noexcept;

/**
Create a new color based on this one but with added floating-point value on all elements.
Value must be in [0..1] range.
*/
Color plus(float value) const noexcept;

/**
Create a new color based on this one but colors inverted.
*/
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.


+ 40
- 6
source/modules/dgl/EventHandlers.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -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<double> getLastClickPosition() const noexcept;
Point<double> getLastMotionPosition() const noexcept;

@@ -94,7 +97,8 @@ class KnobEventHandler
public:
enum Orientation {
Horizontal,
Vertical
Vertical,
Both
};

// NOTE hover not implemented yet
@@ -112,6 +116,7 @@ public:
virtual void knobDragStarted(SubWidget* widget) = 0;
virtual void knobDragFinished(SubWidget* widget) = 0;
virtual void knobValueChanged(SubWidget* widget, float value) = 0;
virtual void knobDoubleClicked(SubWidget*) {};
};

explicit KnobEventHandler(SubWidget* self);
@@ -119,6 +124,12 @@ 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;

// returns raw value, is assumed to be scaled if using log
float getValue() const noexcept;

@@ -128,9 +139,14 @@ public:
// returns 0-1 ranged value, already with log scale as needed
float getNormalizedValue() const noexcept;

float getDefault() const noexcept;

// 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;

@@ -139,16 +155,19 @@ public:
void setUsingLogScale(bool yesNo) noexcept;

Orientation getOrientation() const noexcept;
void setOrientation(const Orientation orientation) noexcept;
void setOrientation(Orientation orientation) noexcept;

void setCallback(Callback* callback) noexcept;

bool mouseEvent(const Widget::MouseEvent& ev);
bool motionEvent(const Widget::MotionEvent& ev);
// default 200, higher means slower
void setMouseDeceleration(float accel) noexcept;

bool mouseEvent(const Widget::MouseEvent& ev, double scaleFactor = 1.0);
bool motionEvent(const Widget::MotionEvent& ev, double scaleFactor = 1.0);
bool scrollEvent(const Widget::ScrollEvent& ev);

protected:
State getState() const noexcept;
State getState() const noexcept;

private:
struct PrivateData;
@@ -168,6 +187,21 @@ private:

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

class SliderEventHandler
{
public:
explicit SliderEventHandler(SubWidget* self);
virtual ~SliderEventHandler();

private:
struct PrivateData;
PrivateData* const pData;

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SliderEventHandler)
};

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

END_NAMESPACE_DGL

#endif // DGL_EVENT_HANDLERS_HPP_INCLUDED


+ 2
- 0
source/modules/dgl/ImageBaseWidgets.hpp View File

@@ -138,6 +138,7 @@ public:
virtual void imageKnobDragStarted(ImageBaseKnob* imageKnob) = 0;
virtual void imageKnobDragFinished(ImageBaseKnob* imageKnob) = 0;
virtual void imageKnobValueChanged(ImageBaseKnob* imageKnob, float value) = 0;
virtual void imageKnobDoubleClicked(ImageBaseKnob*) {};
};

explicit ImageBaseKnob(Widget* parentWidget, const ImageType& image, Orientation orientation = Vertical) noexcept;
@@ -192,6 +193,7 @@ public:
void setEndPos(const Point<int>& endPos) noexcept;
void setEndPos(int x, int y) noexcept;

void setCheckable(bool checkable) noexcept;
void setInverted(bool inverted) noexcept;
void setRange(float min, float max) noexcept;
void setStep(float step) noexcept;


+ 27
- 13
source/modules/dgl/NanoVG.hpp View File

@@ -112,6 +112,11 @@ public:
*/
GLuint getTextureHandle() const;

/**
Update the image data in-place.
*/
void update(const uchar* data);

private:
Handle fHandle;
Size<uint> fSize;
@@ -319,10 +324,9 @@ public:

/**
Constructor reusing a NanoVG context, used for subwidgets.
Context will not be deleted on class destructor.
*/
/*
NanoVG(NanoWidget* groupWidget);
*/
explicit NanoVG(NVGcontext* context);

/**
Destructor.
@@ -591,14 +595,14 @@ public:
/**
Creates image by loading it from the specified chunk of memory.
*/
NanoImage::Handle createImageFromMemory(uchar* data, uint dataSize, ImageFlags imageFlags);
NanoImage::Handle createImageFromMemory(const uchar* data, uint dataSize, ImageFlags imageFlags);

/**
Creates image by loading it from the specified chunk of memory.
Overloaded function for convenience.
@see ImageFlags
*/
NanoImage::Handle createImageFromMemory(uchar* data, uint dataSize, int imageFlags);
NanoImage::Handle createImageFromMemory(const uchar* data, uint dataSize, int imageFlags);

/**
Creates image from specified raw format image data.
@@ -917,7 +921,17 @@ public:
Constructor for a NanoSubWidget.
@see CreateFlags
*/
explicit NanoBaseWidget(Widget* parentGroupWidget, int flags = CREATE_ANTIALIAS);
explicit NanoBaseWidget(Widget* parentWidget, int flags = CREATE_ANTIALIAS);

/**
Constructor for a NanoSubWidget reusing a parent subwidget nanovg context.
*/
explicit NanoBaseWidget(NanoBaseWidget<SubWidget>* parentWidget);

/**
Constructor for a NanoSubWidget reusing a parent top-level-widget nanovg context.
*/
explicit NanoBaseWidget(NanoBaseWidget<TopLevelWidget>* parentWidget);

/**
Constructor for a NanoTopLevelWidget.
@@ -954,13 +968,7 @@ private:
Widget display function.
Implemented internally to wrap begin/endFrame() automatically.
*/
inline void onDisplay() override
{
// NOTE maybe should use BaseWidget::getWindow().getScaleFactor() as 3rd arg ?
NanoVG::beginFrame(BaseWidget::getWidth(), BaseWidget::getHeight());
onNanoDisplay();
NanoVG::endFrame();
}
void onDisplay() override;

// these should not be used
void beginFrame(uint,uint) {}
@@ -969,6 +977,12 @@ private:
void cancelFrame() {}
void endFrame() {}

/** @internal */
const bool fUsingParentContext;
void displayChildren();
friend class NanoBaseWidget<TopLevelWidget>;
friend class NanoBaseWidget<StandaloneWindow>;

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NanoBaseWidget)
};



+ 5
- 21
source/modules/dgl/OpenGL-include.hpp View File

@@ -22,13 +22,16 @@
// --------------------------------------------------------------------------------------------------------------------
// Fix OpenGL includes for Windows, based on glfw code (part 1)

#undef DGL_CALLBACK_DEFINED
#undef DGL_WINGDIAPI_DEFINED

#ifdef DISTRHO_OS_WINDOWS

#ifndef WINAPI
# define WINAPI __stdcall
#endif

#ifndef APIENTRY
# define APIENTRY __stdcall
# define APIENTRY WINAPI
#endif // APIENTRY

/* We need WINGDIAPI defined */
@@ -43,20 +46,6 @@
# define DGL_WINGDIAPI_DEFINED
#endif // WINGDIAPI

/* Some <GL/glu.h> files also need CALLBACK defined */
#ifndef CALLBACK
# if defined(_MSC_VER)
# if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS)
# define CALLBACK __stdcall
# else
# define CALLBACK
# endif
# else
# define CALLBACK __stdcall
# endif
# define DGL_CALLBACK_DEFINED
#endif // CALLBACK

#endif // DISTRHO_OS_WINDOWS

// --------------------------------------------------------------------------------------------------------------------
@@ -97,11 +86,6 @@
// --------------------------------------------------------------------------------------------------------------------
// Fix OpenGL includes for Windows, based on glfw code (part 2)

#ifdef DGL_CALLBACK_DEFINED
# undef CALLBACK
# undef DGL_CALLBACK_DEFINED
#endif

#ifdef DGL_WINGDIAPI_DEFINED
# undef WINGDIAPI
# undef DGL_WINGDIAPI_DEFINED


+ 2
- 1
source/modules/dgl/OpenGL.hpp View File

@@ -202,8 +202,9 @@ public:
GLenum getType() const noexcept { return GL_UNSIGNED_BYTE; }

private:
GLuint textureId;
bool setupCalled;
bool textureInit;
GLuint textureId;
};

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


+ 11
- 1
source/modules/dgl/StandaloneWindow.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -53,6 +53,15 @@ public:
sgc.done();
}

/**
Get a graphics context back again.
Called when a valid graphics context is needed outside the constructor.
*/
void reinit()
{
sgc.reinit();
}

/**
Overloaded functions to ensure they apply to the Window class.
*/
@@ -64,6 +73,7 @@ public:
uint getHeight() const noexcept { return Window::getHeight(); }
const Size<uint> getSize() const noexcept { return Window::getSize(); }
void repaint() noexcept { Window::repaint(); }
void repaint(const Rectangle<uint>& rect) noexcept { Window::repaint(rect); }
void setWidth(uint width) { Window::setWidth(width); }
void setHeight(uint height) { Window::setHeight(height); }
void setSize(uint width, uint height) { Window::setSize(width, height); }


+ 6
- 0
source/modules/dgl/SubWidget.hpp View File

@@ -137,6 +137,12 @@ public:
*/
void repaint() noexcept override;

/**
Pushes this widget to the "bottom" of the parent widget.
Makes the widget behave as if it was the first to be registered on the parent widget, thus being "on bottom".
*/
virtual void toBottom();

/**
Bring this widget to the "front" of the parent widget.
Makes the widget behave as if it was the last to be registered on the parent widget, thus being "in front".


+ 44
- 15
source/modules/dgl/Widget.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -19,6 +19,8 @@

#include "Geometry.hpp"

#include <list>

START_NAMESPACE_DGL

// --------------------------------------------------------------------------------------------------------------------
@@ -62,7 +64,7 @@ public:
uint mod;
/** Event flags. @see EventFlag */
uint flags;
/** Event timestamp (if any). */
/** Event timestamp in milliseconds (if any). */
uint time;

/** Constructor for default/null values */
@@ -331,16 +333,33 @@ public:

/**
Get the Id associated with this widget.
Returns 0 by default.
@see setId
*/
uint getId() const noexcept;

/**
Get the name associated with this widget.
This is complately optional, mostly useful for debugging purposes.
Returns an empty string by default.
@see setName
*/
const char* getName() const noexcept;

/**
Set an Id to be associated with this widget.
@see getId
*/
void setId(uint id) noexcept;

/**
Set a name to be associated with this widget.
This is complately optional, only useful for debugging purposes.
@note name must not be null
@see getName
*/
void setName(const char* name) noexcept;

/**
Get the application associated with this widget's window.
This is the same as calling `getTopLevelWidget()->getApp()`.
@@ -367,6 +386,11 @@ public:
*/
TopLevelWidget* getTopLevelWidget() const noexcept;

/**
Get list of children (a subwidgets) that belong to this widget.
*/
std::list<SubWidget*> getChildren() const noexcept;

/**
Request repaint of this widget's area to the window this widget belongs to.
On the raw Widget class this function does nothing.
@@ -383,7 +407,7 @@ protected:
/**
A function called to draw the widget contents.
*/
virtual void onDisplay() = 0;
virtual void onDisplay() {};

/**
A function called when a key is pressed or released.
@@ -424,19 +448,24 @@ protected:
A function called when a special key is pressed or released.
DEPRECATED use onKeyboard or onCharacterInput
*/
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4996)
#elif defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
virtual bool onSpecial(const SpecialEvent&) { return false; }
#if defined(__clang__)
# pragma clang diagnostic pop
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460
# pragma GCC diagnostic pop
#endif
#if defined(_MSC_VER)
#pragma warning(pop)
#elif defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460
#pragma GCC diagnostic pop
#endif

private:
struct PrivateData;


+ 38
- 8
source/modules/dgl/Window.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -19,10 +19,14 @@

#include "Geometry.hpp"

#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
# include "FileBrowserDialog.hpp"
#endif

#ifdef DGL_USE_WEB_VIEW
# include "WebView.hpp"
#endif

#include <vector>

#ifdef DISTRHO_NAMESPACE
@@ -105,13 +109,17 @@ public:
/** Early context clearing, useful for standalone windows not created by you. */
void done();

/** Get a valid context back again. */
void reinit();

DISTRHO_DECLARE_NON_COPYABLE(ScopedGraphicsContext)
DISTRHO_PREVENT_HEAP_ALLOCATION

private:
Window& window;
Window::PrivateData* ppData;
Window::PrivateData* const ppData;
bool active;
bool reenter;
};

/**
@@ -390,7 +398,7 @@ public:
*/
void focus();

#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
/**
Open a file browser dialog with this window as transient parent.
A few options can be specified to setup the dialog.
@@ -401,7 +409,28 @@ public:
This function does not block the event loop.
*/
bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions());
#endif
#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.
@@ -513,7 +542,7 @@ protected:
*/
virtual void onScaleFactorChanged(double scaleFactor);

#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
/**
A function called when a path is selected by the user, as triggered by openFileBrowser().
This action happens after the user confirms the action, so the file browser dialog will be closed at this point.
@@ -524,7 +553,7 @@ protected:
/** DEPRECATED Use onFileSelected(). */
DISTRHO_DEPRECATED_BY("onFileSelected(const char*)")
inline virtual void fileBrowserSelected(const char* filename) { return onFileSelected(filename); }
#endif
#endif

private:
PrivateData* const pData;
@@ -541,7 +570,8 @@ private:
uint height,
double scaleFactor,
bool resizable,
bool isVST3,
bool usesScheduledRepaints,
bool usesSizeRequest,
bool doPostInit);

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Window)


+ 141
- 4
source/modules/dgl/src/Application.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -16,12 +16,79 @@

#include "ApplicationPrivateData.hpp"

#ifdef __EMSCRIPTEN__
#if defined(__EMSCRIPTEN__)
# include <emscripten/emscripten.h>
#elif defined(DISTRHO_OS_MAC)
# include <CoreFoundation/CoreFoundation.h>
#endif

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

#define BUILD_CONFIG_SENTINEL(NAME) \
DISTRHO_JOIN_MACRO(_, NAME)::DISTRHO_JOIN_MACRO(_, NAME)() noexcept : ok(false) {}

#ifdef DPF_DEBUG
BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dpf_debug_on)
#else
BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dpf_debug_off)
#endif

#ifdef DGL_USE_FILE_BROWSER
BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_on)
#else
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
BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_no_shared_resources_off)
#endif

#undef BUILD_CONFIG_SENTINEL

static inline
bool dpf_check_build_status() noexcept
{
return (
#ifdef DPF_DEBUG
fail_to_link_is_mismatch_dpf_debug_on.ok &&
#else
fail_to_link_is_mismatch_dpf_debug_off.ok &&
#endif
#ifdef DGL_USE_FILE_BROWSER
fail_to_link_is_mismatch_dgl_use_file_browser_on.ok &&
#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
fail_to_link_is_mismatch_dgl_no_shared_resources_off.ok &&
#endif
true
);
}

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

#ifdef __EMSCRIPTEN__
@@ -32,7 +99,67 @@ static void app_idle(void* const app)
#endif

Application::Application(const bool isStandalone)
: pData(new PrivateData(isStandalone)) {}
: pData(new PrivateData(isStandalone))
{
// 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
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));
#else
// unused
(void)argc;
(void)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
fail_to_link_is_mismatch_dgl_no_shared_resources_off.ok = true;
#endif
DISTRHO_SAFE_ASSERT(dpf_check_build_status());
}

Application::~Application()
{
@@ -48,8 +175,18 @@ void Application::exec(const uint idleTimeInMs)
{
DISTRHO_SAFE_ASSERT_RETURN(pData->isStandalone,);

#ifdef __EMSCRIPTEN__
#if defined(__EMSCRIPTEN__)
emscripten_set_main_loop_arg(app_idle, this, 0, true);
#elif defined(DISTRHO_OS_MAC)
const CFTimeInterval idleTimeInSecs = static_cast<CFTimeInterval>(idleTimeInMs) / 1000;

while (! pData->isQuitting)
{
pData->idle(0);

if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, idleTimeInSecs, true) == kCFRunLoopRunFinished)
break;
}
#else
while (! pData->isQuitting)
pData->idle(idleTimeInMs);


+ 42
- 18
source/modules/dgl/src/ApplicationPrivateData.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -23,31 +23,32 @@

START_NAMESPACE_DGL

typedef std::list<DGL_NAMESPACE::Window*>::iterator WindowListIterator;
typedef std::list<DGL_NAMESPACE::Window*>::reverse_iterator WindowListReverseIterator;

static d_ThreadHandle getCurrentThreadHandle() noexcept
{
#ifdef DISTRHO_OS_WINDOWS
#ifdef DISTRHO_OS_WINDOWS
return GetCurrentThread();
#else
#else
return pthread_self();
#endif
#endif
}

static bool isThisTheMainThread(const d_ThreadHandle mainThreadHandle) noexcept
{
#ifdef DISTRHO_OS_WINDOWS
#ifdef DISTRHO_OS_WINDOWS
return GetCurrentThread() == mainThreadHandle; // IsGUIThread ?
#else
#else
return pthread_equal(getCurrentThreadHandle(), mainThreadHandle) != 0;
#endif
#endif
}

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

const char* Application::getClassName() const noexcept
{
return puglGetClassName(pData->world);
return puglGetWorldString(pData->world, PUGL_CLASS_NAME);
}

// --------------------------------------------------------------------------------------------------------------------
@@ -59,6 +60,7 @@ Application::PrivateData::PrivateData(const bool standalone)
isQuitting(false),
isQuittingInNextCycle(false),
isStarting(true),
needsRepaint(false),
visibleWindows(0),
mainThreadHandle(getCurrentThreadHandle()),
windows(),
@@ -66,10 +68,16 @@ Application::PrivateData::PrivateData(const bool standalone)
{
DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,);

#ifdef DGL_USING_SDL
SDL_Init(SDL_INIT_EVENTS|SDL_INIT_TIMER|SDL_INIT_VIDEO);
#else
puglSetWorldHandle(world, this);
#ifndef __EMSCRIPTEN__
puglSetClassName(world, DISTRHO_MACRO_AS_STRING(DGL_NAMESPACE));
#endif
#ifdef __EMSCRIPTEN__
puglSetWorldString(world, PUGL_CLASS_NAME, "canvas");
#else
puglSetWorldString(world, PUGL_CLASS_NAME, DISTRHO_MACRO_AS_STRING(DGL_NAMESPACE));
#endif
#endif
}

Application::PrivateData::~PrivateData()
@@ -80,8 +88,12 @@ Application::PrivateData::~PrivateData()
windows.clear();
idleCallbacks.clear();

#ifdef DGL_USING_SDL
SDL_Quit();
#else
if (world != nullptr)
puglFreeWorld(world);
#endif
}

// --------------------------------------------------------------------------------------------------------------------
@@ -134,6 +146,20 @@ void Application::PrivateData::triggerIdleCallbacks()
}
}

void Application::PrivateData::repaintIfNeeeded()
{
if (needsRepaint)
{
needsRepaint = false;

for (WindowListIterator it = windows.begin(), ite = windows.end(); it != ite; ++it)
{
DGL_NAMESPACE::Window* const window(*it);
window->repaint();
}
}
}

void Application::PrivateData::quit()
{
if (! isThisTheMainThread(mainThreadHandle))
@@ -147,28 +173,26 @@ void Application::PrivateData::quit()

isQuitting = true;

#ifndef DPF_TEST_APPLICATION_CPP
#ifndef DPF_TEST_APPLICATION_CPP
for (WindowListReverseIterator rit = windows.rbegin(), rite = windows.rend(); rit != rite; ++rit)
{
DGL_NAMESPACE::Window* const window(*rit);
window->close();
}
#endif
#endif
}

double Application::PrivateData::getTime() const
{
DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, 0.0);

return puglGetTime(world);
return world != nullptr ? puglGetTime(world) : 0.0;
}

void Application::PrivateData::setClassName(const char* const name)
{
DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,);
DISTRHO_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0',);

puglSetClassName(world, name);
if (world != nullptr)
puglSetWorldString(world, PUGL_CLASS_NAME, name);
}

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


+ 6
- 0
source/modules/dgl/src/ApplicationPrivateData.hpp View File

@@ -63,6 +63,9 @@ struct Application::PrivateData {
/** Whether the applicating is starting up, that is, no windows have been made visible yet. Defaults to true. */
bool isStarting;

/** When true force all windows to be repainted on next idle. */
bool needsRepaint;

/** Counter of visible windows, only used in standalone mode.
If 0->1, application is starting. If 1->0, application is quitting/stopping. */
uint visibleWindows;
@@ -96,6 +99,9 @@ struct Application::PrivateData {
/** Run each idle callback without updating pugl world. */
void triggerIdleCallbacks();

/** Trigger a repaint of all windows if @a needsRepaint is true. */
void repaintIfNeeeded();

/** Set flag indicating application is quitting, and close all windows in reverse order of registration.
For standalone mode only. */
void quit();


+ 60
- 1
source/modules/dgl/src/Color.cpp View File

@@ -114,13 +114,72 @@ Color::Color(const Color& color1, const Color& color2, const float u) noexcept
interpolate(color2, u);
}

Color Color::withAlpha(const float alpha2) noexcept
Color Color::withAlpha(const float alpha2) const noexcept
{
Color color(*this);
color.alpha = alpha2;
return color;
}

Color Color::minus(const int value) const noexcept
{
const float fvalue = static_cast<float>(value)/255.f;
Color color(*this);
color.red -= fvalue;
color.green -= fvalue;
color.blue -= fvalue;
color.fixBounds();
return color;
}

Color Color::minus(const float value) const noexcept
{
Color color(*this);
color.red -= value;
color.green -= value;
color.blue -= value;
color.fixBounds();
return color;
}

Color Color::plus(const int value) const noexcept
{
const float fvalue = static_cast<float>(value)/255.f;
Color color(*this);
color.red += fvalue;
color.green += fvalue;
color.blue += fvalue;
color.fixBounds();
return color;
}

Color Color::plus(const float value) const noexcept
{
Color color(*this);
color.red += value;
color.green += value;
color.blue += value;
color.fixBounds();
return color;
}

Color Color::invert() const noexcept
{
Color color(*this);
color.red = 1.f - color.red;
color.green = 1.f - color.green;
color.blue = 1.f - color.blue;
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;


+ 202
- 45
source/modules/dgl/src/EventHandlers.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -31,6 +31,8 @@ struct ButtonEventHandler::PrivateData {
int state;
bool checkable;
bool checked;
bool enabled;
bool enabledInput;

Point<double> lastClickPos;
Point<double> 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<double>();
lastMotionPos = Point<double>();
}

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<double> ButtonEventHandler::getLastClickPosition() const noexcept
{
return pData->lastClickPos;
@@ -274,12 +315,15 @@ struct KnobEventHandler::PrivateData {
SubWidget* const widget;
KnobEventHandler::Callback* callback;

float accel;
float minimum;
float maximum;
float step;
float value;
float valueDef;
float valueTmp;
bool enabled;
bool enabledInput;
bool usingDefault;
bool usingLog;
Orientation orientation;
@@ -287,56 +331,69 @@ struct KnobEventHandler::PrivateData {

double lastX;
double lastY;
uint lastClickTime;

PrivateData(KnobEventHandler* const s, SubWidget* const w)
: self(s),
widget(w),
callback(nullptr),
minimum(0.0f),
maximum(1.0f),
accel(200.f),
minimum(0.f),
maximum(1.f),
step(0.0f),
value(0.5f),
valueDef(value),
valueTmp(value),
enabled(true),
enabledInput(true),
usingDefault(false),
usingLog(false),
orientation(Vertical),
state(kKnobStateDefault),
lastX(0.0),
lastY(0.0) {}
lastY(0.0),
lastClickTime(0) {}

PrivateData(KnobEventHandler* const s, SubWidget* const w, PrivateData* const other)
: self(s),
widget(w),
callback(other->callback),
accel(other->accel),
minimum(other->minimum),
maximum(other->maximum),
step(other->step),
value(other->value),
valueDef(other->valueDef),
valueTmp(value),
enabled(other->enabled),
enabledInput(other->enabledInput),
usingDefault(other->usingDefault),
usingLog(other->usingLog),
orientation(other->orientation),
state(kKnobStateDefault),
lastX(0.0),
lastY(0.0) {}
lastY(0.0),
lastClickTime(0) {}

void assignFrom(PrivateData* const other)
{
callback = other->callback;
accel = other->accel;
minimum = other->minimum;
maximum = other->maximum;
step = other->step;
value = other->value;
valueDef = other->valueDef;
valueTmp = value;
enabled = other->enabled;
enabledInput = other->enabledInput;
usingDefault = other->usingDefault;
usingLog = other->usingLog;
orientation = other->orientation;
state = kKnobStateDefault;
lastX = 0.0;
lastY = 0.0;
lastClickTime = 0;
}

inline float logscale(const float v) const
@@ -353,8 +410,11 @@ struct KnobEventHandler::PrivateData {
return std::log(v/a)/b;
}

bool mouseEvent(const Widget::MouseEvent& ev)
bool mouseEvent(const Widget::MouseEvent& ev, const double scaleFactor)
{
if (! enabledInput)
return false;

if (ev.button != 1)
return false;

@@ -370,9 +430,21 @@ struct KnobEventHandler::PrivateData {
return true;
}

lastX = ev.pos.getX() / scaleFactor;
lastY = ev.pos.getY() / scaleFactor;

if (lastClickTime > 0 && ev.time > lastClickTime && ev.time - lastClickTime <= 300)
{
lastClickTime = 0;

if (callback != nullptr)
callback->knobDoubleClicked(widget);

return true;
}

lastClickTime = ev.time;
state |= kKnobStateDragging;
lastX = ev.pos.getX();
lastY = ev.pos.getY();
widget->repaint();

if (callback != nullptr)
@@ -394,73 +466,104 @@ struct KnobEventHandler::PrivateData {
return false;
}

bool motionEvent(const Widget::MotionEvent& ev)
bool motionEvent(const Widget::MotionEvent& ev, const double scaleFactor)
{
if (! enabledInput)
return false;

if ((state & kKnobStateDragging) == 0x0)
return false;

bool doVal = false;
float d, value2 = 0.0f;
double movDiff;

if (orientation == Horizontal)
switch (orientation)
{
if (const double movX = ev.pos.getX() - lastX)
case Horizontal:
movDiff = ev.pos.getX() / scaleFactor - lastX;
break;
case Vertical:
movDiff = lastY - ev.pos.getY() / scaleFactor;
break;
case Both:
{
d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f;
value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + (float(maximum - minimum) / d * float(movX));
doVal = true;
}
}
else if (orientation == Vertical)
{
if (const double movY = lastY - ev.pos.getY())
{
d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f;
value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + (float(maximum - minimum) / d * float(movY));
doVal = true;
const double movDiffX = ev.pos.getX() / scaleFactor - lastX;
const double movDiffY = lastY - ev.pos.getY() / scaleFactor;
movDiff = std::abs(movDiffX) > std::abs(movDiffY) ? movDiffX : movDiffY;
}
break;
default:
return false;
}

if (! doVal)
return false;
if (d_isZero(movDiff))
return true;

const float divisor = (ev.mod & kModifierControl) ? accel * 10.f : accel;
valueTmp += (maximum - minimum) / divisor * static_cast<float>(movDiff);

if (usingLog)
value2 = logscale(value2);
valueTmp = logscale(valueTmp);

if (value2 < minimum)
float value2;
bool valueChanged = false;

if (valueTmp < minimum)
{
valueTmp = value2 = minimum;
valueChanged = true;
}
else if (value2 > maximum)
else if (valueTmp > maximum)
{
valueTmp = value2 = maximum;
valueChanged = true;
}
else
{
valueTmp = value2;

if (d_isNotZero(step))
{
const float rest = std::fmod(value2, step);
value2 -= rest + (rest > step/2.0f ? step : 0.0f);
if (std::abs(valueTmp - value) >= step)
{
const float rest = std::fmod(valueTmp, step);
valueChanged = true;
value2 = valueTmp - rest;

if (rest < 0 && rest < step * -0.5f)
value2 -= step;
else if (rest > 0 && rest > step * 0.5f)
value2 += step;

if (value2 < minimum)
value2 = minimum;
else if (value2 > maximum)
value2 = maximum;
}
}
else
{
value2 = valueTmp;
valueChanged = true;
}
}

setValue(value2, true);
if (valueChanged)
setValue(value2, true);

lastX = ev.pos.getX();
lastY = ev.pos.getY();
lastX = ev.pos.getX() / scaleFactor;
lastY = ev.pos.getY() / scaleFactor;

return true;
}

bool scrollEvent(const Widget::ScrollEvent& ev)
{
if (! enabledInput)
return false;

if (! widget->contains(ev.pos))
return false;

const float dir = (ev.delta.getY() > 0.f) ? 1.f : -1.f;
const float d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f;
const float d = (ev.mod & kModifierControl) ? accel * 10.f : accel;
float value2 = (usingLog ? invlogscale(valueTmp) : valueTmp)
+ ((maximum - minimum) / d * 10.f * dir);

@@ -496,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,);
@@ -553,6 +678,21 @@ 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);
}

float KnobEventHandler::getValue() const noexcept
{
return pData->value;
@@ -568,12 +708,27 @@ float KnobEventHandler::getNormalizedValue() const noexcept
return pData->getNormalizedValue();
}

float KnobEventHandler::getDefault() const noexcept
{
return pData->valueDef;
}

void KnobEventHandler::setDefault(const float def) noexcept
{
pData->valueDef = def;
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);
@@ -596,9 +751,6 @@ KnobEventHandler::Orientation KnobEventHandler::getOrientation() const noexcept

void KnobEventHandler::setOrientation(const Orientation orientation) noexcept
{
if (pData->orientation == orientation)
return;

pData->orientation = orientation;
}

@@ -607,14 +759,19 @@ void KnobEventHandler::setCallback(Callback* const callback) noexcept
pData->callback = callback;
}

bool KnobEventHandler::mouseEvent(const Widget::MouseEvent& ev)
void KnobEventHandler::setMouseDeceleration(float accel) noexcept
{
return pData->mouseEvent(ev);
pData->accel = accel;
}

bool KnobEventHandler::motionEvent(const Widget::MotionEvent& ev)
bool KnobEventHandler::mouseEvent(const Widget::MouseEvent& ev, const double scaleFactor)
{
return pData->motionEvent(ev);
return pData->mouseEvent(ev, scaleFactor);
}

bool KnobEventHandler::motionEvent(const Widget::MotionEvent& ev, const double scaleFactor)
{
return pData->motionEvent(ev, scaleFactor);
}

bool KnobEventHandler::scrollEvent(const Widget::ScrollEvent& ev)


+ 56
- 10
source/modules/dgl/src/ImageBaseWidgets.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -61,13 +61,20 @@ void ImageBaseAboutWindow<ImageType>::setImage(const ImageType& image)
if (img == image)
return;

img = image;

if (image.isInvalid())
{
img = image;
return;
}

reinit();

img = image;

setSize(image.getSize());
setGeometryConstraints(image.getWidth(), image.getHeight(), true, true);

done();
}

template <class ImageType>
@@ -180,12 +187,24 @@ void ImageBaseButton<ImageType>::onDisplay()

const State state = ButtonEventHandler::getState();

if (state & kButtonStateActive)
pData->imageDown.draw(context);
else if (state & kButtonStateHover)
pData->imageHover.draw(context);
if (ButtonEventHandler::isCheckable())
{
if (ButtonEventHandler::isChecked())
pData->imageDown.draw(context);
else if (state & kButtonStateHover)
pData->imageHover.draw(context);
else
pData->imageNormal.draw(context);
}
else
pData->imageNormal.draw(context);
{
if (state & kButtonStateActive)
pData->imageDown.draw(context);
else if (state & kButtonStateHover)
pData->imageHover.draw(context);
else
pData->imageNormal.draw(context);
}
}

template <class ImageType>
@@ -297,6 +316,13 @@ struct ImageBaseKnob<ImageType>::PrivateData : public KnobEventHandler::Callback
callback->imageKnobValueChanged(imageKnob, value);
}

void knobDoubleClicked(SubWidget* const widget) override
{
if (callback != nullptr)
if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget))
callback->imageKnobDoubleClicked(imageKnob);
}

// implemented independently per graphics backend
void init();
void cleanup();
@@ -395,7 +421,7 @@ bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev)
{
if (SubWidget::onMouse(ev))
return true;
return KnobEventHandler::mouseEvent(ev);
return KnobEventHandler::mouseEvent(ev, getTopLevelWidget()->getScaleFactor());
}

template <class ImageType>
@@ -403,7 +429,7 @@ bool ImageBaseKnob<ImageType>::onMotion(const MotionEvent& ev)
{
if (SubWidget::onMotion(ev))
return true;
return KnobEventHandler::motionEvent(ev);
return KnobEventHandler::motionEvent(ev, getTopLevelWidget()->getScaleFactor());
}

template <class ImageType>
@@ -428,6 +454,7 @@ struct ImageBaseSlider<ImageType>::PrivateData {
bool usingDefault;

bool dragging;
bool checkable;
bool inverted;
bool valueIsSet;
double startedX;
@@ -449,6 +476,7 @@ struct ImageBaseSlider<ImageType>::PrivateData {
valueTmp(value),
usingDefault(false),
dragging(false),
checkable(false),
inverted(false),
valueIsSet(false),
startedX(0.0),
@@ -553,6 +581,16 @@ void ImageBaseSlider<ImageType>::setEndPos(int x, int y) noexcept
setEndPos(Point<int>(x, y));
}

template <class ImageType>
void ImageBaseSlider<ImageType>::setCheckable(bool checkable) noexcept
{
if (pData->checkable == checkable)
return;

pData->checkable = checkable;
repaint();
}

template <class ImageType>
void ImageBaseSlider<ImageType>::setInverted(bool inverted) noexcept
{
@@ -674,6 +712,14 @@ bool ImageBaseSlider<ImageType>::onMouse(const MouseEvent& ev)
return true;
}

if (pData->checkable)
{
const float value = d_isEqual(pData->valueTmp, pData->minimum) ? pData->maximum : pData->minimum;
setValue(value, true);
pData->valueTmp = pData->value;
return true;
}

float vper;
const double x = ev.pos.getX();
const double y = ev.pos.getY();


+ 107
- 9
source/modules/dgl/src/NanoVG.cpp View File

@@ -14,6 +14,11 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifdef _MSC_VER
// instantiated template classes whose methods are defined elsewhere
# pragma warning(disable:4661)
#endif

#include "../NanoVG.hpp"
#include "SubWidgetPrivateData.hpp"

@@ -274,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;
@@ -327,6 +340,14 @@ NanoVG::NanoVG(int flags)
DISTRHO_CUSTOM_SAFE_ASSERT("Failed to create NanoVG context, expect a black screen", fContext != nullptr);
}

NanoVG::NanoVG(NVGcontext* const context)
: fContext(context),
fInFrame(false),
fIsSubWidget(true)
{
DISTRHO_CUSTOM_SAFE_ASSERT("Failed to create NanoVG context, expect a black screen", fContext != nullptr);
}

NanoVG::~NanoVG()
{
DISTRHO_CUSTOM_SAFE_ASSERT("Destroying NanoVG context with still active frame", ! fInFrame);
@@ -664,18 +685,18 @@ NanoImage::Handle NanoVG::createImageFromFile(const char* filename, int imageFla
return NanoImage::Handle(fContext, nvgCreateImage(fContext, filename, imageFlags));
}

NanoImage::Handle NanoVG::createImageFromMemory(uchar* data, uint dataSize, ImageFlags imageFlags)
NanoImage::Handle NanoVG::createImageFromMemory(const uchar* data, uint dataSize, ImageFlags imageFlags)
{
return createImageFromMemory(data, dataSize, static_cast<int>(imageFlags));
}

NanoImage::Handle NanoVG::createImageFromMemory(uchar* data, uint dataSize, int imageFlags)
NanoImage::Handle NanoVG::createImageFromMemory(const uchar* data, uint dataSize, int imageFlags)
{
if (fContext == nullptr) return NanoImage::Handle();
DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, NanoImage::Handle());
DISTRHO_SAFE_ASSERT_RETURN(dataSize > 0, NanoImage::Handle());

return NanoImage::Handle(fContext, nvgCreateImageMem(fContext, imageFlags, data,static_cast<int>(dataSize)));
return NanoImage::Handle(fContext, nvgCreateImageMem(fContext, imageFlags, data, static_cast<int>(dataSize)));
}

NanoImage::Handle NanoVG::createImageFromRawMemory(uint w, uint h, const uchar* data,
@@ -1057,17 +1078,73 @@ bool NanoVG::loadSharedResources()
}
#endif

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

template <class BaseWidget>
void NanoBaseWidget<BaseWidget>::displayChildren()
{
std::list<SubWidget*> children(BaseWidget::getChildren());

for (std::list<SubWidget*>::iterator it = children.begin(); it != children.end(); ++it)
{
if (NanoSubWidget* const subwidget = dynamic_cast<NanoSubWidget*>(*it))
{
if (subwidget->fUsingParentContext && subwidget->isVisible())
subwidget->onDisplay();
}
}
}

// -----------------------------------------------------------------------
// NanoSubWidget

template <>
NanoBaseWidget<SubWidget>::NanoBaseWidget(Widget* const parent, int flags)
: SubWidget(parent),
NanoVG(flags)
NanoBaseWidget<SubWidget>::NanoBaseWidget(Widget* const parentWidget, int flags)
: SubWidget(parentWidget),
NanoVG(flags),
fUsingParentContext(false)
{
setNeedsViewportScaling();
}

template <>
NanoBaseWidget<SubWidget>::NanoBaseWidget(NanoSubWidget* const parentWidget)
: SubWidget(parentWidget),
NanoVG(parentWidget->getContext()),
fUsingParentContext(true)
{
setSkipDrawing();
}

template <>
NanoBaseWidget<SubWidget>::NanoBaseWidget(NanoTopLevelWidget* const parentWidget)
: SubWidget(parentWidget),
NanoVG(parentWidget->getContext()),
fUsingParentContext(true)
{
setSkipDrawing();
}

template <>
inline void NanoBaseWidget<SubWidget>::onDisplay()
{
if (fUsingParentContext)
{
NanoVG::save();
translate(SubWidget::getAbsoluteX(), SubWidget::getAbsoluteY());
onNanoDisplay();
NanoVG::restore();
displayChildren();
}
else
{
NanoVG::beginFrame(SubWidget::getWidth(), SubWidget::getHeight());
onNanoDisplay();
displayChildren();
NanoVG::endFrame();
}
}

template class NanoBaseWidget<SubWidget>;

// -----------------------------------------------------------------------
@@ -1076,7 +1153,17 @@ template class NanoBaseWidget<SubWidget>;
template <>
NanoBaseWidget<TopLevelWidget>::NanoBaseWidget(Window& windowToMapTo, int flags)
: TopLevelWidget(windowToMapTo),
NanoVG(flags) {}
NanoVG(flags),
fUsingParentContext(false) {}

template <>
inline void NanoBaseWidget<TopLevelWidget>::onDisplay()
{
NanoVG::beginFrame(TopLevelWidget::getWidth(), TopLevelWidget::getHeight());
onNanoDisplay();
displayChildren();
NanoVG::endFrame();
}

template class NanoBaseWidget<TopLevelWidget>;

@@ -1086,12 +1173,23 @@ template class NanoBaseWidget<TopLevelWidget>;
template <>
NanoBaseWidget<StandaloneWindow>::NanoBaseWidget(Application& app, int flags)
: StandaloneWindow(app),
NanoVG(flags) {}
NanoVG(flags),
fUsingParentContext(false) {}

template <>
NanoBaseWidget<StandaloneWindow>::NanoBaseWidget(Application& app, Window& parentWindow, int flags)
: StandaloneWindow(app, parentWindow),
NanoVG(flags) {}
NanoVG(flags),
fUsingParentContext(false) {}

template <>
inline void NanoBaseWidget<StandaloneWindow>::onDisplay()
{
NanoVG::beginFrame(Window::getWidth(), Window::getHeight());
onNanoDisplay();
displayChildren();
NanoVG::endFrame();
}

template class NanoBaseWidget<StandaloneWindow>;



+ 49
- 58
source/modules/dgl/src/OpenGL.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -35,17 +35,7 @@ START_NAMESPACE_DGL

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

#if defined(DGL_USE_GLES2)
static void notImplemented(const char* const name)
{
// d_stderr2("GLES2 function not implemented: %s", name);
}
#elif defined(DGL_USE_GLES3)
static void notImplemented(const char* const name)
{
d_stderr2("GLES3 function not implemented: %s", name);
}
#elif defined(DGL_USE_OPENGL3)
#ifdef DGL_USE_OPENGL3
static void notImplemented(const char* const name)
{
d_stderr2("OpenGL3 function not implemented: %s", name);
@@ -445,17 +435,17 @@ static void drawOpenGLImage(const OpenGLImage& image, const Point<int>& pos, con

OpenGLImage::OpenGLImage()
: ImageBase(),
textureId(0),
setupCalled(false)
setupCalled(false),
textureInit(false),
textureId(0)
{
glGenTextures(1, &textureId);
DISTRHO_SAFE_ASSERT(textureId != 0);
}

OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, const ImageFormat fmt)
: ImageBase(rdata, w, h, fmt),
textureId(0),
setupCalled(false)
setupCalled(false),
textureInit(true),
textureId(0)
{
glGenTextures(1, &textureId);
DISTRHO_SAFE_ASSERT(textureId != 0);
@@ -463,8 +453,9 @@ OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, co

OpenGLImage::OpenGLImage(const char* const rdata, const Size<uint>& s, const ImageFormat fmt)
: ImageBase(rdata, s, fmt),
textureId(0),
setupCalled(false)
setupCalled(false),
textureInit(true),
textureId(0)
{
glGenTextures(1, &textureId);
DISTRHO_SAFE_ASSERT(textureId != 0);
@@ -472,8 +463,9 @@ OpenGLImage::OpenGLImage(const char* const rdata, const Size<uint>& s, const Ima

OpenGLImage::OpenGLImage(const OpenGLImage& image)
: ImageBase(image),
textureId(0),
setupCalled(false)
setupCalled(false),
textureInit(true),
textureId(0)
{
glGenTextures(1, &textureId);
DISTRHO_SAFE_ASSERT(textureId != 0);
@@ -487,6 +479,12 @@ OpenGLImage::~OpenGLImage()

void OpenGLImage::loadFromMemory(const char* const rdata, const Size<uint>& s, const ImageFormat fmt) noexcept
{
if (!textureInit)
{
textureInit = true;
glGenTextures(1, &textureId);
DISTRHO_SAFE_ASSERT(textureId != 0);
}
setupCalled = false;
ImageBase::loadFromMemory(rdata, s, fmt);
}
@@ -502,14 +500,23 @@ OpenGLImage& OpenGLImage::operator=(const OpenGLImage& image) noexcept
size = image.size;
format = image.format;
setupCalled = false;

if (image.isValid() && !textureInit)
{
textureInit = true;
glGenTextures(1, &textureId);
DISTRHO_SAFE_ASSERT(textureId != 0);
}

return *this;
}

// deprecated calls
OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, const GLenum fmt)
: ImageBase(rdata, w, h, asDISTRHOImageFormat(fmt)),
textureId(0),
setupCalled(false)
setupCalled(false),
textureInit(true),
textureId(0)
{
glGenTextures(1, &textureId);
DISTRHO_SAFE_ASSERT(textureId != 0);
@@ -517,8 +524,9 @@ OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, co

OpenGLImage::OpenGLImage(const char* const rdata, const Size<uint>& s, const GLenum fmt)
: ImageBase(rdata, s, asDISTRHOImageFormat(fmt)),
textureId(0),
setupCalled(false)
setupCalled(false),
textureInit(true),
textureId(0)
{
glGenTextures(1, &textureId);
DISTRHO_SAFE_ASSERT(textureId != 0);
@@ -683,12 +691,12 @@ void SubWidget::PrivateData::display(const uint width, const uint height, const
const int w = static_cast<int>(self->getWidth());
const int h = static_cast<int>(self->getHeight());

if (viewportScaleFactor != 0.0 && viewportScaleFactor != 1.0)
if (d_isNotZero(viewportScaleFactor) && d_isNotEqual(viewportScaleFactor, 1.0))
{
glViewport(x,
-static_cast<int>(height * viewportScaleFactor - height + absolutePos.getY() + 0.5),
static_cast<int>(width * viewportScaleFactor + 0.5),
static_cast<int>(height * viewportScaleFactor + 0.5));
-d_roundToIntPositive(height * viewportScaleFactor - height + absolutePos.getY()),
d_roundToIntPositive(width * viewportScaleFactor),
d_roundToIntPositive(height * viewportScaleFactor));
}
else
{
@@ -699,26 +707,21 @@ void SubWidget::PrivateData::display(const uint width, const uint height, const
else if (needsFullViewportForDrawing || (absolutePos.isZero() && self->getSize() == Size<uint>(width, height)))
{
// full viewport size
glViewport(0,
-static_cast<int>(height * autoScaleFactor - height + 0.5),
static_cast<int>(width * autoScaleFactor + 0.5),
static_cast<int>(height * autoScaleFactor + 0.5));
glViewport(0, 0, static_cast<int>(width), static_cast<int>(height));
}
else
{
// set viewport pos
glViewport(static_cast<int>(absolutePos.getX() * autoScaleFactor + 0.5),
-static_cast<int>(std::round((height * autoScaleFactor - height)
+ (absolutePos.getY() * autoScaleFactor))),
static_cast<int>(std::round(width * autoScaleFactor)),
static_cast<int>(std::round(height * autoScaleFactor)));
glViewport(d_roundToIntPositive(absolutePos.getX() * autoScaleFactor),
-d_roundToIntPositive(absolutePos.getY() * autoScaleFactor),
static_cast<int>(width),
static_cast<int>(height));

// then cut the outer bounds
glScissor(static_cast<int>(absolutePos.getX() * autoScaleFactor + 0.5),
static_cast<int>(height - std::round((static_cast<int>(self->getHeight()) + absolutePos.getY())
* autoScaleFactor)),
static_cast<int>(std::round(self->getWidth() * autoScaleFactor)),
static_cast<int>(std::round(self->getHeight() * autoScaleFactor)));
glScissor(d_roundToIntPositive(absolutePos.getX() * autoScaleFactor),
d_roundToIntPositive(height - (static_cast<int>(self->getHeight()) + absolutePos.getY()) * autoScaleFactor),
d_roundToIntPositive(self->getWidth() * autoScaleFactor),
d_roundToIntPositive(self->getHeight() * autoScaleFactor));

glEnable(GL_SCISSOR_TEST);
needsDisableScissor = true;
@@ -744,26 +747,14 @@ void TopLevelWidget::PrivateData::display()
const uint width = size.getWidth();
const uint height = size.getHeight();

const double autoScaleFactor = window.pData->autoScaleFactor;

// full viewport size
if (window.pData->autoScaling)
{
glViewport(0,
-static_cast<int>(height * autoScaleFactor - height + 0.5),
static_cast<int>(width * autoScaleFactor + 0.5),
static_cast<int>(height * autoScaleFactor + 0.5));
}
else
{
glViewport(0, 0, static_cast<int>(width), static_cast<int>(height));
}
glViewport(0, 0, static_cast<int>(width), static_cast<int>(height));

// main widget drawing
self->onDisplay();

// now draw subwidgets if there are any
selfw->pData->displaySubWidgets(width, height, autoScaleFactor);
selfw->pData->displaySubWidgets(width, height, window.pData->autoScaleFactor);
}

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


+ 12
- 3
source/modules/dgl/src/SubWidget.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -71,7 +71,7 @@ Rectangle<uint> SubWidget::getConstrainedAbsoluteArea() const noexcept
const int y = getAbsoluteY();

if (x >= 0 && y >= 0)
return Rectangle<uint>(x, y, getSize());
return Rectangle<uint>(static_cast<uint>(x), static_cast<uint>(y), getSize());

const int xOffset = std::min(0, x);
const int yOffset = std::min(0, y);
@@ -139,12 +139,21 @@ void SubWidget::repaint() noexcept
if (TopLevelWidget* const topw = getTopLevelWidget())
{
if (pData->needsFullViewportForDrawing)
topw->repaint();
// repaint is virtual and we want precisely the top-level specific implementation, not any higher level
topw->TopLevelWidget::repaint();
else
topw->repaint(getConstrainedAbsoluteArea());
}
}

void SubWidget::toBottom()
{
std::list<SubWidget*>& subwidgets(pData->parentWidget->pData->subWidgets);

subwidgets.remove(this);
subwidgets.insert(subwidgets.begin(), this);
}

void SubWidget::toFront()
{
std::list<SubWidget*>& subwidgets(pData->parentWidget->pData->subWidgets);


+ 13
- 2
source/modules/dgl/src/TopLevelWidgetPrivateData.cpp View File

@@ -28,6 +28,17 @@ TopLevelWidget::PrivateData::PrivateData(TopLevelWidget* const s, Window& w)
selfw(s),
window(w)
{
/* if window already has a top-level-widget, make the new one match the first one in size
* this is needed because window creation and resize is a synchronous operation in some systems.
* as such, there's a chance the non-1st top-level-widgets would never get a valid size.
*/
if (!window.pData->topLevelWidgets.empty())
{
TopLevelWidget* const first = window.pData->topLevelWidgets.front();

selfw->pData->size = first->getSize();
}

window.pData->topLevelWidgets.push_back(self);
}

@@ -124,9 +135,9 @@ bool TopLevelWidget::PrivateData::scrollEvent(const ScrollEvent& ev)
return selfw->pData->giveScrollEventForSubWidgets(rev);
}

void TopLevelWidget::PrivateData::fallbackOnResize()
void TopLevelWidget::PrivateData::fallbackOnResize(const uint width, const uint height)
{
puglFallbackOnResize(window.pData->view);
puglFallbackOnResize(window.pData->view, width, height);
}

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


+ 1
- 1
source/modules/dgl/src/TopLevelWidgetPrivateData.hpp View File

@@ -38,7 +38,7 @@ struct TopLevelWidget::PrivateData {
bool mouseEvent(const MouseEvent& ev);
bool motionEvent(const MotionEvent& ev);
bool scrollEvent(const ScrollEvent& ev);
void fallbackOnResize();
void fallbackOnResize(uint width, uint height);

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData)
};


+ 17
- 1
source/modules/dgl/src/Widget.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -148,6 +148,11 @@ TopLevelWidget* Widget::getTopLevelWidget() const noexcept
return pData->topLevelWidget;
}

std::list<SubWidget*> Widget::getChildren() const noexcept
{
return pData->subWidgets;
}

void Widget::repaint() noexcept
{
}
@@ -157,11 +162,22 @@ uint Widget::getId() const noexcept
return pData->id;
}

const char* Widget::getName() const noexcept
{
return pData->name != nullptr ? pData->name : "";
}

void Widget::setId(uint id) noexcept
{
pData->id = id;
}

void Widget::setName(const char* const name) noexcept
{
std::free(pData->name);
pData->name = strdup(name);
}

bool Widget::onKeyboard(const KeyboardEvent& ev)
{
return pData->giveKeyboardEventForSubWidgets(ev);


+ 4
- 1
source/modules/dgl/src/WidgetPrivateData.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -33,6 +33,7 @@ Widget::PrivateData::PrivateData(Widget* const s, TopLevelWidget* const tlw)
topLevelWidget(tlw),
parentWidget(nullptr),
id(0),
name(nullptr),
needsScaling(false),
visible(true),
size(0, 0),
@@ -43,6 +44,7 @@ Widget::PrivateData::PrivateData(Widget* const s, Widget* const pw)
topLevelWidget(findTopLevelWidget(pw)),
parentWidget(pw),
id(0),
name(nullptr),
needsScaling(false),
visible(true),
size(0, 0),
@@ -51,6 +53,7 @@ Widget::PrivateData::PrivateData(Widget* const s, Widget* const pw)
Widget::PrivateData::~PrivateData()
{
subWidgets.clear();
std::free(name);
}

void Widget::PrivateData::displaySubWidgets(const uint width, const uint height, const double autoScaleFactor)


+ 2
- 1
source/modules/dgl/src/WidgetPrivateData.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -30,6 +30,7 @@ struct Widget::PrivateData {
TopLevelWidget* const topLevelWidget;
Widget* const parentWidget;
uint id;
char* name;
bool needsScaling;
bool visible;
Size<uint> size;


+ 109
- 41
source/modules/dgl/src/Window.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -27,15 +27,20 @@ START_NAMESPACE_DGL
Window::ScopedGraphicsContext::ScopedGraphicsContext(Window& win)
: window(win),
ppData(nullptr),
active(puglBackendEnter(window.pData->view)) {}
active(window.pData->view != nullptr && puglBackendEnter(window.pData->view)),
reenter(false) {}

Window::ScopedGraphicsContext::ScopedGraphicsContext(Window& win, Window& transientWin)
: window(win),
ppData(transientWin.pData),
active(false)
active(false),
reenter(window.pData->view != nullptr)
{
puglBackendLeave(ppData->view);
active = puglBackendEnter(window.pData->view);
if (reenter)
{
puglBackendLeave(ppData->view);
active = puglBackendEnter(window.pData->view);
}
}

Window::ScopedGraphicsContext::~ScopedGraphicsContext()
@@ -51,13 +56,26 @@ void Window::ScopedGraphicsContext::done()
active = false;
}

if (ppData != nullptr)
if (reenter)
{
reenter = false;
DISTRHO_SAFE_ASSERT_RETURN(ppData != nullptr,);

puglBackendEnter(ppData->view);
ppData = nullptr;
}
}

void Window::ScopedGraphicsContext::reinit()
{
DISTRHO_SAFE_ASSERT_RETURN(!active,);
DISTRHO_SAFE_ASSERT_RETURN(!reenter,);
DISTRHO_SAFE_ASSERT_RETURN(ppData != nullptr,);

reenter = true;
puglBackendLeave(ppData->view);
active = puglBackendEnter(window.pData->view);
}

// -----------------------------------------------------------------------
// Window

@@ -88,7 +106,7 @@ Window::Window(Application& app,
const uint height,
const double scaleFactor,
const bool resizable)
: pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, false))
: pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, false, false))
{
pData->initPost();
}
@@ -99,9 +117,11 @@ Window::Window(Application& app,
const uint height,
const double scaleFactor,
const bool resizable,
const bool isVST3,
const bool usesScheduledRepaints,
const bool usesSizeRequest,
const bool doPostInit)
: pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, isVST3))
: pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable,
usesScheduledRepaints, usesSizeRequest))
{
if (doPostInit)
pData->initPost();
@@ -147,7 +167,8 @@ void Window::close()

bool Window::isResizable() const noexcept
{
return puglGetViewHint(pData->view, PUGL_RESIZABLE) == PUGL_TRUE;
return pData->view != nullptr
&& puglGetViewHint(pData->view, PUGL_RESIZABLE) == PUGL_TRUE;
}

void Window::setResizable(const bool resizable)
@@ -189,7 +210,11 @@ void Window::setOffsetY(const int y)

void Window::setOffset(const int x, const int y)
{
puglSetPosition(pData->view, x, y);
// do not call this for embed windows!
DISTRHO_SAFE_ASSERT_RETURN(!pData->isEmbed,);

if (pData->view != nullptr)
puglSetPosition(pData->view, x, y);
}

void Window::setOffset(const Point<int>& offset)
@@ -202,7 +227,7 @@ uint Window::getWidth() const noexcept
DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0);

const double width = puglGetFrame(pData->view).width;
DISTRHO_SAFE_ASSERT_RETURN(width >= 0.0, 0);
DISTRHO_SAFE_ASSERT_RETURN(width > 0.0, 0);
return static_cast<uint>(width + 0.5);
}

@@ -211,7 +236,7 @@ uint Window::getHeight() const noexcept
DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0);

const double height = puglGetFrame(pData->view).height;
DISTRHO_SAFE_ASSERT_RETURN(height >= 0.0, 0);
DISTRHO_SAFE_ASSERT_RETURN(height > 0.0, 0);
return static_cast<uint>(height + 0.5);
}

@@ -220,8 +245,8 @@ Size<uint> Window::getSize() const noexcept
DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, Size<uint>());

const PuglRect rect = puglGetFrame(pData->view);
DISTRHO_SAFE_ASSERT_RETURN(rect.width >= 0.0, Size<uint>());
DISTRHO_SAFE_ASSERT_RETURN(rect.height >= 0.0, Size<uint>());
DISTRHO_SAFE_ASSERT_RETURN(rect.width > 0.0, Size<uint>());
DISTRHO_SAFE_ASSERT_RETURN(rect.height > 0.0, Size<uint>());
return Size<uint>(static_cast<uint>(rect.width + 0.5),
static_cast<uint>(rect.height + 0.5));
}
@@ -246,10 +271,10 @@ void Window::setSize(uint width, uint height)
uint minWidth = pData->minWidth;
uint minHeight = pData->minHeight;

if (pData->autoScaling && scaleFactor != 1.0)
if (pData->autoScaling && d_isNotEqual(scaleFactor, 1.0))
{
minWidth *= scaleFactor;
minHeight *= scaleFactor;
minWidth = d_roundToUnsignedInt(minWidth * scaleFactor);
minHeight = d_roundToUnsignedInt(minHeight * scaleFactor);
}

// handle geometry constraints here
@@ -270,10 +295,10 @@ void Window::setSize(uint width, uint height)
{
// fix width
if (reqRatio > ratio)
width = static_cast<uint>(height * ratio + 0.5);
width = d_roundToUnsignedInt(height * ratio);
// fix height
else
height = static_cast<uint>(static_cast<double>(width) / ratio + 0.5);
height = d_roundToUnsignedInt(static_cast<double>(width) / ratio);
}
}
}
@@ -287,9 +312,19 @@ void Window::setSize(uint width, uint height)

topLevelWidget->requestSizeChange(width, height);
}
else
else if (pData->view != nullptr)
{
puglSetSizeAndDefault(pData->view, width, height);

// there are no resize events for closed windows, so short-circuit the top-level widgets here
if (pData->isClosed)
{
for (std::list<TopLevelWidget*>::iterator it = pData->topLevelWidgets.begin(),
end = pData->topLevelWidgets.end(); it != end; ++it)
{
((Widget*)*it)->setSize(width, height);
}
}
}
}

@@ -300,23 +335,25 @@ void Window::setSize(const Size<uint>& size)

const char* Window::getTitle() const noexcept
{
return puglGetWindowTitle(pData->view);
return pData->view != nullptr ? puglGetViewString(pData->view, PUGL_WINDOW_TITLE) : "";
}

void Window::setTitle(const char* const title)
{
if (pData->view != nullptr)
puglSetWindowTitle(pData->view, title);
puglSetViewString(pData->view, PUGL_WINDOW_TITLE, title);
}

bool Window::isIgnoringKeyRepeat() const noexcept
{
return puglGetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT) == PUGL_TRUE;
return pData->view != nullptr
&& puglGetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT) == PUGL_TRUE;
}

void Window::setIgnoringKeyRepeat(const bool ignore) noexcept
{
puglSetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT, ignore);
if (pData->view != nullptr)
puglSetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT, ignore);
}

const void* Window::getClipboard(size_t& dataSize)
@@ -326,12 +363,14 @@ const void* Window::getClipboard(size_t& dataSize)

bool Window::setClipboard(const char* const mimeType, const void* const data, const size_t dataSize)
{
return puglSetClipboard(pData->view, mimeType != nullptr ? mimeType : "text/plain", data, dataSize) == PUGL_SUCCESS;
return pData->view != nullptr
&& puglSetClipboard(pData->view, mimeType != nullptr ? mimeType : "text/plain", data, dataSize) == PUGL_SUCCESS;
}

bool Window::setCursor(const MouseCursor cursor)
{
return puglSetCursor(pData->view, static_cast<PuglCursor>(cursor)) == PUGL_SUCCESS;
return pData->view != nullptr
&& puglSetCursor(pData->view, static_cast<PuglCursor>(cursor)) == PUGL_SUCCESS;
}

bool Window::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs)
@@ -362,7 +401,7 @@ const GraphicsContext& Window::getGraphicsContext() const noexcept

uintptr_t Window::getNativeWindowHandle() const noexcept
{
return puglGetNativeView(pData->view);
return pData->view != nullptr ? puglGetNativeView(pData->view) : 0;
}

double Window::getScaleFactor() const noexcept
@@ -375,18 +414,35 @@ void Window::focus()
pData->focus();
}

#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
bool Window::openFileBrowser(const FileBrowserOptions& options)
{
return pData->openFileBrowser(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)
return;

if (pData->usesScheduledRepaints)
pData->appData->needsRepaint = true;

puglPostRedisplay(pData->view);
}

@@ -395,6 +451,9 @@ void Window::repaint(const Rectangle<uint>& rect) noexcept
if (pData->view == nullptr)
return;

if (pData->usesScheduledRepaints)
pData->appData->needsRepaint = true;

PuglRect prect = {
static_cast<PuglCoord>(rect.getX()),
static_cast<PuglCoord>(rect.getY()),
@@ -405,10 +464,10 @@ void Window::repaint(const Rectangle<uint>& rect) noexcept
{
const double autoScaleFactor = pData->autoScaleFactor;

prect.x *= autoScaleFactor;
prect.y *= autoScaleFactor;
prect.width *= autoScaleFactor;
prect.height *= autoScaleFactor;
prect.x = static_cast<PuglCoord>(prect.x * autoScaleFactor);
prect.y = static_cast<PuglCoord>(prect.y * autoScaleFactor);
prect.width = static_cast<PuglSpan>(prect.width * autoScaleFactor + 0.5);
prect.height = static_cast<PuglSpan>(prect.height * autoScaleFactor + 0.5);
}
puglPostRedisplayRect(pData->view, prect);
}
@@ -433,11 +492,15 @@ void Window::setGeometryConstraints(uint minimumWidth,
uint minimumHeight,
const bool keepAspectRatio,
const bool automaticallyScale,
const bool resizeNowIfAutoScaling)
bool resizeNowIfAutoScaling)
{
DISTRHO_SAFE_ASSERT_RETURN(minimumWidth > 0,);
DISTRHO_SAFE_ASSERT_RETURN(minimumHeight > 0,);

// prevent auto-scaling up 2x
if (resizeNowIfAutoScaling && automaticallyScale && pData->autoScaling == automaticallyScale)
resizeNowIfAutoScaling = false;

pData->minWidth = minimumWidth;
pData->minHeight = minimumHeight;
pData->autoScaling = automaticallyScale;
@@ -450,8 +513,8 @@ void Window::setGeometryConstraints(uint minimumWidth,

if (automaticallyScale && scaleFactor != 1.0)
{
minimumWidth *= scaleFactor;
minimumHeight *= scaleFactor;
minimumWidth = d_roundToUnsignedInt(minimumWidth * scaleFactor);
minimumHeight = d_roundToUnsignedInt(minimumHeight * scaleFactor);
}

puglSetGeometryConstraints(pData->view, minimumWidth, minimumHeight, keepAspectRatio);
@@ -467,13 +530,17 @@ void Window::setGeometryConstraints(uint minimumWidth,

void Window::setTransientParent(const uintptr_t transientParentWindowHandle)
{
puglSetTransientParent(pData->view, transientParentWindowHandle);
if (pData->view != nullptr)
puglSetTransientParent(pData->view, transientParentWindowHandle);
}

std::vector<ClipboardDataOffer> Window::getClipboardDataOfferTypes()
{
std::vector<ClipboardDataOffer> offerTypes;

if (pData->view == nullptr)
return offerTypes;

if (const uint32_t numTypes = puglGetNumClipboardTypes(pData->view))
{
offerTypes.reserve(numTypes);
@@ -511,16 +578,17 @@ void Window::onFocus(bool, CrossingMode)
{
}

void Window::onReshape(uint, uint)
void Window::onReshape(const uint width, const uint height)
{
puglFallbackOnResize(pData->view);
if (pData->view != nullptr)
puglFallbackOnResize(pData->view, width, height);
}

void Window::onScaleFactorChanged(double)
{
}

#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
void Window::onFileSelected(const char*)
{
}


+ 216
- 119
source/modules/dgl/src/WindowPrivateData.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -29,16 +29,22 @@
# endif
#endif

#ifdef DISTRHO_OS_WINDOWS
# include <windows.h>
#endif

START_NAMESPACE_DGL

#if defined(DEBUG) && defined(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);
#ifdef DISTRHO_OS_WINDOWS
# include "pugl-upstream/src/win.h"
#endif

#ifdef DGL_DEBUG_EVENTS
# 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
@@ -52,21 +58,22 @@ START_NAMESPACE_DGL

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

static double getScaleFactorFromParent(const PuglView* const view)
static double getScaleFactor(const PuglView* const view)
{
// allow custom scale for testing
if (const char* const scale = getenv("DPF_SCALE_FACTOR"))
return std::max(1.0, std::atof(scale));

if (view != nullptr)
return puglGetScaleFactorFromParent(view);
return puglGetScaleFactor(view);

return 1.0;
}

static PuglView* puglNewViewWithTransientParent(PuglWorld* const world, PuglView* const transientParentView)
{
DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, nullptr);
if (world == nullptr)
return nullptr;

if (PuglView* const view = puglNewView(world))
{
@@ -79,11 +86,16 @@ static PuglView* puglNewViewWithTransientParent(PuglWorld* const world, PuglView

static PuglView* puglNewViewWithParentWindow(PuglWorld* const world, const uintptr_t parentWindowHandle)
{
DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, nullptr);
if (world == nullptr)
return nullptr;

if (PuglView* const view = puglNewView(world))
{
puglSetParentWindow(view, parentWindowHandle);

if (parentWindowHandle != 0)
puglSetPosition(view, 0, 0);

return view;
}

@@ -101,8 +113,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s)
isClosed(true),
isVisible(false),
isEmbed(false),
usesScheduledRepaints(false),
usesSizeRequest(false),
scaleFactor(getScaleFactorFromParent(view)),
scaleFactor(DGL_NAMESPACE::getScaleFactor(view)),
autoScaling(false),
autoScaleFactor(1.0),
minWidth(0),
@@ -113,9 +126,12 @@ Window::PrivateData::PrivateData(Application& a, Window* const s)
waitingForClipboardEvents(false),
clipboardTypeId(0),
filenameToRenderInto(nullptr),
#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
fileBrowserHandle(nullptr),
#endif
#endif
#ifdef DGL_USE_WEB_VIEW
webViewHandle(nullptr),
#endif
modal()
{
initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false);
@@ -130,6 +146,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c
isClosed(true),
isVisible(false),
isEmbed(false),
usesScheduledRepaints(false),
usesSizeRequest(false),
scaleFactor(ppData->scaleFactor),
autoScaling(false),
@@ -142,9 +159,12 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c
waitingForClipboardEvents(false),
clipboardTypeId(0),
filenameToRenderInto(nullptr),
#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
fileBrowserHandle(nullptr),
#endif
#endif
#ifdef DGL_USE_WEB_VIEW
webViewHandle(nullptr),
#endif
modal(ppData)
{
initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false);
@@ -161,8 +181,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s,
isClosed(parentWindowHandle == 0),
isVisible(parentWindowHandle != 0),
isEmbed(parentWindowHandle != 0),
usesScheduledRepaints(false),
usesSizeRequest(false),
scaleFactor(scale != 0.0 ? scale : getScaleFactorFromParent(view)),
scaleFactor(scale != 0.0 ? scale : DGL_NAMESPACE::getScaleFactor(view)),
autoScaling(false),
autoScaleFactor(1.0),
minWidth(0),
@@ -173,9 +194,12 @@ Window::PrivateData::PrivateData(Application& a, Window* const s,
waitingForClipboardEvents(false),
clipboardTypeId(0),
filenameToRenderInto(nullptr),
#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
fileBrowserHandle(nullptr),
#endif
#endif
#ifdef DGL_USE_WEB_VIEW
webViewHandle(nullptr),
#endif
modal()
{
initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, resizable);
@@ -184,7 +208,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s,
Window::PrivateData::PrivateData(Application& a, Window* const s,
const uintptr_t parentWindowHandle,
const uint width, const uint height,
const double scale, const bool resizable, const bool isVST3)
const double scale, const bool resizable,
const bool _usesScheduledRepaints,
const bool _usesSizeRequest)
: app(a),
appData(a.pData),
self(s),
@@ -193,8 +219,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s,
isClosed(parentWindowHandle == 0),
isVisible(parentWindowHandle != 0 && view != nullptr),
isEmbed(parentWindowHandle != 0),
usesSizeRequest(isVST3),
scaleFactor(scale != 0.0 ? scale : getScaleFactorFromParent(view)),
usesScheduledRepaints(_usesScheduledRepaints),
usesSizeRequest(_usesSizeRequest),
scaleFactor(scale != 0.0 ? scale : DGL_NAMESPACE::getScaleFactor(view)),
autoScaling(false),
autoScaleFactor(1.0),
minWidth(0),
@@ -205,14 +232,14 @@ Window::PrivateData::PrivateData(Application& a, Window* const s,
waitingForClipboardEvents(false),
clipboardTypeId(0),
filenameToRenderInto(nullptr),
#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
fileBrowserHandle(nullptr),
#endif
#endif
#ifdef DGL_USE_WEB_VIEW
webViewHandle(nullptr),
#endif
modal()
{
if (isEmbed)
puglSetParentWindow(view, parentWindowHandle);

initPre(width != 0 ? width : DEFAULT_WIDTH, height != 0 ? height : DEFAULT_HEIGHT, resizable);
}

@@ -227,10 +254,14 @@ Window::PrivateData::~PrivateData()

if (isEmbed)
{
#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
if (fileBrowserHandle != nullptr)
fileBrowserClose(fileBrowserHandle);
#endif
#endif
#ifdef DGL_USE_WEB_VIEW
if (webViewHandle != nullptr)
webViewDestroy(webViewHandle);
#endif
puglHide(view);
appData->oneWindowClosed();
isClosed = true;
@@ -259,29 +290,18 @@ void Window::PrivateData::initPre(const uint width, const uint height, const boo

puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE);
puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_FALSE);
#if DGL_USE_RGBA
#if defined(DGL_USE_RGBA) && DGL_USE_RGBA
puglSetViewHint(view, PUGL_DEPTH_BITS, 24);
#else
#else
puglSetViewHint(view, PUGL_DEPTH_BITS, 16);
#endif
#endif
puglSetViewHint(view, PUGL_STENCIL_BITS, 8);

#if defined(DGL_USE_OPENGL3) || defined(DGL_USE_GLES3)
puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE);
puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3);
#elif defined(DGL_USE_GLES2)
puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE);
puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2);
#else
puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_TRUE);
puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2);
#endif

// PUGL_SAMPLES ??
puglSetEventFunc(view, puglEventCallback);

// setting default size triggers system-level calls, do it last
puglSetSizeHint(view, PUGL_DEFAULT_SIZE, width, height);
puglSetSizeHint(view, PUGL_DEFAULT_SIZE, static_cast<PuglSpan>(width), static_cast<PuglSpan>(height));
}

bool Window::PrivateData::initPost()
@@ -300,7 +320,7 @@ bool Window::PrivateData::initPost()
if (isEmbed)
{
appData->oneWindowShown();
puglShow(view);
puglShow(view, PUGL_SHOW_PASSIVE);
}

return true;
@@ -355,7 +375,7 @@ void Window::PrivateData::show()
#elif defined(DISTRHO_OS_MAC)
puglMacOSShowCentered(view);
#else
puglShow(view);
puglShow(view, PUGL_SHOW_RAISE);
#endif
}
else
@@ -363,7 +383,7 @@ void Window::PrivateData::show()
#ifdef DISTRHO_OS_WINDOWS
puglWin32RestoreWindow(view);
#else
puglShow(view);
puglShow(view, PUGL_SHOW_RAISE);
#endif
}

@@ -388,13 +408,21 @@ void Window::PrivateData::hide()
if (modal.enabled)
stopModal();

#ifndef DGL_FILE_BROWSER_DISABLED
#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);

@@ -429,7 +457,7 @@ void Window::PrivateData::setResizable(const bool resizable)

void Window::PrivateData::idleCallback()
{
#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
if (fileBrowserHandle != nullptr && fileBrowserIdle(fileBrowserHandle))
{
self->onFileSelected(fileBrowserGetPath(fileBrowserHandle));
@@ -437,6 +465,11 @@ void Window::PrivateData::idleCallback()
fileBrowserHandle = nullptr;
}
#endif

#ifdef DGL_USE_WEB_VIEW
if (webViewHandle != nullptr)
webViewIdle(webViewHandle);
#endif
}

// -----------------------------------------------------------------------
@@ -444,7 +477,7 @@ void Window::PrivateData::idleCallback()

bool Window::PrivateData::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs)
{
if (ignoreIdleCallbacks)
if (ignoreIdleCallbacks || view == nullptr)
return false;

if (timerFrequencyInMs == 0)
@@ -458,7 +491,7 @@ bool Window::PrivateData::addIdleCallback(IdleCallback* const callback, const ui

bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback)
{
if (ignoreIdleCallbacks)
if (ignoreIdleCallbacks || view == nullptr)
return false;

if (std::find(appData->idleCallbacks.begin(),
@@ -471,9 +504,9 @@ bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback)
return puglStopTimer(view, (uintptr_t)callback) == PUGL_SUCCESS;
}

#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
// -----------------------------------------------------------------------
// file handling
// file browser dialog

bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options)
{
@@ -483,7 +516,9 @@ bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options)
FileBrowserOptions options2 = options;

if (options2.title == nullptr)
options2.title = puglGetWindowTitle(view);
options2.title = puglGetViewString(view, PUGL_WINDOW_TITLE);

options2.className = puglGetViewString(view, PUGL_CLASS_NAME);

fileBrowserHandle = fileBrowserCreate(isEmbed,
puglGetNativeView(view),
@@ -492,14 +527,40 @@ bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options)

return fileBrowserHandle != nullptr;
}
#endif // ! DGL_FILE_BROWSER_DISABLED
#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<uint>(rect.width) - options.offset.x;
uint initialHeight = static_cast<uint>(rect.height) - options.offset.y;

webViewOffset = Point<int>(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
@@ -521,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;
@@ -573,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)
{
@@ -585,16 +646,28 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh
const double scaleVertical = height / static_cast<double>(minHeight);
autoScaleFactor = scaleHorizontal < scaleVertical ? scaleHorizontal : scaleVertical;
}
else
{
autoScaleFactor = 1.0;
}

const uint uwidth = static_cast<uint>(width + 0.5);
const uint uheight = static_cast<uint>(height + 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);

#ifndef DPF_TEST_WINDOW_CPP
FOR_EACH_TOP_LEVEL_WIDGET(it)
{
TopLevelWidget* const widget(*it);
TopLevelWidget* const widget = *it;

/* Some special care here, we call Widget::setSize instead of the TopLevelWidget one.
* This is because we want TopLevelWidget::setSize to handle both window and widget size,
@@ -614,7 +687,7 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh

void Window::PrivateData::onPuglExpose()
{
DGL_DBG("PUGL: onPuglExpose\n");
// DGL_DBG("PUGL: onPuglExpose\n");

puglOnDisplayPrepare(view);

@@ -850,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
@@ -891,65 +964,68 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu
case PUGL_NOTHING:
break;

///< View created, a #PuglEventCreate
case PUGL_CREATE:
#ifdef DGL_USING_X11
if (! pData->isEmbed)
///< View realized, a #PuglRealizeEvent
case PUGL_REALIZE:
if (! pData->isEmbed && ! puglGetTransientParent(view))
{
#if defined(DISTRHO_OS_WINDOWS) && defined(DGL_WINDOWS_ICON_ID)
WNDCLASSEX wClass = {};
const HINSTANCE hInstance = GetModuleHandle(nullptr);

if (GetClassInfoEx(hInstance, view->world->strings[PUGL_CLASS_NAME], &wClass))
wClass.hIcon = LoadIcon(nullptr, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID));

SetClassLongPtr(view->impl->hwnd, GCLP_HICON, (LONG_PTR) LoadIcon(hInstance, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID)));
#endif
#ifdef DGL_USING_X11
puglX11SetWindowTypeAndPID(view, pData->appData->isStandalone);
#endif
#endif
}
break;

///< View destroyed, a #PuglEventDestroy
case PUGL_DESTROY:
///< View unrealizeed, a #PuglUnrealizeEvent
case PUGL_UNREALIZE:
break;

///< View moved/resized, a #PuglEventConfigure
///< View configured, a #PuglConfigureEvent
case PUGL_CONFIGURE:
// unused x, y (double)
pData->onPuglConfigure(event->configure.width, event->configure.height);
break;

///< View made visible, a #PuglEventMap
case PUGL_MAP:
break;

///< View made invisible, a #PuglEventUnmap
case PUGL_UNMAP:
break;

///< View ready to draw, a #PuglEventUpdate
///< View ready to draw, a #PuglUpdateEvent
case PUGL_UPDATE:
break;

///< View must be drawn, a #PuglEventExpose
///< View must be drawn, a #PuglExposeEvent
case PUGL_EXPOSE:
// unused x, y, width, height (double)
pData->onPuglExpose();
break;

///< View will be closed, a #PuglEventClose
///< View will be closed, a #PuglCloseEvent
case PUGL_CLOSE:
pData->onPuglClose();
break;

///< Keyboard focus entered view, a #PuglEventFocus
///< Keyboard focus entered view, a #PuglFocusEvent
case PUGL_FOCUS_IN:
///< Keyboard focus left view, a #PuglEventFocus
///< Keyboard focus left view, a #PuglFocusEvent
case PUGL_FOCUS_OUT:
pData->onPuglFocus(event->type == PUGL_FOCUS_IN,
static_cast<CrossingMode>(event->focus.mode));
break;

///< Key pressed, a #PuglEventKey
///< Key pressed, a #PuglKeyEvent
case PUGL_KEY_PRESS:
///< Key released, a #PuglEventKey
///< Key released, a #PuglKeyEvent
case PUGL_KEY_RELEASE:
{
// unused x, y, xRoot, yRoot (double)
Widget::KeyboardEvent ev;
ev.mod = event->key.state;
ev.flags = event->key.flags;
ev.time = static_cast<uint>(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;
@@ -965,14 +1041,14 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu
break;
}

///< Character entered, a #PuglEventText
///< Character entered, a #PuglTextEvent
case PUGL_TEXT:
{
// unused x, y, xRoot, yRoot (double)
Widget::CharacterInputEvent ev;
ev.mod = event->text.state;
ev.flags = event->text.flags;
ev.time = static_cast<uint>(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));
@@ -980,69 +1056,94 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu
break;
}

///< Pointer entered view, a #PuglEventCrossing
///< Pointer entered view, a #PuglCrossingEvent
case PUGL_POINTER_IN:
break;
///< Pointer left view, a #PuglEventCrossing
///< Pointer left view, a #PuglCrossingEvent
case PUGL_POINTER_OUT:
break;

///< Mouse button pressed, a #PuglEventButton
///< Mouse button pressed, a #PuglButtonEvent
case PUGL_BUTTON_PRESS:
///< Mouse button released, a #PuglEventButton
///< Mouse button released, a #PuglButtonEvent
case PUGL_BUTTON_RELEASE:
{
Widget::MouseEvent ev;
ev.mod = event->button.state;
ev.flags = event->button.flags;
ev.time = static_cast<uint>(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;
ev.pos = Point<double>(event->button.x, event->button.y);
if (pData->autoScaling && 0)
{
const double scaleFactor = pData->autoScaleFactor;
ev.pos = Point<double>(event->button.x / scaleFactor, event->button.y / scaleFactor);
}
else
{
ev.pos = Point<double>(event->button.x, event->button.y);
}
ev.absolutePos = ev.pos;
pData->onPuglMouse(ev);
break;
}

///< Pointer moved, a #PuglEventMotion
///< Pointer moved, a #PuglMotionEvent
case PUGL_MOTION:
{
Widget::MotionEvent ev;
ev.mod = event->motion.state;
ev.flags = event->motion.flags;
ev.time = static_cast<uint>(event->motion.time * 1000.0 + 0.5);
ev.pos = Point<double>(event->motion.x, event->motion.y);
ev.time = d_roundToUnsignedInt(event->motion.time * 1000.0);
if (pData->autoScaling && 0)
{
const double scaleFactor = pData->autoScaleFactor;
ev.pos = Point<double>(event->motion.x / scaleFactor, event->motion.y / scaleFactor);
}
else
{
ev.pos = Point<double>(event->motion.x, event->motion.y);
}
ev.absolutePos = ev.pos;
pData->onPuglMotion(ev);
break;
}

///< Scrolled, a #PuglEventScroll
///< Scrolled, a #PuglScrollEvent
case PUGL_SCROLL:
{
Widget::ScrollEvent ev;
ev.mod = event->scroll.state;
ev.flags = event->scroll.flags;
ev.time = static_cast<uint>(event->scroll.time * 1000.0 + 0.5);
ev.pos = Point<double>(event->scroll.x, event->scroll.y);
ev.delta = Point<double>(event->scroll.dx, event->scroll.dy);
ev.time = d_roundToUnsignedInt(event->scroll.time * 1000.0);
if (pData->autoScaling && 0)
{
const double scaleFactor = pData->autoScaleFactor;
ev.pos = Point<double>(event->scroll.x / scaleFactor, event->scroll.y / scaleFactor);
ev.delta = Point<double>(event->scroll.dx / scaleFactor, event->scroll.dy / scaleFactor);
}
else
{
ev.pos = Point<double>(event->scroll.x, event->scroll.y);
ev.delta = Point<double>(event->scroll.dx, event->scroll.dy);
}
ev.direction = static_cast<ScrollDirection>(event->scroll.direction);
ev.absolutePos = ev.pos;
pData->onPuglScroll(ev);
break;
}

///< Custom client message, a #PuglEventClient
///< Custom client message, a #PuglClientEvent
case PUGL_CLIENT:
break;

///< Timer triggered, a #PuglEventTimer
///< Timer triggered, a #PuglTimerEvent
case PUGL_TIMER:
if (IdleCallback* const idleCallback = reinterpret_cast<IdleCallback*>(event->timer.id))
idleCallback->idleCallback();
break;

///< Recursive loop entered, a #PuglEventLoopEnter
///< Recursive loop left, a #PuglLoopLeaveEvent
case PUGL_LOOP_ENTER:
break;

@@ -1081,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:
@@ -1150,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,


+ 24
- 9
source/modules/dgl/src/WindowPrivateData.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -60,7 +60,10 @@ struct Window::PrivateData : IdleCallback {
/** Whether this Window is embed into another (usually not DGL-controlled) Window. */
const bool isEmbed;

/** Whether to ignore resize requests and feed them into the host instead. used for VST3 */
/** Whether to schedule repaints on the next idle call, used for AU */
const bool usesScheduledRepaints;

/** Whether to ignore resize requests and feed them into the host instead, used for CLAP and VST3 */
const bool usesSizeRequest;

/** Scale factor to report to widgets on request, purely informational. */
@@ -87,10 +90,16 @@ struct Window::PrivateData : IdleCallback {
/** Render to a picture file when non-null, automatically free+unset after saving. */
char* filenameToRenderInto;

#ifndef DGL_FILE_BROWSER_DISABLED
#ifdef DGL_USE_FILE_BROWSER
/** Handle for file browser dialog operations. */
DGL_NAMESPACE::FileBrowserHandle fileBrowserHandle;
#endif
#endif

#ifdef DGL_USE_WEB_VIEW
/** Handle for web view operations. */
DGL_NAMESPACE::WebViewHandle webViewHandle;
DGL_NAMESPACE::Point<int> webViewOffset;
#endif

/** Modal window setup. */
struct Modal {
@@ -131,7 +140,8 @@ struct Window::PrivateData : IdleCallback {

/** Constructor for an embed Window, with a few extra hints from the host side. */
explicit PrivateData(Application& app, Window* self, uintptr_t parentWindowHandle,
uint width, uint height, double scaling, bool resizable, bool isVST3);
uint width, uint height, double scaling, bool resizable,
bool usesScheduledRepaints, bool usesSizeRequest);

/** Destructor. */
~PrivateData() override;
@@ -164,10 +174,15 @@ struct Window::PrivateData : IdleCallback {
bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs);
bool removeIdleCallback(IdleCallback* callback);

#ifndef DGL_FILE_BROWSER_DISABLED
// file handling
#ifdef DGL_USE_FILE_BROWSER
// file browser dialog
bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options);
#endif
#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);

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


+ 42
- 1
source/modules/dgl/src/nanovg/nanovg.c View File

@@ -23,10 +23,46 @@

#include "nanovg.h"
#define FONTSTASH_IMPLEMENTATION
#define stbtt_fontinfo dpf_nvg_stbtt_fontinfo
#define stbrp_context dpf_nvg_stbrp_context
#define stbrp_rect dpf_nvg_stbrp_rect
#define stbrp_node dpf_nvg_stbrp_node
#define stbrp_coord dpf_nvg_stbrp_coord
#include "fontstash.h"

#ifndef NVG_NO_STB
#define STB_IMAGE_IMPLEMENTATION
#define stbi_convert_iphone_png_to_rgb dpf_stbi_convert_iphone_png_to_rgb
#define stbi_failure_reason dpf_stbi_failure_reason
#define stbi_hdr_to_ldr_gamma dpf_stbi_hdr_to_ldr_gamma
#define stbi_hdr_to_ldr_scale dpf_stbi_hdr_to_ldr_scale
#define stbi_image_free dpf_stbi_image_free
#define stbi_info dpf_stbi_info
#define stbi_info_from_callbacks dpf_stbi_info_from_callbacks
#define stbi_info_from_file dpf_stbi_info_from_file
#define stbi_info_from_memory dpf_stbi_info_from_memory
#define stbi_is_hdr dpf_stbi_is_hdr
#define stbi_is_hdr_from_callbacks dpf_stbi_is_hdr_from_callbacks
#define stbi_is_hdr_from_file dpf_stbi_is_hdr_from_file
#define stbi_is_hdr_from_memory dpf_stbi_is_hdr_from_memory
#define stbi_ldr_to_hdr_gamma dpf_stbi_ldr_to_hdr_gamma
#define stbi_ldr_to_hdr_scale dpf_stbi_ldr_to_hdr_scale
#define stbi_load dpf_stbi_load
#define stbi_load_from_callbacks dpf_stbi_load_from_callbacks
#define stbi_load_from_file dpf_stbi_load_from_file
#define stbi_load_from_memory dpf_stbi_load_from_memory
#define stbi_loadf dpf_stbi_loadf
#define stbi_loadf_from_callbacks dpf_stbi_loadf_from_callbacks
#define stbi_loadf_from_file dpf_stbi_loadf_from_file
#define stbi_loadf_from_memory dpf_stbi_loadf_from_memory
#define stbi_set_flip_vertically_on_load dpf_stbi_set_flip_vertically_on_load
#define stbi_set_unpremultiply_on_load dpf_stbi_set_unpremultiply_on_load
#define stbi_zlib_decode_buffer dpf_stbi_zlib_decode_buffer
#define stbi_zlib_decode_malloc dpf_stbi_zlib_decode_malloc
#define stbi_zlib_decode_malloc_guesssize dpf_stbi_zlib_decode_malloc_guesssize
#define stbi_zlib_decode_malloc_guesssize_headerflag dpf_stbi_zlib_decode_malloc_guesssize_headerflag
#define stbi_zlib_decode_noheader_buffer dpf_stbi_zlib_decode_noheader_buffer
#define stbi_zlib_decode_noheader_malloc dpf_stbi_zlib_decode_noheader_malloc
#include "stb_image.h"
#endif

@@ -869,7 +905,7 @@ int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags)
return image;
}

int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata)
int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, const unsigned char* data, int ndata)
{
int w, h, n, image;
unsigned char* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
@@ -2592,6 +2628,11 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char*
nvgTransformPoint(&c[6],&c[7], state->xform, q.x0*invscale, q.y1*invscale);
// Create triangles
if (nverts+6 <= cverts) {
#if NVG_FONT_TEXTURE_FLAGS
// align font kerning to integer pixel positions
for (int i = 0; i < 8; ++i)
c[i] = (int)(c[i] + 0.5f);
#endif
nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++;
nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++;
nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); nverts++;


+ 1
- 1
source/modules/dgl/src/nanovg/nanovg.h View File

@@ -385,7 +385,7 @@ int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags);

// Creates image by loading it from the specified chunk of memory.
// Returns handle to the image.
int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata);
int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, const unsigned char* data, int ndata);

// Creates image from specified image data and texture format.
// Returns handle to the image.


+ 22
- 0
source/modules/dgl/src/nanovg/nanovg_gl.h View File

@@ -18,6 +18,28 @@
#ifndef NANOVG_GL_H
#define NANOVG_GL_H

#if defined NANOVG_GL2_FORCED
# undef NANOVG_GL3
# undef NANOVG_GLES2
# undef NANOVG_GLES3
# define NANOVG_GL2 1
#elif defined NANOVG_GL3_FORCED
# undef NANOVG_GL2
# undef NANOVG_GLES2
# undef NANOVG_GLES3
# define NANOVG_GL3 1
#elif defined NANOVG_GLES2_FORCED
# undef NANOVG_GL2
# undef NANOVG_GL3
# undef NANOVG_GLES3
# define NANOVG_GLES2 1
#elif defined NANOVG_GLES3_FORCED
# undef NANOVG_GL2
# undef NANOVG_GL3
# undef NANOVG_GLES2
# define NANOVG_GLES3 1
#endif

#ifdef __cplusplus
extern "C" {
#endif


+ 1
- 1
source/modules/dgl/src/pugl-upstream/COPYING View File

@@ -1,4 +1,4 @@
Copyright 2011-2021 David Robillard <d@drobilla.net>
Copyright 2011-2022 David Robillard <d@drobilla.net>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above


+ 59
- 0
source/modules/dgl/src/pugl-upstream/include/pugl/attributes.h View File

@@ -0,0 +1,59 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_ATTRIBUTES_H
#define PUGL_ATTRIBUTES_H

// Public declaration scope
#ifdef __cplusplus
# define PUGL_BEGIN_DECLS extern "C" {
# define PUGL_END_DECLS }
#else
# define PUGL_BEGIN_DECLS ///< Begin public API definitions
# define PUGL_END_DECLS ///< End public API definitions
#endif

// Symbol exposed in the public API
#ifndef PUGL_API
# if defined(_WIN32) && !defined(PUGL_STATIC) && defined(PUGL_INTERNAL)
# define PUGL_API __declspec(dllexport)
# elif defined(_WIN32) && !defined(PUGL_STATIC)
# define PUGL_API __declspec(dllimport)
# elif defined(__GNUC__)
# define PUGL_API __attribute__((visibility("default")))
# else
# define PUGL_API
# endif
#endif

// Deprecated API
#ifndef PUGL_DISABLE_DEPRECATED
# if defined(__clang__)
# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("", rep)))
# elif defined(__GNUC__)
# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("Use " rep)))
# else
# define PUGL_DEPRECATED_BY(rep)
# endif
#endif

// GCC function attributes
#if defined(__GNUC__)
# define PUGL_CONST_FUNC __attribute__((const))
# define PUGL_MALLOC_FUNC __attribute__((malloc))
#else
# define PUGL_CONST_FUNC ///< Only reads its parameters
# define PUGL_MALLOC_FUNC ///< Allocates memory
#endif

/// A const function in the public API that only reads parameters
#define PUGL_CONST_API \
PUGL_API \
PUGL_CONST_FUNC

/// A malloc function in the public API that returns allocated memory
#define PUGL_MALLOC_API \
PUGL_API \
PUGL_MALLOC_FUNC

#endif // PUGL_ATTRIBUTES_H

+ 3
- 13
source/modules/dgl/src/pugl-upstream/include/pugl/gl.h View File

@@ -1,9 +1,10 @@
// Copyright 2012-2020 David Robillard <d@drobilla.net>
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_GL_H
#define PUGL_GL_H

#include "pugl/attributes.h"
#include "pugl/pugl.h"

// IWYU pragma: begin_exports
@@ -22,23 +23,12 @@
# endif
#endif

#ifndef PUGL_NO_INCLUDE_GLU_H
# ifdef __APPLE__
# include <OpenGL/glu.h>
# else
# ifdef _WIN32
# include <windows.h>
# endif
# include <GL/glu.h>
# endif
#endif

// IWYU pragma: end_exports

PUGL_BEGIN_DECLS

/**
@defgroup gl OpenGL
@defgroup pugl_gl OpenGL
OpenGL graphics support.
@ingroup pugl
@{


+ 22
- 0
source/modules/dgl/src/pugl-upstream/include/pugl/glu.h View File

@@ -0,0 +1,22 @@
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_GLU_H
#define PUGL_GLU_H

// IWYU pragma: begin_exports

#ifndef PUGL_NO_INCLUDE_GLU_H
# ifdef __APPLE__
# include <OpenGL/glu.h>
# else
# ifdef _WIN32
# include <windows.h>
# endif
# include <GL/glu.h>
# endif
#endif

// IWYU pragma: end_exports

#endif // PUGL_GLU_H

+ 545
- 288
source/modules/dgl/src/pugl-upstream/include/pugl/pugl.h
File diff suppressed because it is too large
View File


+ 3
- 2
source/modules/dgl/src/pugl-upstream/include/pugl/stub.h View File

@@ -1,15 +1,16 @@
// Copyright 2019-2020 David Robillard <d@drobilla.net>
// Copyright 2019-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_STUB_H
#define PUGL_STUB_H

#include "pugl/attributes.h"
#include "pugl/pugl.h"

PUGL_BEGIN_DECLS

/**
@defgroup stub Stub
@defgroup pugl_stub Stub
Native graphics support.
@ingroup pugl
@{


+ 59
- 0
source/modules/dgl/src/pugl-upstream/pugl/attributes.h View File

@@ -0,0 +1,59 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_ATTRIBUTES_H
#define PUGL_ATTRIBUTES_H

// Public declaration scope
#ifdef __cplusplus
# define PUGL_BEGIN_DECLS extern "C" {
# define PUGL_END_DECLS }
#else
# define PUGL_BEGIN_DECLS ///< Begin public API definitions
# define PUGL_END_DECLS ///< End public API definitions
#endif

// Symbol exposed in the public API
#ifndef PUGL_API
# if defined(_WIN32) && !defined(PUGL_STATIC) && defined(PUGL_INTERNAL)
# define PUGL_API __declspec(dllexport)
# elif defined(_WIN32) && !defined(PUGL_STATIC)
# define PUGL_API __declspec(dllimport)
# elif defined(__GNUC__)
# define PUGL_API __attribute__((visibility("default")))
# else
# define PUGL_API
# endif
#endif

// Deprecated API
#ifndef PUGL_DISABLE_DEPRECATED
# if defined(__clang__)
# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("", rep)))
# elif defined(__GNUC__)
# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("Use " rep)))
# else
# define PUGL_DEPRECATED_BY(rep)
# endif
#endif

// GCC function attributes
#if defined(__GNUC__)
# define PUGL_CONST_FUNC __attribute__((const))
# define PUGL_MALLOC_FUNC __attribute__((malloc))
#else
# define PUGL_CONST_FUNC ///< Only reads its parameters
# define PUGL_MALLOC_FUNC ///< Allocates memory
#endif

/// A const function in the public API that only reads parameters
#define PUGL_CONST_API \
PUGL_API \
PUGL_CONST_FUNC

/// A malloc function in the public API that returns allocated memory
#define PUGL_MALLOC_API \
PUGL_API \
PUGL_MALLOC_FUNC

#endif // PUGL_ATTRIBUTES_H

+ 34
- 0
source/modules/dgl/src/pugl-upstream/pugl/cairo.h View File

@@ -0,0 +1,34 @@
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_CAIRO_H
#define PUGL_CAIRO_H

#include "pugl/attributes.h"
#include "pugl/pugl.h"

PUGL_BEGIN_DECLS

/**
@defgroup pugl_cairo Cairo
Cairo graphics support.
@ingroup pugl
@{
*/

/**
Cairo graphics backend accessor.

Pass the returned value to puglSetBackend() to draw to a view with Cairo.
*/
PUGL_CONST_API
const PuglBackend*
puglCairoBackend(void);

/**
@}
*/

PUGL_END_DECLS

#endif // PUGL_CAIRO_H

+ 84
- 0
source/modules/dgl/src/pugl-upstream/pugl/gl.h View File

@@ -0,0 +1,84 @@
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_GL_H
#define PUGL_GL_H

#include "pugl/attributes.h"
#include "pugl/pugl.h"

// IWYU pragma: begin_exports

/* Unfortunately, GL includes vary across platforms, so include them here to
enable pure portable programs. */

#ifndef PUGL_NO_INCLUDE_GL_H
# ifdef __APPLE__
# include <OpenGL/gl.h>
# else
# ifdef _WIN32
# include <windows.h>
# endif
# include <GL/gl.h>
# endif
#endif

// IWYU pragma: end_exports

PUGL_BEGIN_DECLS

/**
@defgroup pugl_gl OpenGL
OpenGL graphics support.
@ingroup pugl
@{
*/

/**
OpenGL extension function.
*/
typedef void (*PuglGlFunc)(void);

/**
Return the address of an OpenGL extension function.
*/
PUGL_API
PuglGlFunc
puglGetProcAddress(const char* name);

/**
Enter the OpenGL context.

This can be used to enter the graphics context in unusual situations, for
doing things like loading textures. Note that this must not be used for
drawing, which may only be done while processing an expose event.
*/
PUGL_API
PuglStatus
puglEnterContext(PuglView* view);

/**
Leave the OpenGL context.

This must only be called after puglEnterContext().
*/
PUGL_API
PuglStatus
puglLeaveContext(PuglView* view);

/**
OpenGL graphics backend.

Pass the returned value to puglSetBackend() to draw to a view with OpenGL.
*/
PUGL_CONST_API
const PuglBackend*
puglGlBackend(void);

PUGL_END_DECLS

/**
@}
*/

#endif // PUGL_GL_H

+ 22
- 0
source/modules/dgl/src/pugl-upstream/pugl/glu.h View File

@@ -0,0 +1,22 @@
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_GLU_H
#define PUGL_GLU_H

// IWYU pragma: begin_exports

#ifndef PUGL_NO_INCLUDE_GLU_H
# ifdef __APPLE__
# include <OpenGL/glu.h>
# else
# ifdef _WIN32
# include <windows.h>
# endif
# include <GL/glu.h>
# endif
#endif

// IWYU pragma: end_exports

#endif // PUGL_GLU_H

+ 2189
- 0
source/modules/dgl/src/pugl-upstream/pugl/pugl.h
File diff suppressed because it is too large
View File


+ 35
- 0
source/modules/dgl/src/pugl-upstream/pugl/stub.h View File

@@ -0,0 +1,35 @@
// Copyright 2019-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_STUB_H
#define PUGL_STUB_H

#include "pugl/attributes.h"
#include "pugl/pugl.h"

PUGL_BEGIN_DECLS

/**
@defgroup pugl_stub Stub
Native graphics support.
@ingroup pugl
@{
*/

/**
Stub graphics backend accessor.

This backend just creates a simple native window without setting up any
portable graphics API.
*/
PUGL_CONST_API
const PuglBackend*
puglStubBackend(void);

/**
@}
*/

PUGL_END_DECLS

#endif // PUGL_STUB_H

+ 153
- 0
source/modules/dgl/src/pugl-upstream/pugl/vulkan.h View File

@@ -0,0 +1,153 @@
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

/*
Note that this header includes Vulkan headers, so if you are writing a
program or plugin that dynamically loads vulkan, you should first define
`VK_NO_PROTOTYPES` before including it.
*/

#ifndef PUGL_VULKAN_H
#define PUGL_VULKAN_H

#include "pugl/attributes.h"
#include "pugl/pugl.h"

#include <vulkan/vulkan_core.h>

#include <stdint.h>

PUGL_BEGIN_DECLS

/**
@defgroup pugl_vulkan Vulkan
Vulkan graphics support.

Vulkan support differs from OpenGL because almost all most configuration is
done using the Vulkan API itself, rather than by setting view hints to
configure the context. Pugl only provides a minimal loader for loading the
Vulkan library, and a portable function to create a Vulkan surface for a
view, which hides the platform-specific implementation details.

@ingroup pugl
@{
*/

/**
Dynamic Vulkan loader.

This can be used to dynamically load the Vulkan library. Applications or
plugins should not link against the Vulkan library, but instead use this at
runtime. This ensures that things will work on as many systems as possible,
and allows errors to be handled gracefully.

This is not a "loader" in the sense of loading all the required Vulkan
functions (which is the application's responsibility), but just a minimal
implementation to portably load the Vulkan library and get the two functions
that are used to load everything else.

Note that this owns the loaded Vulkan library, so it must outlive all use of
the Vulkan API.

@see https://www.khronos.org/registry/vulkan/specs/1.0/html/chap4.html
*/
typedef struct PuglVulkanLoaderImpl PuglVulkanLoader;

/**
Create a new dynamic loader for Vulkan functions.

This dynamically loads the Vulkan library and gets the load functions from
it.

@param world The world the returned loader is a part of.

@param libraryName The name of the Vulkan library to load, or null.
Typically, this is left unset, which will load the standard Vulkan library
for the current platform. It can be set to an alternative name, or an
absolute path, to support special packaging scenarios or unusual system
configurations. This name is passed directly to the underlying platform
library loading function (`dlopen` or `LoadLibrary`).

@return A new Vulkan loader, or null on failure.
*/
PUGL_API
PuglVulkanLoader*
puglNewVulkanLoader(PuglWorld* world, const char* libraryName);

/**
Free a loader created with puglNewVulkanLoader().

Note that this closes the Vulkan library, so no Vulkan objects or API may be
used after this is called.
*/
PUGL_API
void
puglFreeVulkanLoader(PuglVulkanLoader* loader);

/**
Return the `vkGetInstanceProcAddr` function.

@return Null if the Vulkan library does not contain this function (which is
unlikely and indicates a broken system).
*/
PUGL_API
PFN_vkGetInstanceProcAddr
puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader);

/**
Return the `vkGetDeviceProcAddr` function.

@return Null if the Vulkan library does not contain this function (which is
unlikely and indicates a broken system).
*/
PUGL_API
PFN_vkGetDeviceProcAddr
puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader);

/**
Return the Vulkan instance extensions required to draw to a PuglView.

This simply returns static strings, it does not access Vulkan or the window
system. The returned array always contains at least "VK_KHR_surface".

@param[out] count The number of extensions in the returned array.
@return An array of extension name strings.
*/
PUGL_API
const char* const*
puglGetInstanceExtensions(uint32_t* count);

/**
Create a Vulkan surface for a Pugl view.

@param vkGetInstanceProcAddr Accessor for Vulkan functions.
@param view The view the surface is to be displayed on.
@param instance The Vulkan instance.
@param allocator Vulkan allocation callbacks, may be NULL.
@param[out] surface Pointed to a newly created Vulkan surface.
@return `VK_SUCCESS` on success, or a Vulkan error code.
*/
PUGL_API
VkResult
puglCreateSurface(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr,
PuglView* view,
VkInstance instance,
const VkAllocationCallbacks* allocator,
VkSurfaceKHR* surface);

/**
Vulkan graphics backend.

Pass the returned value to puglSetBackend() to draw to a view with Vulkan.
*/
PUGL_CONST_API
const PuglBackend*
puglVulkanBackend(void);

/**
@}
*/

PUGL_END_DECLS

#endif // PUGL_VULKAN_H

+ 115
- 34
source/modules/dgl/src/pugl-upstream/src/common.c View File

@@ -1,4 +1,4 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

// Common implementations of public API functions in the core library
@@ -10,7 +10,9 @@

#include "pugl/pugl.h"

#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

@@ -48,12 +50,9 @@ puglNewWorld(PuglWorldType type, PuglWorldFlags flags)
}

world->startTime = puglGetTime(world);
world->type = type;

#ifdef __EMSCRIPTEN__
puglSetString(&world->className, "canvas");
#else
puglSetString(&world->className, "Pugl");
#endif
puglSetString(&world->strings[PUGL_CLASS_NAME], "Pugl");

return world;
}
@@ -62,7 +61,11 @@ void
puglFreeWorld(PuglWorld* const world)
{
puglFreeWorldInternals(world);
free(world->className);

for (size_t i = 0; i < PUGL_NUM_STRING_HINTS; ++i) {
free(world->strings[i]);
}

free(world->views);
free(world);
}
@@ -80,36 +83,50 @@ puglGetWorldHandle(PuglWorld* world)
}

PuglStatus
puglSetClassName(PuglWorld* const world, const char* const name)
puglSetWorldString(PuglWorld* const world,
const PuglStringHint key,
const char* const value)
{
puglSetString(&world->className, name);
if ((unsigned)key >= PUGL_NUM_STRING_HINTS) {
return PUGL_BAD_PARAMETER;
}

puglSetString(&world->strings[key], value);
return PUGL_SUCCESS;
}

const char*
puglGetClassName(const PuglWorld* world)
puglGetWorldString(const PuglWorld* const world, const PuglStringHint key)
{
return world->className;
if ((unsigned)key >= PUGL_NUM_STRING_HINTS) {
return NULL;
}

return world->strings[key];
}

static void
puglSetDefaultHints(PuglHints hints)
{
hints[PUGL_USE_COMPAT_PROFILE] = PUGL_TRUE;
hints[PUGL_CONTEXT_API] = PUGL_OPENGL_API;
hints[PUGL_CONTEXT_VERSION_MAJOR] = 2;
hints[PUGL_CONTEXT_VERSION_MINOR] = 0;
hints[PUGL_CONTEXT_PROFILE] = PUGL_OPENGL_CORE_PROFILE;
hints[PUGL_CONTEXT_DEBUG] = PUGL_FALSE;
hints[PUGL_RED_BITS] = 8;
hints[PUGL_GREEN_BITS] = 8;
hints[PUGL_BLUE_BITS] = 8;
hints[PUGL_ALPHA_BITS] = 8;
hints[PUGL_DEPTH_BITS] = 0;
hints[PUGL_STENCIL_BITS] = 0;
hints[PUGL_SAMPLE_BUFFERS] = PUGL_DONT_CARE;
hints[PUGL_SAMPLES] = 0;
hints[PUGL_DOUBLE_BUFFER] = PUGL_TRUE;
hints[PUGL_SWAP_INTERVAL] = PUGL_DONT_CARE;
hints[PUGL_RESIZABLE] = PUGL_FALSE;
hints[PUGL_IGNORE_KEY_REPEAT] = PUGL_FALSE;
hints[PUGL_REFRESH_RATE] = PUGL_DONT_CARE;
hints[PUGL_VIEW_TYPE] = PUGL_DONT_CARE;
}

PuglView*
@@ -124,15 +141,25 @@ puglNewView(PuglWorld* const world)
view->world = world;
view->sizeHints[PUGL_MIN_SIZE].width = 1;
view->sizeHints[PUGL_MIN_SIZE].height = 1;
view->defaultX = INT_MIN;
view->defaultY = INT_MIN;

puglSetDefaultHints(view->hints);

// Add to world view list
++world->numViews;
world->views =
(PuglView**)realloc(world->views, world->numViews * sizeof(PuglView*));
// Enlarge world view list
const size_t newNumViews = world->numViews + 1U;
PuglView** const views =
(PuglView**)realloc(world->views, newNumViews * sizeof(PuglView*));

if (!views) {
free(view);
return NULL;
}

world->views[world->numViews - 1] = view;
// Add to world view list
world->views = views;
world->views[world->numViews] = view;
world->numViews = newNumViews;

return view;
}
@@ -140,10 +167,6 @@ puglNewView(PuglWorld* const world)
void
puglFreeView(PuglView* view)
{
if (view->eventFunc && view->backend) {
puglDispatchSimpleEvent(view, PUGL_DESTROY);
}

// Remove from world view list
PuglWorld* world = view->world;
for (size_t i = 0; i < world->numViews; ++i) {
@@ -160,7 +183,10 @@ puglFreeView(PuglView* view)
}
}

free(view->title);
for (size_t i = 0; i < PUGL_NUM_STRING_HINTS; ++i) {
free(view->strings[i]);
}

puglFreeViewInternals(view);
free(view);
}
@@ -208,10 +234,11 @@ puglSetViewHint(PuglView* view, PuglViewHint hint, int value)
{
if (value == PUGL_DONT_CARE) {
switch (hint) {
case PUGL_USE_COMPAT_PROFILE:
case PUGL_USE_DEBUG_CONTEXT:
case PUGL_CONTEXT_API:
case PUGL_CONTEXT_VERSION_MAJOR:
case PUGL_CONTEXT_VERSION_MINOR:
case PUGL_CONTEXT_PROFILE:
case PUGL_CONTEXT_DEBUG:
case PUGL_SWAP_INTERVAL:
return PUGL_BAD_PARAMETER;
default:
@@ -219,26 +246,73 @@ puglSetViewHint(PuglView* view, PuglViewHint hint, int value)
}
}

view->hints[hint] = value;
return PUGL_SUCCESS;
if ((unsigned)hint < PUGL_NUM_VIEW_HINTS) {
view->hints[hint] = value;
return PUGL_SUCCESS;
}

return PUGL_BAD_PARAMETER;
}

int
puglGetViewHint(const PuglView* view, PuglViewHint hint)
{
return view->hints[hint];
if ((unsigned)hint < PUGL_NUM_VIEW_HINTS) {
return view->hints[hint];
}

return PUGL_DONT_CARE;
}

PuglRect
puglGetFrame(const PuglView* view)
PuglStatus
puglSetViewString(PuglView* const view,
const PuglStringHint key,
const char* const value)
{
return view->frame;
if ((unsigned)key >= PUGL_NUM_STRING_HINTS) {
return PUGL_BAD_PARAMETER;
}

puglSetString(&view->strings[key], value);
return puglViewStringChanged(view, key, view->strings[key]);
}

const char*
puglGetWindowTitle(const PuglView* const view)
puglGetViewString(const PuglView* const view, const PuglStringHint key)
{
return view->title;
if ((unsigned)key >= PUGL_NUM_STRING_HINTS) {
return NULL;
}

return view->strings[key];
}

PuglRect
puglGetFrame(const PuglView* view)
{
if (view->lastConfigure.type == PUGL_CONFIGURE) {
// Return the last configured frame
const PuglRect frame = {view->lastConfigure.x,
view->lastConfigure.y,
view->lastConfigure.width,
view->lastConfigure.height};
return frame;
}

// Get the default position if set, or fallback to (0, 0)
int x = view->defaultX;
int y = view->defaultY;
if (x < INT16_MIN || x > INT16_MAX || y < INT16_MIN || y > INT16_MAX) {
x = 0;
y = 0;
}

// Return the default frame, sanitized if necessary
const PuglRect frame = {(PuglCoord)x,
(PuglCoord)y,
view->sizeHints[PUGL_DEFAULT_SIZE].width,
view->sizeHints[PUGL_DEFAULT_SIZE].height};
return frame;
}

PuglStatus
@@ -263,11 +337,18 @@ puglGetTransientParent(const PuglView* const view)
bool
puglGetVisible(const PuglView* view)
{
return view->visible;
return (view->lastConfigure.style & PUGL_VIEW_STYLE_MAPPED) &&
!(view->lastConfigure.style & PUGL_VIEW_STYLE_HIDDEN);
}

void*
puglGetContext(PuglView* view)
puglGetContext(PuglView* const view)
{
return view->backend->getContext(view);
}

PuglViewStyleFlags
puglGetViewStyle(const PuglView* const view)
{
return view->lastConfigure.style;
}

+ 113
- 50
source/modules/dgl/src/pugl-upstream/src/internal.c View File

@@ -1,4 +1,4 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#include "internal.h"
@@ -12,6 +12,20 @@
#include <stdlib.h>
#include <string.h>

bool
puglIsValidSize(const PuglViewSize size)
{
return size.width && size.height;
}

void
puglEnsureHint(PuglView* const view, const PuglViewHint hint, const int value)
{
if (view->hints[hint] == PUGL_DONT_CARE) {
view->hints[hint] = value;
}
}

PuglStatus
puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len)
{
@@ -39,11 +53,18 @@ puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len)
void
puglSetString(char** dest, const char* string)
{
if (*dest != string) {
const size_t len = strlen(string);
if (*dest == string) {
return;
}

const size_t len = string ? strlen(string) : 0U;

*dest = (char*)realloc(*dest, len + 1);
strncpy(*dest, string, len + 1);
if (!len) {
free(*dest);
*dest = NULL;
} else {
*dest = (char*)realloc(*dest, len + 1U);
strncpy(*dest, string, len + 1U);
}
}

@@ -67,40 +88,90 @@ puglDecodeUTF8(const uint8_t* buf)
}

if (buf[0] < 0xE0) {
FAIL_IF((buf[1] & 0xC0u) != 0x80);
return ((uint32_t)buf[0] << 6u) + buf[1] - 0x3080u;
FAIL_IF((buf[1] & 0xC0U) != 0x80);
return ((uint32_t)buf[0] << 6U) + buf[1] - 0x3080U;
}

if (buf[0] < 0xF0) {
FAIL_IF((buf[1] & 0xC0u) != 0x80);
FAIL_IF((buf[1] & 0xC0U) != 0x80);
FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0);
FAIL_IF((buf[2] & 0xC0u) != 0x80);
return ((uint32_t)buf[0] << 12u) + //
((uint32_t)buf[1] << 6u) + //
((uint32_t)buf[2] - 0xE2080u);
FAIL_IF((buf[2] & 0xC0U) != 0x80);
return ((uint32_t)buf[0] << 12U) + //
((uint32_t)buf[1] << 6U) + //
((uint32_t)buf[2] - 0xE2080U);
}

if (buf[0] < 0xF5) {
FAIL_IF((buf[1] & 0xC0u) != 0x80);
FAIL_IF((buf[1] & 0xC0U) != 0x80);
FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90);
FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90);
FAIL_IF((buf[2] & 0xC0u) != 0x80u);
FAIL_IF((buf[3] & 0xC0u) != 0x80u);
return (((uint32_t)buf[0] << 18u) + //
((uint32_t)buf[1] << 12u) + //
((uint32_t)buf[2] << 6u) + //
((uint32_t)buf[3] - 0x3C82080u));
FAIL_IF((buf[2] & 0xC0U) != 0x80U);
FAIL_IF((buf[3] & 0xC0U) != 0x80U);
return (((uint32_t)buf[0] << 18U) + //
((uint32_t)buf[1] << 12U) + //
((uint32_t)buf[2] << 6U) + //
((uint32_t)buf[3] - 0x3C82080U));
}

return 0xFFFD;
}

PuglMods
puglFilterMods(const PuglMods state, const PuglKey key)
{
switch (key) {
case PUGL_KEY_SHIFT_L:
case PUGL_KEY_SHIFT_R:
return state & ~(PuglMods)PUGL_MOD_SHIFT;
case PUGL_KEY_CTRL_L:
case PUGL_KEY_CTRL_R:
return state & ~(PuglMods)PUGL_MOD_CTRL;
case PUGL_KEY_ALT_L:
case PUGL_KEY_ALT_R:
return state & ~(PuglMods)PUGL_MOD_ALT;
case PUGL_KEY_SUPER_L:
case PUGL_KEY_SUPER_R:
return state & ~(PuglMods)PUGL_MOD_SUPER;
case PUGL_KEY_NUM_LOCK:
return state & ~(PuglMods)PUGL_MOD_NUM_LOCK;
case PUGL_KEY_SCROLL_LOCK:
return state & ~(PuglMods)PUGL_MOD_SCROLL_LOCK;
case PUGL_KEY_CAPS_LOCK:
return state & ~(PuglMods)PUGL_MOD_CAPS_LOCK;
default:
break;
}

return state;
}

PuglStatus
puglPreRealize(PuglView* const view)
{
// Ensure that a backend with at least a configure method has been set
if (!view->backend || !view->backend->configure) {
return PUGL_BAD_BACKEND;
}

// Ensure that the view has an event handler
if (!view->eventFunc) {
return PUGL_BAD_CONFIGURATION;
}

// Ensure that the default size is set to a valid size
if (!puglIsValidSize(view->sizeHints[PUGL_DEFAULT_SIZE])) {
return PUGL_BAD_CONFIGURATION;
}

return PUGL_SUCCESS;
}

PuglStatus
puglDispatchSimpleEvent(PuglView* view, const PuglEventType type)
{
assert(type == PUGL_CREATE || type == PUGL_DESTROY || type == PUGL_MAP ||
type == PUGL_UNMAP || type == PUGL_UPDATE || type == PUGL_CLOSE ||
type == PUGL_LOOP_ENTER || type == PUGL_LOOP_LEAVE);
assert(type == PUGL_REALIZE || type == PUGL_UNREALIZE ||
type == PUGL_UPDATE || type == PUGL_CLOSE || type == PUGL_LOOP_ENTER ||
type == PUGL_LOOP_LEAVE);

const PuglEvent event = {{type, 0}};
return puglDispatchEvent(view, &event);
@@ -118,12 +189,6 @@ puglConfigure(PuglView* view, const PuglEvent* event)
PuglStatus st = PUGL_SUCCESS;

assert(event->type == PUGL_CONFIGURE);

view->frame.x = event->configure.x;
view->frame.y = event->configure.y;
view->frame.width = event->configure.width;
view->frame.height = event->configure.height;

if (puglMustConfigure(view, &event->configure)) {
st = view->eventFunc(view, event);
view->lastConfigure = event->configure;
@@ -132,14 +197,6 @@ puglConfigure(PuglView* view, const PuglEvent* event)
return st;
}

PuglStatus
puglExpose(PuglView* view, const PuglEvent* event)
{
return (event->expose.width > 0.0 && event->expose.height > 0.0)
? view->eventFunc(view, event)
: PUGL_SUCCESS;
}

PuglStatus
puglDispatchEvent(PuglView* view, const PuglEvent* event)
{
@@ -149,13 +206,25 @@ puglDispatchEvent(PuglView* view, const PuglEvent* event)
switch (event->type) {
case PUGL_NOTHING:
break;
case PUGL_CREATE:
case PUGL_DESTROY:

case PUGL_REALIZE:
assert(view->stage == PUGL_VIEW_STAGE_ALLOCATED);
if (!(st0 = view->backend->enter(view, NULL))) {
st0 = view->eventFunc(view, event);
st1 = view->backend->leave(view, NULL);
}
view->stage = PUGL_VIEW_STAGE_REALIZED;
break;

case PUGL_UNREALIZE:
assert(view->stage >= PUGL_VIEW_STAGE_REALIZED);
if (!(st0 = view->backend->enter(view, NULL))) {
st0 = view->eventFunc(view, event);
st1 = view->backend->leave(view, NULL);
}
view->stage = PUGL_VIEW_STAGE_ALLOCATED;
break;

case PUGL_CONFIGURE:
if (puglMustConfigure(view, &event->configure)) {
if (!(st0 = view->backend->enter(view, NULL))) {
@@ -163,25 +232,19 @@ puglDispatchEvent(PuglView* view, const PuglEvent* event)
st1 = view->backend->leave(view, NULL);
}
}
break;
case PUGL_MAP:
if (!view->visible) {
view->visible = true;
st0 = view->eventFunc(view, event);
}
break;
case PUGL_UNMAP:
if (view->visible) {
view->visible = false;
st0 = view->eventFunc(view, event);
if (view->stage == PUGL_VIEW_STAGE_REALIZED) {
view->stage = PUGL_VIEW_STAGE_CONFIGURED;
}
break;

case PUGL_EXPOSE:
assert(view->stage == PUGL_VIEW_STAGE_CONFIGURED);
if (!(st0 = view->backend->enter(view, &event->expose))) {
st0 = puglExpose(view, event);
st0 = view->eventFunc(view, event);
st1 = view->backend->leave(view, &event->expose);
}
break;

default:
st0 = view->eventFunc(view, event);
}


+ 24
- 6
source/modules/dgl/src/pugl-upstream/src/internal.h View File

@@ -1,4 +1,4 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

// Internal utilities available to platform implementations
@@ -9,13 +9,23 @@
#include "attributes.h"
#include "types.h"

#include "pugl/attributes.h"
#include "pugl/pugl.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

PUGL_BEGIN_DECLS

/// Return true if `size` is a valid view size
bool
puglIsValidSize(PuglViewSize size);

/// Set hint to a default value if it is unset (PUGL_DONT_CARE)
void
puglEnsureHint(PuglView* view, PuglViewHint hint, int value);

/// Set `blob` to `data` with length `len`, reallocating if necessary
PuglStatus
puglSetBlob(PuglBlob* dest, const void* data, size_t len);
@@ -24,10 +34,23 @@ puglSetBlob(PuglBlob* dest, const void* data, size_t len);
void
puglSetString(char** dest, const char* string);

/// Handle a changed string property
PUGL_API
PuglStatus
puglViewStringChanged(PuglView* view, PuglStringHint key, const char* value);

/// Return the Unicode code point for `buf` or the replacement character
uint32_t
puglDecodeUTF8(const uint8_t* buf);

/// Return `state` with any flags related to `key` removed
PuglMods
puglFilterMods(PuglMods state, PuglKey key);

/// Prepare a view to be realized by the platform implementation if possible
PuglStatus
puglPreRealize(PuglView* view);

/// Dispatch an event with a simple `type` to `view`
PuglStatus
puglDispatchSimpleEvent(PuglView* view, PuglEventType type);
@@ -37,11 +60,6 @@ PUGL_WARN_UNUSED_RESULT
PuglStatus
puglConfigure(PuglView* view, const PuglEvent* event);

/// Process expose event while already in the graphics context
PUGL_WARN_UNUSED_RESULT
PuglStatus
puglExpose(PuglView* view, const PuglEvent* event);

/// Dispatch `event` to `view`, entering graphics context if necessary
PuglStatus
puglDispatchEvent(PuglView* view, const PuglEvent* event);


+ 5
- 5
source/modules/dgl/src/pugl-upstream/src/mac.h View File

@@ -9,6 +9,8 @@

#import <Cocoa/Cocoa.h>

#include <mach/mach_time.h>

#include <stdint.h>

@interface PuglWrapperView : NSView<NSTextInputClient>
@@ -19,14 +21,12 @@
@end

@interface PuglWindow : NSWindow

- (void)setPuglview:(PuglView*)view;

@end

struct PuglWorldInternalsImpl {
NSApplication* app;
NSAutoreleasePool* autoreleasePool;
NSApplication* app;
NSAutoreleasePool* autoreleasePool;
struct mach_timebase_info timebaseInfo;
};

struct PuglInternalsImpl {


+ 570
- 226
source/modules/dgl/src/pugl-upstream/src/mac.m
File diff suppressed because it is too large
View File


+ 3
- 5
source/modules/dgl/src/pugl-upstream/src/mac_cairo.m View File

@@ -90,13 +90,11 @@ puglMacCairoEnter(PuglView* view, const PuglExposeEvent* expose)
CGContextRef context =
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];

const CGSize sizePx = {(CGFloat)view->frame.width,
(CGFloat)view->frame.height};

const CGSize sizePt = CGContextConvertSizeToUserSpace(context, sizePx);
const CGSize sizePx = {(CGFloat)view->lastConfigure.width,
(CGFloat)view->lastConfigure.height};

// Convert coordinates to standard Cairo space
CGContextTranslateCTM(context, 0.0, -sizePt.height);
CGContextTranslateCTM(context, 0.0, sizePx.height * scale);
CGContextScaleCTM(context, scale, -scale);

drawView->surface = cairo_quartz_surface_create_for_cg_context(


+ 19
- 18
source/modules/dgl/src/pugl-upstream/src/mac_gl.m View File

@@ -11,6 +11,14 @@
# define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core
#endif

static void
ensureHint(PuglView* const view, const PuglViewHint hint, const int value)
{
if (view->hints[hint] == PUGL_DONT_CARE) {
view->hints[hint] = value;
}
}

@interface PuglOpenGLView : NSOpenGLView
@end

@@ -21,7 +29,9 @@

- (id)initWithFrame:(NSRect)frame
{
const bool compat = puglview->hints[PUGL_USE_COMPAT_PROFILE];
const bool compat =
puglview->hints[PUGL_CONTEXT_PROFILE] == PUGL_OPENGL_COMPATIBILITY_PROFILE;

const unsigned samples = (unsigned)puglview->hints[PUGL_SAMPLES];
const int major = puglview->hints[PUGL_CONTEXT_VERSION_MAJOR];
const unsigned profile =
@@ -31,21 +41,12 @@

// Set attributes to default if they are unset
// (There is no GLX_DONT_CARE equivalent on MacOS)
if (puglview->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) {
puglview->hints[PUGL_DEPTH_BITS] = 0;
}
if (puglview->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) {
puglview->hints[PUGL_STENCIL_BITS] = 0;
}
if (puglview->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) {
puglview->hints[PUGL_SAMPLES] = 1;
}
if (puglview->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) {
puglview->hints[PUGL_DOUBLE_BUFFER] = 1;
}
if (puglview->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) {
puglview->hints[PUGL_SWAP_INTERVAL] = 1;
}
ensureHint(puglview, PUGL_DEPTH_BITS, 0);
ensureHint(puglview, PUGL_STENCIL_BITS, 0);
ensureHint(puglview, PUGL_SAMPLES, 1);
ensureHint(puglview, PUGL_SAMPLE_BUFFERS, puglview->hints[PUGL_SAMPLES] > 0);
ensureHint(puglview, PUGL_DOUBLE_BUFFER, 1);
ensureHint(puglview, PUGL_SWAP_INTERVAL, 1);

const unsigned colorSize = (unsigned)(puglview->hints[PUGL_RED_BITS] +
puglview->hints[PUGL_BLUE_BITS] +
@@ -60,8 +61,8 @@
NSOpenGLPFAColorSize, colorSize,
NSOpenGLPFADepthSize, (unsigned)puglview->hints[PUGL_DEPTH_BITS],
NSOpenGLPFAStencilSize, (unsigned)puglview->hints[PUGL_STENCIL_BITS],
NSOpenGLPFAMultisample, samples ? 1u : 0u,
NSOpenGLPFASampleBuffers, samples ? 1u : 0u,
NSOpenGLPFAMultisample, samples ? 1U : 0U,
NSOpenGLPFASampleBuffers, samples ? 1U : 0U,
NSOpenGLPFASamples, samples,
0};
// clang-format on


+ 5
- 2
source/modules/dgl/src/pugl-upstream/src/mac_stub.m View File

@@ -41,8 +41,11 @@ puglMacStubCreate(PuglView* view)
PuglStubView* drawView = [PuglStubView alloc];

drawView->puglview = view;
[drawView
initWithFrame:NSMakeRect(0, 0, view->frame.width, view->frame.height)];
[drawView initWithFrame:NSMakeRect(0,
0,
view->lastConfigure.width,
view->lastConfigure.height)];

if (view->hints[PUGL_RESIZABLE]) {
[drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
} else {


+ 6
- 3
source/modules/dgl/src/pugl-upstream/src/mac_vulkan.m View File

@@ -75,7 +75,8 @@ puglMacVulkanCreate(PuglView* view)
{
PuglInternals* impl = view->impl;
PuglVulkanView* drawView = [PuglVulkanView alloc];
const NSRect rect = NSMakeRect(0, 0, view->frame.width, view->frame.height);
const NSRect rect =
NSMakeRect(0, 0, view->lastConfigure.width, view->lastConfigure.height);

drawView->puglview = view;
[drawView initWithFrame:rect];
@@ -107,7 +108,8 @@ struct PuglVulkanLoaderImpl {
};

PuglVulkanLoader*
puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world))
puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world),
const char* const libraryName)
{
PuglVulkanLoader* loader =
(PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader));
@@ -115,7 +117,8 @@ puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world))
return NULL;
}

if (!(loader->libvulkan = dlopen("libvulkan.dylib", RTLD_LAZY))) {
const char* const filename = libraryName ? libraryName : "libvulkan.dylib";
if (!(loader->libvulkan = dlopen(filename, RTLD_LAZY))) {
free(loader);
return NULL;
}


+ 15
- 0
source/modules/dgl/src/pugl-upstream/src/macros.h View File

@@ -0,0 +1,15 @@
// Copyright 2016-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef PUGL_SRC_MACROS_H
#define PUGL_SRC_MACROS_H

#ifndef MIN
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif

#ifndef MAX
# define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif

#endif // PUGL_SRC_MACROS_H

+ 2
- 0
source/modules/dgl/src/pugl-upstream/src/platform.h View File

@@ -13,6 +13,7 @@
PUGL_BEGIN_DECLS

/// Allocate and initialise world internals (implemented once per platform)
PUGL_MALLOC_FUNC
PuglWorldInternals*
puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags);

@@ -21,6 +22,7 @@ void
puglFreeWorldInternals(PuglWorld* world);

/// Allocate and initialise view internals (implemented once per platform)
PUGL_MALLOC_FUNC
PuglInternals*
puglInitViewInternals(PuglWorld* world);



+ 20
- 4
source/modules/dgl/src/pugl-upstream/src/types.h View File

@@ -21,6 +21,12 @@ typedef struct PuglInternalsImpl PuglInternals;
/// View hints
typedef int PuglHints[PUGL_NUM_VIEW_HINTS];

/// View position (both X and Y coordinates) or general point
typedef struct {
PuglCoord x;
PuglCoord y;
} PuglPoint;

/// View size (both X and Y coordinates)
typedef struct {
PuglSpan width;
@@ -33,6 +39,13 @@ typedef struct {
size_t len; ///< Length of data in bytes
} PuglBlob;

/// Stage of a view along its lifespan
typedef enum {
PUGL_VIEW_STAGE_ALLOCATED,
PUGL_VIEW_STAGE_REALIZED,
PUGL_VIEW_STAGE_CONFIGURED,
} PuglViewStage;

/// Cross-platform view definition
struct PuglViewImpl {
PuglWorld* world;
@@ -40,24 +53,27 @@ struct PuglViewImpl {
PuglInternals* impl;
PuglHandle handle;
PuglEventFunc eventFunc;
char* title;
PuglNativeView parent;
uintptr_t transientParent;
PuglRect frame;
PuglConfigureEvent lastConfigure;
PuglHints hints;
PuglViewSize sizeHints[PUGL_NUM_SIZE_HINTS];
bool visible;
char* strings[PUGL_NUM_STRING_HINTS];
int defaultX;
int defaultY;
PuglViewStage stage;
bool resizing;
};

/// Cross-platform world definition
struct PuglWorldImpl {
PuglWorldInternals* impl;
PuglWorldHandle handle;
char* className;
double startTime;
size_t numViews;
PuglView** views;
char* strings[PUGL_NUM_STRING_HINTS];
PuglWorldType type;
};

/// Opaque surface used by graphics backend


+ 0
- 734
source/modules/dgl/src/pugl-upstream/src/wasm.c View File

@@ -1,734 +0,0 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// Copyright 2021-2022 Filipe Coelho <falktx@falktx.com>
// SPDX-License-Identifier: ISC

#include "wasm.h"

#include "internal.h"

#include <stdio.h>

#include <emscripten/html5.h>

#ifdef __cplusplus
# define PUGL_INIT_STRUCT \
{}
#else
# define PUGL_INIT_STRUCT \
{ \
0 \
}
#endif

PuglWorldInternals*
puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags)
{
printf("DONE: %s %d\n", __func__, __LINE__);

PuglWorldInternals* impl =
(PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals));

impl->scaleFactor = emscripten_get_device_pixel_ratio();

return impl;
}

void*
puglGetNativeWorld(PuglWorld*)
{
printf("DONE: %s %d\n", __func__, __LINE__);
return NULL;
}

PuglInternals*
puglInitViewInternals(PuglWorld* const world)
{
printf("DONE: %s %d\n", __func__, __LINE__);
PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));

return impl;
}

static PuglStatus
puglDispatchEventWithContext(PuglView* const view, const PuglEvent* event)
{
PuglStatus st0 = PUGL_SUCCESS;
PuglStatus st1 = PUGL_SUCCESS;

if (!(st0 = view->backend->enter(view, NULL))) {
st0 = view->eventFunc(view, event);
st1 = view->backend->leave(view, NULL);
}

return st0 ? st0 : st1;
}

static PuglKey
keyCodeToSpecial(const unsigned long code, const unsigned long location)
{
switch (code) {
case 0x08: return PUGL_KEY_BACKSPACE;
case 0x1B: return PUGL_KEY_ESCAPE;
case 0x2E: return PUGL_KEY_DELETE;
case 0x70: return PUGL_KEY_F1;
case 0x71: return PUGL_KEY_F2;
case 0x72: return PUGL_KEY_F3;
case 0x73: return PUGL_KEY_F4;
case 0x74: return PUGL_KEY_F5;
case 0x75: return PUGL_KEY_F6;
case 0x76: return PUGL_KEY_F7;
case 0x77: return PUGL_KEY_F8;
case 0x78: return PUGL_KEY_F9;
case 0x79: return PUGL_KEY_F10;
case 0x7A: return PUGL_KEY_F11;
case 0x7B: return PUGL_KEY_F12;
case 0x25: return PUGL_KEY_LEFT;
case 0x26: return PUGL_KEY_UP;
case 0x27: return PUGL_KEY_RIGHT;
case 0x28: return PUGL_KEY_DOWN;
case 0x21: return PUGL_KEY_PAGE_UP;
case 0x22: return PUGL_KEY_PAGE_DOWN;
case 0x24: return PUGL_KEY_HOME;
case 0x23: return PUGL_KEY_END;
case 0x2D: return PUGL_KEY_INSERT;
case 0x10: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SHIFT_R : PUGL_KEY_SHIFT_L;
case 0x11: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_CTRL_R : PUGL_KEY_CTRL_L;
case 0x12: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_ALT_R : PUGL_KEY_ALT_L;
case 0xE0: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SUPER_R : PUGL_KEY_SUPER_L;
case 0x5D: return PUGL_KEY_MENU;
case 0x14: return PUGL_KEY_CAPS_LOCK;
case 0x91: return PUGL_KEY_SCROLL_LOCK;
case 0x90: return PUGL_KEY_NUM_LOCK;
case 0x2C: return PUGL_KEY_PRINT_SCREEN;
case 0x13: return PUGL_KEY_PAUSE;
case '\r': return (PuglKey)'\r';
default: break;
}

return (PuglKey)0;
}

static PuglMods
translateModifiers(const EM_BOOL ctrlKey,
const EM_BOOL shiftKey,
const EM_BOOL altKey,
const EM_BOOL metaKey)
{
return (ctrlKey ? PUGL_MOD_CTRL : 0u) |
(shiftKey ? PUGL_MOD_SHIFT : 0u) |
(altKey ? PUGL_MOD_ALT : 0u) |
(metaKey ? PUGL_MOD_SUPER : 0u);
}

static bool
decodeCharacterString(const unsigned long keyCode,
const EM_UTF8 key[EM_HTML5_SHORT_STRING_LEN_BYTES],
char str[8])
{
if (key[1] == 0)
{
str[0] = key[0];
return true;
}

return false;
}

static EM_BOOL
puglKeyCallback(const int eventType, const EmscriptenKeyboardEvent* const keyEvent, void* const userData)
{
PuglView* const view = (PuglView*)userData;

if (!view->visible) {
return EM_FALSE;
}

if (keyEvent->repeat && view->hints[PUGL_IGNORE_KEY_REPEAT])
return EM_TRUE;

PuglStatus st0 = PUGL_SUCCESS;
PuglStatus st1 = PUGL_SUCCESS;

const uint state = translateModifiers(keyEvent->ctrlKey,
keyEvent->shiftKey,
keyEvent->altKey,
keyEvent->metaKey);

const PuglKey special = keyCodeToSpecial(keyEvent->keyCode, keyEvent->location);

uint key = keyEvent->keyCode;
if (key >= 'A' && key <= 'Z' && !keyEvent->shiftKey)
key += 'a' - 'A';

PuglEvent event = {{PUGL_NOTHING, 0}};
event.key.type = eventType == EMSCRIPTEN_EVENT_KEYDOWN ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
event.key.time = keyEvent->timestamp / 1000;
// event.key.x = xevent.xkey.x;
// event.key.y = xevent.xkey.y;
// event.key.xRoot = xevent.xkey.x_root;
// event.key.yRoot = xevent.xkey.y_root;
event.key.key = special ? special : key;
event.key.keycode = keyEvent->keyCode;
event.key.state = state;
st0 = puglDispatchEventWithContext(view, &event);

d_debug("key event \n"
"\tdown: %d\n"
"\trepeat: %d\n"
"\tlocation: %d\n"
"\tstate: 0x%x\n"
"\tkey[]: '%s'\n"
"\tcode[]: '%s'\n"
"\tlocale[]: '%s'\n"
"\tkeyCode: 0x%lx:'%c' [deprecated, use key]\n"
"\twhich: 0x%lx:'%c' [deprecated, use key, same as keycode?]\n"
"\tspecial: 0x%x",
eventType == EMSCRIPTEN_EVENT_KEYDOWN,
keyEvent->repeat,
keyEvent->location,
state,
keyEvent->key,
keyEvent->code,
keyEvent->locale,
keyEvent->keyCode, keyEvent->keyCode >= ' ' && keyEvent->keyCode <= '~' ? keyEvent->keyCode : 0,
keyEvent->which, keyEvent->which >= ' ' && keyEvent->which <= '~' ? keyEvent->which : 0,
special);

if (event.type == PUGL_KEY_PRESS && !special && !(keyEvent->ctrlKey|keyEvent->altKey|keyEvent->metaKey)) {
char str[8] = PUGL_INIT_STRUCT;

if (decodeCharacterString(keyEvent->keyCode, keyEvent->key, str)) {
d_debug("resulting string is '%s'", str);

event.text.type = PUGL_TEXT;
event.text.character = event.key.key;
memcpy(event.text.string, str, sizeof(event.text.string));
st1 = puglDispatchEventWithContext(view, &event);
}
}

return (st0 ? st0 : st1) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE;
}

static EM_BOOL
puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEvent, void* const userData)
{
PuglView* const view = (PuglView*)userData;

if (!view->visible) {
return EM_FALSE;
}

PuglEvent event = {{PUGL_NOTHING, 0}};

const double time = mouseEvent->timestamp / 1000;
const PuglMods state = translateModifiers(mouseEvent->ctrlKey,
mouseEvent->shiftKey,
mouseEvent->altKey,
mouseEvent->metaKey);

const double scaleFactor = view->world->impl->scaleFactor;

switch (eventType) {
case EMSCRIPTEN_EVENT_MOUSEDOWN:
case EMSCRIPTEN_EVENT_MOUSEUP:
event.button.type = eventType == EMSCRIPTEN_EVENT_MOUSEDOWN ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE;
event.button.time = time;
event.button.x = mouseEvent->targetX * scaleFactor;
event.button.y = mouseEvent->targetY * scaleFactor;
event.button.xRoot = mouseEvent->screenX * scaleFactor;
event.button.yRoot = mouseEvent->screenY * scaleFactor;
event.button.state = state;
switch (mouseEvent->button) {
case 1:
event.button.button = 2;
break;
case 2:
event.button.button = 1;
break;
default:
event.button.button = mouseEvent->button;
break;
}
break;
case EMSCRIPTEN_EVENT_MOUSEMOVE:
event.motion.type = PUGL_MOTION;
event.motion.time = time;
if (view->impl->lastX == mouseEvent->targetX && view->impl->lastY == mouseEvent->targetY) {
// adjust local values for delta
const double movementX = mouseEvent->movementX * scaleFactor;
const double movementY = mouseEvent->movementY * scaleFactor;
view->impl->lockedX += movementX;
view->impl->lockedY += movementY;
view->impl->lockedRootX += movementX;
view->impl->lockedRootY += movementY;
// now set x, y, xRoot and yRoot
event.motion.x = view->impl->lockedX;
event.motion.y = view->impl->lockedY;
event.motion.xRoot = view->impl->lockedRootX;
event.motion.yRoot = view->impl->lockedRootY;
} else {
// cache unmodified value first, for pointer lock detection
view->impl->lastX = mouseEvent->targetX;
view->impl->lastY = mouseEvent->targetY;
// now set x, y, xRoot and yRoot
view->impl->lockedX = event.motion.x = mouseEvent->targetX * scaleFactor;
view->impl->lockedY = event.motion.y = mouseEvent->targetY * scaleFactor;
view->impl->lockedRootX = event.motion.xRoot = mouseEvent->screenX * scaleFactor;
view->impl->lockedRootY = event.motion.yRoot = mouseEvent->screenY * scaleFactor;
}
event.motion.state = state;
break;
case EMSCRIPTEN_EVENT_MOUSEENTER:
case EMSCRIPTEN_EVENT_MOUSELEAVE:
event.crossing.type = eventType == EMSCRIPTEN_EVENT_MOUSEENTER ? PUGL_POINTER_IN : PUGL_POINTER_OUT;
event.crossing.time = time;
event.crossing.x = mouseEvent->targetX * scaleFactor;
event.crossing.y = mouseEvent->targetY * scaleFactor;
event.crossing.xRoot = mouseEvent->screenX * scaleFactor;
event.crossing.yRoot = mouseEvent->screenY * scaleFactor;
event.crossing.state = state;
event.crossing.mode = PUGL_CROSSING_NORMAL;
break;
}

if (event.type == PUGL_NOTHING)
return EM_FALSE;

puglDispatchEventWithContext(view, &event);

// note: we must always return false, otherwise canvas never gets keyboard input
return EM_FALSE;
}

static EM_BOOL
puglFocusCallback(const int eventType, const EmscriptenFocusEvent* /*const focusEvent*/, void* const userData)
{
PuglView* const view = (PuglView*)userData;

if (!view->visible) {
return EM_FALSE;
}

PuglEvent event = {{eventType == EMSCRIPTEN_EVENT_FOCUSIN ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT, 0}};
event.focus.mode = PUGL_CROSSING_NORMAL;

puglDispatchEventWithContext(view, &event);

// note: we must always return false, otherwise canvas never gets proper focus
return EM_FALSE;
}

static EM_BOOL
puglPointerLockChangeCallback(const int eventType, const EmscriptenPointerlockChangeEvent* event, void* const userData)
{
PuglView* const view = (PuglView*)userData;

printf("puglPointerLockChangeCallback %d\n", event->isActive);
view->impl->pointerLocked = event->isActive;
return EM_TRUE;
}

static EM_BOOL
puglWheelCallback(const int eventType, const EmscriptenWheelEvent* const wheelEvent, void* const userData)
{
PuglView* const view = (PuglView*)userData;

if (!view->visible) {
return EM_FALSE;
}

const double scaleFactor = view->world->impl->scaleFactor;

PuglEvent event = {{PUGL_SCROLL, 0}};
event.scroll.time = wheelEvent->mouse.timestamp / 1000;
event.scroll.x = wheelEvent->mouse.targetX;
event.scroll.y = wheelEvent->mouse.targetY;
event.scroll.xRoot = wheelEvent->mouse.screenX;
event.scroll.yRoot = wheelEvent->mouse.screenY;
event.scroll.state = translateModifiers(wheelEvent->mouse.ctrlKey,
wheelEvent->mouse.shiftKey,
wheelEvent->mouse.altKey,
wheelEvent->mouse.metaKey);
event.scroll.direction = PUGL_SCROLL_SMOOTH;
// FIXME handle wheelEvent->deltaMode
event.scroll.dx = wheelEvent->deltaX * 0.01 * scaleFactor;
event.scroll.dy = -wheelEvent->deltaY * 0.01 * scaleFactor;

return puglDispatchEventWithContext(view, &event) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE;
}

static EM_BOOL
puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void* const userData)
{
PuglView* const view = (PuglView*)userData;

// FIXME
const int width = EM_ASM_INT({ return canvas.parentElement.clientWidth; });
const int height = EM_ASM_INT({ return canvas.parentElement.clientHeight; });

if (!width || !height)
return EM_FALSE;

const double scaleFactor = view->world->impl->scaleFactor = emscripten_get_device_pixel_ratio();

emscripten_set_canvas_element_size(view->world->className, width * scaleFactor, height * scaleFactor);

PuglEvent event = {{PUGL_CONFIGURE, 0}};
event.configure.x = view->frame.x;
event.configure.y = view->frame.y;
event.configure.width = width * scaleFactor;
event.configure.height = height * scaleFactor;
puglDispatchEvent(view, &event);
return EM_TRUE;
}

PuglStatus
puglRealize(PuglView* const view)
{
printf("TODO: %s %d\n", __func__, __LINE__);
PuglStatus st = PUGL_SUCCESS;

// Ensure that we do not have a parent
if (view->parent) {
printf("TODO: %s %d\n", __func__, __LINE__);
return PUGL_FAILURE;
}

if (!view->backend || !view->backend->configure) {
printf("TODO: %s %d\n", __func__, __LINE__);
return PUGL_BAD_BACKEND;
}

const char* const className = view->world->className;
d_stdout("className is %s", className);

// Set the size to the default if it has not already been set
if (view->frame.width <= 0.0 && view->frame.height <= 0.0) {
PuglViewSize defaultSize = view->sizeHints[PUGL_DEFAULT_SIZE];
if (!defaultSize.width || !defaultSize.height) {
return PUGL_BAD_CONFIGURATION;
}

view->frame.width = defaultSize.width;
view->frame.height = defaultSize.height;
}

// Configure and create the backend
if ((st = view->backend->configure(view)) || (st = view->backend->create(view))) {
view->backend->destroy(view);
return st;
}

if (view->title) {
puglSetWindowTitle(view, view->title);
}

puglDispatchSimpleEvent(view, PUGL_CREATE);

PuglEvent event = {{PUGL_CONFIGURE, 0}};
event.configure.x = view->frame.x;
event.configure.y = view->frame.y;
event.configure.width = view->frame.width;
event.configure.height = view->frame.height;
puglDispatchEvent(view, &event);

emscripten_set_canvas_element_size(className, view->frame.width, view->frame.height);
// emscripten_set_keypress_callback(className, view, false, puglKeyCallback);
emscripten_set_keydown_callback(className, view, false, puglKeyCallback);
emscripten_set_keyup_callback(className, view, false, puglKeyCallback);
emscripten_set_mousedown_callback(className, view, false, puglMouseCallback);
emscripten_set_mouseup_callback(className, view, false, puglMouseCallback);
emscripten_set_mousemove_callback(className, view, false, puglMouseCallback);
emscripten_set_mouseenter_callback(className, view, false, puglMouseCallback);
emscripten_set_mouseleave_callback(className, view, false, puglMouseCallback);
emscripten_set_focusin_callback(className, view, false, puglFocusCallback);
emscripten_set_focusout_callback(className, view, false, puglFocusCallback);
emscripten_set_pointerlockchange_callback(className, view, false, puglPointerLockChangeCallback);
emscripten_set_wheel_callback(className, view, false, puglWheelCallback);
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglUiCallback);
view->impl->pointerLocked = true;

printf("TODO: %s %d\n", __func__, __LINE__);
return PUGL_SUCCESS;
}

PuglStatus
puglShow(PuglView* const view)
{
printf("TODO: %s %d\n", __func__, __LINE__);
view->visible = true;
return puglPostRedisplay(view);
}

PuglStatus
puglHide(PuglView* const view)
{
printf("TODO: %s %d\n", __func__, __LINE__);
view->visible = false;
return PUGL_FAILURE;
}

void
puglFreeViewInternals(PuglView* const view)
{
printf("DONE: %s %d\n", __func__, __LINE__);
if (view && view->impl) {
if (view->backend) {
view->backend->destroy(view);
}
free(view->impl->timers);
free(view->impl);
}
}

void
puglFreeWorldInternals(PuglWorld* const world)
{
printf("DONE: %s %d\n", __func__, __LINE__);
free(world->impl);
}

PuglStatus
puglGrabFocus(PuglView*)
{
return PUGL_FAILURE;
}

PuglStatus
puglAcceptOffer(PuglView* const view,
const PuglDataOfferEvent* const offer,
const uint32_t typeIndex)
{
printf("TODO: %s %d\n", __func__, __LINE__);
return PUGL_FAILURE;
}

PuglStatus
puglPaste(PuglView* const view)
{
printf("TODO: %s %d\n", __func__, __LINE__);
return PUGL_FAILURE;
}

uint32_t
puglGetNumClipboardTypes(const PuglView* const view)
{
printf("TODO: %s %d\n", __func__, __LINE__);
return 0;
}

const char*
puglGetClipboardType(const PuglView* const view, const uint32_t typeIndex)
{
printf("TODO: %s %d\n", __func__, __LINE__);
return NULL;
}

double
puglGetScaleFactor(const PuglView* const view)
{
printf("DONE: %s %d\n", __func__, __LINE__);
return view->world->impl->scaleFactor;
}

double
puglGetTime(const PuglWorld*)
{
// d_stdout("DONE %s %d", __func__, __LINE__);
return emscripten_get_now() / 1000;
}

PuglStatus
puglUpdate(PuglWorld* const world, const double timeout)
{
// printf("TODO: %s %d\n", __func__, __LINE__);

for (size_t i = 0; i < world->numViews; ++i) {
PuglView* const view = world->views[i];

if (view->visible) {
puglDispatchSimpleEvent(view, PUGL_UPDATE);
}

if (!view->impl->needsRepaint) {
continue;
}

view->impl->needsRepaint = false;

PuglEvent event = {{PUGL_EXPOSE, 0}};
event.expose.x = view->frame.x;
event.expose.y = view->frame.y;
event.expose.width = view->frame.width;
event.expose.height = view->frame.height;
puglDispatchEvent(view, &event);

static bool p = true;
if (p) {
p = false;
d_stdout("drawing at %d %d %u %u", (int)view->frame.x, (int)view->frame.y,
(uint)view->frame.width, (uint)view->frame.height);
}
}

return PUGL_SUCCESS;
}

PuglStatus
puglPostRedisplay(PuglView* const view)
{
// printf("TODO: %s %d\n", __func__, __LINE__);
view->impl->needsRepaint = true;
return PUGL_SUCCESS;
}

PuglStatus
puglPostRedisplayRect(PuglView* const view, const PuglRect rect)
{
// printf("TODO: %s %d\n", __func__, __LINE__);
view->impl->needsRepaint = true;
return PUGL_FAILURE;
}

PuglNativeView
puglGetNativeView(PuglView* const view)
{
printf("TODO: %s %d\n", __func__, __LINE__);
return 0;
}

PuglStatus
puglSetWindowTitle(PuglView* const view, const char* const title)
{
printf("DONE: %s %d\n", __func__, __LINE__);
puglSetString(&view->title, title);
emscripten_set_window_title(title);
return PUGL_SUCCESS;
}

PuglStatus
puglSetSizeHint(PuglView* const view,
const PuglSizeHint hint,
const PuglSpan width,
const PuglSpan height)
{
printf("DONE: %s %d\n", __func__, __LINE__);
view->sizeHints[hint].width = width;
view->sizeHints[hint].height = height;
return PUGL_SUCCESS;
}

static EM_BOOL
puglTimerLoopCallback(double timeout, void* const arg)
{
PuglTimer* const timer = (PuglTimer*)arg;
PuglInternals* const impl = timer->view->impl;

// only handle active timers
for (uint32_t i=0; i<impl->numTimers; ++i)
{
if (impl->timers[i].id == timer->id)
{
PuglEvent event = {{PUGL_TIMER, 0}};
event.timer.id = timer->id;
puglDispatchEventWithContext(timer->view, &event);
return EM_TRUE;
}
}

return EM_FALSE;

// unused
(void)timeout;
}

PuglStatus
puglStartTimer(PuglView* const view, const uintptr_t id, const double timeout)
{
printf("DONE: %s %d\n", __func__, __LINE__);
PuglInternals* const impl = view->impl;
const uint32_t timerIndex = impl->numTimers++;

if (impl->timers == NULL)
impl->timers = (PuglTimer*)malloc(sizeof(PuglTimer));
else
impl->timers = (PuglTimer*)realloc(impl->timers, sizeof(PuglTimer) * timerIndex);

PuglTimer* const timer = &impl->timers[timerIndex];
timer->view = view;
timer->id = id;

emscripten_set_timeout_loop(puglTimerLoopCallback, timeout * 1000, timer);
return PUGL_SUCCESS;
}

PuglStatus
puglStopTimer(PuglView* const view, const uintptr_t id)
{
printf("DONE: %s %d\n", __func__, __LINE__);
PuglInternals* const impl = view->impl;

if (impl->timers == NULL || impl->numTimers == 0)
return PUGL_FAILURE;

for (uint32_t i=0; i<impl->numTimers; ++i)
{
if (impl->timers[i].id == id)
{
memmove(impl->timers + i, impl->timers + (i + 1), sizeof(PuglTimer) * (impl->numTimers - 1));
--impl->numTimers;
return PUGL_SUCCESS;
}
}

return PUGL_FAILURE;
}

const void*
puglGetClipboard(PuglView* const view,
const uint32_t typeIndex,
size_t* const len)
{
printf("TODO: %s %d\n", __func__, __LINE__);
return NULL;
}

PuglStatus
puglSetClipboard(PuglView* const view,
const char* const type,
const void* const data,
const size_t len)
{
printf("TODO: %s %d\n", __func__, __LINE__);
return PUGL_FAILURE;
}

PuglStatus
puglSetCursor(PuglView* const view, const PuglCursor cursor)
{
printf("TODO: %s %d\n", __func__, __LINE__);
return PUGL_FAILURE;
}

PuglStatus
puglSetTransientParent(PuglView* const view, const PuglNativeView parent)
{
printf("TODO: %s %d\n", __func__, __LINE__);
view->transientParent = parent;
return PUGL_FAILURE;
}

PuglStatus
puglSetPosition(PuglView* const view, const int x, const int y)
{
printf("TODO: %s %d\n", __func__, __LINE__);

if (x > INT16_MAX || y > INT16_MAX) {
return PUGL_BAD_PARAMETER;
}

view->frame.x = (PuglCoord)x;
view->frame.y = (PuglCoord)y;
return PUGL_FAILURE;
}

+ 0
- 33
source/modules/dgl/src/pugl-upstream/src/wasm.h View File

@@ -1,33 +0,0 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// Copyright 2021-2022 Filipe Coelho <falktx@falktx.com>
// SPDX-License-Identifier: ISC

#ifndef PUGL_SRC_WASM_H
#define PUGL_SRC_WASM_H

// #include "attributes.h"
#include "types.h"

#include "pugl/pugl.h"

struct PuglTimer {
PuglView* view;
uintptr_t id;
};

struct PuglWorldInternalsImpl {
double scaleFactor;
};

struct PuglInternalsImpl {
PuglSurface* surface;
bool needsRepaint;
bool pointerLocked;
uint32_t numTimers;
long lastX, lastY;
double lockedX, lockedY;
double lockedRootX, lockedRootY;
struct PuglTimer* timers;
};

#endif // PUGL_SRC_WASM_H

+ 0
- 255
source/modules/dgl/src/pugl-upstream/src/wasm_gl.c View File

@@ -1,255 +0,0 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// Copyright 2021-2022 Filipe Coelho <falktx@falktx.com>
// SPDX-License-Identifier: ISC

// #include "attributes.h"
#include "stub.h"
// #include "types.h"
#include "wasm.h"

// #include "pugl/gl.h"
#include "pugl/pugl.h"

#include <stdio.h>
#include <stdlib.h>

#include <EGL/egl.h>

typedef struct {
EGLDisplay display;
EGLConfig config;
EGLContext context;
EGLSurface surface;
} PuglWasmGlSurface;

static EGLint
puglWasmGlHintValue(const int value)
{
return value == PUGL_DONT_CARE ? EGL_DONT_CARE : value;
}

static int
puglWasmGlGetAttrib(const EGLDisplay display,
const EGLConfig config,
const EGLint attrib)
{
EGLint value = 0;
eglGetConfigAttrib(display, config, attrib, &value);
return value;
}

static PuglStatus
puglWasmGlConfigure(PuglView* view)
{
PuglInternals* const impl = view->impl;
// const int screen = impl->screen;
// Display* const display = view->world->impl->display;

printf("TODO: %s %d | start\n", __func__, __LINE__);

const EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

if (display == EGL_NO_DISPLAY) {
printf("eglGetDisplay Failed\n");
return PUGL_CREATE_CONTEXT_FAILED;
}

int major, minor;
if (eglInitialize(display, &major, &minor) != EGL_TRUE) {
printf("eglInitialize Failed\n");
return PUGL_CREATE_CONTEXT_FAILED;
}

EGLConfig config;
int numConfigs;

if (eglGetConfigs(display, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) {
printf("eglGetConfigs Failed\n");
eglTerminate(display);
return PUGL_CREATE_CONTEXT_FAILED;
}

// clang-format off
const EGLint attrs[] = {
// GLX_X_RENDERABLE, True,
// GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
// GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
// GLX_RENDER_TYPE, GLX_RGBA_BIT,

// GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]),

EGL_SAMPLES, puglWasmGlHintValue(view->hints[PUGL_SAMPLES]),
EGL_RED_SIZE, puglWasmGlHintValue(view->hints[PUGL_RED_BITS]),
EGL_GREEN_SIZE, puglWasmGlHintValue(view->hints[PUGL_GREEN_BITS]),
EGL_BLUE_SIZE, puglWasmGlHintValue(view->hints[PUGL_BLUE_BITS]),
EGL_ALPHA_SIZE, puglWasmGlHintValue(view->hints[PUGL_ALPHA_BITS]),
EGL_DEPTH_SIZE, puglWasmGlHintValue(view->hints[PUGL_DEPTH_BITS]),
EGL_STENCIL_SIZE, puglWasmGlHintValue(view->hints[PUGL_STENCIL_BITS]),
EGL_NONE
};
// clang-format on

if (eglChooseConfig(display, attrs, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) {
printf("eglChooseConfig Failed\n");
eglTerminate(display);
return PUGL_CREATE_CONTEXT_FAILED;
}

PuglWasmGlSurface* const surface =
(PuglWasmGlSurface*)calloc(1, sizeof(PuglWasmGlSurface));
impl->surface = surface;

surface->display = display;
surface->config = config;
surface->context = EGL_NO_SURFACE;
surface->surface = EGL_NO_CONTEXT;

view->hints[PUGL_RED_BITS] =
puglWasmGlGetAttrib(display, config, EGL_RED_SIZE);
view->hints[PUGL_GREEN_BITS] =
puglWasmGlGetAttrib(display, config, EGL_GREEN_SIZE);
view->hints[PUGL_BLUE_BITS] =
puglWasmGlGetAttrib(display, config, EGL_BLUE_SIZE);
view->hints[PUGL_ALPHA_BITS] =
puglWasmGlGetAttrib(display, config, EGL_ALPHA_SIZE);
view->hints[PUGL_DEPTH_BITS] =
puglWasmGlGetAttrib(display, config, EGL_DEPTH_SIZE);
view->hints[PUGL_STENCIL_BITS] =
puglWasmGlGetAttrib(display, config, EGL_STENCIL_SIZE);
view->hints[PUGL_SAMPLES] =
puglWasmGlGetAttrib(display, config, EGL_SAMPLES);

// always enabled for EGL
view->hints[PUGL_DOUBLE_BUFFER] = 1;

printf("TODO: %s %d | ok\n", __func__, __LINE__);

return PUGL_SUCCESS;
}

PUGL_WARN_UNUSED_RESULT
static PuglStatus
puglWasmGlEnter(PuglView* view, const PuglExposeEvent* PUGL_UNUSED(expose))
{
// printf("DONE: %s %d\n", __func__, __LINE__);
PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface;
if (!surface || !surface->context || !surface->surface) {
return PUGL_FAILURE;
}

// TESTING: is it faster if we never unset context?
return PUGL_SUCCESS;

return eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context) ? PUGL_SUCCESS : PUGL_FAILURE;
}

PUGL_WARN_UNUSED_RESULT
static PuglStatus
puglWasmGlLeave(PuglView* view, const PuglExposeEvent* expose)
{
// printf("DONE: %s %d\n", __func__, __LINE__);
PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface;

if (expose) { // note: swap buffers always enabled for EGL
eglSwapBuffers(surface->display, surface->surface);
}

// TESTING: is it faster if we never unset context?
return PUGL_SUCCESS;

return eglMakeCurrent(surface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) ? PUGL_SUCCESS : PUGL_FAILURE;
}

static PuglStatus
puglWasmGlCreate(PuglView* view)
{
printf("TODO: %s %d | start\n", __func__, __LINE__);
PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface;
const EGLDisplay display = surface->display;
const EGLConfig config = surface->config;

const EGLint attrs[] = {
EGL_CONTEXT_CLIENT_VERSION,
view->hints[PUGL_CONTEXT_VERSION_MAJOR],

EGL_CONTEXT_MAJOR_VERSION,
view->hints[PUGL_CONTEXT_VERSION_MAJOR],

/*
EGL_CONTEXT_MINOR_VERSION,
view->hints[PUGL_CONTEXT_VERSION_MINOR],

EGL_CONTEXT_OPENGL_DEBUG,
(view->hints[PUGL_USE_DEBUG_CONTEXT] ? EGL_TRUE : EGL_FALSE),

EGL_CONTEXT_OPENGL_PROFILE_MASK,
(view->hints[PUGL_USE_COMPAT_PROFILE]
? EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT
: EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT),
*/

EGL_NONE
};

surface->context = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);

if (surface->context == EGL_NO_CONTEXT) {
printf("eglCreateContext Failed\n");
return PUGL_CREATE_CONTEXT_FAILED;
}

#if 0
eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context);

printf("GL_VENDOR=%s\n", glGetString(GL_VENDOR));
printf("GL_RENDERER=%s\n", glGetString(GL_RENDERER));
printf("GL_VERSION=%s\n", glGetString(GL_VERSION));

eglMakeCurrent(surface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
#endif

surface->surface = eglCreateWindowSurface(display, config, 0, NULL);

if (surface->surface == EGL_NO_SURFACE) {
printf("eglCreateWindowSurface Failed\n");
return PUGL_CREATE_CONTEXT_FAILED;
}

printf("TODO: %s %d | ok\n", __func__, __LINE__);

// TESTING: is it faster if we never unset context?
eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context);

return PUGL_SUCCESS;
}

static void
puglWasmGlDestroy(PuglView* view)
{
printf("DONE: %s %d\n", __func__, __LINE__);
PuglWasmGlSurface* surface = (PuglWasmGlSurface*)view->impl->surface;
if (surface) {
const EGLDisplay display = surface->display;
if (surface->surface != EGL_NO_SURFACE)
eglDestroySurface(display, surface->surface);
if (surface->context != EGL_NO_CONTEXT)
eglDestroyContext(display, surface->context);
eglTerminate(display);
free(surface);
view->impl->surface = NULL;
}
}

const PuglBackend*
puglGlBackend(void)
{
printf("DONE: %s %d\n", __func__, __LINE__);
static const PuglBackend backend = {puglWasmGlConfigure,
puglWasmGlCreate,
puglWasmGlDestroy,
puglWasmGlEnter,
puglWasmGlLeave,
puglStubGetContext};

return &backend;
}

+ 0
- 26
source/modules/dgl/src/pugl-upstream/src/wasm_stub.c View File

@@ -1,26 +0,0 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// Copyright 2021-2022 Filipe Coelho <falktx@falktx.com>
// SPDX-License-Identifier: ISC

#include "pugl/stub.h"

#include "stub.h"
// #include "types.h"
// #include "wasm.h"

#include "pugl/pugl.h"

const PuglBackend*
puglStubBackend(void)
{
static const PuglBackend backend = {
puglStubConfigure,
puglStubCreate,
puglStubDestroy,
puglStubEnter,
puglStubLeave,
puglStubGetContext,
};

return &backend;
}

+ 481
- 249
source/modules/dgl/src/pugl-upstream/src/win.c
File diff suppressed because it is too large
View File


+ 16
- 10
source/modules/dgl/src/pugl-upstream/src/win.h View File

@@ -19,16 +19,22 @@ struct PuglWorldInternalsImpl {
};

struct PuglInternalsImpl {
PuglWinPFD pfd;
int pfId;
HWND hwnd;
HCURSOR cursor;
HDC hdc;
PuglBlob clipboard;
PuglSurface* surface;
double scaleFactor;
bool flashing;
bool mouseTracked;
PuglWinPFD pfd;
int pfId;
HWND hwnd;
HCURSOR cursor;
HDC hdc;
WINDOWPLACEMENT oldPlacement;
PAINTSTRUCT paint;
PuglBlob clipboard;
PuglSurface* surface;
double scaleFactor;
bool mapped;
bool flashing;
bool mouseTracked;
bool minimized;
bool maximized;
bool fullscreen;
};

PUGL_API


+ 6
- 10
source/modules/dgl/src/pugl-upstream/src/win_cairo.c View File

@@ -27,7 +27,7 @@ puglWinCairoCreateDrawContext(PuglView* view)

surface->drawDc = CreateCompatibleDC(impl->hdc);
surface->drawBitmap = CreateCompatibleBitmap(
impl->hdc, (int)view->frame.width, (int)view->frame.height);
impl->hdc, (int)view->lastConfigure.width, (int)view->lastConfigure.height);

DeleteObject(SelectObject(surface->drawDc, surface->drawBitmap));

@@ -108,8 +108,7 @@ puglWinCairoEnter(PuglView* view, const PuglExposeEvent* expose)

if (expose && !(st = puglWinCairoCreateDrawContext(view)) &&
!(st = puglWinCairoOpen(view))) {
PAINTSTRUCT ps;
BeginPaint(view->impl->hwnd, &ps);
st = puglWinEnter(view, expose);
}

return st;
@@ -126,8 +125,8 @@ puglWinCairoLeave(PuglView* view, const PuglExposeEvent* expose)
BitBlt(impl->hdc,
0,
0,
(int)view->frame.width,
(int)view->frame.height,
(int)view->lastConfigure.width,
(int)view->lastConfigure.height,
surface->drawDc,
0,
0,
@@ -135,12 +134,9 @@ puglWinCairoLeave(PuglView* view, const PuglExposeEvent* expose)

puglWinCairoClose(view);
puglWinCairoDestroyDrawContext(view);

PAINTSTRUCT ps;
EndPaint(view->impl->hwnd, &ps);
}

return PUGL_SUCCESS;
return puglWinLeave(view, expose);
}

static void*
@@ -150,7 +146,7 @@ puglWinCairoGetContext(PuglView* view)
}

const PuglBackend*
puglCairoBackend()
puglCairoBackend(void)
{
static const PuglBackend backend = {puglWinCairoConfigure,
puglStubCreate,


+ 24
- 32
source/modules/dgl/src/pugl-upstream/src/win_gl.c View File

@@ -89,6 +89,14 @@ puglWinGlGetProcs(void)
return procs;
}

static void
ensureHint(PuglView* const view, const PuglViewHint hint, const int value)
{
if (view->hints[hint] == PUGL_DONT_CARE) {
view->hints[hint] = value;
}
}

static PuglStatus
puglWinGlConfigure(PuglView* view)
{
@@ -96,21 +104,12 @@ puglWinGlConfigure(PuglView* view)

// Set attributes to default if they are unset
// (There is no GLX_DONT_CARE equivalent on Windows)
if (view->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) {
view->hints[PUGL_DEPTH_BITS] = 0;
}
if (view->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) {
view->hints[PUGL_STENCIL_BITS] = 0;
}
if (view->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) {
view->hints[PUGL_SAMPLES] = 1;
}
if (view->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) {
view->hints[PUGL_DOUBLE_BUFFER] = 1;
}
if (view->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) {
view->hints[PUGL_SWAP_INTERVAL] = 1;
}
ensureHint(view, PUGL_DEPTH_BITS, 0);
ensureHint(view, PUGL_STENCIL_BITS, 0);
ensureHint(view, PUGL_SAMPLES, 0);
ensureHint(view, PUGL_SAMPLE_BUFFERS, view->hints[PUGL_SAMPLES] > 0);
ensureHint(view, PUGL_DOUBLE_BUFFER, 1);
ensureHint(view, PUGL_SWAP_INTERVAL, 1);

// clang-format off
const int pixelAttrs[] = {
@@ -119,7 +118,7 @@ puglWinGlConfigure(PuglView* view)
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_DOUBLE_BUFFER_ARB, view->hints[PUGL_DOUBLE_BUFFER],
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_SAMPLE_BUFFERS_ARB, view->hints[PUGL_SAMPLES] ? 1 : 0,
WGL_SAMPLE_BUFFERS_ARB, view->hints[PUGL_SAMPLE_BUFFERS],
WGL_SAMPLES_ARB, view->hints[PUGL_SAMPLES],
WGL_RED_BITS_ARB, view->hints[PUGL_RED_BITS],
WGL_GREEN_BITS_ARB, view->hints[PUGL_GREEN_BITS],
@@ -164,7 +163,7 @@ puglWinGlConfigure(PuglView* view)
// Choose pixel format based on attributes
UINT numFormats = 0;
if (!surface->procs.wglChoosePixelFormat(
fakeWin.hdc, pixelAttrs, NULL, 1u, &impl->pfId, &numFormats)) {
fakeWin.hdc, pixelAttrs, NULL, 1U, &impl->pfId, &numFormats)) {
return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED);
}

@@ -199,12 +198,12 @@ puglWinGlCreate(PuglView* view)
view->hints[PUGL_CONTEXT_VERSION_MINOR],

WGL_CONTEXT_FLAGS_ARB,
(view->hints[PUGL_USE_DEBUG_CONTEXT] ? WGL_CONTEXT_DEBUG_BIT_ARB : 0),
(view->hints[PUGL_CONTEXT_DEBUG] ? WGL_CONTEXT_DEBUG_BIT_ARB : 0),

WGL_CONTEXT_PROFILE_MASK_ARB,
(view->hints[PUGL_USE_COMPAT_PROFILE]
? WGL_CONTEXT_CORE_PROFILE_BIT_ARB
: WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB),
((view->hints[PUGL_CONTEXT_PROFILE] == PUGL_OPENGL_COMPATIBILITY_PROFILE)
? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
: WGL_CONTEXT_CORE_PROFILE_BIT_ARB),

0};

@@ -262,27 +261,20 @@ puglWinGlEnter(PuglView* view, const PuglExposeEvent* expose)
return PUGL_FAILURE;
}

const PuglStatus st = puglWinEnter(view, expose);
wglMakeCurrent(view->impl->hdc, surface->hglrc);

if (expose) {
PAINTSTRUCT ps;
BeginPaint(view->impl->hwnd, &ps);
}

return PUGL_SUCCESS;
return st;
}

static PuglStatus
puglWinGlLeave(PuglView* view, const PuglExposeEvent* expose)
{
if (expose) {
PAINTSTRUCT ps;
EndPaint(view->impl->hwnd, &ps);
SwapBuffers(view->impl->hdc);
}

wglMakeCurrent(NULL, NULL);
return PUGL_SUCCESS;
return puglWinLeave(view, expose);
}

PuglGlFunc
@@ -296,7 +288,7 @@ puglGetProcAddress(const char* name)

return func
? func
: (PuglGlFunc)GetProcAddress(GetModuleHandle("opengl32.dll"), name);
: (PuglGlFunc)GetProcAddress(GetModuleHandleA("opengl32.dll"), name);
}

PuglStatus


+ 6
- 3
source/modules/dgl/src/pugl-upstream/src/win_vulkan.c View File

@@ -21,7 +21,8 @@ struct PuglVulkanLoaderImpl {
};

PuglVulkanLoader*
puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world))
puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world),
const char* const libraryName)
{
PuglVulkanLoader* loader =
(PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader));
@@ -29,7 +30,9 @@ puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world))
return NULL;
}

if (!(loader->libvulkan = LoadLibrary("vulkan-1.dll"))) {
const char* const filename = libraryName ? libraryName : "vulkan-1.dll";
if (!(loader->libvulkan =
LoadLibraryExA(filename, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS))) {
free(loader);
return NULL;
}
@@ -65,7 +68,7 @@ puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader)
}

const PuglBackend*
puglVulkanBackend()
puglVulkanBackend(void)
{
static const PuglBackend backend = {puglWinConfigure,
puglStubCreate,


+ 743
- 339
source/modules/dgl/src/pugl-upstream/src/x11.c
File diff suppressed because it is too large
View File


+ 19
- 0
source/modules/dgl/src/pugl-upstream/src/x11.h View File

@@ -7,6 +7,7 @@
#include "attributes.h"
#include "types.h"

#include "pugl/attributes.h"
#include "pugl/pugl.h"

#include <X11/X.h>
@@ -20,13 +21,28 @@
typedef struct {
Atom CLIPBOARD;
Atom UTF8_STRING;
Atom WM_CLIENT_MACHINE;
Atom WM_PROTOCOLS;
Atom WM_DELETE_WINDOW;
Atom PUGL_CLIENT_MSG;
Atom NET_CLOSE_WINDOW;
Atom NET_FRAME_EXTENTS;
Atom NET_WM_NAME;
Atom NET_WM_PID;
Atom NET_WM_PING;
Atom NET_WM_STATE;
Atom NET_WM_STATE_ABOVE;
Atom NET_WM_STATE_BELOW;
Atom NET_WM_STATE_DEMANDS_ATTENTION;
Atom NET_WM_STATE_FULLSCREEN;
Atom NET_WM_STATE_HIDDEN;
Atom NET_WM_STATE_MAXIMIZED_HORZ;
Atom NET_WM_STATE_MAXIMIZED_VERT;
Atom NET_WM_STATE_MODAL;
Atom NET_WM_WINDOW_TYPE;
Atom NET_WM_WINDOW_TYPE_DIALOG;
Atom NET_WM_WINDOW_TYPE_NORMAL;
Atom NET_WM_WINDOW_TYPE_UTILITY;
Atom TARGETS;
Atom text_uri_list;
} PuglX11Atoms;
@@ -70,8 +86,11 @@ struct PuglInternalsImpl {
PuglEvent pendingConfigure;
PuglEvent pendingExpose;
PuglX11Clipboard clipboard;
long frameExtentLeft;
long frameExtentTop;
int screen;
const char* cursorName;
bool mapped;
};

PUGL_WARN_UNUSED_RESULT


+ 39
- 16
source/modules/dgl/src/pugl-upstream/src/x11_cairo.c View File

@@ -1,13 +1,13 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// Copyright 2012-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#include "macros.h"
#include "types.h"
#include "x11.h"

#include "pugl/cairo.h"
#include "pugl/pugl.h"

#include <X11/Xutil.h>
#include <cairo-xlib.h>
#include <cairo.h>

@@ -19,6 +19,24 @@ typedef struct {
cairo_t* cr;
} PuglX11CairoSurface;

static PuglViewSize
puglX11CairoGetViewSize(const PuglView* const view)
{
PuglViewSize size = {0U, 0U};

if (view->lastConfigure.type == PUGL_CONFIGURE) {
// Use the size of the last configured frame
size.width = view->lastConfigure.width;
size.height = view->lastConfigure.height;
} else {
// Use the default size
size.width = view->sizeHints[PUGL_DEFAULT_SIZE].width;
size.height = view->sizeHints[PUGL_DEFAULT_SIZE].height;
}

return size;
}

static void
puglX11CairoClose(PuglView* view)
{
@@ -31,22 +49,16 @@ puglX11CairoClose(PuglView* view)
}

static PuglStatus
puglX11CairoOpen(PuglView* view)
puglX11CairoOpen(PuglView* view, const PuglSpan width, const PuglSpan height)
{
PuglInternals* const impl = view->impl;
PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;

surface->back = cairo_xlib_surface_create(view->world->impl->display,
impl->win,
impl->vi->visual,
(int)view->frame.width,
(int)view->frame.height);
surface->back = cairo_xlib_surface_create(
view->world->impl->display, impl->win, impl->vi->visual, width, height);

surface->front =
cairo_surface_create_similar(surface->back,
cairo_surface_get_content(surface->back),
(int)view->frame.width,
(int)view->frame.height);
surface->front = cairo_surface_create_similar(
surface->back, cairo_surface_get_content(surface->back), width, height);

if (cairo_surface_status(surface->back) ||
cairo_surface_status(surface->front)) {
@@ -84,9 +96,20 @@ puglX11CairoEnter(PuglView* view, const PuglExposeEvent* expose)
PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
PuglStatus st = PUGL_SUCCESS;

if (expose && !(st = puglX11CairoOpen(view))) {
surface->cr = cairo_create(surface->front);
st = cairo_status(surface->cr) ? PUGL_CREATE_CONTEXT_FAILED : PUGL_SUCCESS;
if (expose) {
const PuglViewSize viewSize = puglX11CairoGetViewSize(view);
const PuglSpan right = (PuglSpan)(expose->x + expose->width);
const PuglSpan bottom = (PuglSpan)(expose->y + expose->height);
const PuglSpan surfaceWidth = MAX(right, viewSize.width);
const PuglSpan surfaceHeight = MAX(bottom, viewSize.height);
if (!(st = puglX11CairoOpen(view, surfaceWidth, surfaceHeight))) {
surface->cr = cairo_create(surface->front);
if (cairo_status(surface->cr)) {
cairo_destroy(surface->cr);
surface->cr = NULL;
st = PUGL_CREATE_CONTEXT_FAILED;
}
}
}

return st;


+ 21
- 16
source/modules/dgl/src/pugl-upstream/src/x11_gl.c View File

@@ -52,18 +52,19 @@ puglX11GlConfigure(PuglView* view)

// clang-format off
const int attrs[] = {
GLX_X_RENDERABLE, True,
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_SAMPLES, puglX11GlHintValue(view->hints[PUGL_SAMPLES]),
GLX_RED_SIZE, puglX11GlHintValue(view->hints[PUGL_RED_BITS]),
GLX_GREEN_SIZE, puglX11GlHintValue(view->hints[PUGL_GREEN_BITS]),
GLX_BLUE_SIZE, puglX11GlHintValue(view->hints[PUGL_BLUE_BITS]),
GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints[PUGL_ALPHA_BITS]),
GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints[PUGL_DEPTH_BITS]),
GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints[PUGL_STENCIL_BITS]),
GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]),
GLX_X_RENDERABLE, True,
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_SAMPLE_BUFFERS, puglX11GlHintValue(view->hints[PUGL_SAMPLE_BUFFERS]),
GLX_SAMPLES, puglX11GlHintValue(view->hints[PUGL_SAMPLES]),
GLX_RED_SIZE, puglX11GlHintValue(view->hints[PUGL_RED_BITS]),
GLX_GREEN_SIZE, puglX11GlHintValue(view->hints[PUGL_GREEN_BITS]),
GLX_BLUE_SIZE, puglX11GlHintValue(view->hints[PUGL_BLUE_BITS]),
GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints[PUGL_ALPHA_BITS]),
GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints[PUGL_DEPTH_BITS]),
GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints[PUGL_STENCIL_BITS]),
GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]),
None
};
// clang-format on
@@ -89,6 +90,8 @@ puglX11GlConfigure(PuglView* view)
puglX11GlGetAttrib(display, fbc[0], GLX_DEPTH_SIZE);
view->hints[PUGL_STENCIL_BITS] =
puglX11GlGetAttrib(display, fbc[0], GLX_STENCIL_SIZE);
view->hints[PUGL_SAMPLE_BUFFERS] =
puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLE_BUFFERS);
view->hints[PUGL_SAMPLES] = puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLES);
view->hints[PUGL_DOUBLE_BUFFER] =
puglX11GlGetAttrib(display, fbc[0], GLX_DOUBLEBUFFER);
@@ -142,12 +145,14 @@ puglX11GlCreate(PuglView* view)
view->hints[PUGL_CONTEXT_VERSION_MINOR],

GLX_CONTEXT_FLAGS_ARB,
(view->hints[PUGL_USE_DEBUG_CONTEXT] ? GLX_CONTEXT_DEBUG_BIT_ARB : 0),
(view->hints[PUGL_CONTEXT_DEBUG] ? GLX_CONTEXT_DEBUG_BIT_ARB : 0),

GLX_CONTEXT_PROFILE_MASK_ARB,
(view->hints[PUGL_USE_COMPAT_PROFILE]
? GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
: GLX_CONTEXT_CORE_PROFILE_BIT_ARB),
(view->hints[PUGL_CONTEXT_API] == PUGL_OPENGL_ES_API
? GLX_CONTEXT_ES2_PROFILE_BIT_EXT
: (view->hints[PUGL_CONTEXT_PROFILE] == PUGL_OPENGL_COMPATIBILITY_PROFILE
? GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
: GLX_CONTEXT_CORE_PROFILE_BIT_ARB)),
0};

const char* const extensions =


+ 12
- 3
source/modules/dgl/src/pugl-upstream/src/x11_vulkan.c View File

@@ -26,16 +26,25 @@ struct PuglVulkanLoaderImpl {
};

PuglVulkanLoader*
puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world))
puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world),
const char* const libraryName)
{
const char* const filename = libraryName ? libraryName : "libvulkan.so";
void* const libvulkan = dlopen(filename, RTLD_LAZY);
if (!libvulkan) {
return NULL;
}

PuglVulkanLoader* const loader =
(PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader));

if (!loader || !(loader->libvulkan = dlopen("libvulkan.so", RTLD_LAZY))) {
free(loader);
if (!loader) {
dlclose(libvulkan);
return NULL;
}

loader->libvulkan = libvulkan;

loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(
loader->libvulkan, "vkGetInstanceProcAddr");



+ 181
- 142
source/modules/dgl/src/pugl.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -37,16 +37,20 @@
#include <cstring>
#include <ctime>

#if defined(DISTRHO_OS_MAC)
#if defined(DISTRHO_OS_HAIKU)
# include <Application.h>
# include <Window.h>
# ifdef DGL_OPENGL
# include <GL/gl.h>
# include <opengl/GLView.h>
# endif
#elif defined(DISTRHO_OS_MAC)
# import <Cocoa/Cocoa.h>
# include <dlfcn.h>
# include <mach/mach_time.h>
# ifdef DGL_CAIRO
# include <cairo-quartz.h>
# endif
# ifdef DGL_OPENGL
# include <OpenGL/gl.h>
# endif
# ifdef DGL_VULKAN
# import <QuartzCore/CAMetalLayer.h>
# include <vulkan/vulkan_macos.h>
@@ -106,33 +110,56 @@
# 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

#ifndef DISTRHO_OS_MAC
START_NAMESPACE_DGL
#endif

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

#if defined(DISTRHO_OS_MAC)
#if defined(DISTRHO_OS_HAIKU)
# include "pugl-extra/haiku.cpp"
# include "pugl-extra/haiku_stub.cpp"
# ifdef DGL_OPENGL
# include "pugl-extra/haiku_gl.cpp"
# endif
#elif defined(DISTRHO_OS_MAC)
# ifndef DISTRHO_MACOS_NAMESPACE_MACRO
# define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, SEP, INTERFACE) NS ## SEP ## INTERFACE
# define DISTRHO_MACOS_NAMESPACE_MACRO(NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, _, INTERFACE)
# define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglCairoView)
# define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglOpenGLView)
# define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglStubView)
# define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglVulkanView)
# define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWindow)
# define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWindowDelegate)
# define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWrapperView)
# ifndef DISTRHO_MACOS_NAMESPACE_TIME
# define DISTRHO_MACOS_NAMESPACE_TIME __apple_build_version__
# endif
# define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, SEP, TIME, INTERFACE) NS ## SEP ## TIME ## SEP ## INTERFACE
# define DISTRHO_MACOS_NAMESPACE_MACRO(NS, TIME, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, _, TIME, INTERFACE)
# define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglCairoView)
# define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglOpenGLView)
# define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglStubView)
# define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglVulkanView)
# define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglWindow)
# define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglWindowDelegate)
# define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglWrapperView)
# endif
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -149,10 +176,10 @@ START_NAMESPACE_DGL
# endif
# pragma clang diagnostic pop
#elif defined(DISTRHO_OS_WASM)
# include "pugl-upstream/src/wasm.c"
# include "pugl-upstream/src/wasm_stub.c"
# include "pugl-extra/wasm.c"
# include "pugl-extra/wasm_stub.c"
# ifdef DGL_OPENGL
# include "pugl-upstream/src/wasm_gl.c"
# include "pugl-extra/wasm_gl.c"
# endif
#elif defined(DISTRHO_OS_WINDOWS)
# include "pugl-upstream/src/win.c"
@@ -167,7 +194,14 @@ START_NAMESPACE_DGL
# include "pugl-upstream/src/win_vulkan.c"
# endif
#elif defined(HAVE_X11)
# if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wsign-conversion"
# endif
# include "pugl-upstream/src/x11.c"
# if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
# pragma GCC diagnostic pop
# endif
# include "pugl-upstream/src/x11_stub.c"
# ifdef DGL_CAIRO
# include "pugl-upstream/src/x11_cairo.c"
@@ -204,17 +238,38 @@ bool puglBackendLeave(PuglView* const view)

void puglSetMatchingBackendForCurrentBuild(PuglView* const view)
{
#ifdef DGL_CAIRO
#ifdef DGL_CAIRO
puglSetBackend(view, puglCairoBackend());
#endif
#ifdef DGL_OPENGL
#endif
#ifdef DGL_OPENGL
puglSetBackend(view, puglGlBackend());
#endif
#ifdef DGL_VULKAN
#endif
#ifdef DGL_VULKAN
puglSetBackend(view, puglVulkanBackend());
#endif
if (view->backend == nullptr)
#endif

if (view->backend != nullptr)
{
#ifdef DGL_OPENGL
#if defined(DGL_USE_GLES2)
puglSetViewHint(view, PUGL_CONTEXT_API, PUGL_OPENGL_ES_API);
puglSetViewHint(view, PUGL_CONTEXT_PROFILE, PUGL_OPENGL_CORE_PROFILE);
puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2);
#elif defined(DGL_USE_OPENGL3)
puglSetViewHint(view, PUGL_CONTEXT_API, PUGL_OPENGL_API);
puglSetViewHint(view, PUGL_CONTEXT_PROFILE, PUGL_OPENGL_CORE_PROFILE);
puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3);
#else
puglSetViewHint(view, PUGL_CONTEXT_API, PUGL_OPENGL_API);
puglSetViewHint(view, PUGL_CONTEXT_PROFILE, PUGL_OPENGL_COMPATIBILITY_PROFILE);
puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2);
#endif
#endif
}
else
{
puglSetBackend(view, puglStubBackend());
}
}

// --------------------------------------------------------------------------------------------------------------------
@@ -222,7 +277,8 @@ void puglSetMatchingBackendForCurrentBuild(PuglView* const view)

void puglRaiseWindow(PuglView* const view)
{
#if defined(DISTRHO_OS_MAC)
#if defined(DISTRHO_OS_HAIKU)
#elif defined(DISTRHO_OS_MAC)
if (NSWindow* const window = view->impl->window ? view->impl->window
: [view->impl->wrapperView window])
[window orderFrontRegardless];
@@ -236,60 +292,28 @@ void puglRaiseWindow(PuglView* const view)
#endif
}

// --------------------------------------------------------------------------------------------------------------------
// get scale factor from parent window if possible, fallback to puglGetScaleFactor

double puglGetScaleFactorFromParent(const PuglView* const view)
{
const PuglNativeView parent = view->parent ? view->parent : view->transientParent ? view->transientParent : 0;
#if defined(DISTRHO_OS_MAC)
// some of these can return 0 as backingScaleFactor, pick the most relevant valid one
const NSWindow* possibleWindows[] = {
parent != 0 ? [(NSView*)parent window] : nullptr,
view->impl->window,
[view->impl->wrapperView window]
};
for (size_t i=0; i<ARRAY_SIZE(possibleWindows); ++i)
{
if (possibleWindows[i] == nullptr)
continue;
if (const double scaleFactor = [[possibleWindows[i] screen] backingScaleFactor])
return scaleFactor;
}
return [[NSScreen mainScreen] backingScaleFactor];
#elif defined(DISTRHO_OS_WINDOWS)
const HWND hwnd = parent != 0 ? (HWND)parent : view->impl->hwnd;
return puglWinGetViewScaleFactor(hwnd);
#else
return puglGetScaleFactor(view);
// unused
(void)parent;
#endif
}

// --------------------------------------------------------------------------------------------------------------------
// Combined puglSetSizeHint using PUGL_MIN_SIZE and PUGL_FIXED_ASPECT

PuglStatus puglSetGeometryConstraints(PuglView* const view, const uint width, const uint height, const bool aspect)
{
view->sizeHints[PUGL_MIN_SIZE].width = width;
view->sizeHints[PUGL_MIN_SIZE].height = height;
view->sizeHints[PUGL_MIN_SIZE].width = static_cast<PuglSpan>(width);
view->sizeHints[PUGL_MIN_SIZE].height = static_cast<PuglSpan>(height);

if (aspect)
{
view->sizeHints[PUGL_FIXED_ASPECT].width = width;
view->sizeHints[PUGL_FIXED_ASPECT].height = height;
view->sizeHints[PUGL_FIXED_ASPECT].width = static_cast<PuglSpan>(width);
view->sizeHints[PUGL_FIXED_ASPECT].height = static_cast<PuglSpan>(height);
}

#if defined(DISTRHO_OS_MAC)
#if defined(DISTRHO_OS_HAIKU)
#elif defined(DISTRHO_OS_MAC)
if (view->impl->window)
{
PuglStatus status;

if ((status = updateSizeHint(view, PUGL_MIN_SIZE)) != PUGL_SUCCESS)
if (const PuglStatus status = updateSizeHint(view, PUGL_MIN_SIZE))
return status;

if (aspect && (status = updateSizeHint(view, PUGL_FIXED_ASPECT)) != PUGL_SUCCESS)
if (const PuglStatus status = updateSizeHint(view, PUGL_FIXED_ASPECT))
return status;
}
#elif defined(DISTRHO_OS_WASM)
@@ -297,10 +321,13 @@ PuglStatus puglSetGeometryConstraints(PuglView* const view, const uint width, co
#elif defined(DISTRHO_OS_WINDOWS)
// nothing
#elif defined(HAVE_X11)
if (const PuglStatus status = updateSizeHints(view))
return status;
if (view->impl->win)
{
if (const PuglStatus status = updateSizeHints(view))
return status;

XFlush(view->world->impl->display);
XFlush(view->world->impl->display);
}
#endif

return PUGL_SUCCESS;
@@ -313,7 +340,8 @@ void puglSetResizable(PuglView* const view, const bool resizable)
{
puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE);

#if defined(DISTRHO_OS_MAC)
#if defined(DISTRHO_OS_HAIKU)
#elif defined(DISTRHO_OS_MAC)
if (PuglWindow* const window = view->impl->window)
{
const uint style = (NSClosableWindowMask | NSTitledWindowMask | NSMiniaturizableWindowMask)
@@ -343,58 +371,66 @@ PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height)
if (width > INT16_MAX || height > INT16_MAX)
return PUGL_BAD_PARAMETER;

view->sizeHints[PUGL_DEFAULT_SIZE].width = view->frame.width = static_cast<PuglSpan>(width);
view->sizeHints[PUGL_DEFAULT_SIZE].height = view->frame.height = static_cast<PuglSpan>(height);

#if defined(DISTRHO_OS_MAC)
// mostly matches upstream pugl, simplified
PuglInternals* const impl = view->impl;

const PuglRect frame = view->frame;
const NSRect framePx = rectToNsRect(frame);
const NSRect framePt = nsRectToPoints(view, framePx);

if (PuglWindow* const window = view->impl->window)
#ifdef DGL_USING_X11
// workaround issues in fluxbox, see https://github.com/lv2/pugl/issues/118
// 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;
}
else
#endif
// set default size first
{
const NSRect screenPt = rectToScreen(viewScreen(view), framePt);
const NSRect winFrame = [window frameRectForContentRect:screenPt];
[window setFrame:winFrame display:NO];
view->sizeHints[PUGL_DEFAULT_SIZE].width = static_cast<PuglSpan>(width);
view->sizeHints[PUGL_DEFAULT_SIZE].height = static_cast<PuglSpan>(height);
}

const NSSize sizePx = NSMakeSize(frame.width, frame.height);
const NSSize sizePt = [impl->drawView convertSizeFromBacking:sizePx];
[impl->wrapperView setFrameSize:sizePt];
[impl->drawView setFrameSize:sizePt];
#if defined(DISTRHO_OS_HAIKU)
#elif defined(DISTRHO_OS_MAC)
// matches upstream pugl
if (view->impl->wrapperView)
{
if (const PuglStatus status = puglSetSize(view, width, height))
return status;

// nothing to do for PUGL_DEFAULT_SIZE hint
}
#elif defined(DISTRHO_OS_WASM)
d_stdout("className is %s", view->world->className);
emscripten_set_canvas_element_size(view->world->className, width, height);
d_stdout("className is %s", view->world->strings[PUGL_CLASS_NAME]);
emscripten_set_canvas_element_size(view->world->strings[PUGL_CLASS_NAME], width, height);
#elif defined(DISTRHO_OS_WINDOWS)
// matches upstream pugl, except we re-enter context after resize
if (const HWND hwnd = view->impl->hwnd)
if (view->impl->hwnd)
{
const RECT rect = adjustedWindowRect(view, view->frame.x, view->frame.y,
static_cast<long>(width), static_cast<long>(height));
if (const PuglStatus status = puglSetSize(view, width, height))
return status;

if (!SetWindowPos(hwnd, HWND_TOP, 0, 0, rect.right - rect.left, rect.bottom - rect.top,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE))
return PUGL_UNKNOWN_ERROR;
// nothing to do for PUGL_DEFAULT_SIZE hint

// make sure to return context back to ourselves
puglBackendEnter(view);
}
#elif defined(HAVE_X11)
// matches upstream pugl, all in one
if (const Window window = view->impl->win)
// matches upstream pugl, adds flush at the end
if (view->impl->win)
{
Display* const display = view->world->impl->display;
if (const PuglStatus status = puglSetSize(view, width, height))
return status;

if (! XResizeWindow(display, window, width, height))
return PUGL_UNKNOWN_ERROR;
// updateSizeHints will use last known size, which is not yet updated
const PuglSpan lastWidth = view->lastConfigure.width;
const PuglSpan lastHeight = view->lastConfigure.height;
view->lastConfigure.width = static_cast<PuglSpan>(width);
view->lastConfigure.height = static_cast<PuglSpan>(height);

if (const PuglStatus status = updateSizeHints(view))
return status;
updateSizeHints(view);

XFlush(display);
view->lastConfigure.width = lastWidth;
view->lastConfigure.height = lastHeight;

// flush size changes
XFlush(view->world->impl->display);
}
#endif

@@ -406,42 +442,46 @@ PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height)

void puglOnDisplayPrepare(PuglView*)
{
#ifdef DGL_OPENGL
#ifdef DGL_OPENGL
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
# ifndef DGL_USE_GLES
#ifndef DGL_USE_OPENGL3
glLoadIdentity();
# endif
#endif
#endif
#endif
}

// --------------------------------------------------------------------------------------------------------------------
// DGL specific, build-specific fallback resize

void puglFallbackOnResize(PuglView* const view)
void puglFallbackOnResize(PuglView* const view, const uint width, const uint height)
{
#ifdef DGL_OPENGL
#ifdef DGL_OPENGL
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
# ifndef DGL_USE_GLES
#ifdef DGL_USE_OPENGL3
glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height));
#else
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, static_cast<GLdouble>(view->frame.width), static_cast<GLdouble>(view->frame.height), 0.0, 0.0, 1.0);
glViewport(0, 0, static_cast<GLsizei>(view->frame.width), static_cast<GLsizei>(view->frame.height));
glOrtho(0.0, static_cast<GLdouble>(width), static_cast<GLdouble>(height), 0.0, 0.0, 1.0);
glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height));
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
# else
glViewport(0, 0, static_cast<GLsizei>(view->frame.width), static_cast<GLsizei>(view->frame.height));
# endif
#else
#endif
#else
return;
// unused
(void)view;
#endif
#endif
}

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

#if defined(DISTRHO_OS_MAC)
#if defined(DISTRHO_OS_HAIKU)

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

#elif defined(DISTRHO_OS_MAC)

// --------------------------------------------------------------------------------------------------------------------
// macOS specific, add another view's window as child
@@ -488,7 +528,7 @@ puglMacOSRemoveChildWindow(PuglView* const view, PuglView* const child)

void puglMacOSShowCentered(PuglView* const view)
{
if (puglShow(view) != PUGL_SUCCESS)
if (puglShow(view, PUGL_SHOW_RAISE) != PUGL_SUCCESS)
return;

if (view->transientParent != 0)
@@ -540,34 +580,21 @@ void puglWin32ShowCentered(PuglView* const view)
GetWindowRect(impl->hwnd, &rectChild) &&
GetWindowRect((HWND)view->transientParent, &rectParent))
{
SetWindowPos(impl->hwnd, (HWND)view->transientParent,
rectParent.left + (rectChild.right-rectChild.left)/2,
rectParent.top + (rectChild.bottom-rectChild.top)/2,
SetWindowPos(impl->hwnd, HWND_TOP,
rectParent.left + (rectParent.right-rectParent.left)/2 - (rectChild.right-rectChild.left)/2,
rectParent.top + (rectParent.bottom-rectParent.top)/2 - (rectChild.bottom-rectChild.top)/2,
0, 0, SWP_SHOWWINDOW|SWP_NOSIZE);
}
else
{
#ifdef DGL_WINDOWS_ICON_ID
WNDCLASSEX wClass;
std::memset(&wClass, 0, sizeof(wClass));

const HINSTANCE hInstance = GetModuleHandle(nullptr);

if (GetClassInfoEx(hInstance, view->world->className, &wClass))
wClass.hIcon = LoadIcon(nullptr, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID));

SetClassLongPtr(impl->hwnd, GCLP_HICON, (LONG_PTR) LoadIcon(hInstance, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID)));
#endif

MONITORINFO mInfo;
std::memset(&mInfo, 0, sizeof(mInfo));
mInfo.cbSize = sizeof(mInfo);

if (GetMonitorInfo(MonitorFromWindow(impl->hwnd, MONITOR_DEFAULTTOPRIMARY), &mInfo))
SetWindowPos(impl->hwnd,
HWND_TOP,
mInfo.rcWork.left + (mInfo.rcWork.right - mInfo.rcWork.left - view->frame.width) / 2,
mInfo.rcWork.top + (mInfo.rcWork.bottom - mInfo.rcWork.top - view->frame.height) / 2,
SetWindowPos(impl->hwnd, HWND_TOP,
mInfo.rcWork.left + (mInfo.rcWork.right - mInfo.rcWork.left - view->lastConfigure.width) / 2,
mInfo.rcWork.top + (mInfo.rcWork.bottom - mInfo.rcWork.top - view->lastConfigure.height) / 2,
0, 0, SWP_SHOWWINDOW|SWP_NOSIZE);
else
ShowWindow(impl->hwnd, SW_NORMAL);
@@ -586,6 +613,9 @@ void puglWin32ShowCentered(PuglView* const view)

#elif defined(HAVE_X11)

// --------------------------------------------------------------------------------------------------------------------
// X11 specific, update world without triggering exposure events

PuglStatus puglX11UpdateWithoutExposures(PuglWorld* const world)
{
const bool wasDispatchingEvents = world->impl->dispatchingEvents;
@@ -617,6 +647,15 @@ void puglX11SetWindowTypeAndPID(const PuglView* const view, const bool isStandal
const Atom _nwp = XInternAtom(display, "_NET_WM_PID", False);
XChangeProperty(display, impl->win, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);

#if defined(DGL_X11_WINDOW_ICON_NAME) && defined(DGL_X11_WINDOW_ICON_SIZE)
if (isStandalone)
{
const Atom _nwi = XInternAtom(display, "_NET_WM_ICON", False);
XChangeProperty(display, impl->win, _nwi, XA_CARDINAL, 32, PropModeReplace,
(const uchar*)DGL_X11_WINDOW_ICON_NAME, DGL_X11_WINDOW_ICON_SIZE);
}
#endif

const Atom _wt = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);

Atom _wts[2];


+ 28
- 10
source/modules/dgl/src/pugl.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2023 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -19,7 +19,7 @@

#include "../Base.hpp"

/* we will include all header files used in pugl.h in their C++ friendly form, then pugl stuff in custom namespace */
// we will include all header files used in pugl.h in their C++ friendly form, then pugl stuff in custom namespace
#include <cstddef>
#ifdef DISTRHO_PROPER_CPP11_SUPPORT
# include <cstdbool>
@@ -29,9 +29,26 @@
# include <stdint.h>
#endif

// hidden api
// custom attributes
#define PUGL_ATTRIBUTES_H
#define PUGL_BEGIN_DECLS
#define PUGL_END_DECLS
#define PUGL_API
#define PUGL_DISABLE_DEPRECATED

// GCC function attributes
#if defined(__GNUC__) && !defined(__clang__)
#define PUGL_CONST_FUNC __attribute__((const))
#define PUGL_MALLOC_FUNC __attribute__((malloc))
#else
#define PUGL_CONST_FUNC
#define PUGL_MALLOC_FUNC
#endif

#define PUGL_CONST_API PUGL_CONST_FUNC
#define PUGL_MALLOC_API PUGL_MALLOC_FUNC

// we do our own OpenGL inclusion
#define PUGL_NO_INCLUDE_GL_H
#define PUGL_NO_INCLUDE_GLU_H

@@ -39,7 +56,7 @@
START_NAMESPACE_DGL
#endif

#include "pugl-upstream/include/pugl/pugl.h"
#include "pugl/pugl.h"

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

@@ -55,9 +72,6 @@ void puglSetMatchingBackendForCurrentBuild(PuglView* view);
// bring view window into the foreground, aka "raise" window
void puglRaiseWindow(PuglView* view);

// get scale factor from parent window if possible, fallback to puglGetScaleFactor
double puglGetScaleFactorFromParent(const PuglView* view);

// combined puglSetSizeHint using PUGL_MIN_SIZE, PUGL_MIN_ASPECT and PUGL_MAX_ASPECT
PuglStatus puglSetGeometryConstraints(PuglView* view, uint width, uint height, bool aspect);

@@ -71,9 +85,13 @@ PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height);
void puglOnDisplayPrepare(PuglView* view);

// DGL specific, build-specific fallback resize
void puglFallbackOnResize(PuglView* view);
void puglFallbackOnResize(PuglView* view, uint width, uint height);

#if defined(DISTRHO_OS_HAIKU)

// nothing here yet

#if defined(DISTRHO_OS_MAC)
#elif defined(DISTRHO_OS_MAC)

// macOS specific, add another view's window as child
PuglStatus puglMacOSAddChildWindow(PuglView* view, PuglView* child);
@@ -100,7 +118,7 @@ void puglWin32ShowCentered(PuglView* view);

#define DGL_USING_X11

// X11 specific, update world without triggering exposure evente
// X11 specific, update world without triggering exposure events
PuglStatus puglX11UpdateWithoutExposures(PuglWorld* world);

// X11 specific, set dialog window type and pid hints


+ 1084
- 0
source/modules/distrho/DistrhoDetails.hpp
File diff suppressed because it is too large
View File


+ 68
- 851
source/modules/distrho/DistrhoPlugin.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -17,857 +17,12 @@
#ifndef DISTRHO_PLUGIN_HPP_INCLUDED
#define DISTRHO_PLUGIN_HPP_INCLUDED

#include "extra/String.hpp"
#include "DistrhoDetails.hpp"
#include "extra/LeakDetector.hpp"
#include "src/DistrhoPluginChecks.h"

START_NAMESPACE_DISTRHO

/* ------------------------------------------------------------------------------------------------------------
* Audio Port Hints */

/**
@defgroup AudioPortHints Audio Port Hints

Various audio port hints.
@see AudioPort::hints
@{
*/

/**
Audio port can be used as control voltage (LV2 and JACK standalone only).
*/
static const uint32_t kAudioPortIsCV = 0x1;

/**
Audio port should be used as sidechan (LV2 and VST3 only).
This hint should not be used with CV style ports.
@note non-sidechain audio ports must exist in the plugin if this flag is set.
*/
static const uint32_t kAudioPortIsSidechain = 0x2;

/**
CV port has bipolar range (-1 to +1, or -5 to +5 if scaled).
This is merely a hint to tell the host what value range to expect.
*/
static const uint32_t kCVPortHasBipolarRange = 0x10;

/**
CV port has negative unipolar range (-1 to 0, or -10 to 0 if scaled).
This is merely a hint to tell the host what value range to expect.
*/
static const uint32_t kCVPortHasNegativeUnipolarRange = 0x20;

/**
CV port has positive unipolar range (0 to +1, or 0 to +10 if scaled).
This is merely a hint to tell the host what value range to expect.
*/
static const uint32_t kCVPortHasPositiveUnipolarRange = 0x40;

/**
CV port has scaled range to match real values (-5 to +5v bipolar, +/-10 to 0v unipolar).
One other range flag is required if this flag is set.

When enabled, this makes the port a mod:CVPort, compatible with the MOD Devices platform.
*/
static const uint32_t kCVPortHasScaledRange = 0x80;

/** @} */

/* ------------------------------------------------------------------------------------------------------------
* Parameter Hints */

/**
@defgroup ParameterHints Parameter Hints

Various parameter hints.
@see Parameter::hints
@{
*/

/**
Parameter is automatable (real-time safe).
@see Plugin::setParameterValue(uint32_t, float)
*/
static const uint32_t kParameterIsAutomatable = 0x01;

/** It was a typo, sorry.. */
DISTRHO_DEPRECATED_BY("kParameterIsAutomatable")
static const uint32_t kParameterIsAutomable = kParameterIsAutomatable;

/**
Parameter value is boolean.@n
It's always at either minimum or maximum value.
*/
static const uint32_t kParameterIsBoolean = 0x02;

/**
Parameter value is integer.
*/
static const uint32_t kParameterIsInteger = 0x04;

/**
Parameter value is logarithmic.
*/
static const uint32_t kParameterIsLogarithmic = 0x08;

/**
Parameter is of output type.@n
When unset, parameter is assumed to be of input type.

Parameter inputs are changed by the host and typically should not be changed by the plugin.@n
One exception is when changing programs, see Plugin::loadProgram().@n
The other exception is with parameter change requests, see Plugin::requestParameterValueChange().@n
Outputs are changed by the plugin and never modified by the host.

If you are targetting VST2, make sure to order your parameters so that all inputs are before any outputs.
*/
static const uint32_t kParameterIsOutput = 0x10;

/**
Parameter value is a trigger.@n
This means the value resets back to its default after each process/run call.@n
Cannot be used for output parameters.

@note Only officially supported under LV2. For other formats DPF simulates the behaviour.
*/
static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean;

/** @} */

/* ------------------------------------------------------------------------------------------------------------
* State Hints */

/**
@defgroup StateHints State Hints

Various state hints.
@see State::hints
@{
*/

/**
State is visible and readable by hosts that support string-type plugin parameters.
*/
static const uint32_t kStateIsHostReadable = 0x01;

/**
State is writable by the host, allowing users to arbitrarily change the state.@n
For obvious reasons a writable state is also readable by the host.
*/
static const uint32_t kStateIsHostWritable = 0x02 | kStateIsHostReadable;

/**
State is a filename path instead of a regular string.@n
The readable and writable hints are required for filenames to work, and thus are automatically set.
*/
static const uint32_t kStateIsFilenamePath = 0x04 | kStateIsHostWritable;

/**
State is a base64 encoded string.
*/
static const uint32_t kStateIsBase64Blob = 0x08;

/**
State is for Plugin/DSP side only, meaning there is never a need to notify the UI when it changes.
*/
static const uint32_t kStateIsOnlyForDSP = 0x10;

/**
State is for UI side only.@n
If the DSP and UI are separate and the UI is not available, this property won't be saved.
*/
static const uint32_t kStateIsOnlyForUI = 0x20;

/** @} */

/* ------------------------------------------------------------------------------------------------------------
* Base Plugin structs */

/**
@defgroup BasePluginStructs Base Plugin Structs
@{
*/

/**
Parameter designation.@n
Allows a parameter to be specially designated for a task, like bypass.

Each designation is unique, there must be only one parameter that uses it.@n
The use of designated parameters is completely optional.

@note Designated parameters have strict ranges.
@see ParameterRanges::adjustForDesignation()
*/
enum ParameterDesignation {
/**
Null or unset designation.
*/
kParameterDesignationNull = 0,

/**
Bypass designation.@n
When on (> 0.5f), it means the plugin must run in a bypassed state.
*/
kParameterDesignationBypass = 1
};

/**
Predefined Port Groups Ids.

This enumeration provides a few commonly used groups for convenient use in plugins.
For preventing conflicts with user code, negative values are used here.
When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential.

@see PortGroup
*/
enum PredefinedPortGroupsIds {
/**
Null or unset port group.
*/
kPortGroupNone = (uint32_t)-1,

/**
A single channel audio group.
*/
kPortGroupMono = (uint32_t)-2,

/**
A 2-channel discrete stereo audio group,
where the 1st audio port is the left channel and the 2nd port is the right channel.
*/
kPortGroupStereo = (uint32_t)-3
};

/**
Audio Port.

Can be used as CV port by specifying kAudioPortIsCV in hints,@n
but this is only supported in LV2 and JACK standalone formats.
*/
struct AudioPort {
/**
Hints describing this audio port.
@see AudioPortHints
*/
uint32_t hints;

/**
The name of this audio port.@n
An audio port name can contain any character, but hosts might have a hard time with non-ascii ones.@n
The name doesn't have to be unique within a plugin instance, but it's recommended.
*/
String name;

/**
The symbol of this audio port.@n
An audio port symbol is a short restricted name used as a machine and human readable identifier.@n
The first character must be one of _, a-z or A-Z and subsequent characters can be from _, a-z, A-Z and 0-9.
@note Audio port and parameter symbols MUST be unique within a plugin instance.
*/
String symbol;

/**
The group id that this audio/cv port belongs to.
No group is assigned by default.

You can use a group from PredefinedPortGroups or roll your own.@n
When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential.
@see PortGroup, Plugin::initPortGroup
*/
uint32_t groupId;

/**
Default constructor for a regular audio port.
*/
AudioPort() noexcept
: hints(0x0),
name(),
symbol(),
groupId(kPortGroupNone) {}
};

/**
Parameter ranges.@n
This is used to set the default, minimum and maximum values of a parameter.

By default a parameter has 0.0 as minimum, 1.0 as maximum and 0.0 as default.@n
When changing this struct values you must ensure maximum > minimum and default is within range.
*/
struct ParameterRanges {
/**
Default value.
*/
float def;

/**
Minimum value.
*/
float min;

/**
Maximum value.
*/
float max;

/**
Default constructor, using 0.0 as default, 0.0 as minimum, 1.0 as maximum.
*/
ParameterRanges() noexcept
: def(0.0f),
min(0.0f),
max(1.0f) {}

/**
Constructor using custom values.
*/
ParameterRanges(float df, float mn, float mx) noexcept
: def(df),
min(mn),
max(mx) {}

/**
Fix the default value within range.
*/
void fixDefault() noexcept
{
fixValue(def);
}

/**
Fix a value within range.
*/
void fixValue(float& value) const noexcept
{
if (value < min)
value = min;
else if (value > max)
value = max;
}

/**
Get a fixed value within range.
*/
float getFixedValue(const float& value) const noexcept
{
if (value <= min)
return min;
if (value >= max)
return max;
return value;
}

/**
Get a value normalized to 0.0<->1.0.
*/
float getNormalizedValue(const float& value) const noexcept
{
const float normValue((value - min) / (max - min));

if (normValue <= 0.0f)
return 0.0f;
if (normValue >= 1.0f)
return 1.0f;
return normValue;
}

/**
Get a value normalized to 0.0<->1.0, fixed within range.
*/
float getFixedAndNormalizedValue(const float& value) const noexcept
{
if (value <= min)
return 0.0f;
if (value >= max)
return 1.0f;

const float normValue((value - min) / (max - min));

if (normValue <= 0.0f)
return 0.0f;
if (normValue >= 1.0f)
return 1.0f;

return normValue;
}

/**
Get a proper value previously normalized to 0.0<->1.0.
*/
float getUnnormalizedValue(const float& value) const noexcept
{
if (value <= 0.0f)
return min;
if (value >= 1.0f)
return max;

return value * (max - min) + min;
}
};

/**
Parameter enumeration value.@n
A string representation of a plugin parameter value.@n
Used together can be used to give meaning to parameter values, working as an enumeration.
*/
struct ParameterEnumerationValue {
/**
Parameter value.
*/
float value;

/**
String representation of this value.
*/
String label;

/**
Default constructor, using 0.0 as value and empty label.
*/
ParameterEnumerationValue() noexcept
: value(0.0f),
label() {}

/**
Constructor using custom values.
*/
ParameterEnumerationValue(float v, const char* l) noexcept
: value(v),
label(l) {}
};

/**
Collection of parameter enumeration values.@n
Handy class to handle the lifetime and count of all enumeration values.
*/
struct ParameterEnumerationValues {
/**
Number of elements allocated in @values.
*/
uint8_t count;

/**
Wherever the host is to be restricted to only use enumeration values.

@note This mode is only a hint! Not all hosts and plugin formats support this mode.
*/
bool restrictedMode;

/**
Array of @ParameterEnumerationValue items.@n
This pointer must be null or have been allocated on the heap with `new ParameterEnumerationValue[count]`.
*/
ParameterEnumerationValue* values;

/**
Default constructor, for zero enumeration values.
*/
ParameterEnumerationValues() noexcept
: count(0),
restrictedMode(false),
values() {}

/**
Constructor using custom values.@n
The pointer to @values must have been allocated on the heap with `new`.
*/
ParameterEnumerationValues(uint32_t c, bool r, ParameterEnumerationValue* v) noexcept
: count(c),
restrictedMode(r),
values(v) {}

~ParameterEnumerationValues() noexcept
{
count = 0;
restrictedMode = false;

if (values != nullptr)
{
delete[] values;
values = nullptr;
}
}

DISTRHO_DECLARE_NON_COPYABLE(ParameterEnumerationValues)
};

/**
Parameter.
*/
struct Parameter {
/**
Hints describing this parameter.
@see ParameterHints
*/
uint32_t hints;

/**
The name of this parameter.@n
A parameter name can contain any character, but hosts might have a hard time with non-ascii ones.@n
The name doesn't have to be unique within a plugin instance, but it's recommended.
*/
String name;

/**
The short name of this parameter.@n
Used when displaying the parameter name in a very limited space.
@note This value is optional, the full name is used when the short one is missing.
*/
String shortName;

/**
The symbol of this parameter.@n
A parameter symbol is a short restricted name used as a machine and human readable identifier.@n
The first character must be one of _, a-z or A-Z and subsequent characters can be from _, a-z, A-Z and 0-9.
@note Parameter symbols MUST be unique within a plugin instance.
*/
String symbol;

/**
The unit of this parameter.@n
This means something like "dB", "kHz" and "ms".@n
Can be left blank if a unit does not apply to this parameter.
*/
String unit;

/**
An extensive description/comment about the parameter.
@note This value is optional and only used for LV2.
*/
String description;

/**
Ranges of this parameter.@n
The ranges describe the default, minimum and maximum values.
*/
ParameterRanges ranges;

/**
Enumeration values.@n
Can be used to give meaning to parameter values, working as an enumeration.
*/
ParameterEnumerationValues enumValues;

/**
Designation for this parameter.
*/
ParameterDesignation designation;

/**
MIDI CC to use by default on this parameter.@n
A value of 0 or 32 (bank change) is considered invalid.@n
Must also be less or equal to 120.
@note This value is only a hint! Hosts might map it automatically or completely ignore it.
*/
uint8_t midiCC;

/**
The group id that this parameter belongs to.
No group is assigned by default.

You can use a group from PredefinedPortGroups or roll your own.@n
When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential.
@see PortGroup, Plugin::initPortGroup
*/
uint32_t groupId;

/**
Default constructor for a null parameter.
*/
Parameter() noexcept
: hints(0x0),
name(),
shortName(),
symbol(),
unit(),
ranges(),
enumValues(),
designation(kParameterDesignationNull),
midiCC(0),
groupId(kPortGroupNone) {}

/**
Constructor using custom values.
*/
Parameter(uint32_t h, const char* n, const char* s, const char* u, float def, float min, float max) noexcept
: hints(h),
name(n),
shortName(),
symbol(s),
unit(u),
ranges(def, min, max),
enumValues(),
designation(kParameterDesignationNull),
midiCC(0),
groupId(kPortGroupNone) {}

/**
Initialize a parameter for a specific designation.
*/
void initDesignation(ParameterDesignation d) noexcept
{
designation = d;

switch (d)
{
case kParameterDesignationNull:
break;
case kParameterDesignationBypass:
hints = kParameterIsAutomatable|kParameterIsBoolean|kParameterIsInteger;
name = "Bypass";
shortName = "Bypass";
symbol = "dpf_bypass";
unit = "";
midiCC = 0;
groupId = kPortGroupNone;
ranges.def = 0.0f;
ranges.min = 0.0f;
ranges.max = 1.0f;
break;
}
}
};

/**
Port Group.@n
Allows to group together audio/cv ports or parameters.

Each unique group MUST have an unique symbol and a name.
A group can be applied to both inputs and outputs (at the same time).
The same group cannot be used in audio ports and parameters.

When both audio and parameter groups are used, audio groups MUST be defined first.
That is, group indexes start with audio ports, then parameters.

An audio port group logically combines ports which should be considered part of the same stream.@n
For example, two audio ports in a group may form a stereo stream.

A parameter group provides meta-data to the host to indicate that some parameters belong together.

The use of port groups is completely optional.

@see Plugin::initPortGroup, AudioPort::group, Parameter::group
*/
struct PortGroup {
/**
The name of this port group.@n
A port group name can contain any character, but hosts might have a hard time with non-ascii ones.@n
The name doesn't have to be unique within a plugin instance, but it's recommended.
*/
String name;

/**
The symbol of this port group.@n
A port group symbol is a short restricted name used as a machine and human readable identifier.@n
The first character must be one of _, a-z or A-Z and subsequent characters can be from _, a-z, A-Z and 0-9.
@note Port group symbols MUST be unique within a plugin instance.
*/
String symbol;
};

/**
State.

In DPF states refer to key:value string pairs, used to store arbitrary non-parameter data.@n
By default states are completely internal to the plugin and not visible by the host.@n
Flags can be set to allow hosts to see and/or change them.

TODO API under construction
*/
struct State {
/**
Hints describing this state.
@note Changing these hints can break compatibility with previously saved data.
@see StateHints
*/
uint32_t hints;

/**
The key or "symbol" of this state.@n
A state key is a short restricted name used as a machine and human readable identifier.
@note State keys MUST be unique within a plugin instance.
TODO define rules for allowed characters, must be usable as URI non-encoded parameters
*/
String key;

/**
The default value of this state.@n
Can be left empty if considered a valid initial state.
*/
String defaultValue;

/**
String representation of this state.
*/
String label;

/**
An extensive description/comment about this state.
@note This value is optional and only used for LV2.
*/
String description;
};

/**
MIDI event.
*/
struct MidiEvent {
/**
Size of internal data.
*/
static const uint32_t kDataSize = 4;

/**
Time offset in frames.
*/
uint32_t frame;

/**
Number of bytes used.
*/
uint32_t size;

/**
MIDI data.@n
If size > kDataSize, dataExt is used (otherwise null).

When dataExt is used, the event holder is responsible for
keeping the pointer valid during the entirety of the run function.
*/
uint8_t data[kDataSize];
const uint8_t* dataExt;
};

/**
Time position.@n
The @a playing and @a frame values are always valid.@n
BBT values are only valid when @a bbt.valid is true.

This struct is inspired by the [JACK Transport API](https://jackaudio.org/api/structjack__position__t.html).
*/
struct TimePosition {
/**
Wherever the host transport is playing/rolling.
*/
bool playing;

/**
Current host transport position in frames.
@note This value is not always monotonic,
with some plugin hosts assigning it based on a source that can accumulate rounding errors.
*/
uint64_t frame;

/**
Bar-Beat-Tick time position.
*/
struct BarBeatTick {
/**
Wherever the host transport is using BBT.@n
If false you must not read from this struct.
*/
bool valid;

/**
Current bar.@n
Should always be > 0.@n
The first bar is bar '1'.
*/
int32_t bar;

/**
Current beat within bar.@n
Should always be > 0 and <= @a beatsPerBar.@n
The first beat is beat '1'.
*/
int32_t beat;

/**
Current tick within beat.@n
Should always be >= 0 and < @a ticksPerBeat.@n
The first tick is tick '0'.
@note Fraction part of tick is only available on some plugin formats.
*/
double tick;

/**
Number of ticks that have elapsed between frame 0 and the first beat of the current measure.
*/
double barStartTick;

/**
Time signature "numerator".
*/
float beatsPerBar;

/**
Time signature "denominator".
*/
float beatType;

/**
Number of ticks within a beat.@n
Usually a moderately large integer with many denominators, such as 1920.0.
*/
double ticksPerBeat;

/**
Number of beats per minute.
*/
double beatsPerMinute;

/**
Default constructor for a null BBT time position.
*/
BarBeatTick() noexcept
: valid(false),
bar(0),
beat(0),
tick(0),
barStartTick(0.0),
beatsPerBar(0.0f),
beatType(0.0f),
ticksPerBeat(0.0),
beatsPerMinute(0.0) {}

/**
Reinitialize this position using the default null initialization.
*/
void clear() noexcept
{
valid = false;
bar = 0;
beat = 0;
tick = 0;
barStartTick = 0.0;
beatsPerBar = 0.0f;
beatType = 0.0f;
ticksPerBeat = 0.0;
beatsPerMinute = 0.0;
}
} bbt;

/**
Default constructor for a time position.
*/
TimePosition() noexcept
: playing(false),
frame(0),
bbt() {}

/**
Reinitialize this position using the default null initialization.
*/
void clear() noexcept
{
playing = false;
frame = 0;
bbt.clear();
}
};

/** @} */

/* ------------------------------------------------------------------------------------------------------------
* DPF Plugin */

@@ -897,7 +52,7 @@ struct TimePosition {
When enabled you need to implement initProgramName() and loadProgram().

DISTRHO_PLUGIN_WANT_STATE activates internal state features.@n
When enabled you need to implement initStateKey() and setState().
When enabled you need to implement initState() and setState().

The process function run() changes wherever DISTRHO_PLUGIN_WANT_MIDI_INPUT is enabled or not.@n
When enabled it provides midi input events.
@@ -951,6 +106,16 @@ public:
*/
bool isDummyInstance() const noexcept;

/**
Check if this plugin instance is a "selftest" one used for automated plugin tests.@n
To enable this mode build with `DPF_RUNTIME_TESTING` macro defined (i.e. set as compiler build flag),
and run the JACK/Standalone executable with "selftest" as its only and single argument.

A few basic DSP and UI tests will run in self-test mode, with once instance having this function returning true.@n
You can use this chance to do a few tests of your own as well.
*/
bool isSelfTestInstance() const noexcept;

#if DISTRHO_PLUGIN_WANT_TIMEPOS
/**
Get the current host transport time position.@n
@@ -1024,30 +189,65 @@ protected:
Get the plugin label.@n
This label is a short restricted name consisting of only _, a-z, A-Z and 0-9 characters.
*/
#ifdef DISTRHO_PLUGIN_LABEL
virtual const char* getLabel() const
{
return DISTRHO_PLUGIN_LABEL;
}
#else
virtual const char* getLabel() const = 0;
#endif

/**
Get an extensive comment/description about the plugin.@n
Optional, returns nothing by default.
*/
virtual const char* getDescription() const { return ""; }
virtual const char* getDescription() const
{
#ifdef DISTRHO_PLUGIN_DESCRIPTION
return DISTRHO_PLUGIN_DESCRIPTION;
#else
return "";
#endif
}

/**
Get the plugin author/maker.
*/
#ifdef DISTRHO_PLUGIN_MAKER
virtual const char* getMaker() const
{
return DISTRHO_PLUGIN_MAKER;
}
#else
virtual const char* getMaker() const = 0;
#endif

/**
Get the plugin homepage.@n
Optional, returns nothing by default.
*/
virtual const char* getHomePage() const { return ""; }
virtual const char* getHomePage() const
{
#ifdef DISTRHO_PLUGIN_HOMEPAGE
return DISTRHO_PLUGIN_HOMEPAGE;
#else
return "";
#endif
}

/**
Get the plugin license (a single line of text or a URL).@n
For commercial plugins this should return some short copyright information.
*/
#ifdef DISTRHO_PLUGIN_LICENSE
virtual const char* getLicense() const
{
return DISTRHO_PLUGIN_LICENSE;
}
#else
virtual const char* getLicense() const = 0;
#endif

/**
Get the plugin version, in hexadecimal.
@@ -1057,10 +257,19 @@ protected:

/**
Get the plugin unique Id.@n
This value is used by LADSPA, DSSI and VST plugin formats.
This value is used by LADSPA, DSSI, VST2, VST3 and AUv2 plugin formats.@n
@note It is preferred that you set DISTRHO_PLUGIN_UNIQUE_ID macro instead of overriding this call,
as that is required for AUv2 plugins anyhow.
@see d_cconst()
*/
#ifdef DISTRHO_PLUGIN_UNIQUE_ID
virtual int64_t getUniqueId() const
{
return d_cconst(STRINGIFY(DISTRHO_PLUGIN_UNIQUE_ID));
}
#else
virtual int64_t getUniqueId() const = 0;
#endif

/* --------------------------------------------------------------------------------------------------------
* Init */
@@ -1199,6 +408,14 @@ protected:
*/
virtual void sampleRateChanged(double newSampleRate);

/**
Optional callback to inform the plugin about audio port IO changes.@n
This function will only be called when the plugin is deactivated.@n
Only used in AU (AudioUnit) format when DISTRHO_PLUGIN_EXTRA_IO is defined.
@see DISTRHO_PLUGIN_EXTRA_IO
*/
virtual void ioChanged(uint16_t numInputs, uint16_t numOutputs);

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

private:


+ 8
- 2
source/modules/distrho/DistrhoPluginMain.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -16,8 +16,12 @@

#include "src/DistrhoPlugin.cpp"

#if defined(DISTRHO_PLUGIN_TARGET_CARLA)
#if defined(DISTRHO_PLUGIN_TARGET_AU)
# include "src/DistrhoPluginAU.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_CARLA)
# include "src/DistrhoPluginCarla.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_CLAP)
# include "src/DistrhoPluginCLAP.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_JACK)
# include "src/DistrhoPluginJACK.cpp"
#elif (defined(DISTRHO_PLUGIN_TARGET_LADSPA) || defined(DISTRHO_PLUGIN_TARGET_DSSI))
@@ -29,6 +33,8 @@
# include "src/DistrhoPluginVST2.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_VST3)
# include "src/DistrhoPluginVST3.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_EXPORT)
# include "src/DistrhoPluginExport.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_SHARED)
DISTRHO_PLUGIN_EXPORT DISTRHO_NAMESPACE::Plugin* createSharedPlugin();
DISTRHO_PLUGIN_EXPORT DISTRHO_NAMESPACE::Plugin* createSharedPlugin() { return DISTRHO_NAMESPACE::createPlugin(); }


+ 27
- 5
source/modules/distrho/DistrhoPluginUtils.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -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.
@@ -53,13 +73,15 @@ const char* getPluginFormatName() noexcept;
Returns a path inside the bundle where the plugin is meant to store its resources in.@n
This path varies between systems and plugin formats, like so:

- AU: <bundle>/Contents/Resources
- CLAP+VST2 macOS: <bundle>/Contents/Resources
- CLAP+VST2 non-macOS: <bundle>/resources (see note)
- LV2: <bundle>/resources (can be stored anywhere inside the bundle really, DPF just uses this one)
- VST2 macOS: <bundle>/Contents/Resources
- VST2 non-macOS: <bundle>/resources (see note)
- VST3: <bundle>/Contents/Resources

The other non-mentioned formats do not support bundles.@n

@note For VST2 on non-macOS systems, this assumes you have your plugin inside a dedicated directory
@note For CLAP and VST2 on non-macOS systems, this assumes you have your plugin inside a dedicated directory
rather than only shipping with the binary (e.g. <myplugin.vst>/myplugin.dll)
*/
const char* getResourcePath(const char* bundlePath) noexcept;


+ 11
- 53
source/modules/distrho/DistrhoUI.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -17,6 +17,7 @@
#ifndef DISTRHO_UI_HPP_INCLUDED
#define DISTRHO_UI_HPP_INCLUDED

#include "DistrhoDetails.hpp"
#include "extra/LeakDetector.hpp"
#include "src/DistrhoPluginChecks.h"

@@ -30,11 +31,7 @@
# include "Vulkan.hpp"
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# include "../dgl/Base.hpp"
# include "extra/ExternalWindow.hpp"
typedef DISTRHO_NAMESPACE::ExternalWindow UIWidget;
#elif DISTRHO_UI_USE_CUSTOM
#if DISTRHO_UI_USE_CUSTOM
# include DISTRHO_UI_CUSTOM_INCLUDE_PATH
typedef DISTRHO_UI_CUSTOM_WIDGET_TYPE UIWidget;
#elif DISTRHO_UI_USE_CAIRO
@@ -52,6 +49,8 @@ typedef DGL_NAMESPACE::TopLevelWidget UIWidget;
# include "extra/FileBrowserDialog.hpp"
#endif

#include <vector>

START_NAMESPACE_DISTRHO

class PluginWindow;
@@ -186,15 +185,15 @@ 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.

This function does not block the event loop.

@note This is exactly the same API as provided by the Window class,
but redeclared here so that non-embed/DGL based UIs can still use file browser related functions.
@note This is exactly the same API as provided by the Window class, but redeclared here for convenience.
*/
bool openFileBrowser(const DISTRHO_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions());
#endif
@@ -210,34 +209,6 @@ public:
void* getPluginInstancePointer() const noexcept;
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
/* --------------------------------------------------------------------------------------------------------
* External UI helpers */

/**
Get the bundle path that will be used for the next UI.
@note: This function is only valid during createUI(),
it will return null when called from anywhere else.
*/
static const char* getNextBundlePath() noexcept;

/**
Get the scale factor that will be used for the next UI.
@note: This function is only valid during createUI(),
it will return 1.0 when called from anywhere else.
*/
static double getNextScaleFactor() noexcept;

# if DISTRHO_PLUGIN_HAS_EMBED_UI
/**
Get the Window Id that will be used for the next created window.
@note: This function is only valid during createUI(),
it will return 0 when called from anywhere else.
*/
static uintptr_t getNextWindowId() noexcept;
# endif
#endif

protected:
/* --------------------------------------------------------------------------------------------------------
* DSP/Plugin Callbacks */
@@ -246,14 +217,14 @@ protected:
A parameter has changed on the plugin side.@n
This is called by the host to inform the UI about parameter changes.
*/
virtual void parameterChanged(uint32_t index, float value) = 0;
virtual void parameterChanged(uint32_t index, float value);

#if DISTRHO_PLUGIN_WANT_PROGRAMS
/**
A program has been loaded on the plugin side.@n
This is called by the host to inform the UI about program changes.
*/
virtual void programLoaded(uint32_t index) = 0;
virtual void programLoaded(uint32_t index);
#endif

#if DISTRHO_PLUGIN_WANT_STATE
@@ -261,7 +232,7 @@ protected:
A state has changed on the plugin side.@n
This is called by the host to inform the UI about state changes.
*/
virtual void stateChanged(const char* key, const char* value) = 0;
virtual void stateChanged(const char* key, const char* value);
#endif

/* --------------------------------------------------------------------------------------------------------
@@ -293,7 +264,6 @@ protected:
*/
virtual void uiScaleFactorChanged(double scaleFactor);

#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
/**
Get the types available for the data in a clipboard.
Must only be called within the context of uiClipboardDataOffer.
@@ -330,7 +300,6 @@ protected:
The most common exception is custom OpenGL setup, but only really needed for custom OpenGL drawing code.
*/
virtual void uiReshape(uint width, uint height);
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI

#if DISTRHO_UI_FILE_BROWSER
/**
@@ -348,21 +317,12 @@ protected:
/* --------------------------------------------------------------------------------------------------------
* UI Resize Handling, internal */

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
/**
External Window resize function, called when the window is resized.
This is overriden here so the host knows when the UI is resized by you.
@see ExternalWindow::sizeChanged(uint,uint)
*/
void sizeChanged(uint width, uint height) override;
#else
/**
Widget resize function, called when the widget is resized.
This is overriden here so the host knows when the UI is resized by you.
@see Widget::onResize(const ResizeEvent&)
*/
void onResize(const ResizeEvent& ev) override;
#endif

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

@@ -371,10 +331,8 @@ private:
PrivateData* const uiData;
friend class PluginWindow;
friend class UIExporter;
#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
/** @internal */
void requestSizeChange(uint width, uint height) override;
#endif

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UI)
};


+ 43
- 7
source/modules/distrho/DistrhoUIMain.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -16,29 +16,65 @@

#include "src/DistrhoUI.cpp"

#if defined(DISTRHO_PLUGIN_TARGET_CARLA)
// nothing
// we might be building a plugin with external UI, which works on most formats except VST2/3
#if ! DISTRHO_PLUGIN_HAS_UI && ! defined(DISTRHO_PLUGIN_VST_HPP_INCLUDED)
# error Trying to build UI without DISTRHO_PLUGIN_HAS_UI set to 1
#endif

#if DISTRHO_PLUGIN_HAS_UI

#if defined(DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT)
# if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
# warning Using single/monolithic LV2 target while DISTRHO_PLUGIN_WANT_DIRECT_ACCESS is 0
# endif
#elif defined(DISTRHO_PLUGIN_TARGET_AU)
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1
# import "src/DistrhoUIAU.mm"
#elif defined(DISTRHO_PLUGIN_TARGET_CARLA)
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1
#elif defined(DISTRHO_PLUGIN_TARGET_CLAP)
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1
#elif defined(DISTRHO_PLUGIN_TARGET_JACK)
// nothing
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1
#elif defined(DISTRHO_PLUGIN_TARGET_DSSI)
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 0
# include "src/DistrhoUIDSSI.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_LV2)
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
# include "src/DistrhoUILV2.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_VST2)
// nothing
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1
#elif defined(DISTRHO_PLUGIN_TARGET_VST3)
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1
# include "src/DistrhoUIVST3.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_EXPORT)
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1
#elif defined(DISTRHO_PLUGIN_TARGET_SHARED) || defined(DISTRHO_PLUGIN_TARGET_STATIC)
// nothing
# define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1
#else
# error unsupported format
#endif

#if !DISTRHO_PLUGIN_WANT_DIRECT_ACCESS && !defined(DISTRHO_PLUGIN_TARGET_CARLA) && !defined(DISTRHO_PLUGIN_TARGET_JACK) && !defined(DISTRHO_PLUGIN_TARGET_VST2) && !defined(DISTRHO_PLUGIN_TARGET_VST3)
#if !DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT
# ifdef DISTRHO_PLUGIN_TARGET_DSSI
# define DISTRHO_IS_STANDALONE 1
# else
# define DISTRHO_IS_STANDALONE 0
# endif
# include "src/DistrhoUtils.cpp"
#else
# ifdef DISTRHO_PLUGIN_TARGET_JACK
# define DISTRHO_IS_STANDALONE 1
# else
# define DISTRHO_IS_STANDALONE 0
# endif
#endif

#if defined(DISTRHO_UI_LINUX_WEBVIEW_START) && !DISTRHO_IS_STANDALONE
int main(int argc, char* argv[])
{
return DISTRHO_NAMESPACE::dpf_webview_start(argc, argv);
}
#endif

#endif

+ 149
- 33
source/modules/distrho/DistrhoUtils.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -19,10 +19,8 @@

#include "src/DistrhoDefines.h"

#if defined(DISTRHO_OS_WINDOWS) && !defined(_MSC_VER)
#include <winsock2.h>
#undef max
#undef min
#ifndef __STDC_LIMIT_MACROS
# define __STDC_LIMIT_MACROS
#endif

#include <cstdarg>
@@ -44,7 +42,7 @@
typedef SSIZE_T ssize_t;
#endif

#if ! defined(CARLA_MATH_UTILS_HPP_INCLUDED) && ! defined(DISTRHO_PROPER_CPP11_SUPPORT)
#if ! defined(CARLA_MATH_UTILS_HPP_INCLUDED) && ! defined(DISTRHO_PROPER_CPP11_SUPPORT) && ! defined(DISTRHO_OS_MAC)
namespace std {
inline float fmin(float __x, float __y)
{ return __builtin_fminf(__x, __y); }
@@ -61,10 +59,7 @@ inline float round(float __x)
# define M_PI 3.14159265358979323846
#endif

#define DISTRHO_MACRO_AS_STRING_VALUE(MACRO) #MACRO
#define DISTRHO_MACRO_AS_STRING(MACRO) DISTRHO_MACRO_AS_STRING_VALUE(MACRO)

/* ------------------------------------------------------------------------------------------------------------
/* --------------------------------------------------------------------------------------------------------------------
* misc functions */

/**
@@ -83,6 +78,15 @@ int64_t d_cconst(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_
return (a << 24) | (b << 16) | (c << 8) | (d << 0);
}

/**
Return a 32-bit number from 4 ASCII characters.
*/
static inline constexpr
uint32_t d_cconst(const char str[4])
{
return (str[0] << 24) | (str[1] << 16) | (str[2] << 8) | str[3];
}

/**
Return an hexadecimal representation of a MAJ.MIN.MICRO version number.
*/
@@ -100,7 +104,7 @@ void d_pass() noexcept {}

/** @} */

/* ------------------------------------------------------------------------------------------------------------
/* --------------------------------------------------------------------------------------------------------------------
* string print functions */

/**
@@ -109,6 +113,27 @@ void d_pass() noexcept {}
@{
*/

/*
* Internal noexcept-safe fopen function.
*/
static inline
FILE* __d_fopen(const char* const filename, FILE* const fallback) noexcept
{
if (std::getenv("DPF_CAPTURE_CONSOLE_OUTPUT") == nullptr)
return fallback;

FILE* ret = nullptr;

try {
ret = std::fopen(filename, "a+");
} catch (...) {}

if (ret == nullptr)
ret = fallback;

return ret;
}

/**
Print a string to stdout with newline (gray color).
Does nothing if DEBUG is not defined.
@@ -119,12 +144,30 @@ void d_pass() noexcept {}
static inline
void d_debug(const char* const fmt, ...) noexcept
{
static FILE* const output = __d_fopen("/tmp/dpf.debug.log", stdout);

try {
va_list args;
va_start(args, fmt);
std::fprintf(stdout, "\x1b[30;1m");
std::vfprintf(stdout, fmt, args);
std::fprintf(stdout, "\x1b[0m\n");

if (output == stdout)
{
#ifdef DISTRHO_OS_MAC
std::fprintf(output, "\x1b[37;1m[dpf] ");
#else
std::fprintf(output, "\x1b[30;1m[dpf] ");
#endif
std::vfprintf(output, fmt, args);
std::fprintf(output, "\x1b[0m\n");
}
else
{
std::fprintf(output, "[dpf] ");
std::vfprintf(output, fmt, args);
std::fprintf(output, "\n");
}

std::fflush(output);
va_end(args);
} catch (...) {}
}
@@ -136,11 +179,18 @@ void d_debug(const char* const fmt, ...) noexcept
static inline
void d_stdout(const char* const fmt, ...) noexcept
{
static FILE* const output = __d_fopen("/tmp/dpf.stdout.log", stdout);

try {
va_list args;
va_start(args, fmt);
std::vfprintf(stdout, fmt, args);
std::fprintf(stdout, "\n");
std::fprintf(output, "[dpf] ");
std::vfprintf(output, fmt, args);
std::fprintf(output, "\n");
#ifndef DEBUG
if (output != stdout)
#endif
std::fflush(output);
va_end(args);
} catch (...) {}
}
@@ -151,11 +201,18 @@ void d_stdout(const char* const fmt, ...) noexcept
static inline
void d_stderr(const char* const fmt, ...) noexcept
{
static FILE* const output = __d_fopen("/tmp/dpf.stderr.log", stderr);

try {
va_list args;
va_start(args, fmt);
std::vfprintf(stderr, fmt, args);
std::fprintf(stderr, "\n");
std::fprintf(output, "[dpf] ");
std::vfprintf(output, fmt, args);
std::fprintf(output, "\n");
#ifndef DEBUG
if (output != stderr)
#endif
std::fflush(output);
va_end(args);
} catch (...) {}
}
@@ -166,12 +223,26 @@ void d_stderr(const char* const fmt, ...) noexcept
static inline
void d_stderr2(const char* const fmt, ...) noexcept
{
static FILE* const output = __d_fopen("/tmp/dpf.stderr2.log", stderr);

try {
va_list args;
va_start(args, fmt);
std::fprintf(stderr, "\x1b[31m");
std::vfprintf(stderr, fmt, args);
std::fprintf(stderr, "\x1b[0m\n");

if (output == stdout)
{
std::fprintf(output, "\x1b[31m[dpf] ");
std::vfprintf(output, fmt, args);
std::fprintf(output, "\x1b[0m\n");
}
else
{
std::fprintf(output, "[dpf] ");
std::vfprintf(output, fmt, args);
std::fprintf(output, "\n");
}

std::fflush(output);
va_end(args);
} catch (...) {}
}
@@ -246,7 +317,7 @@ void d_safe_exception(const char* const exception, const char* const file, const

/** @} */

/* ------------------------------------------------------------------------------------------------------------
/* --------------------------------------------------------------------------------------------------------------------
* math functions */

/**
@@ -260,7 +331,7 @@ void d_safe_exception(const char* const exception, const char* const file, const
Returns true if they match.
*/
template<typename T>
static inline
static inline constexpr
bool d_isEqual(const T& v1, const T& v2)
{
return std::abs(v1-v2) < std::numeric_limits<T>::epsilon();
@@ -271,7 +342,7 @@ bool d_isEqual(const T& v1, const T& v2)
Returns true if they don't match.
*/
template<typename T>
static inline
static inline constexpr
bool d_isNotEqual(const T& v1, const T& v2)
{
return std::abs(v1-v2) >= std::numeric_limits<T>::epsilon();
@@ -281,7 +352,7 @@ bool d_isNotEqual(const T& v1, const T& v2)
Safely check if a floating point number is zero.
*/
template<typename T>
static inline
static inline constexpr
bool d_isZero(const T& value)
{
return std::abs(value) < std::numeric_limits<T>::epsilon();
@@ -291,7 +362,7 @@ bool d_isZero(const T& value)
Safely check if a floating point number is not zero.
*/
template<typename T>
static inline
static inline constexpr
bool d_isNotZero(const T& value)
{
return std::abs(value) >= std::numeric_limits<T>::epsilon();
@@ -315,17 +386,62 @@ uint32_t d_nextPowerOf2(uint32_t size) noexcept
return ++size;
}

/**
Round a floating point number to an integer.
Fast operation for values known to be 0 or positive.
*/
template<typename T>
static inline constexpr
int32_t d_roundToIntPositive(const T& value)
{
return static_cast<int32_t>(value + static_cast<T>(0.5));
}

/**
Round a floating point number to an unsigned integer.
Fast operation for values known to be 0 or positive.
*/
template<typename T>
static inline constexpr
uint32_t d_roundToUnsignedInt(const T& value)
{
return static_cast<uint32_t>(value + static_cast<T>(0.5));
}

/**
Round a floating point number to an integer.
Fast operation for values known to be 0 or negative.
*/
template<typename T>
static inline constexpr
int32_t d_roundToIntNegative(const T& value)
{
return static_cast<int32_t>(value - static_cast<T>(0.5));
}

/**
Round a floating point number to integer.
*/
template<typename T>
static inline constexpr
int32_t d_roundToInt(const T& value)
{
return value >= 0 ? static_cast<int32_t>(value + static_cast<T>(0.5))
: static_cast<int32_t>(value - static_cast<T>(0.5));
}

/** @} */

// -----------------------------------------------------------------------
/* --------------------------------------------------------------------------------------------------------------------
* other stuff */

#ifndef DONT_SET_USING_DISTRHO_NAMESPACE
// If your code uses a lot of DISTRHO classes, then this will obviously save you
// a lot of typing, but can be disabled by setting DONT_SET_USING_DISTRHO_NAMESPACE.
namespace DISTRHO_NAMESPACE {}
using namespace DISTRHO_NAMESPACE;
/**
If your code uses a lot of DISTRHO classes, then this will obviously save you a lot of typing,
but can be disabled by setting DONT_SET_USING_DISTRHO_NAMESPACE.
*/
namespace DISTRHO_NAMESPACE {}
using namespace DISTRHO_NAMESPACE;
#endif

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

#endif // DISTRHO_UTILS_HPP_INCLUDED

+ 2
- 2
source/modules/distrho/extra/LeakDetector.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2016 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2023 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -50,7 +50,7 @@ START_NAMESPACE_DISTRHO
#define DISTRHO_JOIN_MACRO_HELPER(a, b) a ## b
#define DISTRHO_JOIN_MACRO(item1, item2) DISTRHO_JOIN_MACRO_HELPER(item1, item2)

#ifdef DEBUG
#if defined(DPF_DEBUG) && !defined(NDEBUG)
/** This macro lets you embed a leak-detecting object inside a class.\n
To use it, simply declare a DISTRHO_LEAK_DETECTOR(YourClassName) inside a private section
of the class declaration. E.g.


+ 216
- 16
source/modules/distrho/extra/String.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -22,6 +22,10 @@

#include <algorithm>

#if __cplusplus >= 201703L
# include <string_view>
#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.
*/
@@ -591,7 +605,7 @@ public:
*/
String& toLower() noexcept
{
static const char kCharDiff('a' - 'A');
static constexpr const char kCharDiff = 'a' - 'A';

for (std::size_t i=0; i < fBufferLen; ++i)
{
@@ -607,7 +621,7 @@ public:
*/
String& toUpper() noexcept
{
static const char kCharDiff('a' - 'A');
static constexpr const char kCharDiff = 'a' - 'A';

for (std::size_t i=0; i < fBufferLen; ++i)
{
@@ -676,16 +690,16 @@ public:

static String asBase64(const void* const data, const std::size_t dataSize)
{
static const char* const kBase64Chars =
static constexpr const char* const kBase64Chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

#ifndef _MSC_VER
#ifndef _MSC_VER
const std::size_t kTmpBufSize = std::min(d_nextPowerOf2(static_cast<uint32_t>(dataSize/3)), 65536U);
#else
#else
constexpr std::size_t kTmpBufSize = 65536U;
#endif
#endif

const uchar* bytesToEncode((const uchar*)data);

@@ -749,6 +763,192 @@ public:
return ret;
}

/*
* Convert to a URL encoded string.
*/
String& urlEncode() noexcept
{
static constexpr const char* const kHexChars = "0123456789ABCDEF";

if (fBufferLen == 0)
return *this;

char* const newbuf = static_cast<char*>(std::malloc(fBufferLen * 3 + 1));
DISTRHO_SAFE_ASSERT_RETURN(newbuf != nullptr, *this);

char* newbufptr = newbuf;

for (std::size_t i=0; i < fBufferLen; ++i)
{
const char c = fBuffer[i];

switch (c)
{
case '!': // 33
case '#': // 35
case '$': // 36
case '&': // 38
case '\'': // 39
case '(': // 40
case ')': // 41
case '*': // 42
case '+': // 43
case ',': // 44
case '-': // 45
case '.': // 46
case '/': // 47
case '0': // 48
case '1': // 49
case '2': // 50
case '3': // 51
case '4': // 52
case '5': // 53
case '6': // 54
case '7': // 55
case '8': // 56
case '9': // 57
case ':': // 58
case ';': // 59
case '=': // 61
case '?': // 63
case '@': // 64
case 'A': // 65
case 'B': // 66
case 'C': // 67
case 'D': // 68
case 'E': // 69
case 'F': // 70
case 'G': // 71
case 'H': // 72
case 'I': // 73
case 'J': // 74
case 'K': // 75
case 'L': // 76
case 'M': // 77
case 'N': // 78
case 'O': // 79
case 'P': // 80
case 'Q': // 81
case 'R': // 82
case 'S': // 83
case 'T': // 84
case 'U': // 85
case 'V': // 86
case 'W': // 87
case 'X': // 88
case 'Y': // 89
case 'Z': // 90
case '[': // 91
case ']': // 93
case '_': // 95
case 'a': // 97
case 'b': // 98
case 'c': // 99
case 'd': // 100
case 'e': // 101
case 'f': // 102
case 'g': // 103
case 'h': // 104
case 'i': // 105
case 'j': // 106
case 'k': // 107
case 'l': // 108
case 'm': // 109
case 'n': // 110
case 'o': // 111
case 'p': // 112
case 'q': // 113
case 'r': // 114
case 's': // 115
case 't': // 116
case 'u': // 117
case 'v': // 118
case 'w': // 119
case 'x': // 120
case 'y': // 121
case 'z': // 122
case '~': // 126
*newbufptr++ = c;
break;
default:
*newbufptr++ = '%';
*newbufptr++ = kHexChars[(c >> 4) & 0xf];
*newbufptr++ = kHexChars[c & 0xf];
break;
}
}

*newbufptr = '\0';

std::free(fBuffer);
fBuffer = newbuf;
fBufferLen = std::strlen(newbuf);
fBufferAlloc = true;

return *this;
}

/*
* Convert to a URL decoded string.
*/
String& urlDecode() noexcept
{
if (fBufferLen == 0)
return *this;

char* const newbuf = static_cast<char*>(std::malloc(fBufferLen + 1));
DISTRHO_SAFE_ASSERT_RETURN(newbuf != nullptr, *this);

char* newbufptr = newbuf;

for (std::size_t i=0; i < fBufferLen; ++i)
{
const char c = fBuffer[i];

if (c == '%')
{
DISTRHO_SAFE_ASSERT_CONTINUE(fBufferLen > i + 2);

char c1 = fBuffer[i + 1];
char c2 = fBuffer[i + 2];
i += 2;

/**/ if (c1 >= '0' && c1 <= '9')
c1 -= '0';
else if (c1 >= 'A' && c1 <= 'Z')
c1 -= 'A' - 10;
else if (c1 >= 'a' && c1 <= 'z')
c1 -= 'a' - 10;
else
continue;

/**/ if (c2 >= '0' && c2 <= '9')
c2 -= '0';
else if (c2 >= 'A' && c2 <= 'Z')
c2 -= 'A' - 10;
else if (c2 >= 'a' && c2 <= 'z')
c2 -= 'a' - 10;
else
continue;

*newbufptr++ = c1 << 4 | c2;
}
else
{
*newbufptr++ = c;
}
}

*newbufptr = '\0';

std::free(fBuffer);
fBuffer = newbuf;
fBufferLen = std::strlen(newbuf);
fBufferAlloc = true;

return *this;
}

// -------------------------------------------------------------------
// public operators

@@ -830,7 +1030,7 @@ public:
}

// we have some data ourselves, reallocate to add the new stuff
char* const newBuf = (char*)realloc(fBuffer, fBufferLen + strBufLen + 1);
char* const newBuf = static_cast<char*>(std::realloc(fBuffer, fBufferLen + strBufLen + 1));
DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, *this);

std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1);
@@ -855,7 +1055,7 @@ public:

const std::size_t strBufLen = std::strlen(strBuf);
const std::size_t newBufSize = fBufferLen + strBufLen;
char* const newBuf = (char*)malloc(newBufSize + 1);
char* const newBuf = static_cast<char*>(std::malloc(newBufSize + 1));
DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String());

std::memcpy(newBuf, fBuffer, fBufferLen);
@@ -912,7 +1112,7 @@ private:
std::free(fBuffer);

fBufferLen = (size > 0) ? size : std::strlen(strBuf);
fBuffer = (char*)std::malloc(fBufferLen+1);
fBuffer = static_cast<char*>(std::malloc(fBufferLen + 1));

if (fBuffer == nullptr)
{
@@ -960,7 +1160,7 @@ String operator+(const String& strBefore, const char* const strBufAfter) noexcep
const std::size_t strBeforeLen = strBefore.length();
const std::size_t strBufAfterLen = std::strlen(strBufAfter);
const std::size_t newBufSize = strBeforeLen + strBufAfterLen;
char* const newBuf = (char*)malloc(newBufSize + 1);
char* const newBuf = static_cast<char*>(malloc(newBufSize + 1));
DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String());

std::memcpy(newBuf, strBefore.buffer(), strBeforeLen);
@@ -980,7 +1180,7 @@ String operator+(const char* const strBufBefore, const String& strAfter) noexcep
const std::size_t strBufBeforeLen = std::strlen(strBufBefore);
const std::size_t strAfterLen = strAfter.length();
const std::size_t newBufSize = strBufBeforeLen + strAfterLen;
char* const newBuf = (char*)malloc(newBufSize + 1);
char* const newBuf = static_cast<char*>(malloc(newBufSize + 1));
DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String());

std::memcpy(newBuf, strBufBefore, strBufBeforeLen);


+ 10
- 1
source/modules/distrho/src/DistrhoDefines.h View File

@@ -211,7 +211,15 @@ private: \
#endif

/* Useful macros */
#define ARRAY_SIZE(ARRAY) sizeof(ARRAY)/sizeof(ARRAY[0])
#define ARRAY_SIZE(ARRAY) (sizeof(ARRAY)/sizeof(ARRAY[0]))
#define STRINGIFY2(s) #s
#define STRINGIFY(s) STRINGIFY2(s)

#ifdef DISTRHO_PROPER_CPP11_SUPPORT
#define CPP_AGGREGATE_INIT(ClassName) ClassName
#else
#define CPP_AGGREGATE_INIT(ClassName) (ClassName)
#endif

/* Useful typedefs */
typedef unsigned char uchar;
@@ -223,5 +231,6 @@ typedef unsigned long long int ulonglong;
/* Deprecated macros */
#define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) DISTRHO_DECLARE_NON_COPYABLE(ClassName)
#define DISTRHO_DECLARE_NON_COPY_STRUCT(StructName) DISTRHO_DECLARE_NON_COPYABLE(StructName)
#define DISTRHO_MACRO_AS_STRING(MACRO) STRINGIFY2(MACRO)

#endif // DISTRHO_DEFINES_H_INCLUDED

+ 32
- 20
source/modules/distrho/src/DistrhoPlugin.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -25,6 +25,7 @@ uint32_t d_nextBufferSize = 0;
double d_nextSampleRate = 0.0;
const char* d_nextBundlePath = nullptr;
bool d_nextPluginIsDummy = false;
bool d_nextPluginIsSelfTest = false;
bool d_nextCanRequestParameterValueChanges = false;

/* ------------------------------------------------------------------------------------------------------------
@@ -42,45 +43,45 @@ const PortGroupWithId PluginExporter::sFallbackPortGroup;
Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCount)
: pData(new PrivateData())
{
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
pData->audioPorts = new AudioPortWithBusId[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS];
#endif
#endif

#ifdef DPF_ABORT_ON_ERROR
# define DPF_ABORT abort();
#else
# define DPF_ABORT
#endif
#if defined(DPF_ABORT_ON_ERROR) || defined(DPF_RUNTIME_TESTING)
#define DPF_ABORT abort();
#else
#define DPF_ABORT
#endif

if (parameterCount > 0)
{
pData->parameterCount = parameterCount;
pData->parameters = new Parameter[parameterCount];
pData->parameters = new Parameter[parameterCount];
}

if (programCount > 0)
{
#if DISTRHO_PLUGIN_WANT_PROGRAMS
#if DISTRHO_PLUGIN_WANT_PROGRAMS
pData->programCount = programCount;
pData->programNames = new String[programCount];
#else
#else
d_stderr2("DPF warning: Plugins with programs must define `DISTRHO_PLUGIN_WANT_PROGRAMS` to 1");
DPF_ABORT
#endif
#endif
}

if (stateCount > 0)
{
#if DISTRHO_PLUGIN_WANT_STATE
#if DISTRHO_PLUGIN_WANT_STATE
pData->stateCount = stateCount;
pData->states = new State[stateCount];
#else
pData->states = new State[stateCount];
#else
d_stderr2("DPF warning: Plugins with state must define `DISTRHO_PLUGIN_WANT_STATE` to 1");
DPF_ABORT
#endif
#endif
}

#undef DPF_ABORT
#undef DPF_ABORT
}

Plugin::~Plugin()
@@ -111,6 +112,11 @@ bool Plugin::isDummyInstance() const noexcept
return pData->isDummy;
}

bool Plugin::isSelfTestInstance() const noexcept
{
return pData->isSelfTest;
}

#if DISTRHO_PLUGIN_WANT_TIMEPOS
const TimePosition& Plugin::getTimePosition() const noexcept
{
@@ -119,7 +125,7 @@ const TimePosition& Plugin::getTimePosition() const noexcept
#endif

#if DISTRHO_PLUGIN_WANT_LATENCY
void Plugin::setLatency(uint32_t frames) noexcept
void Plugin::setLatency(const uint32_t frames) noexcept
{
pData->latency = frames;
}
@@ -189,7 +195,10 @@ void Plugin::initState(const uint32_t index, State& state)
uint hints = 0x0;
String stateKey, defaultStateValue;

#if defined(__clang__)
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4996)
#elif defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
@@ -199,7 +208,9 @@ void Plugin::initState(const uint32_t index, State& state)
initState(index, stateKey, defaultStateValue);
if (isStateFile(index))
hints = kStateIsFilenamePath;
#if defined(__clang__)
#if defined(_MSC_VER)
#pragma warning(pop)
#elif defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic pop
@@ -235,6 +246,7 @@ void Plugin::setState(const char*, const char*) {}

void Plugin::bufferSizeChanged(uint32_t) {}
void Plugin::sampleRateChanged(double) {}
void Plugin::ioChanged(uint16_t, uint16_t) {}

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



+ 141
- 41
source/modules/distrho/src/DistrhoPluginChecks.h View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -17,9 +17,13 @@
#ifndef DISTRHO_PLUGIN_CHECKS_H_INCLUDED
#define DISTRHO_PLUGIN_CHECKS_H_INCLUDED

#ifndef DISTRHO_DETAILS_HPP_INCLUDED
# error wrong include order
#endif

#include "DistrhoPluginInfo.h"

// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// Check if all required macros are defined

#ifndef DISTRHO_PLUGIN_NAME
@@ -38,17 +42,13 @@
# error DISTRHO_PLUGIN_URI undefined!
#endif

// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// Define optional macros if not done yet

#ifndef DISTRHO_PLUGIN_HAS_UI
# define DISTRHO_PLUGIN_HAS_UI 0
#endif

#ifndef DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# define DISTRHO_PLUGIN_HAS_EXTERNAL_UI 0
#endif

#ifndef DISTRHO_PLUGIN_IS_RT_SAFE
# define DISTRHO_PLUGIN_IS_RT_SAFE 0
#endif
@@ -65,6 +65,10 @@
# define DISTRHO_PLUGIN_WANT_LATENCY 0
#endif

#ifndef DISTRHO_PLUGIN_WANT_MIDI_AS_MPE
# define DISTRHO_PLUGIN_WANT_MIDI_AS_MPE 0
#endif

#ifndef DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
# define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0
#endif
@@ -91,47 +95,101 @@
#endif

#ifndef DISTRHO_UI_FILE_BROWSER
# if defined(DGL_FILE_BROWSER_DISABLED) || DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# define DISTRHO_UI_FILE_BROWSER 0
# else
# define DISTRHO_UI_FILE_BROWSER 1
# endif
# define DISTRHO_UI_FILE_BROWSER 0
#endif
#ifndef DISTRHO_UI_WEB_VIEW
# define DISTRHO_UI_WEB_VIEW 0
#endif

#ifndef DISTRHO_UI_USER_RESIZABLE
# define DISTRHO_UI_USER_RESIZABLE 0
#endif

// --------------------------------------------------------------------------------------------------------------------
// set UI type

#ifndef DISTRHO_UI_USE_CAIRO
# define DISTRHO_UI_USE_CAIRO 0
#endif

#ifndef DISTRHO_UI_USE_CUSTOM
# define DISTRHO_UI_USE_CUSTOM 0
#endif

#ifndef DISTRHO_UI_USE_EXTERNAL
# define DISTRHO_UI_USE_EXTERNAL 0
#endif

#ifndef DISTRHO_UI_USE_NANOVG
# define DISTRHO_UI_USE_NANOVG 0
#endif

// -----------------------------------------------------------------------
// Define DISTRHO_PLUGIN_HAS_EMBED_UI if needed
#ifndef DISTRHO_UI_USE_WEB_VIEW
# define DISTRHO_UI_USE_WEB_VIEW 0
#endif

#ifndef DISTRHO_PLUGIN_HAS_EMBED_UI
# if (defined(DGL_CAIRO) && defined(HAVE_CAIRO)) || (defined(DGL_OPENGL) && defined(HAVE_OPENGL))
# define DISTRHO_PLUGIN_HAS_EMBED_UI 1
# else
# define DISTRHO_PLUGIN_HAS_EMBED_UI 0
# endif
// --------------------------------------------------------------------------------------------------------------------
// Define DISTRHO_UI_WEB_VIEW if needed
#if DISTRHO_UI_USE_WEB_VIEW && !DISTRHO_UI_WEB_VIEW
# undef DISTRHO_UI_WEB_VIEW
# define DISTRHO_UI_WEB_VIEW 1
#endif

// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// Define DISTRHO_UI_URI if needed

#ifndef DISTRHO_UI_URI
# define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#DPF_UI"
#endif

// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// Test for wrong compiler macros

#if defined(DISTRHO_PLUGIN_HAS_EMBED_UI)
# warning DISTRHO_PLUGIN_HAS_EMBED_UI has been removed, it is now always on
#endif

#if defined(DISTRHO_PLUGIN_HAS_EXTERNAL_UI)
# error DISTRHO_PLUGIN_HAS_EXTERNAL_UI has been replaced by DISTRHO_UI_USE_EXTERNAL
#endif

#ifdef DISTRHO_UI_FILEBROWSER
# error typo detected use DISTRHO_UI_FILE_BROWSER instead of DISTRHO_UI_FILEBROWSER
#endif

#ifdef DISTRHO_UI_WEBVIEW
# error typo detected use DISTRHO_UI_WEB_VIEW instead of DISTRHO_UI_WEBVIEW
#endif

#ifdef DISTRHO_UI_USE_WEBVIEW
# error typo detected use DISTRHO_UI_USE_WEB_VIEW instead of DISTRHO_UI_USE_WEBVIEW
#endif

#if DISTRHO_UI_FILE_BROWSER && !defined(DGL_USE_FILE_BROWSER)
# error invalid build config: file browser requested but `USE_FILE_BROWSER` build option is not set
#endif

#if DISTRHO_UI_WEB_VIEW && !defined(DGL_USE_WEB_VIEW)
# error invalid build config: web view requested but `USE_WEB_VIEW` build option is not set
#endif

// --------------------------------------------------------------------------------------------------------------------
// Test if synth has audio outputs

#if DISTRHO_PLUGIN_IS_SYNTH && DISTRHO_PLUGIN_NUM_OUTPUTS == 0
# error Synths need audio output to work!
#endif

// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// Test if MIDI as MPE enabled where it doesn't make sense

#if DISTRHO_PLUGIN_WANT_MIDI_AS_MPE && ! (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT)
# error MIDI as MPE needs MIDI input or output to work!
#endif

// --------------------------------------------------------------------------------------------------------------------
// Enable MIDI input if synth, test if midi-input disabled when synth

#ifndef DISTRHO_PLUGIN_WANT_MIDI_INPUT
@@ -140,7 +198,7 @@
# error Synths need MIDI input to work!
#endif

// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// Enable state if plugin wants state files (deprecated)

#ifdef DISTRHO_PLUGIN_WANT_STATEFILES
@@ -152,7 +210,7 @@
# endif
#endif

// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// Enable full state if plugin exports presets

#if DISTRHO_PLUGIN_WANT_PROGRAMS && DISTRHO_PLUGIN_WANT_STATE && defined(DISTRHO_PLUGIN_WANT_FULL_STATE_WAS_NOT_SET)
@@ -161,35 +219,77 @@
# define DISTRHO_PLUGIN_WANT_FULL_STATE 1
#endif

// -----------------------------------------------------------------------
// Disable file browser if using external UI
// --------------------------------------------------------------------------------------------------------------------
// Disable UI if DGL is not available

#if DISTRHO_UI_FILE_BROWSER && DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# warning file browser APIs do not work for external UIs
# undef DISTRHO_UI_FILE_BROWSER 0
# define DISTRHO_UI_FILE_BROWSER 0
#if DISTRHO_PLUGIN_HAS_UI && !defined(HAVE_DGL)
# undef DISTRHO_PLUGIN_HAS_UI
# define DISTRHO_PLUGIN_HAS_UI 0
#endif

// -----------------------------------------------------------------------
// Disable UI if DGL or external UI is not available
// --------------------------------------------------------------------------------------------------------------------
// Make sure both default width and height are provided

#if (defined(DGL_CAIRO) && ! defined(HAVE_CAIRO)) || (defined(DGL_OPENGL) && ! defined(HAVE_OPENGL))
# undef DISTRHO_PLUGIN_HAS_EMBED_UI
# define DISTRHO_PLUGIN_HAS_EMBED_UI 0
#if defined(DISTRHO_UI_DEFAULT_WIDTH) && !defined(DISTRHO_UI_DEFAULT_HEIGHT)
# error DISTRHO_UI_DEFAULT_WIDTH is defined but DISTRHO_UI_DEFAULT_HEIGHT is not
#endif

#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# undef DISTRHO_PLUGIN_HAS_UI
# define DISTRHO_PLUGIN_HAS_UI 0
#if defined(DISTRHO_UI_DEFAULT_HEIGHT) && !defined(DISTRHO_UI_DEFAULT_WIDTH)
# error DISTRHO_UI_DEFAULT_HEIGHT is defined but DISTRHO_UI_DEFAULT_WIDTH is not
#endif

// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// Define DISTRHO_PLUGIN_AU_TYPE if needed

#ifndef DISTRHO_PLUGIN_AU_TYPE
# if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) && DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
# define DISTRHO_PLUGIN_AU_TYPE aumf /* kAudioUnitType_MusicEffect */
# elif (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) && DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
# define DISTRHO_PLUGIN_AU_TYPE aumu /* kAudioUnitType_MusicDevice */
# elif DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
# define DISTRHO_PLUGIN_AU_TYPE aumi /* kAudioUnitType_MIDIProcessor */
# elif DISTRHO_PLUGIN_NUM_INPUTS == 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
# define DISTRHO_PLUGIN_AU_TYPE augn /* kAudioUnitType_Generator */
# else
# define DISTRHO_PLUGIN_AU_TYPE aufx /* kAudioUnitType_Effect */
# endif
#endif

// --------------------------------------------------------------------------------------------------------------------
// Check that symbol macros are well defined

#ifdef DISTRHO_PROPER_CPP11_SUPPORT

#ifdef DISTRHO_PLUGIN_AU_TYPE
static_assert(sizeof(STRINGIFY(DISTRHO_PLUGIN_AU_TYPE)) == 5, "The macro DISTRHO_PLUGIN_AU_TYPE has incorrect length");
# if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0
static constexpr const char _aut[5] = STRINGIFY(DISTRHO_PLUGIN_AU_TYPE);
static_assert(_aut[0] != 'a' || _aut[0] != 'u' || _aut[0] != 'm' || _aut[0] != 'u',
"The 'aumu' type requires both audio input and output");
# endif
#endif

#ifdef DISTRHO_PLUGIN_BRAND_ID
static_assert(sizeof(STRINGIFY(DISTRHO_PLUGIN_BRAND_ID)) == 5, "The macro DISTRHO_PLUGIN_BRAND_ID has incorrect length");
#endif

#ifdef DISTRHO_PLUGIN_UNIQUE_ID
static_assert(sizeof(STRINGIFY(DISTRHO_PLUGIN_UNIQUE_ID)) == 5, "The macro DISTRHO_PLUGIN_UNIQUE_ID has incorrect length");
#endif

#endif

// --------------------------------------------------------------------------------------------------------------------
// Prevent users from messing about with DPF internals

#ifdef DISTRHO_UI_IS_STANDALONE
# error DISTRHO_UI_IS_STANDALONE must not be defined
#endif

// -----------------------------------------------------------------------
#ifdef DISTRHO_UI_LINUX_WEBVIEW_START
# error DISTRHO_UI_LINUX_WEBVIEW_START must not be defined
#endif

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

#endif // DISTRHO_PLUGIN_CHECKS_H_INCLUDED

+ 193
- 75
source/modules/distrho/src/DistrhoPluginInternal.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -39,6 +39,7 @@ extern uint32_t d_nextBufferSize;
extern double d_nextSampleRate;
extern const char* d_nextBundlePath;
extern bool d_nextPluginIsDummy;
extern bool d_nextPluginIsSelfTest;
extern bool d_nextCanRequestParameterValueChanges;

// -----------------------------------------------------------------------
@@ -67,7 +68,8 @@ struct PortGroupWithId : PortGroup {
groupId(kPortGroupNone) {}
};

static void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& portGroup)
static inline
void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& portGroup)
{
switch (groupId)
{
@@ -86,12 +88,62 @@ static void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& por
}
}

static inline
void d_strncpy(char* const dst, const char* const src, const size_t length)
{
DISTRHO_SAFE_ASSERT_RETURN(length > 0,);

if (const size_t len = std::min(std::strlen(src), length-1U))
{
std::memcpy(dst, src, len);
dst[len] = '\0';
}
else
{
dst[0] = '\0';
}
}

template<typename T>
static inline
void snprintf_t(char* const dst, const T value, const char* const format, const size_t size)
{
DISTRHO_SAFE_ASSERT_RETURN(size > 0,);
std::snprintf(dst, size-1, format, value);
dst[size-1] = '\0';
}

static inline
void snprintf_f32(char* const dst, const float value, const size_t size)
{
return snprintf_t<float>(dst, value, "%f", size);
}

static inline
void snprintf_f32(char* const dst, const double value, const size_t size)
{
return snprintf_t<double>(dst, value, "%f", size);
}

static inline
void snprintf_i32(char* const dst, const int32_t value, const size_t size)
{
return snprintf_t<int32_t>(dst, value, "%d", size);
}

static inline
void snprintf_u32(char* const dst, const uint32_t value, const size_t size)
{
return snprintf_t<uint32_t>(dst, value, "%u", size);
}

// -----------------------------------------------------------------------
// Plugin private data

struct Plugin::PrivateData {
const bool canRequestParameterValueChanges;
const bool isDummy;
const bool isSelfTest;
bool isProcessing;

#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
@@ -136,6 +188,7 @@ struct Plugin::PrivateData {
PrivateData() noexcept
: canRequestParameterValueChanges(d_nextCanRequestParameterValueChanges),
isDummy(d_nextPluginIsDummy),
isSelfTest(d_nextPluginIsSelfTest),
isProcessing(false),
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
audioPorts(nullptr),
@@ -256,6 +309,9 @@ struct Plugin::PrivateData {
#if DISTRHO_PLUGIN_WANT_STATE
bool updateStateValueCallback(const char* const key, const char* const value)
{
DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0', false);
DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, false);

d_stdout("updateStateValueCallback %p", updateStateValueCallbackFunc);
if (updateStateValueCallbackFunc != nullptr)
return updateStateValueCallbackFunc(callbacksPtr, key, value);
@@ -282,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<uint32_t> 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<uint32_t>(portGroupIndices.size()))
{
fData->portGroups = new PortGroupWithId[portGroupSize];
fData->portGroupCount = portGroupSize;

uint32_t index = 0;
for (std::set<uint32_t>::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.
@@ -324,16 +438,30 @@ public:
# if DISTRHO_PLUGIN_WANT_STATE
if (fData->stateCount != 0)
{
if ((void*)(fPlugin->*(&Plugin::initState)) == (void*)&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<void(Plugin::*)(uint32_t,State&)>(&Plugin::initState))) ==
(void*)static_cast<void(Plugin::*)(uint32_t,State&)>(&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
@@ -355,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<uint32_t> 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<uint32_t>(portGroupIndices.size()))
{
fData->portGroups = new PortGroupWithId[portGroupSize];
fData->portGroupCount = portGroupSize;

uint32_t index = 0;
for (std::set<uint32_t>::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;
@@ -593,6 +663,11 @@ public:
return (getParameterHints(index) & kParameterIsOutput) != 0x0;
}

bool isParameterInteger(const uint32_t index) const noexcept
{
return (getParameterHints(index) & kParameterIsInteger) != 0x0;
}

bool isParameterTrigger(const uint32_t index) const noexcept
{
return (getParameterHints(index) & kParameterIsTrigger) == kParameterIsTrigger;
@@ -697,6 +772,24 @@ public:
fPlugin->setParameterValue(index, value);
}

/*
bool getParameterIndexForSymbol(const char* const symbol, uint32_t& index)
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, false);

for (uint32_t i=0; i < fData->parameterCount; ++i)
{
if (fData->parameters[i].symbol == symbol)
{
index = i;
return true;
}
}

return false;
}
*/

uint32_t getPortGroupCount() const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0);
@@ -800,7 +893,16 @@ public:
return fData->states[index].description;
}

# if DISTRHO_PLUGIN_WANT_FULL_STATE
#ifdef __MOD_DEVICES__
const String& getStateFileTypes(const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString);

return fData->states[index].fileTypes;
}
#endif

#if DISTRHO_PLUGIN_WANT_FULL_STATE
String getStateValue(const char* const key) const
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackString);
@@ -808,7 +910,7 @@ public:

return fPlugin->getState(key);
}
# endif
#endif

void setState(const char* const key, const char* const value)
{
@@ -879,7 +981,7 @@ public:
}
}

#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
void run(const float** const inputs, float** const outputs, const uint32_t frames,
const MidiEvent* const midiEvents, const uint32_t midiEventCount)
{
@@ -896,7 +998,7 @@ public:
fPlugin->run(inputs, outputs, frames, midiEvents, midiEventCount);
fData->isProcessing = false;
}
#else
#else
void run(const float** const inputs, float** const outputs, const uint32_t frames)
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,);
@@ -912,7 +1014,21 @@ public:
fPlugin->run(inputs, outputs, frames);
fData->isProcessing = false;
}
#endif
#endif

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

#ifdef DISTRHO_PLUGIN_TARGET_AU
void setAudioPortIO(const uint16_t numInputs, const uint16_t numOutputs)
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,);
DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,);

if (fIsActive) fPlugin->deactivate();
fPlugin->ioChanged(numInputs, numOutputs);
if (fIsActive) fPlugin->activate();
}
#endif

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

@@ -928,14 +1044,14 @@ public:
return fData->sampleRate;
}

void setBufferSize(const uint32_t bufferSize, const bool doCallback = false)
bool setBufferSize(const uint32_t bufferSize, const bool doCallback = false)
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,);
DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, false);
DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, false);
DISTRHO_SAFE_ASSERT(bufferSize >= 2);

if (fData->bufferSize == bufferSize)
return;
return false;

fData->bufferSize = bufferSize;

@@ -945,6 +1061,8 @@ public:
fPlugin->bufferSizeChanged(bufferSize);
if (fIsActive) fPlugin->activate();
}

return true;
}

void setSampleRate(const double sampleRate, const bool doCallback = false)


+ 286
- 103
source/modules/distrho/src/DistrhoUI.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -14,8 +14,9 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include "DistrhoDetails.hpp"
#include "DistrhoPluginUtils.hpp"
#include "src/DistrhoPluginChecks.h"
#include "src/DistrhoDefines.h"

#include <cstddef>

@@ -25,6 +26,17 @@
# include <stdint.h>
#endif

#if defined(DISTRHO_OS_WASM)
# include <emscripten/emscripten.h>
#elif defined(DISTRHO_OS_WINDOWS)
# include <winsock2.h>
# include <windows.h>
#elif defined(HAVE_X11)
# define Window X11Window
# include <X11/Xresource.h>
# undef Window
#endif

#if DISTRHO_UI_FILE_BROWSER && !defined(DISTRHO_OS_MAC)
# define DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION
# define DISTRHO_PUGL_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION)
@@ -49,35 +61,29 @@
START_NAMESPACE_DISTRHO
# include "../extra/FileBrowserDialogImpl.hpp"
END_NAMESPACE_DISTRHO
# define Window X11Window
# include "../extra/FileBrowserDialogImpl.cpp"
# undef Window
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# if defined(DISTRHO_OS_WINDOWS)
# include <winsock2.h>
# include <windows.h>
# elif defined(HAVE_X11)
# include <X11/Xresource.h>
# endif
#else
# include "src/TopLevelWidgetPrivateData.hpp"
# include "src/WindowPrivateData.hpp"
#if DISTRHO_UI_WEB_VIEW && !defined(DISTRHO_OS_MAC)
# define DISTRHO_WEB_VIEW_HPP_INCLUDED
# define WEB_VIEW_NAMESPACE DISTRHO_NAMESPACE
# define WEB_VIEW_DISTRHO_NAMESPACE
START_NAMESPACE_DISTRHO
# include "../extra/WebViewImpl.hpp"
END_NAMESPACE_DISTRHO
# define Window X11Window
# include "../extra/WebViewImpl.cpp"
# undef Window
#endif

#include "src/TopLevelWidgetPrivateData.hpp"
#include "src/WindowPrivateData.hpp"
#include "DistrhoUIPrivateData.hpp"

START_NAMESPACE_DISTRHO

/* ------------------------------------------------------------------------------------------------------------
* Static data, see DistrhoUIInternal.hpp */

const char* g_nextBundlePath = nullptr;
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
uintptr_t g_nextWindowId = 0;
double g_nextScaleFactor = 1.0;
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
/* ------------------------------------------------------------------------------------------------------------
* get global scale factor */

@@ -90,23 +96,25 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
if (const char* const scale = getenv("DPF_SCALE_FACTOR"))
return std::max(1.0, std::atof(scale));

#if defined(DISTRHO_OS_WINDOWS)
#if defined(DISTRHO_OS_WASM)
return emscripten_get_device_pixel_ratio();
#elif defined(DISTRHO_OS_WINDOWS)
if (const HMODULE Shcore = LoadLibraryA("Shcore.dll"))
{
typedef HRESULT(WINAPI* PFN_GetProcessDpiAwareness)(HANDLE, DWORD*);
typedef HRESULT(WINAPI* PFN_GetScaleFactorForMonitor)(HMONITOR, DWORD*);

# if defined(__GNUC__) && (__GNUC__ >= 9)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#if defined(__GNUC__) && (__GNUC__ >= 9)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
const PFN_GetProcessDpiAwareness GetProcessDpiAwareness
= (PFN_GetProcessDpiAwareness)GetProcAddress(Shcore, "GetProcessDpiAwareness");
const PFN_GetScaleFactorForMonitor GetScaleFactorForMonitor
= (PFN_GetScaleFactorForMonitor)GetProcAddress(Shcore, "GetScaleFactorForMonitor");
# if defined(__GNUC__) && (__GNUC__ >= 9)
# pragma GCC diagnostic pop
# endif
#if defined(__GNUC__) && (__GNUC__ >= 9)
#pragma GCC diagnostic pop
#endif

DWORD dpiAware = 0;
DWORD scaleFactor = 100;
@@ -115,14 +123,14 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
{
const HMONITOR hMon = parentWindowHandle != 0
? MonitorFromWindow((HWND)parentWindowHandle, MONITOR_DEFAULTTOPRIMARY)
: MonitorFromPoint(POINT{0,0}, MONITOR_DEFAULTTOPRIMARY);
: MonitorFromPoint(POINT(), MONITOR_DEFAULTTOPRIMARY);
GetScaleFactorForMonitor(hMon, &scaleFactor);
}

FreeLibrary(Shcore);
return static_cast<double>(scaleFactor) / 100.0;
}
#elif defined(HAVE_X11)
#elif defined(HAVE_X11)
::Display* const display = XOpenDisplay(nullptr);
DISTRHO_SAFE_ASSERT_RETURN(display != nullptr, 1.0);

@@ -153,7 +161,7 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle)

XCloseDisplay(display);
return dpi / 96;
#endif
#endif

return 1.0;

@@ -162,50 +170,173 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
}
#endif // !DISTRHO_OS_MAC

#endif

/* ------------------------------------------------------------------------------------------------------------
* UI::PrivateData special handling */

UI::PrivateData* UI::PrivateData::s_nextPrivateData = nullptr;

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
ExternalWindow::PrivateData
#else
PluginWindow&
#endif
UI::PrivateData::createNextWindow(UI* const ui, const uint width, const uint height)
PluginWindow& UI::PrivateData::createNextWindow(UI* const ui, uint width, uint height)
{
UI::PrivateData* const pData = s_nextPrivateData;
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
pData->window = new PluginWindow(ui, pData->app);
ExternalWindow::PrivateData ewData;
ewData.parentWindowHandle = pData->winId;
ewData.width = width;
ewData.height = height;
ewData.scaleFactor = pData->scaleFactor != 0.0 ? pData->scaleFactor : getDesktopScaleFactor(pData->winId);
ewData.title = DISTRHO_PLUGIN_NAME;
ewData.isStandalone = DISTRHO_UI_IS_STANDALONE;
return ewData;
#else
pData->window = new PluginWindow(ui, pData->app, pData->winId, width, height, pData->scaleFactor);
UI::PrivateData* const uiData = s_nextPrivateData;
const double scaleFactor = d_isNotZero(uiData->scaleFactor) ? uiData->scaleFactor : getDesktopScaleFactor(uiData->winId);

// If there are no callbacks, this is most likely a temporary window, so ignore idle callbacks
if (pData->callbacksPtr == nullptr)
pData->window->setIgnoreIdleCallbacks();
if (d_isNotZero(scaleFactor) && d_isNotEqual(scaleFactor, 1.0))
{
width *= scaleFactor;
height *= scaleFactor;
}

d_stdout("createNextWindow %u %u %f", width, height, scaleFactor);
uiData->window = new PluginWindow(ui, uiData->app, uiData->winId, width, height, scaleFactor);

if (uiData->callbacksPtr != nullptr)
{
#if DISTRHO_UI_USE_WEB_VIEW
String path;
if (uiData->bundlePath != nullptr)
{
path = getResourcePath(uiData->bundlePath);
}
else
{
path = getBinaryFilename();
path.truncate(path.rfind(DISTRHO_OS_SEP));
path += "/resources";
}

return pData->window.getObject();
path.urlEncode();

// TODO convert win32 paths to web

WebViewOptions opts;
opts.initialJS = ""
"editParameter = function(index, started){ postMessage('editparam ' + index + ' ' + (started ? '1' : '0')) };"
"setParameterValue = function(index, value){ postMessage('setparam ' + index + ' ' + value) };"
#if DISTRHO_PLUGIN_WANT_STATE
"setState = function(key, value){ postMessage('setstate ' + key + ' ' + value) };"
"requestStateFile = function(key){ postMessage('reqstatefile ' + key) };"
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
"sendNote = function(channel, note, velocity){ postMessage('sendnote ' + channel + ' ' + note + ' ' + velocity) };"
#endif
;
opts.callback = webViewMessageCallback;
opts.callbackPtr = uiData;
uiData->webview = webViewCreate("file://" + path + "/index.html",
uiData->winId != 0 ? uiData->winId : uiData->window->getNativeWindowHandle(),
width,
height,
scaleFactor,
opts);
#endif
}
// If there are no callbacks, this is most likely a temporary window, so ignore idle callbacks
else
{
uiData->window->setIgnoreIdleCallbacks();
}

return uiData->window.getObject();
}

#if DISTRHO_UI_USE_WEB_VIEW
void UI::PrivateData::webViewMessageCallback(void* const arg, char* const msg)
{
UI::PrivateData* const uiData = static_cast<UI::PrivateData*>(arg);

if (std::strncmp(msg, "setparam ", 9) == 0)
{
const char* const strindex = msg + 9;
char* strvalue = nullptr;
const ulong index = std::strtoul(strindex, &strvalue, 10);
DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,);

float value;
{
const ScopedSafeLocale ssl;
value = std::atof(strvalue);
}
uiData->setParamCallback(index + uiData->parameterOffset, value);
return;
}

if (std::strncmp(msg, "editparam ", 10) == 0)
{
const char* const strindex = msg + 10;
char* strvalue = nullptr;
const ulong index = std::strtoul(strindex, &strvalue, 10);
DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,);

const bool started = strvalue[0] != '0';
uiData->editParamCallback(index + uiData->parameterOffset, started);
return;
}

#if DISTRHO_PLUGIN_WANT_STATE
if (std::strncmp(msg, "setstate ", 9) == 0)
{
char* const key = msg + 9;
char* const sep = std::strchr(key, ' ');
DISTRHO_SAFE_ASSERT_RETURN(sep != nullptr,);
*sep = '\0';
char* const value = sep + 1;

uiData->setStateCallback(key, value);
return;
}

if (std::strncmp(msg, "reqstatefile ", 13) == 0)
{
const char* const key = msg + 13;
uiData->fileRequestCallback(key);
return;
}
#endif

#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
if (std::strncmp(msg, "sendnote ", 9) == 0)
{
const char* const strchannel = msg + 9;
char* strnote = nullptr;
char* strvelocity = nullptr;
char* end = nullptr;

const ulong channel = std::strtoul(strchannel, &strnote, 10);
DISTRHO_SAFE_ASSERT_RETURN(strnote != nullptr && strchannel != strnote,);

const ulong note = std::strtoul(strnote, &strvelocity, 10);
DISTRHO_SAFE_ASSERT_RETURN(strvelocity != nullptr && strchannel != strvelocity,);

const ulong velocity = std::strtoul(strvelocity, &end, 10);
DISTRHO_SAFE_ASSERT_RETURN(end != nullptr && strvelocity != end,);

uiData->sendNoteCallback(channel, note, velocity);
return;
}
#endif

d_stderr("UI received unknown message '%s'", msg);
}
#endif

/* ------------------------------------------------------------------------------------------------------------
* UI */

UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetAsMinimumSize)
: UIWidget(UI::PrivateData::createNextWindow(this, width, height)),
: UIWidget(UI::PrivateData::createNextWindow(this,
// width
#ifdef DISTRHO_UI_DEFAULT_WIDTH
width == 0 ? DISTRHO_UI_DEFAULT_WIDTH :
#endif
width,
// height
#ifdef DISTRHO_UI_DEFAULT_HEIGHT
height == 0 ? DISTRHO_UI_DEFAULT_HEIGHT :
#endif
height
)),
uiData(UI::PrivateData::s_nextPrivateData)
{
#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
if (width != 0 && height != 0)
{
Widget::setSize(width, height);
@@ -213,14 +344,20 @@ UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetA
if (automaticallyScaleAndSetAsMinimumSize)
setGeometryConstraints(width, height, true, true, true);
}
#else
// unused
(void)automaticallyScaleAndSetAsMinimumSize;
#endif
#ifdef DISTRHO_UI_DEFAULT_WIDTH
else
{
Widget::setSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT);
}
#endif
}

UI::~UI()
{
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
webViewDestroy(uiData->webview);
#endif
}

/* ------------------------------------------------------------------------------------------------------------
@@ -228,15 +365,11 @@ UI::~UI()

bool UI::isResizable() const noexcept
{
#if DISTRHO_UI_USER_RESIZABLE
# if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
return true;
# else
#if DISTRHO_UI_USER_RESIZABLE
return uiData->window->isResizable();
# endif
#else
#else
return false;
#endif
#endif
}

uint UI::getBackgroundColor() const noexcept
@@ -307,33 +440,94 @@ void* UI::getPluginInstancePointer() const noexcept
}
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
/* ------------------------------------------------------------------------------------------------------------
* External UI helpers (static calls) */
* DSP/Plugin Callbacks */

const char* UI::getNextBundlePath() noexcept
void UI::parameterChanged(const uint32_t index, const float value)
{
return g_nextBundlePath;
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
{
char msg[128];
{
const ScopedSafeLocale ssl;
std::snprintf(msg, sizeof(msg) - 1,
"typeof(parameterChanged) === 'function' && parameterChanged(%u,%f)", index, value);
}
webViewEvaluateJS(uiData->webview, msg);
}
#else
// unused
(void)index;
(void)value;
#endif
}

double UI::getNextScaleFactor() noexcept
#if DISTRHO_PLUGIN_WANT_PROGRAMS
void UI::programLoaded(const uint32_t index)
{
return g_nextScaleFactor;
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
{
char msg[128];
std::snprintf(msg, sizeof(msg) - 1,
"typeof(programLoaded) === 'function' && programLoaded(%u)", index);
webViewEvaluateJS(uiData->webview, msg);
}
#else
// unused
(void)index;
#endif
}
#endif

# if DISTRHO_PLUGIN_HAS_EMBED_UI
uintptr_t UI::getNextWindowId() noexcept
#if DISTRHO_PLUGIN_WANT_STATE
void UI::stateChanged(const char* const key, const char* const value)
{
return g_nextWindowId;
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
{
const size_t keylen = std::strlen(key);
const size_t valuelen = std::strlen(value);
const size_t msglen = keylen + valuelen + 60;
if (char* const msg = static_cast<char*>(std::malloc(msglen)))
{
// TODO escape \\'
std::snprintf(msg, msglen - 1,
"typeof(stateChanged) === 'function' && stateChanged('%s','%s')", key, value);
msg[msglen - 1] = '\0';
webViewEvaluateJS(uiData->webview, msg);
std::free(msg);
}
}
#else
// unused
(void)key;
(void)value;
#endif
}
# endif
#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#endif

/* ------------------------------------------------------------------------------------------------------------
* DSP/Plugin Callbacks (optional) */

void UI::sampleRateChanged(double)
void UI::sampleRateChanged(const double sampleRate)
{
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
{
char msg[128];
{
const ScopedSafeLocale ssl;
std::snprintf(msg, sizeof(msg) - 1,
"typeof(sampleRateChanged) === 'function' && sampleRateChanged(%f)", sampleRate);
}
webViewEvaluateJS(uiData->webview, msg);
}
#else
// unused
(void)sampleRate;
#endif
}

/* ------------------------------------------------------------------------------------------------------------
@@ -343,7 +537,6 @@ void UI::uiScaleFactorChanged(double)
{
}

#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
std::vector<DGL_NAMESPACE::ClipboardDataOffer> UI::getClipboardDataOfferTypes()
{
return uiData->window->getClipboardDataOfferTypes();
@@ -367,12 +560,11 @@ void UI::uiFocus(bool, DGL_NAMESPACE::CrossingMode)
{
}

void UI::uiReshape(uint, uint)
void UI::uiReshape(const uint width, const uint height)
{
// NOTE this must be the same as Window::onReshape
pData->fallbackOnResize();
pData->fallbackOnResize(width, height);
}
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI

#if DISTRHO_UI_FILE_BROWSER
void UI::uiFileBrowserSelected(const char*)
@@ -383,43 +575,34 @@ void UI::uiFileBrowserSelected(const char*)
/* ------------------------------------------------------------------------------------------------------------
* UI Resize Handling, internal */

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
void UI::sizeChanged(const uint width, const uint height)
{
UIWidget::sizeChanged(width, height);

uiData->setSizeCallback(width, height);
}
#else
void UI::onResize(const ResizeEvent& ev)
{
UIWidget::onResize(ev);

#ifndef DISTRHO_PLUGIN_TARGET_VST3
#if ! DISTRHO_UI_USES_SIZE_REQUEST
if (uiData->initializing)
return;

const uint width = ev.size.getWidth();
const uint height = ev.size.getHeight();
uiData->setSizeCallback(width, height);
#endif
#endif
}

// NOTE: only used for VST3
// NOTE: only used for CLAP and VST3
void UI::requestSizeChange(const uint width, const uint height)
{
# ifdef DISTRHO_PLUGIN_TARGET_VST3
#if DISTRHO_UI_USES_SIZE_REQUEST
if (uiData->initializing)
uiData->window->setSizeForVST3(width, height);
uiData->window->setSizeFromHost(width, height);
else
uiData->setSizeCallback(width, height);
# else
#else
// unused
(void)width;
(void)height;
# endif
#endif
}
#endif

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



+ 45
- 68
source/modules/distrho/src/DistrhoUIInternal.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -21,15 +21,6 @@

START_NAMESPACE_DISTRHO

// -----------------------------------------------------------------------
// Static data, see DistrhoUI.cpp

extern const char* g_nextBundlePath;
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
extern uintptr_t g_nextWindowId;
extern double g_nextScaleFactor;
#endif

// -----------------------------------------------------------------------
// UI exporter class

@@ -57,9 +48,10 @@ public:
void* const dspPtr = nullptr,
const double scaleFactor = 0.0,
const uint32_t bgColor = 0,
const uint32_t fgColor = 0xffffffff)
const uint32_t fgColor = 0xffffffff,
const char* const appClassName = nullptr)
: ui(nullptr),
uiData(new UI::PrivateData())
uiData(new UI::PrivateData(appClassName))
{
uiData->sampleRate = sampleRate;
uiData->bundlePath = bundlePath != nullptr ? strdup(bundlePath) : nullptr;
@@ -78,41 +70,23 @@ public:
uiData->setSizeCallbackFunc = setSizeCall;
uiData->fileRequestCallbackFunc = fileRequestCall;

g_nextBundlePath = bundlePath;
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
g_nextWindowId = winId;
g_nextScaleFactor = scaleFactor;
#endif
UI::PrivateData::s_nextPrivateData = uiData;

UI* const uiPtr = createUI();

g_nextBundlePath = nullptr;
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
g_nextWindowId = 0;
g_nextScaleFactor = 0.0;
#else
// enter context called in the PluginWindow constructor, see DistrhoUIPrivateData.hpp
uiData->window->leaveContext();
#endif
UI::PrivateData::s_nextPrivateData = nullptr;

DISTRHO_SAFE_ASSERT_RETURN(uiPtr != nullptr,);
ui = uiPtr;
uiData->initializing = false;

#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
// unused
(void)bundlePath;
#endif
}

~UIExporter()
{
quit();
#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
uiData->window->enterContextForDeletion();
#endif
delete ui;
delete uiData;
}
@@ -136,13 +110,9 @@ public:

bool getGeometryConstraints(uint& minimumWidth, uint& minimumHeight, bool& keepAspectRatio) const noexcept
{
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
uiData->window->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio);
#else
const DGL_NAMESPACE::Size<uint> size(uiData->window->getGeometryConstraints(keepAspectRatio));
minimumWidth = size.getWidth();
minimumHeight = size.getHeight();
#endif
return true;
}

@@ -193,16 +163,16 @@ public:
ui->parameterChanged(index, value);
}

#if DISTRHO_PLUGIN_WANT_PROGRAMS
#if DISTRHO_PLUGIN_WANT_PROGRAMS
void programLoaded(const uint32_t index)
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);

ui->programLoaded(index);
}
#endif
#endif

#if DISTRHO_PLUGIN_WANT_STATE
#if DISTRHO_PLUGIN_WANT_STATE
void stateChanged(const char* const key, const char* const value)
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
@@ -211,11 +181,11 @@ public:

ui->stateChanged(key, value);
}
#endif
#endif

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

#if DISTRHO_UI_IS_STANDALONE
#if DISTRHO_UI_IS_STANDALONE
void exec(DGL_NAMESPACE::IdleCallback* const cb)
{
DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,);
@@ -224,13 +194,20 @@ public:
uiData->window->focus();
uiData->app.addIdleCallback(cb);
uiData->app.exec();
uiData->app.removeIdleCallback(cb);
}

void exec_idle()
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, );

#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
webViewIdle(uiData->webview);
#endif

ui->uiIdle();
uiData->app.repaintIfNeeeded();
}

void showAndFocus()
@@ -238,14 +215,21 @@ public:
uiData->window->show();
uiData->window->focus();
}
#endif
#endif

bool plugin_idle()
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false);

uiData->app.idle();

#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
webViewIdle(uiData->webview);
#endif

ui->uiIdle();
uiData->app.repaintIfNeeeded();
return ! uiData->app.isQuitting();
}

@@ -260,52 +244,53 @@ public:
uiData->app.quit();
}

void repaint()
{
uiData->window->repaint();
}

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

#if defined(DISTRHO_PLUGIN_TARGET_VST3) && (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS))
void idleForVST3()
#if defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)
void idleFromNativeIdle()
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);

uiData->app.triggerIdleCallbacks();

#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
webViewIdle(uiData->webview);
#endif

ui->uiIdle();
uiData->app.repaintIfNeeeded();
}

# if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
void addIdleCallbackForVST3(IdleCallback* const cb, const uint timerFrequencyInMs)
void addIdleCallbackForNativeIdle(DGL_NAMESPACE::IdleCallback* const cb, const uint timerFrequencyInMs)
{
uiData->window->addIdleCallback(cb, timerFrequencyInMs);
}

void removeIdleCallbackForVST3(IdleCallback* const cb)
void removeIdleCallbackForNativeIdle(DGL_NAMESPACE::IdleCallback* const cb)
{
uiData->window->removeIdleCallback(cb);
}
# endif
#endif
#endif

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

void setWindowOffset(const int x, const int y)
{
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
// TODO
(void)x; (void)y;
#else
uiData->window->setOffset(x, y);
#endif
}

#ifdef DISTRHO_PLUGIN_TARGET_VST3
void setWindowSizeForVST3(const uint width, const uint height)
#if DISTRHO_UI_USES_SIZE_REQUEST
void setWindowSizeFromHost(const uint width, const uint height)
{
# if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
ui->setSize(width, height);
# else
uiData->window->setSizeForVST3(width, height);
# endif
uiData->window->setSizeFromHost(width, height);
}
#endif
#endif

void setWindowTitle(const char* const uiTitle)
{
@@ -314,11 +299,7 @@ public:

void setWindowTransientWinId(const uintptr_t transientParentWindowHandle)
{
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
ui->setTransientWindowId(transientParentWindowHandle);
#else
uiData->window->setTransientParent(transientParentWindowHandle);
#endif
}

bool setWindowVisible(const bool yesNo)
@@ -328,7 +309,6 @@ public:
return ! uiData->app.isQuitting();
}

#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
bool handlePluginKeyboardVST(const bool press, const bool special, const uint keychar, const uint keycode, const uint16_t mods)
{
using namespace DGL_NAMESPACE;
@@ -361,7 +341,6 @@ public:

return ret;
}
#endif

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

@@ -372,14 +351,12 @@ public:
ui->uiScaleFactorChanged(scaleFactor);
}

#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
void notifyFocusChanged(const bool focus)
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);

ui->uiFocus(focus, DGL_NAMESPACE::kCrossingNormal);
}
#endif

void setSampleRate(const double sampleRate, const bool doCallback = false)
{


+ 112
- 146
source/modules/distrho/src/DistrhoUIPrivateData.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -23,13 +23,17 @@
# include "DistrhoPluginVST.hpp"
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# include "../extra/Sleep.hpp"
// TODO import and use file browser here
#else
# include "../../dgl/src/ApplicationPrivateData.hpp"
# include "../../dgl/src/WindowPrivateData.hpp"
# include "../../dgl/src/pugl.hpp"
#include "../../dgl/src/ApplicationPrivateData.hpp"
#include "../../dgl/src/WindowPrivateData.hpp"
#include "../../dgl/src/pugl.hpp"

#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER
# include <map>
# include <string>
#endif

#if DISTRHO_UI_USE_WEB_VIEW
# include "extra/WebView.hpp"
#endif

#if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_DSSI)
@@ -38,87 +42,57 @@
# define DISTRHO_UI_IS_STANDALONE 0
#endif

#ifdef DISTRHO_PLUGIN_TARGET_VST3
# define DISTRHO_UI_IS_VST3 1
#if defined(DISTRHO_PLUGIN_TARGET_AU)
# define DISTRHO_UI_USES_SCHEDULED_REPAINTS 1
#else
# define DISTRHO_UI_IS_VST3 0
# define DISTRHO_UI_USES_SCHEDULED_REPAINTS 0
#endif

#ifdef DISTRHO_PLUGIN_TARGET_VST2
#if defined(DISTRHO_PLUGIN_TARGET_CLAP) || defined(DISTRHO_PLUGIN_TARGET_VST3)
# define DISTRHO_UI_USES_SIZE_REQUEST 1
#else
# define DISTRHO_UI_USES_SIZE_REQUEST 0
#endif

#if defined(DISTRHO_PLUGIN_TARGET_AU) || defined(DISTRHO_PLUGIN_TARGET_VST2)
# undef DISTRHO_UI_USER_RESIZABLE
# define DISTRHO_UI_USER_RESIZABLE 0
#endif

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

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
struct PluginApplication
{
DGL_NAMESPACE::IdleCallback* idleCallback;
UI* ui;

explicit PluginApplication()
: idleCallback(nullptr),
ui(nullptr) {}

void addIdleCallback(DGL_NAMESPACE::IdleCallback* const cb)
{
DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,);
DISTRHO_SAFE_ASSERT_RETURN(idleCallback == nullptr,);

idleCallback = cb;
}

bool isQuitting() const noexcept
{
return ui->isQuitting();
}

bool isStandalone() const noexcept
{
return DISTRHO_UI_IS_STANDALONE;
}

void exec()
{
while (ui->isRunning())
{
d_msleep(30);
idleCallback->idleCallback();
}

if (! ui->isQuitting())
ui->close();
}

// these are not needed
void idle() {}
void quit() {}
void triggerIdleCallbacks() {}

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication)
};
#else
class PluginApplication : public DGL_NAMESPACE::Application
{
public:
explicit PluginApplication()
explicit PluginApplication(const char* className)
: DGL_NAMESPACE::Application(DISTRHO_UI_IS_STANDALONE)
{
#ifndef DISTRHO_OS_WASM
const char* const className = (
#ifdef DISTRHO_PLUGIN_BRAND
DISTRHO_PLUGIN_BRAND
#else
DISTRHO_MACRO_AS_STRING(DISTRHO_NAMESPACE)
#endif
"-" DISTRHO_PLUGIN_NAME
);
#if defined(__MOD_DEVICES__) || !defined(__EMSCRIPTEN__)
if (className == nullptr)
{
className = (
#ifdef DISTRHO_PLUGIN_BRAND
DISTRHO_PLUGIN_BRAND
#else
DISTRHO_MACRO_AS_STRING(DISTRHO_NAMESPACE)
#endif
"-" DISTRHO_PLUGIN_NAME
);
}
setClassName(className);
#endif
#else
// unused
(void)className;
#endif
}

void triggerIdleCallbacks()
@@ -126,49 +100,17 @@ public:
pData->triggerIdleCallbacks();
}

void repaintIfNeeeded()
{
pData->repaintIfNeeeded();
}

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication)
};
#endif

// -----------------------------------------------------------------------
// Plugin Window, will pass some Window events to UI

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
class PluginWindow
{
UI* const ui;

public:
explicit PluginWindow(UI* const uiPtr, PluginApplication& app)
: ui(uiPtr)
{
app.ui = ui;
}

// fetch cached data
uint getWidth() const noexcept { return ui->pData.width; }
uint getHeight() const noexcept { return ui->pData.height; }
double getScaleFactor() const noexcept { return ui->pData.scaleFactor; }

// direct mappings
void close() { ui->close(); }
void focus() { ui->focus(); }
void show() { ui->show(); }
bool isResizable() const noexcept { return ui->isResizable(); }
bool isVisible() const noexcept { return ui->isVisible(); }
void setTitle(const char* const title) { ui->setTitle(title); }
void setVisible(const bool visible) { ui->setVisible(visible); }
uintptr_t getNativeWindowHandle() const noexcept { return ui->getNativeWindowHandle(); }
void getGeometryConstraints(uint& minimumWidth, uint& minimumHeight, bool& keepAspectRatio) const noexcept
{
minimumWidth = ui->pData.minWidth;
minimumHeight = ui->pData.minHeight;
keepAspectRatio = ui->pData.keepAspectRatio;
}

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow)
};
#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI
class PluginWindow : public DGL_NAMESPACE::Window
{
UI* const ui;
@@ -183,7 +125,10 @@ public:
const uint height,
const double scaleFactor)
: Window(app, parentWindowHandle, width, height, scaleFactor,
DISTRHO_UI_USER_RESIZABLE, DISTRHO_UI_IS_VST3, false),
DISTRHO_UI_USER_RESIZABLE,
DISTRHO_UI_USES_SCHEDULED_REPAINTS,
DISTRHO_UI_USES_SIZE_REQUEST,
false),
ui(uiPtr),
initializing(true),
receivedReshapeDuringInit(false)
@@ -208,14 +153,18 @@ public:
if (pData->view == nullptr)
return;

if (receivedReshapeDuringInit)
ui->uiReshape(getWidth(), getHeight());

initializing = false;
puglBackendLeave(pData->view);

if (receivedReshapeDuringInit)
{
puglBackendEnter(pData->view);
ui->uiReshape(getWidth(), getHeight());
puglBackendLeave(pData->view);
}
}

// used for temporary windows (VST2/3 get size without active/visible view)
// used for temporary windows (VST/CLAP get size without active/visible view)
void setIgnoreIdleCallbacks(const bool ignore = true)
{
pData->ignoreIdleCallbacks = ignore;
@@ -228,8 +177,8 @@ public:
puglBackendEnter(pData->view);
}

#ifdef DISTRHO_PLUGIN_TARGET_VST3
void setSizeForVST3(const uint width, const uint height)
#if DISTRHO_UI_USES_SIZE_REQUEST
void setSizeFromHost(const uint width, const uint height)
{
puglSetSizeAndDefault(pData->view, width, height);
}
@@ -284,13 +233,12 @@ protected:
ui->uiScaleFactorChanged(scaleFactor);
}

# if DISTRHO_UI_FILE_BROWSER
#if DISTRHO_UI_FILE_BROWSER
void onFileSelected(const char* filename) override;
# endif
#endif

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow)
};
#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI

// -----------------------------------------------------------------------
// UI callbacks
@@ -309,6 +257,9 @@ struct UI::PrivateData {
// DGL
PluginApplication app;
ScopedPointer<PluginWindow> window;
#if DISTRHO_UI_USE_WEB_VIEW
WebViewHandle webview;
#endif

// DSP
double sampleRate;
@@ -320,9 +271,10 @@ struct UI::PrivateData {
uint fgColor;
double scaleFactor;
uintptr_t winId;
#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER
char* uiStateFileKeyRequest;
#endif
std::map<std::string,std::string> lastUsedDirnames;
#endif
char* bundlePath;

// Ignore initial resize events while initializing
@@ -337,9 +289,12 @@ struct UI::PrivateData {
setSizeFunc setSizeCallbackFunc;
fileRequestFunc fileRequestCallbackFunc;

PrivateData() noexcept
: app(),
PrivateData(const char* const appClassName) noexcept
: app(appClassName),
window(nullptr),
#if DISTRHO_UI_USE_WEB_VIEW
webview(nullptr),
#endif
sampleRate(0),
parameterOffset(0),
dspPtr(nullptr),
@@ -347,9 +302,9 @@ struct UI::PrivateData {
fgColor(0xffffffff),
scaleFactor(1.0),
winId(0),
#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER
uiStateFileKeyRequest(nullptr),
#endif
#endif
bundlePath(nullptr),
initializing(true),
callbacksPtr(nullptr),
@@ -360,32 +315,32 @@ struct UI::PrivateData {
setSizeCallbackFunc(nullptr),
fileRequestCallbackFunc(nullptr)
{
#if defined(DISTRHO_PLUGIN_TARGET_DSSI) || defined(DISTRHO_PLUGIN_TARGET_LV2)
#if defined(DISTRHO_PLUGIN_TARGET_DSSI) || defined(DISTRHO_PLUGIN_TARGET_LV2)
parameterOffset += DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS;
# if DISTRHO_PLUGIN_WANT_LATENCY
#if DISTRHO_PLUGIN_WANT_LATENCY
parameterOffset += 1;
# endif
#endif
#endif
#endif

#ifdef DISTRHO_PLUGIN_TARGET_LV2
# if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
#ifdef DISTRHO_PLUGIN_TARGET_LV2
#if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
parameterOffset += 1;
# endif
# if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)
#endif
#if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)
parameterOffset += 1;
# endif
#endif
#endif
#endif

#ifdef DISTRHO_PLUGIN_TARGET_VST3
#ifdef DISTRHO_PLUGIN_TARGET_VST3
parameterOffset += kVst3InternalParameterCount;
#endif
#endif
}

~PrivateData() noexcept
{
#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER
std::free(uiStateFileKeyRequest);
#endif
#endif
std::free(bundlePath);
}

@@ -403,6 +358,9 @@ struct UI::PrivateData {

void setStateCallback(const char* const key, const char* const value)
{
DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',);
DISTRHO_SAFE_ASSERT_RETURN(value != nullptr,);

if (setStateCallbackFunc != nullptr)
setStateCallbackFunc(callbacksPtr, key, value);
}
@@ -415,19 +373,20 @@ struct UI::PrivateData {

void setSizeCallback(const uint width, const uint height)
{
DISTRHO_SAFE_ASSERT_RETURN(width != 0 && height != 0,);

if (setSizeCallbackFunc != nullptr)
setSizeCallbackFunc(callbacksPtr, width, height);
}

// implemented below, after PluginWindow
bool fileRequestCallback(const char* const key);
bool fileRequestCallback(const char* key);

static UI::PrivateData* s_nextPrivateData;
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
static ExternalWindow::PrivateData createNextWindow(UI* ui, uint width, uint height);
#else
static PluginWindow& createNextWindow(UI* ui, uint width, uint height);
#endif
#if DISTRHO_UI_USE_WEB_VIEW
static void webViewMessageCallback(void* arg, char* msg);
#endif
};

// -----------------------------------------------------------------------
@@ -438,7 +397,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key)
if (fileRequestCallbackFunc != nullptr)
return fileRequestCallbackFunc(callbacksPtr, key);

#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER
std::free(uiStateFileKeyRequest);
uiStateFileKeyRequest = strdup(key);
DISTRHO_SAFE_ASSERT_RETURN(uiStateFileKeyRequest != nullptr, false);
@@ -449,8 +408,10 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key)

DGL_NAMESPACE::FileBrowserOptions opts;
opts.title = title;
if (lastUsedDirnames.count(key))
opts.startDir = lastUsedDirnames[key].c_str();
return window->openFileBrowser(opts);
#endif
#endif

return false;
}
@@ -458,7 +419,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key)
// -----------------------------------------------------------------------
// PluginWindow onFileSelected that require UI::PrivateData definitions

#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#if DISTRHO_UI_FILE_BROWSER
inline void PluginWindow::onFileSelected(const char* const filename)
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
@@ -474,8 +435,13 @@ inline void PluginWindow::onFileSelected(const char* const filename)
{
// notify DSP
ui->setState(key, filename);

// notify UI
ui->stateChanged(key, filename);

// save dirname for next time
if (const char* const lastsep = std::strrchr(filename, DISTRHO_OS_SEP))
ui->uiData->lastUsedDirnames[key] = std::string(filename, lastsep-filename);
}
std::free(key);
return;


+ 210
- 23
source/modules/distrho/src/DistrhoUtils.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -19,19 +19,29 @@
#endif

#include "../extra/String.hpp"
#include "../DistrhoPluginUtils.hpp"
#include "../DistrhoStandaloneUtils.hpp"

#ifdef DISTRHO_OS_WINDOWS
# include <direct.h>
# include <shlobj.h>
# include <windows.h>
#else
# ifndef STATIC_BUILD
# include <dlfcn.h>
# endif
# include <fcntl.h>
# include <limits.h>
# include <pwd.h>
# include <stdlib.h>
# include <sys/stat.h>
# include <unistd.h>
#endif

#if defined(DISTRHO_OS_WINDOWS) && !defined(STATIC_BUILD) && !DISTRHO_IS_STANDALONE
#ifdef DISTRHO_OS_WINDOWS
# if DISTRHO_IS_STANDALONE || defined(STATIC_BUILD)
static constexpr const HINSTANCE hInstance = nullptr;
# else
static HINSTANCE hInstance = nullptr;

DISTRHO_PLUGIN_EXPORT
@@ -41,49 +51,222 @@ BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID)
hInstance = hInst;
return 1;
}
# endif
#endif

START_NAMESPACE_DISTRHO

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

const char* getBinaryFilename()
{
static String filename;

#ifndef STATIC_BUILD
#ifndef STATIC_BUILD
if (filename.isNotEmpty())
return filename;

# ifdef DISTRHO_OS_WINDOWS
# if DISTRHO_IS_STANDALONE
constexpr const HINSTANCE hInstance = nullptr;
# endif
#ifdef DISTRHO_OS_WINDOWS
CHAR filenameBuf[MAX_PATH];
filenameBuf[0] = '\0';
GetModuleFileNameA(hInstance, filenameBuf, sizeof(filenameBuf));
filename = filenameBuf;
# else
#else
Dl_info info;
dladdr((void*)getBinaryFilename, &info);
char filenameBuf[PATH_MAX];
filename = realpath(info.dli_fname, filenameBuf);
# endif
#endif
#endif
#endif

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<char*>(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_CARLA)
#if defined(DISTRHO_PLUGIN_TARGET_AU)
return "AudioUnit";
#elif defined(DISTRHO_PLUGIN_TARGET_CARLA)
return "Carla";
#elif defined(DISTRHO_PLUGIN_TARGET_JACK)
# ifdef DISTRHO_OS_WASM
#if defined(DISTRHO_OS_WASM)
return "Wasm/Standalone";
# else
#elif defined(HAVE_JACK)
return "JACK/Standalone";
# endif
#else
return "Standalone";
#endif
#elif defined(DISTRHO_PLUGIN_TARGET_LADSPA)
return "LADSPA";
#elif defined(DISTRHO_PLUGIN_TARGET_DSSI)
@@ -94,6 +277,10 @@ const char* getPluginFormatName() noexcept
return "VST2";
#elif defined(DISTRHO_PLUGIN_TARGET_VST3)
return "VST3";
#elif defined(DISTRHO_PLUGIN_TARGET_CLAP)
return "CLAP";
#elif defined(DISTRHO_PLUGIN_TARGET_STATIC) && defined(DISTRHO_PLUGIN_TARGET_STATIC_NAME)
return DISTRHO_PLUGIN_TARGET_STATIC_NAME;
#else
return "Unknown";
#endif
@@ -103,21 +290,21 @@ const char* getResourcePath(const char* const bundlePath) noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(bundlePath != nullptr, nullptr);

#if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_VST2)
#if defined(DISTRHO_PLUGIN_TARGET_AU) || defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_VST2) || defined(DISTRHO_PLUGIN_TARGET_CLAP)
static String resourcePath;

if (resourcePath.isEmpty())
{
resourcePath = bundlePath;
# ifdef DISTRHO_OS_MAC
#ifdef DISTRHO_OS_MAC
resourcePath += "/Contents/Resources";
# else
#else
resourcePath += DISTRHO_OS_SEP_STR "resources";
# endif
#endif
}

return resourcePath.buffer();
#elif defined(DISTRHO_PLUGIN_TARGET_LV2)
#elif defined(DISTRHO_PLUGIN_TARGET_LV2)
static String resourcePath;

if (resourcePath.isEmpty())
@@ -127,7 +314,7 @@ const char* getResourcePath(const char* const bundlePath) noexcept
}

return resourcePath.buffer();
#elif defined(DISTRHO_PLUGIN_TARGET_VST3)
#elif defined(DISTRHO_PLUGIN_TARGET_VST3)
static String resourcePath;

if (resourcePath.isEmpty())
@@ -137,7 +324,7 @@ const char* getResourcePath(const char* const bundlePath) noexcept
}

return resourcePath.buffer();
#endif
#endif

return nullptr;
}
@@ -156,6 +343,6 @@ bool requestBufferSizeChange(uint) { return false; }
bool requestMIDI() { return false; }
#endif

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

END_NAMESPACE_DISTRHO

+ 1
- 0
source/plugin/Makefile View File

@@ -33,6 +33,7 @@ endif
# ---------------------------------------------------------------------------------------------------------------------

BUILD_CXX_FLAGS += -I$(CWD)/backend -I$(CWD)/includes -I$(CWD)/modules -I$(CWD)/utils
BUILD_CXX_FLAGS += -I$(CWD)/modules/dgl/src/pugl-upstream/include
BUILD_CXX_FLAGS += $(NATIVE_PLUGINS_FLAGS)

BUILD_CXX_FLAGS += $(FLUIDSYNTH_FLAGS)


+ 9
- 1
source/plugin/ui_launcher.cpp View File

@@ -41,7 +41,15 @@ class PluginWindow : public DGL_NAMESPACE::Window
{
public:
explicit PluginWindow(PluginApplication& app, const uintptr_t winId)
: Window(app, winId, ui_launcher_res::carla_uiWidth, ui_launcher_res::carla_uiHeight, 0.0, false, false, false)
: Window(app,
winId,
ui_launcher_res::carla_uiWidth,
ui_launcher_res::carla_uiHeight,
0.0,
false,
false,
false,
false)
{
// this is called just before creating UI, ensuring proper context to it
if (pData->view != nullptr && pData->initPost())


Loading…
Cancel
Save