Browse Source

Merge pull request #381 from DISTRHO/develop

Merge develop into main
pull/321/merge
Filipe Coelho GitHub 3 years ago
parent
commit
ac545ac660
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 15408 additions and 5074 deletions
  1. +2
    -1
      .github/workflows/cmake.yml
  2. +105
    -11
      .github/workflows/example-plugins.yml
  3. +20
    -0
      .github/workflows/irc.yml
  4. +2
    -2
      .github/workflows/makefile.yml
  5. +1
    -0
      .gitignore
  6. +93
    -0
      FEATURES.md
  7. +47
    -0
      LICENSING.md
  8. +3
    -4
      Makefile
  9. +260
    -94
      Makefile.base.mk
  10. +275
    -71
      Makefile.plugins.mk
  11. +7
    -1
      README.md
  12. +147
    -34
      cmake/DPF-plugin.cmake
  13. +27
    -3
      dgl/Application.hpp
  14. +72
    -14
      dgl/Base.hpp
  15. +3
    -3
      dgl/Cairo.hpp
  16. +11
    -2
      dgl/EventHandlers.hpp
  17. +12
    -19
      dgl/FileBrowserDialog.hpp
  18. +1
    -1
      dgl/ImageBase.hpp
  19. +48
    -3
      dgl/ImageBaseWidgets.hpp
  20. +58
    -18
      dgl/Makefile
  21. +44
    -7
      dgl/NanoVG.hpp
  22. +112
    -0
      dgl/OpenGL-include.hpp
  23. +16
    -95
      dgl/OpenGL.hpp
  24. +1
    -0
      dgl/StandaloneWindow.hpp
  25. +1
    -1
      dgl/SubWidget.hpp
  26. +8
    -3
      dgl/TopLevelWidget.hpp
  27. +54
    -49
      dgl/Widget.hpp
  28. +131
    -58
      dgl/Window.hpp
  29. +33
    -1
      dgl/src/Application.cpp
  30. +20
    -4
      dgl/src/ApplicationPrivateData.cpp
  31. +15
    -0
      dgl/src/ApplicationPrivateData.hpp
  32. +10
    -3
      dgl/src/Cairo.cpp
  33. +2
    -2
      dgl/src/Color.cpp
  34. +5
    -3
      dgl/src/Geometry.cpp
  35. +4
    -4
      dgl/src/ImageBaseWidgets.cpp
  36. +105
    -17
      dgl/src/NanoVG.cpp
  37. +131
    -0
      dgl/src/OpenGL.cpp
  38. +15
    -4
      dgl/src/SubWidget.cpp
  39. +35
    -14
      dgl/src/TopLevelWidget.cpp
  40. +0
    -34
      dgl/src/TopLevelWidgetPrivateData.cpp
  41. +1
    -2
      dgl/src/TopLevelWidgetPrivateData.hpp
  42. +7
    -0
      dgl/src/Vulkan.cpp
  43. +0
    -5
      dgl/src/Widget.cpp
  44. +12
    -39
      dgl/src/WidgetPrivateData.cpp
  45. +0
    -1
      dgl/src/WidgetPrivateData.hpp
  46. +151
    -45
      dgl/src/Window.cpp
  47. +259
    -266
      dgl/src/WindowPrivateData.cpp
  48. +26
    -133
      dgl/src/WindowPrivateData.hpp
  49. +154
    -143
      dgl/src/nanovg/fontstash.h
  50. +215
    -117
      dgl/src/nanovg/nanovg.c
  51. +19
    -8
      dgl/src/nanovg/nanovg.h
  52. +200
    -41
      dgl/src/nanovg/nanovg_gl.h
  53. +725
    -1767
      dgl/src/nanovg/stb_image.h
  54. +1
    -1
      dgl/src/pugl-upstream
  55. +245
    -380
      dgl/src/pugl.cpp
  56. +39
    -90
      dgl/src/pugl.hpp
  57. +268
    -35
      distrho/DistrhoInfo.hpp
  58. +155
    -21
      distrho/DistrhoPlugin.hpp
  59. +15
    -1
      distrho/DistrhoPluginMain.cpp
  60. +61
    -5
      distrho/DistrhoPluginUtils.hpp
  61. +99
    -0
      distrho/DistrhoStandaloneUtils.hpp
  62. +57
    -13
      distrho/DistrhoUI.hpp
  63. +12
    -1
      distrho/DistrhoUIMain.cpp
  64. +19
    -24
      distrho/DistrhoUI_macOS.mm
  65. +72
    -47
      distrho/DistrhoUtils.hpp
  66. +28
    -9
      distrho/extra/ExternalWindow.hpp
  67. +28
    -0
      distrho/extra/FileBrowserDialog.hpp
  68. +844
    -0
      distrho/extra/FileBrowserDialogImpl.cpp
  69. +121
    -0
      distrho/extra/FileBrowserDialogImpl.hpp
  70. +19
    -1
      distrho/extra/LeakDetector.hpp
  71. +23
    -2
      distrho/extra/RingBuffer.hpp
  72. +251
    -0
      distrho/extra/Runner.hpp
  73. +19
    -1
      distrho/extra/ScopedPointer.hpp
  74. +38
    -2
      distrho/extra/String.hpp
  75. +4
    -0
      distrho/extra/Thread.hpp
  76. +11
    -1
      distrho/extra/sofd/libsofd.c
  77. +0
    -0
      distrho/extra/sofd/libsofd.h
  78. +50
    -29
      distrho/src/DistrhoDefines.h
  79. +97
    -16
      distrho/src/DistrhoPlugin.cpp
  80. +20
    -24
      distrho/src/DistrhoPluginCarla.cpp
  81. +34
    -18
      distrho/src/DistrhoPluginChecks.h
  82. +205
    -39
      distrho/src/DistrhoPluginInternal.hpp
  83. +289
    -17
      distrho/src/DistrhoPluginJACK.cpp
  84. +11
    -9
      distrho/src/DistrhoPluginLADSPA+DSSI.cpp
  85. +193
    -82
      distrho/src/DistrhoPluginLV2.cpp
  86. +323
    -99
      distrho/src/DistrhoPluginLV2export.cpp
  87. +421
    -0
      distrho/src/DistrhoPluginVST.hpp
  88. +139
    -204
      distrho/src/DistrhoPluginVST2.cpp
  89. +4738
    -190
      distrho/src/DistrhoPluginVST3.cpp
  90. +120
    -32
      distrho/src/DistrhoUI.cpp
  91. +120
    -26
      distrho/src/DistrhoUIInternal.hpp
  92. +49
    -12
      distrho/src/DistrhoUILV2.cpp
  93. +97
    -45
      distrho/src/DistrhoUIPrivateData.hpp
  94. +1571
    -0
      distrho/src/DistrhoUIVST3.cpp
  95. +161
    -0
      distrho/src/DistrhoUtils.cpp
  96. +10
    -0
      distrho/src/dssi/seq_event-compat.h
  97. +324
    -177
      distrho/src/jackbridge/JackBridge.cpp
  98. +2
    -17
      distrho/src/jackbridge/JackBridge.hpp
  99. +0
    -257
      distrho/src/jackbridge/Makefile
  100. +300
    -0
      distrho/src/jackbridge/NativeBridge.hpp

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

@@ -30,6 +30,7 @@ jobs:
liblo-dev \
libgl-dev \
libcairo2-dev \
libdbus-1-dev \
libx11-dev
- name: Create Build Environment
shell: bash
@@ -55,7 +56,7 @@ jobs:
path: ${{runner.workspace}}/build/bin/

cmake_macos:
runs-on: macos-10.15
runs-on: macos-11
steps:
- uses: actions/checkout@v2
with:


+ 105
- 11
.github/workflows/example-plugins.yml View File

@@ -26,7 +26,7 @@ jobs:
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list
sudo apt-get update -qq
sudo apt-get install -yq g++-aarch64-linux-gnu libasound2-dev:arm64 libcairo2-dev:arm64 libgl1-mesa-dev:arm64 liblo-dev:arm64 libpulse-dev:arm64 libx11-dev:arm64 libxcursor-dev:arm64 libxext-dev:arm64 libxrandr-dev:arm64 qemu-user-static
sudo apt-get install -yq g++-aarch64-linux-gnu libasound2-dev:arm64 libcairo2-dev:arm64 libdbus-1-dev:arm64 libgl1-mesa-dev:arm64 liblo-dev:arm64 libpulse-dev:arm64 libx11-dev:arm64 libxcursor-dev:arm64 libxext-dev:arm64 libxrandr-dev:arm64 qemu-user-static
# fix broken Ubuntu packages missing pkg-config file in multi-arch package
sudo apt-get install -yq libasound2-dev libgl1-mesa-dev liblo-dev libpulse-dev libxcursor-dev libxrandr-dev
sudo ln -s /usr/lib/aarch64-linux-gnu/liblo.so.7 /usr/lib/aarch64-linux-gnu/liblo.so
@@ -63,7 +63,7 @@ jobs:
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list
sudo apt-get update -qq
sudo apt-get install -yq g++-arm-linux-gnueabihf libasound2-dev:armhf libcairo2-dev:armhf libgl1-mesa-dev:armhf liblo-dev:armhf libpulse-dev:armhf libx11-dev:armhf libxcursor-dev:armhf libxext-dev:armhf libxrandr-dev:armhf qemu-user-static
sudo apt-get install -yq g++-arm-linux-gnueabihf libasound2-dev:armhf libcairo2-dev:armhf libdbus-1-dev:armhf libgl1-mesa-dev:armhf liblo-dev:armhf libpulse-dev:armhf libx11-dev:armhf libxcursor-dev:armhf libxext-dev:armhf libxrandr-dev:armhf qemu-user-static
# fix broken Ubuntu packages missing pkg-config file in multi-arch package
sudo apt-get install -yq libasound2-dev libgl1-mesa-dev liblo-dev libpulse-dev libxcursor-dev libxrandr-dev
sudo ln -s /usr/lib/arm-linux-gnueabihf/liblo.so.7 /usr/lib/arm-linux-gnueabihf/liblo.so
@@ -96,7 +96,7 @@ jobs:
run: |
sudo dpkg --add-architecture i386
sudo apt-get update -qq
sudo apt-get install -yq g++-multilib libasound2-dev:i386 libcairo2-dev:i386 libgl1-mesa-dev:i386 liblo-dev:i386 libpulse-dev:i386 libx11-dev:i386 libxcursor-dev:i386 libxext-dev:i386 libxrandr-dev:i386
sudo apt-get install -yq g++-multilib libasound2-dev:i386 libcairo2-dev:i386 libdbus-1-dev:i386 libgl1-mesa-dev:i386 liblo-dev:i386 libpulse-dev:i386 libx11-dev:i386 libxcursor-dev:i386 libxext-dev:i386 libxrandr-dev:i386
- name: Build linux x86
env:
CFLAGS: -m32
@@ -124,7 +124,7 @@ jobs:
- name: Set up dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev
sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev
- name: Build linux x86_64
env:
LDFLAGS: -static-libgcc -static-libstdc++
@@ -141,15 +141,11 @@ jobs:
bin/*

macos-universal:
runs-on: macos-10.15
runs-on: macos-11
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Fix up Xcode
run: |
sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*
sudo xcode-select -s "/Applications/Xcode_12.3.app"
- name: Build macOS universal
env:
CFLAGS: -arch x86_64 -arch arm64 -DMAC_OS_X_VERSION_MAX_ALLOWED=MAC_OS_X_VERSION_10_12 -mmacosx-version-min=10.12 -mtune=generic -msse -msse2
@@ -157,7 +153,7 @@ jobs:
LDFLAGS: -arch x86_64 -arch arm64 -mmacosx-version-min=10.12
run: |
make features
make NOOPT=true -j $(sysctl -n hw.logicalcpu)
make HAVE_CAIRO=false NOOPT=true -j $(sysctl -n hw.logicalcpu)
./utils/package-osx-bundles.sh
- name: Set sha8
id: slug
@@ -172,9 +168,10 @@ jobs:
!bin/*-dssi.dylib
!bin/lv2
!bin/vst2
!bin/vst3

win32:
runs-on: ubuntu-20.04
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
with:
@@ -235,3 +232,100 @@ jobs:
bin/*
!bin/*-ladspa.dll
!bin/*-dssi.dll

plugin-validation:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Set up dependencies
run: |
# custom repos
wget https://launchpad.net/~kxstudio-debian/+archive/kxstudio/+files/kxstudio-repos_10.0.3_all.deb
sudo dpkg -i kxstudio-repos_10.0.3_all.deb
sudo apt-get update -qq
# build-deps
sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev
# runtime testing
sudo apt-get install -yq carla-git lilv-utils lv2-dev lv2lint valgrind
- name: Build plugins
env:
CFLAGS: -g
CXXFLAGS: -g -DDPF_ABORT_ON_ERROR
LDFLAGS: -static-libgcc -static-libstdc++
run: |
make features
make NOOPT=true SKIP_STRIPPING=true -j $(nproc)
- name: Validate LV2 ttl syntax
run: |
lv2_validate \
/usr/lib/lv2/mod.lv2/*.ttl \
/usr/lib/lv2/kx-meta/*.ttl \
/usr/lib/lv2/kx-control-input-port-change-request.lv2/*.ttl \
/usr/lib/lv2/kx-programs.lv2/*.ttl \
./bin/*.lv2/*.ttl
- name: Validate LV2 metadata and binaries
run: |
export LV2_PATH=/tmp/lv2-path
mkdir ${LV2_PATH}
cp -r bin/*.lv2 \
/usr/lib/lv2/{atom,buf-size,core,data-access,kx-control-input-port-change-request,kx-programs,instance-access,midi,parameters,port-groups,port-props,options,patch,presets,resize-port,state,time,ui,units,urid,worker}.lv2 \
${LV2_PATH}
lv2lint -s lv2_generate_ttl -l ld-linux-x86-64.so.2 -M nopack $(lv2ls)
- name: Test LADSPA plugins
run: |
for p in $(ls bin/ | grep ladspa.so); do \
env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \
valgrind \
--error-exitcode=255 \
--leak-check=full \
--track-origins=yes \
--suppressions=./utils/valgrind-dpf.supp \
/usr/lib/carla/carla-bridge-native ladspa ./bin/${p} "" 1>/dev/null; \
done
- name: Test DSSI plugins
run: |
for p in $(ls bin/ | grep dssi.so); do \
env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \
valgrind \
--error-exitcode=255 \
--leak-check=full \
--track-origins=yes \
--suppressions=./utils/valgrind-dpf.supp \
/usr/lib/carla/carla-bridge-native dssi ./bin/${p} "" 1>/dev/null; \
done
- name: Test LV2 plugins
run: |
export LV2_PATH=/tmp/lv2-path
for p in $(lv2ls); do \
env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \
valgrind \
--error-exitcode=255 \
--leak-check=full \
--track-origins=yes \
--suppressions=./utils/valgrind-dpf.supp \
/usr/lib/carla/carla-bridge-native lv2 "" ${p} 1>/dev/null; \
done
- name: Test VST2 plugins
run: |
for p in $(ls bin/ | grep vst.so); do \
env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \
valgrind \
--error-exitcode=255 \
--leak-check=full \
--track-origins=yes \
--suppressions=./utils/valgrind-dpf.supp \
/usr/lib/carla/carla-bridge-native vst2 ./bin/${p} "" 1>/dev/null; \
done
- name: Test VST3 plugins
run: |
for p in $(ls bin/ | grep vst3); do \
env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \
valgrind \
--error-exitcode=255 \
--leak-check=full \
--track-origins=yes \
--suppressions=./utils/valgrind-dpf.supp \
/usr/lib/carla/carla-bridge-native vst3 ./bin/${p} "" 1>/dev/null; \
done

+ 20
- 0
.github/workflows/irc.yml View File

@@ -0,0 +1,20 @@
name: irc

on: [push]

jobs:
notification:
runs-on: ubuntu-latest
name: IRC notification
steps:
- name: Format message
id: message
run: |
message="${{ github.actor }} pushed $(echo '${{ github.event.commits[0].message }}' | head -n 1) ${{ github.event.commits[0].url }}"
echo ::set-output name=message::"${message}"
- name: IRC notification
uses: Gottox/irc-message-action@v2
with:
channel: '#kxstudio'
nickname: kxstudio-bot
message: ${{ steps.message.outputs.message }}

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

@@ -20,7 +20,7 @@ jobs:
- name: Set up dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev xvfb
sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev xvfb
- name: Without any warnings
env:
CFLAGS: -Werror
@@ -40,7 +40,7 @@ jobs:
CXXFLAGS: -Werror -std=gnu++98
run: |
make clean >/dev/null
make -j $(nproc) VST3_FILENAME=
make -j $(nproc)
- name: No namespace
env:
CFLAGS: -Werror


+ 1
- 0
.gitignore View File

@@ -17,3 +17,4 @@ bin/
build/
docs/
utils/lv2_ttl_generator
utils/lv2_ttl_generator.dSYM/

+ 93
- 0
FEATURES.md View File

@@ -0,0 +1,93 @@
# DPF - DISTRHO Plugin Framework

This file describes the available features for each plugin format.
The limitations could be due to the plugin format itself or within DPF.
If the limitation is within DPF, a link is provided to a description below on the reason for it.

| Feature | JACK/Standalone | LADSPA | DSSI | LV2 | VST2 | VST3 | Feature |
|---------------------|---------------------------------------|-------------------------|---------------------|-------------------------------|--------------------------------|----------------------------------|---------------------|
| Audio port groups | [Yes*](#jack-audio-port-groups) | No | No | Yes | No | [No*](#vst3-is-work-in-progress) | Audio port groups |
| Audio port as CV | Yes | No | No | Yes | No | [No*](#vst3-is-work-in-progress) | Audio port as CV |
| Audio sidechan | Yes | No | No | Yes | [No*](#vst2-potential-support) | [No*](#vst3-is-work-in-progress) | Audio sidechan |
| Bypass control | No | No | No | Yes | [No*](#vst2-potential-support) | [No*](#vst3-is-work-in-progress) | Bypass control |
| MIDI input | Yes | No | Yes | Yes | Yes | Yes | MIDI input |
| MIDI output | Yes | No | No | Yes | Yes | Yes | MIDI output |
| Parameter changes | Yes | No | No | [No*](#lv2-parameter-changes) | Yes | Yes | Parameter changes |
| Parameter groups | No | No | No | Yes | Yes | [No*](#vst3-is-work-in-progress) | Parameter groups |
| Parameter outputs | No | No | No | Yes | No | [No*](#vst3-is-work-in-progress) | Parameter outputs |
| Parameter triggers | Yes | No | No | Yes | [No*](#parameter-triggers) | [No*](#parameter-triggers) | Parameter triggers |
| Programs | [Yes*](#jack-parameters-and-programs) | [No*](#ladspa-programs) | [Yes*](#dssi-state) | Yes | [No*](#vst2-programs) | Yes | Programs |
| States | Yes | No | [Yes*](#dssi-state) | Yes | Yes | Yes | States |
| Full/internal state | Yes | No | No | Yes | Yes | Yes | Full/internal state |
| Time position | Yes | No | No | Yes | Yes | Yes | Time position |
| UI | [Yes*](#jack-custom-ui-only) | No | External only | Yes | Embed only | Embed only | UI |
| UI bg/fg colors | No | No | No | Yes | No | No? | UI bg/fg colors |
| UI direct access | Yes | No | No | Yes | Yes | Yes | UI direct access |
| UI host-filebrowser | No | No | No | Yes | [No*](#vst2-potential-support) | [No*](#vst3-is-work-in-progress) | UI host-filebrowser |
| UI host-resize | Yes | No | Yes | Yes | No | [No*](#vst3-is-work-in-progress) | UI host-resize |
| UI remote control | No | No | Yes | Yes | No | Yes | UI remote control |
| UI send midi note | Yes | No | Yes | Yes | Yes | Yes | UI send midi note |

For things that could be unclear:

- "States" refers to DPF API support, supporting key-value string pairs for internal state saving
- "Full state" refers to plugins updating their state internally without outside intervention (like host or UI)
- "UI direct access" means `DISTRHO_PLUGIN_WANT_DIRECT_ACCESS` is possible, that is, running DSP and UI on the same process
- "UI remote control" means running the UI on a separate machine (for example over the network)
- An external UI on this table means that it cannot be embed into the host window, but the plugin can still provide one

# Special notes

## Parameter triggers

Trigger-style parameters (parameters which value is reset to its default every run) are only supported in JACK and LV2.
For all other plugin formats DPF will simulate the behaviour through a parameter change request.

## JACK audio port groups

DPF will set JACK metadata information for grouping audio ports.
This is not supported by most JACK applications at the moment.

## JACK parameters and programs

Under JACK/Stanlone mode, MIDI input events will trigger program and parameter changes.
MIDI program change events work as expected (that is, MIDI program change 0 will load 1st plugin program).
MIDI CCs are used for parameter changes (matching the `midiCC` value you set on each parameter).

## JACK custom UI only

There is no generic plugin editor view.
If your plugin has no custom UI, the standalone executable will run but not show any window.

## LADSPA programs

Programs for LADSPA could be done via LRDF but this is not supported in DPF.

## DSSI State

DSSI only supports state changes when called via UI, no "full state" possible.
This also makes it impossibe to use programs and state at the same time with DSSI,
because in DPF changing programs can lead to state changes but there is no way to fetch this information on DSSI plugins.

To make it simpler to understand, think of DSSI programs and states as UI properties.
Because in DPF changing the state happens from UI to DSP side, regular DSSI can be supported.
But if we involve programs, they would need to pass through the UI in order to work. Which goes against DPF's design.

## LV2 parameter changes

Although this is already implemented in DPF (through a custom extension), this is not implemented on most hosts.
So for now you can pretty much treat it as if not supported.

## VST2 potential support

Not supported in DPF at the moment.
It could eventually be, but likely not due to VST2 being phased out by Steinberg.
Contact DPF authors if you require such a feature.

## VST2 programs

VST2 program support requires saving state of all programs in memory, which is very expensive and thus not done in DPF.

## VST3 is work in progress

Feature is possible, just not implemented yet in DPF.

+ 47
- 0
LICENSING.md View File

@@ -0,0 +1,47 @@
# DPF - DISTRHO Plugin Framework

Even though DPF is quite liberally licensed, not all plugin formats follow the same ideals.
This is usually due to plugin APIs/headers being tied to a specific license or having commercial restrictions.
This file describes the licensing that applies to each individual plugin format as a way to make it clear what is possible and compatible.
Note that if you are making GPLv2+ licensed plugins this does not apply to you, as so far everything is GPLv2+ compatible.

Regardless of target format, DPF itself needs to be mentioned in attribution.
See the [LICENSE](LICENSE) file for copyright details.

| Target | License(s) | License restrictions | Additional attribution |
|-----------------|----------------------|-----------------------|------------------------|
| JACK/Standalone | MIT (RtAudio) | Copyright attribution | **RtAudio**: 2001-2019 Gary P. Scavone |
| LADSPA | LGPLv2.1+ | ??? (*) | 2000-2002 Richard W. E. Furse, Paul Barton-Davis, Stefan Westerfeld |
| DSSI | LGPLv2.1+ | ??? (*) | **DSSI**: 2004, 2009 Chris Cannam, Steve Harris and Sean Bolton;<br/> **ALSA**: 1998-2001 Jaroslav Kysela, Abramo Bagnara, Takashi Iwai |
| LV2 | ISC | Copyright attribution | 2006-2020 Steve Harris, David Robillard;<br/> 2000-2002 Richard W.E. Furse, Paul Barton-Davis, Stefan Westerfeld |
| VST2 | GPLv2+ or commercial | Must be GPLv2+ compatible or alternatively use Steinberg VST2 SDK (no longer available for new plugins) | GPLv2+ compatible license or custom agreement with Steinberg |
| VST3 | ISC | Copyright attribution | (none, only DPF files used) |

### LADSPA and DSSI special note

The header files on LADSPA and DSSI are LGPLv2.1+ licensed, which is unusual for pure APIs without libraries.
LADSPA authors mention this on ladspa.org homepage:

> LADSPA has been released under LGPL (GNU Lesser General Public License).
> This is not intended to be the final license for LADSPA.
> In the long term it is hoped that LADSPA will have a public license that is even less restrictive, so that commercial applications can use it (in a protected way) without having to use a derived LGPL library.
> It may be that LGPL is already free enough for this, but we aren't sure.

So the situation for LADSPA/DSSI plugins is unclear for commercial plugins.
These formats are very limited and not much used anymore anyway, feel free to skip them if this situation is a potential issue for you.

### VST2 special note

By default DPF uses the free reverse-engineered [vestige header](distrho/src/vestige/vestige.h) file.
This file is GPLv2+ licensed, so that applies to plugins built with it as well.
You can alternatively build DPF-based VST2 plugins using the official Steinberg VST2 SDK,
simply set the `VESTIGE_HEADER` compiler macro to `0` during build.
You will need to provide your own VST2 SDK files then, as DPF does not ship with them.
Note there are legal issues surrounding releasing new VST2 plugins using the official SDK, as that is no longer supported by Steinberg.

### VST3 special note

Contrary to most plugins, DPF does not use the official VST3 SDK.
Instead, the API definitions are provided by the [travesty](distrho/src/travesty/) sub-project, licensed in the same way as DPF.
This allows us to freely build plugins without being encumbered by restrictive licensing deals.
It makes the internal implementation much harder for DPF, but this is not an issue for external developers.

+ 3
- 4
Makefile View File

@@ -21,7 +21,6 @@ dgl:

examples: dgl
$(MAKE) all -C examples/CVPort
$(MAKE) all -C examples/EmbedExternalUI
$(MAKE) all -C examples/FileHandling
$(MAKE) all -C examples/Info
$(MAKE) all -C examples/Latency
@@ -34,13 +33,13 @@ examples: dgl
ifeq ($(HAVE_CAIRO),true)
$(MAKE) all -C examples/CairoUI
endif
ifeq ($(HAVE_DGL),true)
$(MAKE) all -C examples/EmbedExternalUI
endif

ifeq ($(CAN_GENERATE_TTL),true)
gen: examples utils/lv2_ttl_generator
@$(CURDIR)/utils/generate-ttl.sh
ifeq ($(MACOS),true)
@$(CURDIR)/utils/generate-vst-bundles.sh
endif

utils/lv2_ttl_generator:
$(MAKE) -C utils/lv2-ttl-generator


+ 260
- 94
Makefile.base.mk View File

@@ -25,35 +25,36 @@ ifneq ($(HAIKU),true)
ifneq ($(HURD),true)
ifneq ($(LINUX),true)
ifneq ($(MACOS),true)
ifneq ($(WASM),true)
ifneq ($(WINDOWS),true)

ifneq (,$(findstring bsd,$(TARGET_MACHINE)))
BSD=true
endif
ifneq (,$(findstring haiku,$(TARGET_MACHINE)))
HAIKU=true
endif
ifneq (,$(findstring linux,$(TARGET_MACHINE)))
LINUX=true
BSD = true
else ifneq (,$(findstring haiku,$(TARGET_MACHINE)))
HAIKU = true
else ifneq (,$(findstring linux,$(TARGET_MACHINE)))
LINUX = true
else ifneq (,$(findstring gnu,$(TARGET_MACHINE)))
HURD=true
endif
ifneq (,$(findstring apple,$(TARGET_MACHINE)))
MACOS=true
endif
ifneq (,$(findstring mingw,$(TARGET_MACHINE)))
WINDOWS=true
endif
ifneq (,$(findstring windows,$(TARGET_MACHINE)))
WINDOWS=true
endif

endif
endif
endif
endif
endif
endif
HURD = true
else ifneq (,$(findstring apple,$(TARGET_MACHINE)))
MACOS = true
else ifneq (,$(findstring mingw,$(TARGET_MACHINE)))
WINDOWS = true
else ifneq (,$(findstring msys,$(TARGET_MACHINE)))
WINDOWS = true
else ifneq (,$(findstring wasm,$(TARGET_MACHINE)))
WASM = true
else ifneq (,$(findstring windows,$(TARGET_MACHINE)))
WINDOWS = true
endif

endif # WINDOWS
endif # WASM
endif # MACOS
endif # LINUX
endif # HURD
endif # HAIKU
endif # BSD

# ---------------------------------------------------------------------------------------------------------------------
# Auto-detect the processor
@@ -61,81 +62,105 @@ endif
TARGET_PROCESSOR := $(firstword $(subst -, ,$(TARGET_MACHINE)))

ifneq (,$(filter i%86,$(TARGET_PROCESSOR)))
CPU_I386=true
CPU_I386_OR_X86_64=true
CPU_I386 = true
CPU_I386_OR_X86_64 = true
endif
ifneq (,$(filter wasm32,$(TARGET_PROCESSOR)))
CPU_I386 = true
CPU_I386_OR_X86_64 = true
endif
ifneq (,$(filter x86_64,$(TARGET_PROCESSOR)))
CPU_X86_64=true
CPU_I386_OR_X86_64=true
CPU_X86_64 = true
CPU_I386_OR_X86_64 = true
endif
ifneq (,$(filter arm%,$(TARGET_PROCESSOR)))
CPU_ARM=true
CPU_ARM_OR_AARCH64=true
CPU_ARM = true
CPU_ARM_OR_AARCH64 = true
endif
ifneq (,$(filter arm64%,$(TARGET_PROCESSOR)))
CPU_ARM64=true
CPU_ARM_OR_AARCH64=true
CPU_ARM64 = true
CPU_ARM_OR_AARCH64 = true
endif
ifneq (,$(filter aarch64%,$(TARGET_PROCESSOR)))
CPU_AARCH64=true
CPU_ARM_OR_AARCH64=true
CPU_AARCH64 = true
CPU_ARM_OR_AARCH64 = true
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set PKG_CONFIG (can be overridden by environment variable)

ifeq ($(WINDOWS),true)
ifeq ($(WASM),true)
# Skip on wasm by default
PKG_CONFIG ?= false
else ifeq ($(WINDOWS),true)
# Build statically on Windows by default
PKG_CONFIG ?= pkg-config --static
else
PKG_CONFIG ?= pkg-config
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set cross compiling flag

ifeq ($(WASM),true)
CROSS_COMPILING = true
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set LINUX_OR_MACOS

ifeq ($(LINUX),true)
LINUX_OR_MACOS=true
LINUX_OR_MACOS = true
endif

ifeq ($(MACOS),true)
LINUX_OR_MACOS=true
LINUX_OR_MACOS = true
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set MACOS_OR_WINDOWS and HAIKU_OR_MACOS_OR_WINDOWS
# Set MACOS_OR_WINDOWS, MACOS_OR_WASM_OR_WINDOWS, HAIKU_OR_MACOS_OR_WINDOWS and HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS

ifeq ($(HAIKU),true)
HAIKU_OR_MACOS_OR_WINDOWS=true
HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS = true
HAIKU_OR_MACOS_OR_WINDOWS = true
endif

ifeq ($(MACOS),true)
MACOS_OR_WINDOWS=true
HAIKU_OR_MACOS_OR_WINDOWS=true
HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS = true
HAIKU_OR_MACOS_OR_WINDOWS = true
MACOS_OR_WASM_OR_WINDOWS = true
MACOS_OR_WINDOWS = true
endif

ifeq ($(WASM),true)
HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS = true
MACOS_OR_WASM_OR_WINDOWS = true
endif

ifeq ($(WINDOWS),true)
MACOS_OR_WINDOWS=true
HAIKU_OR_MACOS_OR_WINDOWS=true
HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS = true
HAIKU_OR_MACOS_OR_WINDOWS = true
MACOS_OR_WASM_OR_WINDOWS = true
MACOS_OR_WINDOWS = true
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set UNIX

ifeq ($(BSD),true)
UNIX=true
UNIX = true
endif

ifeq ($(HURD),true)
UNIX=true
UNIX = true
endif

ifeq ($(LINUX),true)
UNIX=true
UNIX = true
endif

ifeq ($(MACOS),true)
UNIX=true
UNIX = true
endif

# ---------------------------------------------------------------------------------------------------------------------
@@ -145,7 +170,12 @@ BASE_FLAGS = -Wall -Wextra -pipe -MD -MP
BASE_OPTS = -O3 -ffast-math -fdata-sections -ffunction-sections

ifeq ($(CPU_I386_OR_X86_64),true)
BASE_OPTS += -mtune=generic -msse -msse2 -mfpmath=sse
BASE_OPTS += -mtune=generic
ifeq ($(WASM),true)
BASE_OPTS += -msse -msse2 -msse3 -msimd128
else
BASE_OPTS += -msse -msse2 -mfpmath=sse
endif
endif

ifeq ($(CPU_ARM),true)
@@ -155,27 +185,49 @@ endif
endif

ifeq ($(MACOS),true)

# MacOS linker flags
LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-dead_strip -Wl,-dead_strip_dylibs
LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-dead_strip,-dead_strip_dylibs
ifneq ($(SKIP_STRIPPING),true)
LINK_OPTS += -Wl,-x
endif

else

# Common linker flags
LINK_OPTS = -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,-O1 -Wl,--as-needed
LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-O1,--gc-sections
ifeq ($(WASM),true)
LINK_OPTS += -O3
LINK_OPTS += -sAGGRESSIVE_VARIABLE_ELIMINATION=1
else
LINK_OPTS += -Wl,--as-needed
ifneq ($(SKIP_STRIPPING),true)
LINK_OPTS += -Wl,--strip-all
endif
endif

endif

ifeq ($(SKIP_STRIPPING),true)
BASE_FLAGS += -g
endif

ifeq ($(NOOPT),true)
# Non-CPU-specific optimization flags
BASE_OPTS = -O2 -ffast-math -fdata-sections -ffunction-sections
endif

ifneq ($(MACOS_OR_WASM_OR_WINDOWS),true)
ifneq ($(BSD),true)
BASE_FLAGS += -fno-gnu-unique
endif
endif

ifeq ($(WINDOWS),true)
# Assume we want posix
BASE_FLAGS += -posix -D__STDC_FORMAT_MACROS=1 -D__USE_MINGW_ANSI_STDIO=1
# Needed for windows, see https://github.com/falkTX/Carla/issues/855
BASE_OPTS += -mstackrealign
BASE_FLAGS += -mstackrealign
else
# Not needed for Windows
BASE_FLAGS += -fPIC -DPIC
@@ -184,24 +236,50 @@ endif
ifeq ($(DEBUG),true)
BASE_FLAGS += -DDEBUG -O0 -g
LINK_OPTS =
ifeq ($(WASM),true)
LINK_OPTS += -sASSERTIONS=1
endif
else
BASE_FLAGS += -DNDEBUG $(BASE_OPTS) -fvisibility=hidden
CXXFLAGS += -fvisibility-inlines-hidden
endif

ifeq ($(STATIC_BUILD),true)
BASE_FLAGS += -DSTATIC_BUILD
# LINK_OPTS += -static
endif

ifeq ($(WITH_LTO),true)
BASE_FLAGS += -fno-strict-aliasing -flto
LINK_OPTS += -fno-strict-aliasing -flto -Werror=odr -Werror=lto-type-mismatch
endif

BUILD_C_FLAGS = $(BASE_FLAGS) -std=gnu99 $(CFLAGS)
BUILD_CXX_FLAGS = $(BASE_FLAGS) -std=gnu++11 $(CXXFLAGS)
LINK_FLAGS = $(LINK_OPTS) $(LDFLAGS)

ifneq ($(MACOS),true)
ifeq ($(WASM),true)
# Special flag for emscripten
LINK_FLAGS += -sENVIRONMENT=web -sLLD_REPORT_UNDEFINED
else ifneq ($(MACOS),true)
# Not available on MacOS
LINK_FLAGS += -Wl,--no-undefined
LINK_FLAGS += -Wl,--no-undefined
endif

ifeq ($(MACOS_OLD),true)
BUILD_CXX_FLAGS = $(BASE_FLAGS) $(CXXFLAGS) -DHAVE_CPP11_SUPPORT=0
endif

ifeq ($(WASM_CLIPBOARD),true)
BUILD_CXX_FLAGS += -DPUGL_WASM_ASYNC_CLIPBOARD
LINK_FLAGS += -sASYNCIFY -sASYNCIFY_IMPORTS=puglGetAsyncClipboardData
endif

ifeq ($(WASM_EXCEPTIONS),true)
BUILD_CXX_FLAGS += -fexceptions
LINK_FLAGS += -fexceptions
endif

ifeq ($(WINDOWS),true)
# Always build statically on windows
LINK_FLAGS += -static -static-libgcc -static-libstdc++
@@ -235,33 +313,36 @@ endif

HAVE_CAIRO = $(shell $(PKG_CONFIG) --exists cairo && echo true)

# Vulkan is not supported yet
# HAVE_VULKAN = $(shell $(PKG_CONFIG) --exists vulkan && echo true)

ifeq ($(MACOS_OR_WINDOWS),true)
ifeq ($(MACOS_OR_WASM_OR_WINDOWS),true)
HAVE_OPENGL = true
else
HAVE_OPENGL = $(shell $(PKG_CONFIG) --exists gl && echo true)
ifneq ($(HAIKU),true)
HAVE_OPENGL = $(shell $(PKG_CONFIG) --exists gl && echo true)
HAVE_DBUS = $(shell $(PKG_CONFIG) --exists dbus-1 && echo true)
HAVE_X11 = $(shell $(PKG_CONFIG) --exists x11 && echo true)
HAVE_XCURSOR = $(shell $(PKG_CONFIG) --exists xcursor && echo true)
HAVE_XEXT = $(shell $(PKG_CONFIG) --exists xext && echo true)
HAVE_XRANDR = $(shell $(PKG_CONFIG) --exists xrandr && echo true)
endif
endif

# Vulkan is not supported yet
# HAVE_VULKAN = $(shell $(PKG_CONFIG) --exists vulkan && echo true)

# ---------------------------------------------------------------------------------------------------------------------
# Check for optional libraries

HAVE_LIBLO = $(shell $(PKG_CONFIG) --exists liblo && echo true)

ifneq ($(SKIP_NATIVE_AUDIO_FALLBACK),true)
ifneq ($(SKIP_RTAUDIO_FALLBACK),true)

ifeq ($(MACOS),true)
HAVE_RTAUDIO = true
else ifeq ($(WINDOWS),true)
HAVE_RTAUDIO = true
else ifneq ($(HAIKU),true)
else
HAVE_ALSA = $(shell $(PKG_CONFIG) --exists alsa && echo true)
HAVE_PULSEAUDIO = $(shell $(PKG_CONFIG) --exists libpulse-simple && echo true)
HAVE_SDL2 = $(shell $(PKG_CONFIG) --exists sdl2 && echo true)
ifeq ($(HAVE_ALSA),true)
HAVE_RTAUDIO = true
else ifeq ($(HAVE_PULSEAUDIO),true)
@@ -269,31 +350,39 @@ HAVE_RTAUDIO = true
endif
endif

# backwards compat
endif
endif

# backwards compat, always available/enabled
ifneq ($(FORCE_NATIVE_AUDIO_FALLBACK),true)
ifeq ($(STATIC_BUILD),true)
HAVE_JACK = $(shell $(PKG_CONFIG) --exists jack && echo true)
else
HAVE_JACK = true
endif
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set Generic DGL stuff

ifeq ($(HAIKU),true)
DGL_SYSTEM_LIBS += -lbe
endif

ifeq ($(MACOS),true)
else ifeq ($(MACOS),true)
DGL_SYSTEM_LIBS += -framework Cocoa -framework CoreVideo
endif

ifeq ($(WINDOWS),true)
else ifeq ($(WASM),true)
else ifeq ($(WINDOWS),true)
DGL_SYSTEM_LIBS += -lgdi32 -lcomdlg32
# -lole32
else
ifeq ($(HAVE_DBUS),true)
DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags dbus-1) -DHAVE_DBUS
DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs dbus-1)
endif

ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true)
ifeq ($(HAVE_X11),true)
DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags x11) -DHAVE_X11
DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs x11)
ifeq ($(HAVE_XCURSOR),true)
# TODO -DHAVE_XCURSOR
DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags xcursor)
DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags xcursor) -DHAVE_XCURSOR
DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs xcursor)
endif
ifeq ($(HAVE_XEXT),true)
@@ -331,18 +420,20 @@ DGL_FLAGS += -DHAVE_OPENGL
ifeq ($(HAIKU),true)
OPENGL_FLAGS = $(shell $(PKG_CONFIG) --cflags gl)
OPENGL_LIBS = $(shell $(PKG_CONFIG) --libs gl)
endif

ifeq ($(MACOS),true)
else ifeq ($(MACOS),true)
OPENGL_FLAGS = -DGL_SILENCE_DEPRECATION=1 -Wno-deprecated-declarations
OPENGL_LIBS = -framework OpenGL
else ifeq ($(WASM),true)
ifeq ($(USE_GLES2),true)
OPENGL_LIBS = -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2
else
ifneq ($(USE_GLES3),true)
OPENGL_LIBS = -sLEGACY_GL_EMULATION -sGL_UNSAFE_OPTS=0
endif

ifeq ($(WINDOWS),true)
OPENGL_LIBS = -lopengl32
endif

ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true)
else ifeq ($(WINDOWS),true)
OPENGL_LIBS = -lopengl32
else
OPENGL_FLAGS = $(shell $(PKG_CONFIG) --cflags gl x11)
OPENGL_LIBS = $(shell $(PKG_CONFIG) --libs gl x11)
endif
@@ -354,7 +445,7 @@ endif
# ---------------------------------------------------------------------------------------------------------------------
# Set Stub specific stuff

ifeq ($(HAIKU_OR_MACOS_OR_WINDOWS),true)
ifeq ($(MACOS_OR_WASM_OR_WINDOWS),true)
HAVE_STUB = true
else
HAVE_STUB = $(HAVE_X11)
@@ -394,41 +485,105 @@ PULSEAUDIO_FLAGS = $(shell $(PKG_CONFIG) --cflags libpulse-simple)
PULSEAUDIO_LIBS = $(shell $(PKG_CONFIG) --libs libpulse-simple)
endif

ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true)
ifeq ($(HAVE_SDL2),true)
SDL2_FLAGS = $(shell $(PKG_CONFIG) --cflags sdl2)
SDL2_LIBS = $(shell $(PKG_CONFIG) --libs sdl2)
endif

ifeq ($(HAVE_JACK),true)
ifeq ($(STATIC_BUILD),true)
JACK_FLAGS = $(shell $(PKG_CONFIG) --cflags jack)
JACK_LIBS = $(shell $(PKG_CONFIG) --libs jack)
endif
endif

ifneq ($(HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS),true)
SHARED_MEMORY_LIBS = -lrt
endif

# ---------------------------------------------------------------------------------------------------------------------
# Backwards-compatible HAVE_DGL

ifeq ($(MACOS_OR_WINDOWS),true)
ifeq ($(MACOS_OR_WASM_OR_WINDOWS),true)
HAVE_DGL = true
else ifeq ($(HAVE_OPENGL),true)
ifeq ($(HAIKU),true)
HAVE_DGL = true
else
HAVE_DGL = $(HAVE_X11)
endif

# ---------------------------------------------------------------------------------------------------------------------
# Namespace flags

ifneq ($(DISTRHO_NAMESPACE),)
BUILD_CXX_FLAGS += -DDISTRHO_NAMESPACE=$(DISTRHO_NAMESPACE)
endif

ifneq ($(DGL_NAMESPACE),)
BUILD_CXX_FLAGS += -DDGL_NAMESPACE=$(DGL_NAMESPACE)
endif

# ---------------------------------------------------------------------------------------------------------------------
# Optional flags

ifeq ($(NVG_DISABLE_SKIPPING_WHITESPACE),true)
BUILD_CXX_FLAGS += -DNVG_DISABLE_SKIPPING_WHITESPACE
endif

ifneq ($(NVG_FONT_TEXTURE_FLAGS),)
BUILD_CXX_FLAGS += -DNVG_FONT_TEXTURE_FLAGS=$(NVG_FONT_TEXTURE_FLAGS)
endif

ifeq ($(FILE_BROWSER_DISABLED),true)
BUILD_CXX_FLAGS += -DDGL_FILE_BROWSER_DISABLED
endif

ifneq ($(WINDOWS_ICON_ID),)
BUILD_CXX_FLAGS += -DDGL_WINDOWS_ICON_ID=$(WINDOWS_ICON_ID)
endif

ifeq ($(USE_GLES2),true)
BUILD_CXX_FLAGS += -DDGL_USE_GLES -DDGL_USE_GLES2
endif

ifeq ($(USE_GLES3),true)
BUILD_CXX_FLAGS += -DDGL_USE_GLES -DDGL_USE_GLES3
endif

ifeq ($(USE_OPENGL3),true)
BUILD_CXX_FLAGS += -DDGL_USE_OPENGL3
endif

ifeq ($(USE_NANOVG_FBO),true)
BUILD_CXX_FLAGS += -DDGL_USE_NANOVG_FBO
endif

ifeq ($(USE_NANOVG_FREETYPE),true)
BUILD_CXX_FLAGS += -DFONS_USE_FREETYPE $(shell $(PKG_CONFIG) --cflags freetype2)
endif

ifeq ($(USE_RGBA),true)
BUILD_CXX_FLAGS += -DDGL_USE_RGBA
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set app extension

ifeq ($(WINDOWS),true)
ifeq ($(WASM),true)
APP_EXT = .html
else ifeq ($(WINDOWS),true)
APP_EXT = .exe
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set shared lib extension

LIB_EXT = .so

ifeq ($(MACOS),true)
LIB_EXT = .dylib
endif

ifeq ($(WINDOWS),true)
else ifeq ($(WASM),true)
LIB_EXT = .wasm
else ifeq ($(WINDOWS),true)
LIB_EXT = .dll
else
LIB_EXT = .so
endif

# ---------------------------------------------------------------------------------------------------------------------
@@ -436,6 +591,8 @@ endif

ifeq ($(MACOS),true)
SHARED = -dynamiclib
else ifeq ($(WASM),true)
SHARED = -sSIDE_MODULE=2
else
SHARED = -shared
endif
@@ -443,8 +600,12 @@ endif
# ---------------------------------------------------------------------------------------------------------------------
# Handle the verbosity switch

ifeq ($(VERBOSE),true)
SILENT =

ifeq ($(VERBOSE),1)
else ifeq ($(VERBOSE),y)
else ifeq ($(VERBOSE),yes)
else ifeq ($(VERBOSE),true)
else
SILENT = @
endif
@@ -473,19 +634,24 @@ features:
$(call print_available,HURD)
$(call print_available,LINUX)
$(call print_available,MACOS)
$(call print_available,WASM)
$(call print_available,WINDOWS)
$(call print_available,HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS)
$(call print_available,HAIKU_OR_MACOS_OR_WINDOWS)
$(call print_available,LINUX_OR_MACOS)
$(call print_available,MACOS_OR_WASM_OR_WINDOWS)
$(call print_available,MACOS_OR_WINDOWS)
$(call print_available,UNIX)
@echo === Detected features
$(call print_available,HAVE_ALSA)
$(call print_available,HAVE_DBUS)
$(call print_available,HAVE_CAIRO)
$(call print_available,HAVE_DGL)
$(call print_available,HAVE_LIBLO)
$(call print_available,HAVE_OPENGL)
$(call print_available,HAVE_PULSEAUDIO)
$(call print_available,HAVE_RTAUDIO)
$(call print_available,HAVE_SDL2)
$(call print_available,HAVE_STUB)
$(call print_available,HAVE_VULKAN)
$(call print_available,HAVE_X11)


+ 275
- 71
Makefile.plugins.mk View File

@@ -38,6 +38,10 @@ ifeq ($(HAVE_ALSA),true)
BASE_FLAGS += -DHAVE_ALSA
endif

ifeq ($(HAVE_JACK),true)
BASE_FLAGS += -DHAVE_JACK
endif

ifeq ($(HAVE_LIBLO),true)
BASE_FLAGS += -DHAVE_LIBLO
endif
@@ -46,41 +50,66 @@ ifeq ($(HAVE_PULSEAUDIO),true)
BASE_FLAGS += -DHAVE_PULSEAUDIO
endif

ifeq ($(HAVE_RTAUDIO),true)
BASE_FLAGS += -DHAVE_RTAUDIO
endif

ifeq ($(HAVE_SDL2),true)
BASE_FLAGS += -DHAVE_SDL2
endif

# always needed
ifneq ($(HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS),true)
ifneq ($(STATIC_BUILD),true)
LINK_FLAGS += -ldl
endif
endif

# ---------------------------------------------------------------------------------------------------------------------
# JACK/Standalone setup

ifeq ($(WASM),true)

JACK_FLAGS += -sUSE_SDL=2
JACK_LIBS += -sUSE_SDL=2
JACK_LIBS += -sMAIN_MODULE -ldl

ifneq ($(FILE_BROWSER_DISABLED),true)
JACK_LIBS += -sEXPORTED_RUNTIME_METHODS=FS,cwrap
endif

else ifneq ($(SKIP_RTAUDIO_FALLBACK),true)

ifeq ($(MACOS),true)
JACK_LIBS += -framework CoreAudio -framework CoreFoundation
JACK_LIBS += -framework CoreAudio -framework CoreFoundation -framework CoreMIDI
else ifeq ($(WINDOWS),true)
JACK_LIBS += -lksuser -lmfplat -lmfuuid -lole32 -lwinmm -lwmcodecdspuuid
else ifneq ($(HAIKU),true)
JACK_LIBS = -ldl
JACK_LIBS += -lole32 -lwinmm
# DirectSound
JACK_LIBS += -ldsound
# WASAPI
# JACK_LIBS += -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid
else
ifeq ($(HAVE_PULSEAUDIO),true)
JACK_FLAGS += $(PULSEAUDIO_FLAGS)
JACK_LIBS += $(PULSEAUDIO_LIBS)
endif
ifeq ($(HAVE_ALSA),true)
JACK_FLAGS += $(ALSA_FLAGS)
JACK_LIBS += $(ALSA_LIBS)
endif
ifeq ($(HAVE_PULSEAUDIO),true)
JACK_FLAGS += $(PULSEAUDIO_FLAGS)
JACK_LIBS += $(PULSEAUDIO_LIBS)
endif

ifeq ($(HAVE_RTAUDIO),true)
ifneq ($(HAIKU),true)
JACK_LIBS += -lpthread
endif # !HAIKU
endif

# backwards compat
BASE_FLAGS += -DHAVE_JACK

# ---------------------------------------------------------------------------------------------------------------------
# Set VST3 filename, see https://vst3sdk-doc.diatonic.jp/doc/vstinterfaces/vst3loc.html

ifeq ($(LINUX),true)
VST3_FILENAME = $(TARGET_PROCESSOR)-linux/$(NAME).so
endif
ifeq ($(MACOS),true)
ifneq ($(MACOS_OLD),true)
VST3_FILENAME = MacOS/$(NAME)
endif

ifeq ($(HAVE_SDL2),true)
JACK_FLAGS += $(SDL2_FLAGS)
JACK_LIBS += $(SDL2_LIBS)
endif
ifeq ($(WINDOWS),true)
VST3_FILENAME = $(TARGET_PROCESSOR)-win/$(NAME).vst3

endif

# ---------------------------------------------------------------------------------------------------------------------
@@ -93,33 +122,6 @@ ifeq ($(MACOS),true)
OBJS_UI += $(BUILD_DIR)/DistrhoUI_macOS_$(NAME).mm.o
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set plugin binary file targets

jack = $(TARGET_DIR)/$(NAME)$(APP_EXT)
ladspa_dsp = $(TARGET_DIR)/$(NAME)-ladspa$(LIB_EXT)
dssi_dsp = $(TARGET_DIR)/$(NAME)-dssi$(LIB_EXT)
dssi_ui = $(TARGET_DIR)/$(NAME)-dssi/$(NAME)_ui$(APP_EXT)
lv2 = $(TARGET_DIR)/$(NAME).lv2/$(NAME)$(LIB_EXT)
lv2_dsp = $(TARGET_DIR)/$(NAME).lv2/$(NAME)_dsp$(LIB_EXT)
lv2_ui = $(TARGET_DIR)/$(NAME).lv2/$(NAME)_ui$(LIB_EXT)
vst2 = $(TARGET_DIR)/$(NAME)-vst$(LIB_EXT)
ifneq ($(VST3_FILENAME),)
vst3 = $(TARGET_DIR)/$(NAME).vst3/Contents/$(VST3_FILENAME)
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set plugin symbols to export

ifeq ($(MACOS),true)
SYMBOLS_LADSPA = -Wl,-exported_symbol,_ladspa_descriptor
SYMBOLS_DSSI = -Wl,-exported_symbol,_ladspa_descriptor -Wl,-exported_symbol,_dssi_descriptor
SYMBOLS_LV2 = -Wl,-exported_symbol,_lv2_descriptor -Wl,-exported_symbol,_lv2_generate_ttl
SYMBOLS_LV2UI = -Wl,-exported_symbol,_lv2ui_descriptor
SYMBOLS_VST2 = -Wl,-exported_symbol,_VSTPluginMain
SYMBOLS_VST3 = -Wl,-exported_symbol,_GetPluginFactory -Wl,-exported_symbol,_bundleEntry -Wl,-exported_symbol,_bundleExit
endif

# ---------------------------------------------------------------------------------------------------------------------
# Handle UI stuff, disable UI support automatically

@@ -164,6 +166,18 @@ HAVE_DGL = false
endif
endif

ifeq ($(UI_TYPE),opengl3)
ifeq ($(HAVE_OPENGL),true)
DGL_FLAGS += -DDGL_OPENGL -DDGL_USE_OPENGL3 -DHAVE_DGL
DGL_FLAGS += $(OPENGL_FLAGS)
DGL_LIBS += $(OPENGL_LIBS)
DGL_LIB = $(DPF_PATH)/build/libdgl-opengl3.a
HAVE_DGL = true
else
HAVE_DGL = false
endif
endif

ifeq ($(UI_TYPE),vulkan)
ifeq ($(HAVE_VULKAN),true)
DGL_FLAGS += -DDGL_VULKAN -DHAVE_DGL
@@ -192,6 +206,76 @@ endif

DGL_LIBS += $(DGL_SYSTEM_LIBS) -lm

# TODO split dsp and ui object build flags
BASE_FLAGS += $(DGL_FLAGS)

# ---------------------------------------------------------------------------------------------------------------------
# Set VST2 filename, either single binary or inside a bundle

ifeq ($(MACOS),true)
VST2_CONTENTS = $(NAME).vst/Contents
VST2_FILENAME = $(VST2_CONTENTS)/MacOS/$(NAME)
else ifeq ($(USE_VST2_BUNDLE),true)
VST2_FILENAME = $(NAME).vst/$(NAME)$(LIB_EXT)
else
VST2_FILENAME = $(NAME)-vst$(LIB_EXT)
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set VST3 filename, see https://vst3sdk-doc.diatonic.jp/doc/vstinterfaces/vst3loc.html

ifeq ($(LINUX),true)
VST3_FILENAME = $(NAME).vst3/Contents/$(TARGET_PROCESSOR)-linux/$(NAME).so
else ifeq ($(MACOS),true)
VST3_CONTENTS = $(NAME).vst3/Contents
VST3_FILENAME = $(VST3_CONTENTS)/MacOS/$(NAME)
else ifeq ($(WASM),true)
VST3_FILENAME = $(NAME).vst3/Contents/wasm/$(NAME).vst3
else ifeq ($(WINDOWS),true)
ifeq ($(CPU_I386),true)
VST3_FILENAME = $(NAME).vst3/Contents/x86-win/$(NAME).vst3
else ifeq ($(CPU_X86_64),true)
VST3_FILENAME = $(NAME).vst3/Contents/x86_64-win/$(NAME).vst3
endif
endif

# ---------------------------------------------------------------------------------------------------------------------
# Set plugin binary file targets

ifeq ($(MACOS),true)
ifeq ($(HAVE_DGL),true)
MACOS_APP_BUNDLE = true
endif
endif

ifeq ($(MACOS_APP_BUNDLE),true)
jack = $(TARGET_DIR)/$(NAME).app/Contents/MacOS/$(NAME)
jackfiles = $(TARGET_DIR)/$(NAME).app/Contents/Info.plist
else
jack = $(TARGET_DIR)/$(NAME)$(APP_EXT)
endif
ladspa_dsp = $(TARGET_DIR)/$(NAME)-ladspa$(LIB_EXT)
dssi_dsp = $(TARGET_DIR)/$(NAME)-dssi$(LIB_EXT)
dssi_ui = $(TARGET_DIR)/$(NAME)-dssi/$(NAME)_ui$(APP_EXT)
lv2 = $(TARGET_DIR)/$(NAME).lv2/$(NAME)$(LIB_EXT)
lv2_dsp = $(TARGET_DIR)/$(NAME).lv2/$(NAME)_dsp$(LIB_EXT)
lv2_ui = $(TARGET_DIR)/$(NAME).lv2/$(NAME)_ui$(LIB_EXT)
vst2 = $(TARGET_DIR)/$(VST2_FILENAME)
ifneq ($(VST3_FILENAME),)
vst3 = $(TARGET_DIR)/$(VST3_FILENAME)
endif
shared = $(TARGET_DIR)/$(NAME)$(LIB_EXT)
static = $(TARGET_DIR)/$(NAME).a

ifeq ($(MACOS),true)
vst2files += $(TARGET_DIR)/$(VST2_CONTENTS)/Info.plist
vst2files += $(TARGET_DIR)/$(VST2_CONTENTS)/PkgInfo
vst2files += $(TARGET_DIR)/$(VST2_CONTENTS)/Resources/empty.lproj
vst3files += $(TARGET_DIR)/$(VST3_CONTENTS)/Info.plist
vst3files += $(TARGET_DIR)/$(VST3_CONTENTS)/PkgInfo
vst3files += $(TARGET_DIR)/$(VST3_CONTENTS)/Resources/empty.lproj
endif

ifneq ($(HAVE_DGL),true)
dssi_ui =
lv2_ui =
@@ -203,8 +287,53 @@ ifneq ($(HAVE_LIBLO),true)
dssi_ui =
endif

# TODO split dsp and ui object build flags
BASE_FLAGS += $(DGL_FLAGS)
# ---------------------------------------------------------------------------------------------------------------------
# Set plugin symbols to export

ifeq ($(MACOS),true)
SYMBOLS_LADSPA = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/ladspa.exp
SYMBOLS_DSSI = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/dssi.exp
SYMBOLS_LV2DSP = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/lv2-dsp.exp
SYMBOLS_LV2UI = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/lv2-ui.exp
SYMBOLS_LV2 = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/lv2.exp
SYMBOLS_VST2 = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/vst2.exp
SYMBOLS_VST3 = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/vst3.exp
SYMBOLS_SHARED = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/shared.exp
else ifeq ($(WASM),true)
SYMBOLS_LADSPA = -sEXPORTED_FUNCTIONS="['ladspa_descriptor']"
SYMBOLS_DSSI = -sEXPORTED_FUNCTIONS="['ladspa_descriptor','dssi_descriptor']"
SYMBOLS_LV2DSP = -sEXPORTED_FUNCTIONS="['lv2_descriptor','lv2_generate_ttl']"
SYMBOLS_LV2UI = -sEXPORTED_FUNCTIONS="['lv2ui_descriptor']"
SYMBOLS_LV2 = -sEXPORTED_FUNCTIONS="['lv2_descriptor','lv2_generate_ttl','lv2ui_descriptor']"
SYMBOLS_VST2 = -sEXPORTED_FUNCTIONS="['VSTPluginMain']"
SYMBOLS_VST3 = -sEXPORTED_FUNCTIONS="['GetPluginFactory','ModuleEntry','ModuleExit']"
SYMBOLS_SHARED = -sEXPORTED_FUNCTIONS="['createSharedPlugin']"
else ifeq ($(WINDOWS),true)
SYMBOLS_LADSPA = $(DPF_PATH)/utils/symbols/ladspa.def
SYMBOLS_DSSI = $(DPF_PATH)/utils/symbols/dssi.def
SYMBOLS_LV2DSP = $(DPF_PATH)/utils/symbols/lv2-dsp.def
SYMBOLS_LV2UI = $(DPF_PATH)/utils/symbols/lv2-ui.def
SYMBOLS_LV2 = $(DPF_PATH)/utils/symbols/lv2.def
SYMBOLS_VST2 = $(DPF_PATH)/utils/symbols/vst2.def
SYMBOLS_VST3 = $(DPF_PATH)/utils/symbols/vst3.def
SYMBOLS_SHARED = $(DPF_PATH)/utils/symbols/shared.def
else ifneq ($(DEBUG),true)
SYMBOLS_LADSPA = -Wl,--version-script=$(DPF_PATH)/utils/symbols/ladspa.version
SYMBOLS_DSSI = -Wl,--version-script=$(DPF_PATH)/utils/symbols/dssi.version
SYMBOLS_LV2DSP = -Wl,--version-script=$(DPF_PATH)/utils/symbols/lv2-dsp.version
SYMBOLS_LV2UI = -Wl,--version-script=$(DPF_PATH)/utils/symbols/lv2-ui.version
SYMBOLS_LV2 = -Wl,--version-script=$(DPF_PATH)/utils/symbols/lv2.version
SYMBOLS_VST2 = -Wl,--version-script=$(DPF_PATH)/utils/symbols/vst2.version
SYMBOLS_VST3 = -Wl,--version-script=$(DPF_PATH)/utils/symbols/vst3.version
SYMBOLS_SHARED = -Wl,--version-script=$(DPF_PATH)/utils/symbols/shared.version
endif

# ---------------------------------------------------------------------------------------------------------------------
# Runtime test build

ifeq ($(DPF_RUNTIME_TESTING),true)
BUILD_CXX_FLAGS += -DDPF_RUNTIME_TESTING -Wno-pmf-conversions
endif

# ---------------------------------------------------------------------------------------------------------------------
# all needs to be first
@@ -234,9 +363,23 @@ $(BUILD_DIR)/%.cpp.o: %.cpp
@echo "Compiling $<"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@

$(BUILD_DIR)/%.m.o: %.m
-@mkdir -p "$(shell dirname $(BUILD_DIR)/$<)"
@echo "Compiling $<"
$(SILENT)$(CC) $< $(BUILD_C_FLAGS) -ObjC -c -o $@

$(BUILD_DIR)/%.mm.o: %.mm
-@mkdir -p "$(shell dirname $(BUILD_DIR)/$<)"
@echo "Compiling $<"
$(SILENT)$(CC) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@

clean:
rm -rf $(BUILD_DIR)
rm -rf $(TARGET_DIR)/$(NAME) $(TARGET_DIR)/$(NAME)-* $(TARGET_DIR)/$(NAME).lv2
rm -rf $(TARGET_DIR)/$(NAME)
rm -rf $(TARGET_DIR)/$(NAME)-*
rm -rf $(TARGET_DIR)/$(NAME).lv2
rm -rf $(TARGET_DIR)/$(NAME).vst
rm -rf $(TARGET_DIR)/$(NAME).vst3

# ---------------------------------------------------------------------------------------------------------------------
# DGL
@@ -247,6 +390,9 @@ $(DPF_PATH)/build/libdgl-cairo.a:
$(DPF_PATH)/build/libdgl-opengl.a:
$(MAKE) -C $(DPF_PATH)/dgl opengl

$(DPF_PATH)/build/libdgl-opengl3.a:
$(MAKE) -C $(DPF_PATH)/dgl opengl3

$(DPF_PATH)/build/libdgl-stub.a:
$(MAKE) -C $(DPF_PATH)/dgl stub

@@ -255,37 +401,35 @@ $(DPF_PATH)/build/libdgl-vulkan.a:

# ---------------------------------------------------------------------------------------------------------------------

AS_PUGL_NAMESPACE = $(subst -,_,$(1))

$(BUILD_DIR)/DistrhoPluginMain_%.cpp.o: $(DPF_PATH)/distrho/DistrhoPluginMain.cpp
$(BUILD_DIR)/DistrhoPluginMain_%.cpp.o: $(DPF_PATH)/distrho/DistrhoPluginMain.cpp $(EXTRA_DEPENDENCIES)
-@mkdir -p $(BUILD_DIR)
@echo "Compiling DistrhoPluginMain.cpp ($*)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_$* -c -o $@

$(BUILD_DIR)/DistrhoUIMain_%.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp
$(BUILD_DIR)/DistrhoUIMain_%.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp $(EXTRA_DEPENDENCIES)
-@mkdir -p $(BUILD_DIR)
@echo "Compiling DistrhoUIMain.cpp ($*)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_$* -c -o $@

$(BUILD_DIR)/DistrhoUI_macOS_%.mm.o: $(DPF_PATH)/distrho/DistrhoUI_macOS.mm
$(BUILD_DIR)/DistrhoUI_macOS_%.mm.o: $(DPF_PATH)/distrho/DistrhoUI_macOS.mm $(EXTRA_DEPENDENCIES)
-@mkdir -p $(BUILD_DIR)
@echo "Compiling DistrhoUI_macOS.mm ($*)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DPUGL_NAMESPACE=$(call AS_PUGL_NAMESPACE,$*) -DGL_SILENCE_DEPRECATION -Wno-deprecated-declarations -I$(DPF_PATH)/dgl/src -I$(DPF_PATH)/dgl/src/pugl-upstream/include -ObjC++ -c -o $@
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@

$(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o: $(DPF_PATH)/distrho/DistrhoPluginMain.cpp
$(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o: $(DPF_PATH)/distrho/DistrhoPluginMain.cpp $(EXTRA_DEPENDENCIES)
-@mkdir -p $(BUILD_DIR)
@echo "Compiling DistrhoPluginMain.cpp (JACK)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_JACK $(JACK_FLAGS) -c -o $@

$(BUILD_DIR)/DistrhoUIMain_DSSI.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp
$(BUILD_DIR)/DistrhoUIMain_DSSI.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp $(EXTRA_DEPENDENCIES)
-@mkdir -p $(BUILD_DIR)
@echo "Compiling DistrhoUIMain.cpp (DSSI)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(LIBLO_FLAGS) -DDISTRHO_PLUGIN_TARGET_DSSI -c -o $@
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_DSSI $(LIBLO_FLAGS) -c -o $@

# ---------------------------------------------------------------------------------------------------------------------
# JACK

jack: $(jack)
jack: $(jack) $(jackfiles)

ifeq ($(HAVE_DGL),true)
$(jack): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o $(BUILD_DIR)/DistrhoUIMain_JACK.cpp.o $(DGL_LIB)
@@ -294,7 +438,7 @@ $(jack): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o
endif
-@mkdir -p $(shell dirname $@)
@echo "Creating JACK standalone for $(NAME)"
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(JACK_LIBS) -o $@
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(JACK_LIBS) -o $@

# ---------------------------------------------------------------------------------------------------------------------
# LADSPA
@@ -337,22 +481,22 @@ $(lv2): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_LV2.cpp.o
endif
-@mkdir -p $(shell dirname $@)
@echo "Creating LV2 plugin for $(NAME)"
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_LV2) $(SYMBOLS_LV2UI) -o $@
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_LV2) -o $@

$(lv2_dsp): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_LV2.cpp.o
-@mkdir -p $(shell dirname $@)
@echo "Creating LV2 plugin library for $(NAME)"
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(SHARED) $(SYMBOLS_LV2) -o $@
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(SHARED) $(SYMBOLS_LV2DSP) -o $@

$(lv2_ui): $(OBJS_UI) $(BUILD_DIR)/DistrhoUIMain_LV2.cpp.o $(DGL_LIB)
-@mkdir -p $(shell dirname $@)
@echo "Creating LV2 plugin UI for $(NAME)"
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_LV2UI) -o $@
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_LV2UI) -o $@

# ---------------------------------------------------------------------------------------------------------------------
# VST2

vst2 vst: $(vst2)
vst2 vst: $(vst2) $(vst2files)

ifeq ($(HAVE_DGL),true)
$(vst2): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_VST2.cpp.o $(BUILD_DIR)/DistrhoUIMain_VST2.cpp.o $(DGL_LIB)
@@ -361,17 +505,73 @@ $(vst2): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_VST2.cpp.o
endif
-@mkdir -p $(shell dirname $@)
@echo "Creating VST2 plugin for $(NAME)"
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_VST2) -o $@
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_VST2) -o $@

# ---------------------------------------------------------------------------------------------------------------------
# VST3

vst3: $(vst3)
vst3: $(vst3) $(vst3files)

ifeq ($(HAVE_DGL),true)
$(vst3): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_VST3.cpp.o $(BUILD_DIR)/DistrhoUIMain_VST3.cpp.o $(DGL_LIB)
else
$(vst3): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_VST3.cpp.o
endif
-@mkdir -p $(shell dirname $@)
@echo "Creating VST3 plugin for $(NAME)"
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_VST3) -o $@
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_VST3) -o $@

# ---------------------------------------------------------------------------------------------------------------------
# Shared

shared: $(shared)

ifeq ($(HAVE_DGL),true)
$(shared): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_SHARED.cpp.o $(BUILD_DIR)/DistrhoUIMain_SHARED.cpp.o $(DGL_LIB)
else
$(shared): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_SHARED.cpp.o
endif
-@mkdir -p $(shell dirname $@)
@echo "Creating shared library for $(NAME)"
$(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_SHARED) -o $@

# ---------------------------------------------------------------------------------------------------------------------
# Static

static: $(static)

ifeq ($(HAVE_DGL),true)
$(static): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_STATIC.cpp.o $(BUILD_DIR)/DistrhoUIMain_STATIC.cpp.o
else
$(static): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_STATIC.cpp.o
endif
-@mkdir -p $(shell dirname $@)
@echo "Creating static library for $(NAME)"
$(SILENT)rm -f $@
$(SILENT)$(AR) crs $@ $^

# ---------------------------------------------------------------------------------------------------------------------
# macOS files

$(TARGET_DIR)/%.app/Contents/Info.plist: $(DPF_PATH)/utils/plugin.app/Contents/Info.plist
-@mkdir -p $(shell dirname $@)
$(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@

$(TARGET_DIR)/%.vst/Contents/Info.plist: $(DPF_PATH)/utils/plugin.vst/Contents/Info.plist
-@mkdir -p $(shell dirname $@)
$(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@

$(TARGET_DIR)/%.vst3/Contents/Info.plist: $(DPF_PATH)/utils/plugin.vst/Contents/Info.plist
-@mkdir -p $(shell dirname $@)
$(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@

$(TARGET_DIR)/%/Contents/PkgInfo: $(DPF_PATH)/utils/plugin.vst/Contents/PkgInfo
-@mkdir -p $(shell dirname $@)
$(SILENT)cp $< $@

$(TARGET_DIR)/%/Resources/empty.lproj: $(DPF_PATH)/utils/plugin.vst/Contents/Resources/empty.lproj
-@mkdir -p $(shell dirname $@)
$(SILENT)cp $< $@

# ---------------------------------------------------------------------------------------------------------------------

@@ -386,11 +586,15 @@ endif
-include $(BUILD_DIR)/DistrhoPluginMain_LV2.cpp.d
-include $(BUILD_DIR)/DistrhoPluginMain_VST2.cpp.d
-include $(BUILD_DIR)/DistrhoPluginMain_VST3.cpp.d
-include $(BUILD_DIR)/DistrhoPluginMain_SHARED.cpp.d
-include $(BUILD_DIR)/DistrhoPluginMain_STATIC.cpp.d

-include $(BUILD_DIR)/DistrhoUIMain_JACK.cpp.d
-include $(BUILD_DIR)/DistrhoUIMain_DSSI.cpp.d
-include $(BUILD_DIR)/DistrhoUIMain_LV2.cpp.d
-include $(BUILD_DIR)/DistrhoUIMain_VST2.cpp.d
-include $(BUILD_DIR)/DistrhoUIMain_VST3.cpp.d
-include $(BUILD_DIR)/DistrhoUIMain_SHARED.cpp.d
-include $(BUILD_DIR)/DistrhoUIMain_STATIC.cpp.d

# ---------------------------------------------------------------------------------------------------------------------

+ 7
- 1
README.md View File

@@ -7,7 +7,7 @@ DPF is designed to make development of new plugins an easy and enjoyable task.<b
It allows developers to create plugins with custom UIs using a simple C++ API.<br/>
The framework facilitates exporting various different plugin formats from the same code-base.<br/>

DPF can build for LADSPA, DSSI, LV2 and VST formats.<br/>
DPF can build for LADSPA, DSSI, LV2, VST2 and VST3 formats.<br/>
All current plugin format implementations are complete.<br/>
A JACK/Standalone mode is also available, allowing you to quickly test plugins.<br/>

@@ -19,6 +19,12 @@ Getting time information from the host is possible.<br/>
It uses the same format as the JACK Transport API, making porting some code easier.<br/>


## Licensing

DPF is released under ISC, which basically means you can do whatever you want as long as you credit the original authors.
Some plugin formats may have additional restrictions, see [LICENSING.md](LICENSING.md) for details.


## Help and documentation

Bug reports happen on the [DPF github project](https://github.com/DISTRHO/DPF/issues).


+ 147
- 34
cmake/DPF-plugin.cmake View File

@@ -22,7 +22,7 @@
# add_subdirectory(DPF)
#
# dpf_add_plugin(MyPlugin
# TARGETS lv2 vst2
# TARGETS lv2 vst2 vst3
# UI_TYPE opengl
# FILES_DSP
# src/MyPlugin.cpp
@@ -71,7 +71,7 @@ include(CMakeParseArguments)
#
# `TARGETS` <tgt1>...<tgtN>
# a list of one of more of the following target types:
# `jack`, `ladspa`, `dssi`, `lv2`, `vst2`
# `jack`, `ladspa`, `dssi`, `lv2`, `vst2`, `vst3`
#
# `UI_TYPE` <type>
# the user interface type: `opengl` (default), `cairo`
@@ -122,6 +122,10 @@ function(dpf_add_plugin NAME)
target_include_directories("${NAME}" PUBLIC
"${DPF_ROOT_DIR}/distrho")

if((NOT WIN32) AND (NOT APPLE) AND (NOT HAIKU))
target_link_libraries("${NAME}" PRIVATE "dl")
endif()

if(_dgl_library)
# make sure that all code will see DGL_* definitions
target_link_libraries("${NAME}" PUBLIC
@@ -135,7 +139,10 @@ function(dpf_add_plugin NAME)
if(_dgl_library)
dpf__add_static_library("${NAME}-ui" ${_dpf_plugin_FILES_UI})
target_link_libraries("${NAME}-ui" PUBLIC "${NAME}" ${_dgl_library})
# add the files containing Objective-C classes, recompiled under namespace
if((NOT WIN32) AND (NOT APPLE) AND (NOT HAIKU))
target_link_libraries("${NAME}-ui" PRIVATE "dl")
endif()
# add the files containing Objective-C classes
dpf__add_plugin_specific_ui_sources("${NAME}-ui")
else()
add_library("${NAME}-ui" INTERFACE)
@@ -153,6 +160,8 @@ function(dpf_add_plugin NAME)
dpf__build_lv2("${NAME}" "${_dgl_library}" "${_dpf_plugin_MONOLITHIC}")
elseif(_target STREQUAL "vst2")
dpf__build_vst2("${NAME}" "${_dgl_library}")
elseif(_target STREQUAL "vst3")
dpf__build_vst3("${NAME}" "${_dgl_library}")
else()
message(FATAL_ERROR "Unrecognized target type for plugin: ${_target}")
endif()
@@ -183,11 +192,6 @@ function(dpf__build_jack NAME DGL_LIBRARY)
RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/$<0:>"
OUTPUT_NAME "${NAME}")

# Note: libjack will be linked at runtime
if((NOT WIN32) AND (NOT APPLE) AND (NOT HAIKU))
target_link_libraries("${NAME}-jack" PRIVATE "dl")
endif()

# for RtAudio native fallback
if(APPLE)
find_library(APPLE_COREAUDIO_FRAMEWORK "CoreAudio")
@@ -199,13 +203,14 @@ endfunction()
# dpf__build_ladspa
# ------------------------------------------------------------------------------
#
# Add build rules for a DSSI plugin.
# Add build rules for a LADSPA plugin.
#
function(dpf__build_ladspa NAME)
dpf__create_dummy_source_list(_no_srcs)

dpf__add_module("${NAME}-ladspa" ${_no_srcs})
dpf__add_plugin_main("${NAME}-ladspa" "ladspa")
dpf__set_module_export_list("${NAME}-ladspa" "ladspa")
target_link_libraries("${NAME}-ladspa" PRIVATE "${NAME}-dsp")
set_target_properties("${NAME}-ladspa" PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/$<0:>"
@@ -234,6 +239,7 @@ function(dpf__build_dssi NAME DGL_LIBRARY)

dpf__add_module("${NAME}-dssi" ${_no_srcs})
dpf__add_plugin_main("${NAME}-dssi" "dssi")
dpf__set_module_export_list("${NAME}-dssi" "dssi")
target_link_libraries("${NAME}-dssi" PRIVATE "${NAME}-dsp")
set_target_properties("${NAME}-dssi" PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/$<0:>"
@@ -257,13 +263,18 @@ endfunction()
# dpf__build_lv2
# ------------------------------------------------------------------------------
#
# Add build rules for a LV2 plugin.
# Add build rules for an LV2 plugin.
#
function(dpf__build_lv2 NAME DGL_LIBRARY MONOLITHIC)
dpf__create_dummy_source_list(_no_srcs)

dpf__add_module("${NAME}-lv2" ${_no_srcs})
dpf__add_plugin_main("${NAME}-lv2" "lv2")
if(DGL_LIBRARY AND MONOLITHIC)
dpf__set_module_export_list("${NAME}-lv2" "lv2")
else()
dpf__set_module_export_list("${NAME}-lv2" "lv2-dsp")
endif()
target_link_libraries("${NAME}-lv2" PRIVATE "${NAME}-dsp")
set_target_properties("${NAME}-lv2" PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.lv2/$<0:>"
@@ -280,6 +291,7 @@ function(dpf__build_lv2 NAME DGL_LIBRARY MONOLITHIC)
else()
dpf__add_module("${NAME}-lv2-ui" ${_no_srcs})
dpf__add_ui_main("${NAME}-lv2-ui" "lv2" "${DGL_LIBRARY}")
dpf__set_module_export_list("${NAME}-lv2-ui" "lv2-ui")
target_link_libraries("${NAME}-lv2-ui" PRIVATE "${NAME}-ui")
set_target_properties("${NAME}-lv2-ui" PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.lv2/$<0:>"
@@ -293,7 +305,7 @@ function(dpf__build_lv2 NAME DGL_LIBRARY MONOLITHIC)
add_dependencies("${NAME}-lv2" lv2_ttl_generator)

add_custom_command(TARGET "${NAME}-lv2" POST_BUILD
COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR}
COMMAND
"$<TARGET_FILE:lv2_ttl_generator>"
"$<TARGET_FILE:${NAME}-lv2>"
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.lv2"
@@ -311,6 +323,7 @@ function(dpf__build_vst2 NAME DGL_LIBRARY)
dpf__add_module("${NAME}-vst2" ${_no_srcs})
dpf__add_plugin_main("${NAME}-vst2" "vst2")
dpf__add_ui_main("${NAME}-vst2" "vst2" "${DGL_LIBRARY}")
dpf__set_module_export_list("${NAME}-vst2" "vst2")
target_link_libraries("${NAME}-vst2" PRIVATE "${NAME}-dsp" "${NAME}-ui")
set_target_properties("${NAME}-vst2" PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/$<0:>"
@@ -330,6 +343,95 @@ function(dpf__build_vst2 NAME DGL_LIBRARY)
endif()
endfunction()

# dpf__determine_vst3_package_architecture
# ------------------------------------------------------------------------------
#
# Determines the package architecture for a VST3 plugin target.
#
function(dpf__determine_vst3_package_architecture OUTPUT_VARIABLE)
# if set by variable, override the detection
if(DPF_VST3_ARCHITECTURE)
set("${OUTPUT_VARIABLE}" "${DPF_VST3_ARCHITECTURE}" PARENT_SCOPE)
return()
endif()

# not used on Apple, which supports universal binary
if(APPLE)
set("${OUTPUT_VARIABLE}" "universal" PARENT_SCOPE)
return()
endif()

# identify the target processor (special case of MSVC, problematic sometimes)
if(MSVC)
set(vst3_system_arch "${MSVC_CXX_ARCHITECTURE_ID}")
else()
set(vst3_system_arch "${CMAKE_SYSTEM_PROCESSOR}")
endif()

# transform the processor name to a format that VST3 recognizes
if(vst3_system_arch MATCHES "^(x86_64|amd64|AMD64|x64|X64)$")
set(vst3_package_arch "x86_64")
elseif(vst3_system_arch MATCHES "^(i.86|x86|X86)$")
if(WIN32)
set(vst3_package_arch "x86")
else()
set(vst3_package_arch "i386")
endif()
elseif(vst3_system_arch MATCHES "^(armv[3-8][a-z]*)$")
set(vst3_package_arch "${vst3_system_arch}")
elseif(vst3_system_arch MATCHES "^(aarch64)$")
set(vst3_package_arch "aarch64")
else()
message(FATAL_ERROR "We don't know this architecture for VST3: ${vst3_system_arch}.")
endif()

# TODO: the detections for Windows arm/arm64 when supported

set("${OUTPUT_VARIABLE}" "${vst3_package_arch}" PARENT_SCOPE)
endfunction()

# dpf__build_vst3
# ------------------------------------------------------------------------------
#
# Add build rules for a VST3 plugin.
#
function(dpf__build_vst3 NAME DGL_LIBRARY)
dpf__determine_vst3_package_architecture(vst3_arch)

dpf__create_dummy_source_list(_no_srcs)

dpf__add_module("${NAME}-vst3" ${_no_srcs})
dpf__add_plugin_main("${NAME}-vst3" "vst3")
dpf__add_ui_main("${NAME}-vst3" "vst3" "${DGL_LIBRARY}")
dpf__set_module_export_list("${NAME}-vst3" "vst3")
target_link_libraries("${NAME}-vst3" PRIVATE "${NAME}-dsp" "${NAME}-ui")
set_target_properties("${NAME}-vst3" PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/obj/vst3/$<0:>"
OUTPUT_NAME "${NAME}"
PREFIX "")

if(APPLE)
set_target_properties("${NAME}-vst3" PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents/MacOS/$<0:>"
SUFFIX "")
elseif(WIN32)
set_target_properties("${NAME}-vst3" PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents/${vst3_arch}-win/$<0:>" SUFFIX ".vst3")
else()
set_target_properties("${NAME}-vst3" PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents/${vst3_arch}-linux/$<0:>")
endif()

if(APPLE)
# Uses the same macOS bundle template as VST2
set(INFO_PLIST_PROJECT_NAME "${NAME}")
configure_file("${DPF_ROOT_DIR}/utils/plugin.vst/Contents/Info.plist"
"${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents/Info.plist" @ONLY)
file(COPY "${DPF_ROOT_DIR}/utils/plugin.vst/Contents/PkgInfo"
DESTINATION "${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents")
endif()
endfunction()

# dpf__add_dgl_cairo
# ------------------------------------------------------------------------------
#
@@ -366,9 +468,9 @@ function(dpf__add_dgl_cairo)
if(NOT APPLE)
target_sources(dgl-cairo PRIVATE
"${DPF_ROOT_DIR}/dgl/src/pugl.cpp")
else() # Note: macOS pugl will be built as part of DistrhoUI_macOS.mm
#target_sources(dgl-opengl PRIVATE
# "${DPF_ROOT_DIR}/dgl/src/pugl.mm")
else()
target_sources(dgl-opengl PRIVATE
"${DPF_ROOT_DIR}/dgl/src/pugl.mm")
endif()
target_include_directories(dgl-cairo PUBLIC
"${DPF_ROOT_DIR}/dgl")
@@ -428,9 +530,9 @@ function(dpf__add_dgl_opengl)
if(NOT APPLE)
target_sources(dgl-opengl PRIVATE
"${DPF_ROOT_DIR}/dgl/src/pugl.cpp")
else() # Note: macOS pugl will be built as part of DistrhoUI_macOS.mm
#target_sources(dgl-opengl PRIVATE
# "${DPF_ROOT_DIR}/dgl/src/pugl.mm")
else()
target_sources(dgl-opengl PRIVATE
"${DPF_ROOT_DIR}/dgl/src/pugl.mm")
endif()
target_include_directories(dgl-opengl PUBLIC
"${DPF_ROOT_DIR}/dgl")
@@ -454,19 +556,12 @@ endfunction()
# dpf__add_plugin_specific_ui_sources
# ------------------------------------------------------------------------------
#
# Compile plugin-specific UI sources into the target designated by the given
# name. There are some special considerations here:
# - On most platforms, sources can be compiled only once, as part of DGL;
# - On macOS, for any sources which define Objective-C interfaces, these must
# be recompiled for each plugin under a unique namespace. In this case, the
# name must be a plugin-specific identifier, and it will be used for computing
# the unique ID along with the project version.
# Compile system specific files, for now it is just Objective-C code
#
function(dpf__add_plugin_specific_ui_sources NAME)
if(APPLE)
target_sources("${NAME}" PRIVATE
"${DPF_ROOT_DIR}/distrho/DistrhoUI_macOS.mm")
string(SHA256 _hash "${NAME}:${PROJECT_VERSION}")
target_compile_definitions("${NAME}" PUBLIC "PUGL_NAMESPACE=${_hash}")
endif()
endfunction()

@@ -494,22 +589,22 @@ function(dpf__add_dgl_system_libs)
target_include_directories(dgl-system-libs INTERFACE "${X11_INCLUDE_DIR}")
target_link_libraries(dgl-system-libs INTERFACE "${X11_X11_LIB}")
target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_X11")
if(X11_Xcursor_FOUND)
target_link_libraries(dgl-system-libs INTERFACE "${X11_Xcursor_LIB}")
target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XCURSOR")
endif()
if(X11_Xext_FOUND)
target_link_libraries(dgl-system-libs INTERFACE "${X11_Xext_LIB}")
target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XEXT")
endif()
if(X11_XSync_FOUND)
target_link_libraries(dgl-system-libs INTERFACE "${X11_XSync_LIB}")
target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XSYNC")
endif()
if(X11_Xrandr_FOUND)
target_link_libraries(dgl-system-libs INTERFACE "${X11_Xrandr_LIB}")
target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XRANDR")
endif()
#if(X11_Xcursor_FOUND)
# target_link_libraries(dgl-system-libs INTERFACE "${X11_Xcursor_LIB}")
# target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XCURSOR")
#endif()
if(X11_XSync_FOUND)
target_link_libraries(dgl-system-libs INTERFACE "${X11_XSync_LIB}")
target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XSYNC")
endif()
endif()

if(MSVC)
@@ -566,6 +661,24 @@ function(dpf__add_static_library NAME)
dpf__set_target_defaults("${NAME}")
endfunction()

# dpf__set_module_export_list
# ------------------------------------------------------------------------------
#
# Applies a list of exported symbols to the module target.
#
function(dpf__set_module_export_list NAME EXPORTS)
if(WIN32)
target_sources("${NAME}" PRIVATE "${DPF_ROOT_DIR}/utils/symbols/${EXPORTS}.def")
elseif(APPLE)
set_property(TARGET "${NAME}" APPEND PROPERTY LINK_OPTIONS
"-Xlinker" "-exported_symbols_list"
"-Xlinker" "${DPF_ROOT_DIR}/utils/symbols/${EXPORTS}.exp")
else()
set_property(TARGET "${NAME}" APPEND PROPERTY LINK_OPTIONS
"-Xlinker" "--version-script=${DPF_ROOT_DIR}/utils/symbols/${EXPORTS}.version")
endif()
endfunction()

# dpf__set_target_defaults
# ------------------------------------------------------------------------------
#


+ 27
- 3
dgl/Application.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
@@ -19,6 +19,12 @@

#include "Base.hpp"

#ifdef DISTRHO_NAMESPACE
START_NAMESPACE_DISTRHO
class PluginApplication;
END_NAMESPACE_DISTRHO
#endif

START_NAMESPACE_DGL

// --------------------------------------------------------------------------------------------------------------------
@@ -33,7 +39,7 @@ START_NAMESPACE_DGL

Unless stated otherwise, functions within this class are not thread-safe.
*/
class Application
class DISTRHO_API Application
{
public:
/**
@@ -80,6 +86,15 @@ public:
*/
bool isStandalone() const noexcept;

/**
Return the time in seconds.

This is a monotonically increasing clock with high resolution.@n
The returned time is only useful to compare against other times returned by this function,
its absolute value has no meaning.
*/
double getTime() const;

/**
Add a callback function to be triggered on every idle cycle.
You can add more than one, and remove them at anytime with removeIdleCallback().
@@ -94,7 +109,7 @@ public:
void removeIdleCallback(IdleCallback* callback);

/**
Set the class name of the application.
Get the class name of the application.

This is a stable identifier for the application, used as the window class/instance name on X11 and Windows.
It is not displayed to the user, but can be used in scripts and by window managers,
@@ -102,12 +117,21 @@ public:

Plugins created with DPF have their class name automatically set based on DGL_NAMESPACE and plugin name.
*/
const char* getClassName() const noexcept;

/**
Set the class name of the application.
@see getClassName
*/
void setClassName(const char* name);

private:
struct PrivateData;
PrivateData* const pData;
friend class Window;
#ifdef DISTRHO_NAMESPACE
friend class DISTRHO_NAMESPACE::PluginApplication;
#endif

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Application)
};


+ 72
- 14
dgl/Base.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
@@ -49,16 +49,16 @@ enum Modifier {
/**
Keyboard key codepoints.

All keys are identified by a Unicode code point in PuglEventKey::key. This
enumeration defines constants for special keys that do not have a standard
code point, and some convenience constants for control characters. Note
that all keys are handled in the same way, this enumeration is just for
All keys are identified by a Unicode code point in Widget::KeyboardEvent::key.
This enumeration defines constants for special keys that do not have a standard
code point, and some convenience constants for control characters.
Note that all keys are handled in the same way, this enumeration is just for
convenience when writing hard-coded key bindings.

Keys that do not have a standard code point use values in the Private Use
Area in the Basic Multilingual Plane (`U+E000` to `U+F8FF`). Applications
must take care to not interpret these values beyond key detection, the
mapping used here is arbitrary and specific to DPF.
Area in the Basic Multilingual Plane (`U+E000` to `U+F8FF`).
Applications must take care to not interpret these values beyond key detection,
the mapping used here is arbitrary and specific to DPF.
*/
enum Key {
// Convenience symbols for ASCII control characters
@@ -116,7 +116,7 @@ enum Key {
/**
Common flags for all events.
*/
enum Flag {
enum EventFlag {
kFlagSendEvent = 1, ///< Event is synthetic
kFlagIsHint = 2 ///< Event is a hint (not direct user input)
};
@@ -130,6 +130,46 @@ enum CrossingMode {
kCrossingUngrab ///< Crossing due to a grab release
};

/**
A mouse button.

Mouse button numbers start from 1, and are ordered: primary, secondary, middle.
So, on a typical right-handed mouse, the button numbers are:

Left: 1
Right: 2
Middle (often a wheel): 3

Higher button numbers are reported in the same order they are represented on the system.
There is no universal standard here, but buttons 4 and 5 are typically a pair of buttons or a rocker,
which are usually bound to "back" and "forward" operations.

Note that these numbers may differ from those used on the underlying
platform, since they are manipulated to provide a consistent portable API.
*/
enum MouseButton {
kMouseButtonLeft = 1,
kMouseButtonRight,
kMouseButtonMiddle,
};

/**
A mouse cursor type.

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

/**
Scroll direction.

@@ -138,11 +178,29 @@ enum CrossingMode {
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
};

/**
A clipboard data offer.
@see Window::onClipboardDataOffer
*/
struct ClipboardDataOffer {
/**
The id of this data offer.
@note The value 0 is reserved for null/invalid.
*/
uint32_t id;

/**
The type of this data offer.
Usually a MIME type, but may also be another platform-specific type identifier.
*/
const char* type;
};

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


+ 3
- 3
dgl/Cairo.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
@@ -20,7 +20,7 @@
#include "ImageBase.hpp"
#include "ImageBaseWidgets.hpp"

#include <cairo/cairo.h>
#include <cairo.h>

START_NAMESPACE_DGL

@@ -151,7 +151,7 @@ public:
/**
Destructor.
*/
virtual ~CairoBaseWidget() {}
~CairoBaseWidget() override {}

protected:
/**


+ 11
- 2
dgl/EventHandlers.hpp View File

@@ -52,7 +52,7 @@ public:
};

explicit ButtonEventHandler(SubWidget* self);
~ButtonEventHandler();
virtual ~ButtonEventHandler();

bool isActive() noexcept;
void setActive(bool active, bool sendCallback) noexcept;
@@ -117,7 +117,7 @@ public:
explicit KnobEventHandler(SubWidget* self);
explicit KnobEventHandler(SubWidget* self, const KnobEventHandler& other);
KnobEventHandler& operator=(const KnobEventHandler& other);
~KnobEventHandler();
virtual ~KnobEventHandler();

// returns raw value, is assumed to be scaled if using log
float getValue() const noexcept;
@@ -154,6 +154,15 @@ private:
struct PrivateData;
PrivateData* const pData;

/* not for use */
#ifdef DISTRHO_PROPER_CPP11_SUPPORT
KnobEventHandler(KnobEventHandler& other) = delete;
KnobEventHandler(const KnobEventHandler& other) = delete;
#else
KnobEventHandler(KnobEventHandler& other);
KnobEventHandler(const KnobEventHandler& other);
#endif

DISTRHO_LEAK_DETECTOR(KnobEventHandler)
};



examples/ImguiSimpleGain/ImGuiSrc.cpp → dgl/FileBrowserDialog.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru>
* 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
@@ -14,22 +14,15 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <imgui.h>
#if !defined(IMGUI_GL2) && !defined(IMGUI_GL3)
# define IMGUI_GL2 1
#endif
#if defined(IMGUI_GL2)
# include <imgui_impl_opengl2.h>
#elif defined(IMGUI_GL3)
# include <imgui_impl_opengl3.h>
#endif
#ifndef DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED
#define DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED

#include <imgui.cpp>
#include <imgui_draw.cpp>
#include <imgui_tables.cpp>
#include <imgui_widgets.cpp>
#if defined(IMGUI_GL2)
#include <imgui_impl_opengl2.cpp>
#elif defined(IMGUI_GL3)
#include <imgui_impl_opengl3.cpp>
#endif
#include "Base.hpp"

START_NAMESPACE_DGL

#include "../distrho/extra/FileBrowserDialogImpl.hpp"

END_NAMESPACE_DGL
#endif // DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED

+ 1
- 1
dgl/ImageBase.hpp View File

@@ -39,7 +39,7 @@ enum ImageFormat {
It is an abstract class that provides the common methods to build on top.
Cairo and OpenGL Image classes are based upon this one.

@see Image
@see CairoImage, OpenGLImage
*/
class ImageBase
{


+ 48
- 3
dgl/ImageBaseWidgets.hpp View File

@@ -25,13 +25,36 @@ START_NAMESPACE_DGL

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

/**
DGL Image About Window class.

This is a Window attached (transient) to another Window that simply shows an Image as its content.
It is typically used for "about this project" style pop-up Windows.

Pressing 'Esc' or clicking anywhere on the window will automatically close it.

@see CairoImageAboutWindow, OpenGLImageAboutWindow, Window::runAsModal(bool)
*/
template <class ImageType>
class ImageBaseAboutWindow : public StandaloneWindow
{
public:
explicit ImageBaseAboutWindow(Window& parentWindow, const ImageType& image = ImageType());
explicit ImageBaseAboutWindow(TopLevelWidget* parentTopLevelWidget, const ImageType& image = ImageType());

/**
Constructor taking an existing Window as the parent transient window and an optional image.
If @a image is valid, the about window size will match the image size.
*/
explicit ImageBaseAboutWindow(Window& transientParentWindow, const ImageType& image = ImageType());

/**
Constructor taking a top-level-widget's Window as the parent transient window and an optional image.
If @a image is valid, the about window size will match the image size.
*/
explicit ImageBaseAboutWindow(TopLevelWidget* topLevelWidget, const ImageType& image = ImageType());

/**
Set a new image to use as background for this window.
Window size will adjust to match the image size.
*/
void setImage(const ImageType& image);

protected:
@@ -47,6 +70,16 @@ private:

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

/**
DGL Image Button class.

This is a typical button, where the drawing comes from a pregenerated set of images.
The button can be under "normal", "hover" and "down" states, with one separate image possible for each.

The event logic for this button comes from the ButtonEventHandler class.

@see CairoImageButton, OpenGLImageButton
*/
template <class ImageType>
class ImageBaseButton : public SubWidget,
public ButtonEventHandler
@@ -81,6 +114,18 @@ private:

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

/**
DGL Image Knob class.

This is a typical knob/dial, where the drawing comes from a pregenerated image "filmstrip".
The knob's "filmstrip" image can be either horizontal or vertical,
with the number of steps automatically based on the largest value (ie, horizontal if width>height, vertical if height>width).
There are no different images for "hover" or "down" states.

The event logic for this knob comes from the KnobEventHandler class.

@see CairoImageKnob, OpenGLImageKnob
*/
template <class ImageType>
class ImageBaseKnob : public SubWidget,
public KnobEventHandler


+ 58
- 18
dgl/Makefile View File

@@ -13,14 +13,10 @@ BUILD_CXX_FLAGS += $(DGL_FLAGS) -I. -Isrc -DDONT_SET_USING_DGL_NAMESPACE -Wno-un
BUILD_CXX_FLAGS += -Isrc/pugl-upstream/include
LINK_FLAGS += $(DGL_LIBS)

ifeq ($(USE_OPENGL3),true)
BUILD_CXX_FLAGS += -DDGL_USE_OPENGL3
endif

# TODO fix these after pugl-upstream is done
BUILD_CXX_FLAGS += -Wno-attributes -Wno-extra -Wno-missing-field-initializers
ifneq ($(MACOS),true)
BUILD_CXX_FLAGS += -Wno-narrowing
ifeq ($(MACOS),true)
BUILD_CXX_FLAGS += -Wno-deprecated-declarations
else
PUGL_EXTRA_FLAGS = -Wno-extra -Wmissing-field-initializers
endif

# ifneq ($(MACOS_OLD),true)
@@ -73,6 +69,18 @@ endif

# ---------------------------------------------------------------------------------------------------------------------

OBJS_opengl3 = $(OBJS_common) \
../build/dgl/OpenGL.cpp.opengl3.o \
../build/dgl/NanoVG.cpp.opengl3.o

ifeq ($(MACOS),true)
OBJS_opengl3 += ../build/dgl/pugl.mm.opengl3.o
else
OBJS_opengl3 += ../build/dgl/pugl.cpp.opengl3.o
endif

# ---------------------------------------------------------------------------------------------------------------------

OBJS_stub = $(OBJS_common)

ifeq ($(MACOS),true)
@@ -116,10 +124,11 @@ endif

all: $(TARGETS)

cairo: ../build/libdgl-cairo.a
opengl: ../build/libdgl-opengl.a
stub: ../build/libdgl-stub.a
vulkan: ../build/libdgl-vulkan.a
cairo: ../build/libdgl-cairo.a
opengl: ../build/libdgl-opengl.a
opengl3: ../build/libdgl-opengl3.a
stub: ../build/libdgl-stub.a
vulkan: ../build/libdgl-vulkan.a

# ---------------------------------------------------------------------------------------------------------------------

@@ -135,6 +144,12 @@ vulkan: ../build/libdgl-vulkan.a
$(SILENT)rm -f $@
$(SILENT)$(AR) crs $@ $^

../build/libdgl-opengl3.a: $(OBJS_opengl3)
-@mkdir -p ../build
@echo "Creating libdgl-opengl3.a"
$(SILENT)rm -f $@
$(SILENT)$(AR) crs $@ $^

../build/libdgl-stub.a: $(OBJS_stub)
-@mkdir -p ../build
@echo "Creating libdgl-stub.a"
@@ -171,39 +186,63 @@ vulkan: ../build/libdgl-vulkan.a

# ---------------------------------------------------------------------------------------------------------------------

../build/dgl/pugl.cpp.o: src/pugl.cpp
-@mkdir -p ../build/dgl
@echo "Compiling $<"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) -c -o $@

../build/dgl/pugl.mm.o: src/pugl.mm
-@mkdir -p ../build/dgl
@echo "Compiling $<"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) -c -ObjC++ -o $@

# ---------------------------------------------------------------------------------------------------------------------

../build/dgl/%.cpp.cairo.o: src/%.cpp
-@mkdir -p ../build/dgl
@echo "Compiling $< (Cairo variant)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(CAIRO_FLAGS) -DDGL_CAIRO -c -o $@
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(CAIRO_FLAGS) -DDGL_CAIRO -c -o $@

../build/dgl/%.mm.cairo.o: src/%.mm
-@mkdir -p ../build/dgl
@echo "Compiling $< (Cairo variant)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(CAIRO_FLAGS) -DDGL_CAIRO -c -ObjC++ -o $@
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(CAIRO_FLAGS) -DDGL_CAIRO -c -ObjC++ -o $@

# ---------------------------------------------------------------------------------------------------------------------

../build/dgl/%.cpp.opengl.o: src/%.cpp
-@mkdir -p ../build/dgl
@echo "Compiling $< (OpenGL variant)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -c -o $@
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -c -o $@

../build/dgl/%.mm.opengl.o: src/%.mm
-@mkdir -p ../build/dgl
@echo "Compiling $< (OpenGL variant)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -c -ObjC++ -o $@
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -c -ObjC++ -o $@

# ---------------------------------------------------------------------------------------------------------------------

../build/dgl/%.cpp.opengl3.o: src/%.cpp
-@mkdir -p ../build/dgl
@echo "Compiling $< (OpenGL3 variant)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -DDGL_USE_OPENGL3 -c -o $@

../build/dgl/%.mm.opengl3.o: src/%.mm
-@mkdir -p ../build/dgl
@echo "Compiling $< (OpenGL3 variant)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -DDGL_USE_OPENGL3 -c -ObjC++ -o $@

# ---------------------------------------------------------------------------------------------------------------------

../build/dgl/%.cpp.vulkan.o: src/%.cpp
-@mkdir -p ../build/dgl
@echo "Compiling $< (Vulkan variant)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(VULKAN_FLAGS) -DDGL_VULKAN -c -o $@
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(VULKAN_FLAGS) -DDGL_VULKAN -c -o $@

../build/dgl/%.mm.vulkan.o: src/%.mm
-@mkdir -p ../build/dgl
@echo "Compiling $< (Vulkan variant)"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(VULKAN_FLAGS) -DDGL_VULKAN -c -ObjC++ -o $@
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(VULKAN_FLAGS) -DDGL_VULKAN -c -ObjC++ -o $@

# ---------------------------------------------------------------------------------------------------------------------

@@ -218,6 +257,7 @@ debug:
-include $(OBJS_common:%.o=%.d)
-include $(OBJS_cairo:%.o=%.d)
-include $(OBJS_opengl:%.o=%.d)
-include $(OBJS_opengl3:%.o=%.d)
-include $(OBJS_stub:%.o=%.d)
-include $(OBJS_vulkan:%.o=%.d)



+ 44
- 7
dgl/NanoVG.hpp View File

@@ -23,6 +23,11 @@
#include "TopLevelWidget.hpp"
#include "StandaloneWindow.hpp"

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

#ifndef DGL_NO_SHARED_RESOURCES
# define NANOVG_DEJAVU_SANS_TTF "__dpf_dejavusans_ttf__"
#endif
@@ -37,6 +42,15 @@ START_NAMESPACE_DGL

class NanoVG;

// -----------------------------------------------------------------------
// Helper methods

/**
Create a NanoVG context using the DPF-provided NanoVG library.
On Windows this will load a few extra OpenGL functions required for NanoVG to work.
*/
NVGcontext* nvgCreateGL(int flags);

// -----------------------------------------------------------------------
// NanoImage

@@ -439,6 +453,11 @@ public:
*/
void globalAlpha(float alpha);

/**
Sets the color tint applied to all rendered shapes.
*/
void globalTint(Color tint);

/* --------------------------------------------------------------------
* Transforms */

@@ -582,12 +601,26 @@ public:
NanoImage::Handle createImageFromMemory(uchar* data, uint dataSize, int imageFlags);

/**
Creates image from specified image data.
Creates image from specified raw format image data.
*/
NanoImage::Handle createImageFromRawMemory(uint w, uint h, const uchar* data,
ImageFlags imageFlags, ImageFormat format);

/**
Creates image from specified raw format image data.
Overloaded function for convenience.
@see ImageFlags
*/
NanoImage::Handle createImageFromRawMemory(uint w, uint h, const uchar* data,
int imageFlags, ImageFormat format);

/**
Creates image from specified RGBA image data.
*/
NanoImage::Handle createImageFromRGBA(uint w, uint h, const uchar* data, ImageFlags imageFlags);

/**
Creates image from specified image data.
Creates image from specified RGBA image data.
Overloaded function for convenience.
@see ImageFlags
*/
@@ -884,7 +917,7 @@ public:
Constructor for a NanoSubWidget.
@see CreateFlags
*/
explicit NanoBaseWidget(Widget* const parentGroupWidget, int flags = CREATE_ANTIALIAS);
explicit NanoBaseWidget(Widget* parentGroupWidget, int flags = CREATE_ANTIALIAS);

/**
Constructor for a NanoTopLevelWidget.
@@ -893,21 +926,21 @@ public:
explicit NanoBaseWidget(Window& windowToMapTo, int flags = CREATE_ANTIALIAS);

/**
Constructor for a NanoStandaloneWindow without parent window.
Constructor for a NanoStandaloneWindow without transient parent window.
@see CreateFlags
*/
explicit NanoBaseWidget(Application& app, int flags = CREATE_ANTIALIAS);

/**
Constructor for a NanoStandaloneWindow with parent window.
Constructor for a NanoStandaloneWindow with transient parent window.
@see CreateFlags
*/
explicit NanoBaseWidget(Application& app, Window& parentWindow, int flags = CREATE_ANTIALIAS);
explicit NanoBaseWidget(Application& app, Window& transientParentWindow, int flags = CREATE_ANTIALIAS);

/**
Destructor.
*/
virtual ~NanoBaseWidget() {}
~NanoBaseWidget() override {}

protected:
/**
@@ -950,4 +983,8 @@ typedef NanoSubWidget NanoWidget;

END_NAMESPACE_DGL

#ifdef _MSC_VER
# pragma warning(pop)
#endif

#endif // DGL_NANO_WIDGET_HPP_INCLUDED

+ 112
- 0
dgl/OpenGL-include.hpp View File

@@ -0,0 +1,112 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef DGL_OPENGL_INCLUDE_HPP_INCLUDED
#define DGL_OPENGL_INCLUDE_HPP_INCLUDED

#include "../distrho/src/DistrhoDefines.h"

// --------------------------------------------------------------------------------------------------------------------
// Fix OpenGL includes for Windows, based on glfw code (part 1)

#undef DGL_CALLBACK_DEFINED
#undef DGL_WINGDIAPI_DEFINED

#ifdef DISTRHO_OS_WINDOWS

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

/* We need WINGDIAPI defined */
#ifndef WINGDIAPI
# if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__POCC__)
# define WINGDIAPI __declspec(dllimport)
# elif defined(__LCC__)
# define WINGDIAPI __stdcall
# else
# define WINGDIAPI extern
# endif
# 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

// --------------------------------------------------------------------------------------------------------------------
// OpenGL includes

#ifdef DISTRHO_OS_MAC
# ifdef DGL_USE_OPENGL3
# include <OpenGL/gl3.h>
# include <OpenGL/gl3ext.h>
# else
# include <OpenGL/gl.h>
# endif
#else
# ifndef DISTRHO_OS_WINDOWS
# define GL_GLEXT_PROTOTYPES
# endif
# ifndef __GLEW_H__
# include <GL/gl.h>
# include <GL/glext.h>
# endif
#endif

// --------------------------------------------------------------------------------------------------------------------
// Missing OpenGL defines

#if defined(GL_BGR_EXT) && !defined(GL_BGR)
# define GL_BGR GL_BGR_EXT
#endif

#if defined(GL_BGRA_EXT) && !defined(GL_BGRA)
# define GL_BGRA GL_BGRA_EXT
#endif

#ifndef GL_CLAMP_TO_BORDER
# define GL_CLAMP_TO_BORDER 0x812D
#endif

// --------------------------------------------------------------------------------------------------------------------
// 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
#endif

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

#endif

+ 16
- 95
dgl/OpenGL.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
@@ -20,96 +20,7 @@
#include "ImageBase.hpp"
#include "ImageBaseWidgets.hpp"

// -----------------------------------------------------------------------
// Fix OpenGL includes for Windows, based on glfw code (part 1)

#undef DGL_CALLBACK_DEFINED
#undef DGL_WINGDIAPI_DEFINED

#ifdef DISTRHO_OS_WINDOWS

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

/* We need WINGDIAPI defined */
#ifndef WINGDIAPI
# if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__POCC__)
# define WINGDIAPI __declspec(dllimport)
# elif defined(__LCC__)
# define WINGDIAPI __stdcall
# else
# define WINGDIAPI extern
# endif
# 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

/* Most GL/glu.h variants on Windows need wchar_t */
#include <cstddef>

#endif // DISTRHO_OS_WINDOWS

// -----------------------------------------------------------------------
// OpenGL includes

#ifdef DISTRHO_OS_MAC
# ifdef DGL_USE_OPENGL3
# include <OpenGL/gl3.h>
# include <OpenGL/gl3ext.h>
# else
# include <OpenGL/gl.h>
# endif
#else
# ifndef DISTRHO_OS_WINDOWS
# define GL_GLEXT_PROTOTYPES
# endif
# ifndef __GLEW_H__
# include <GL/gl.h>
# include <GL/glext.h>
# endif
#endif

// -----------------------------------------------------------------------
// Missing OpenGL defines

#if defined(GL_BGR_EXT) && !defined(GL_BGR)
# define GL_BGR GL_BGR_EXT
#endif

#if defined(GL_BGRA_EXT) && !defined(GL_BGRA)
# define GL_BGRA GL_BGRA_EXT
#endif

#ifndef GL_CLAMP_TO_BORDER
# define GL_CLAMP_TO_BORDER 0x812D
#endif

// -----------------------------------------------------------------------
// 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
#endif
#include "OpenGL-include.hpp"

START_NAMESPACE_DGL

@@ -120,6 +31,8 @@ START_NAMESPACE_DGL
*/
struct OpenGLGraphicsContext : GraphicsContext
{
#ifdef DGL_USE_OPENGL3
#endif
};

// -----------------------------------------------------------------------
@@ -129,7 +42,11 @@ ImageFormat asDISTRHOImageFormat(const GLenum format)
{
switch (format)
{
#ifdef DGL_USE_OPENGL3
case GL_RED:
#else
case GL_LUMINANCE:
#endif
return kImageFormatGrayscale;
case GL_BGR:
return kImageFormatBGR;
@@ -152,7 +69,11 @@ GLenum asOpenGLImageFormat(const ImageFormat format)
case kImageFormatNull:
break;
case kImageFormatGrayscale:
#ifdef DGL_USE_OPENGL3
return GL_RED;
#else
return GL_LUMINANCE;
#endif
case kImageFormatBGR:
return GL_BGR;
case kImageFormatBGRA:
@@ -230,11 +151,11 @@ public:

// FIXME this should not be needed
inline void loadFromMemory(const char* rdata, uint w, uint h, ImageFormat fmt = kImageFormatBGRA)
{ loadFromMemory(rdata, Size<uint>(w, h), fmt); };
{ loadFromMemory(rdata, Size<uint>(w, h), fmt); }
inline void draw(const GraphicsContext& context)
{ drawAt(context, Point<int>(0, 0)); };
{ drawAt(context, Point<int>(0, 0)); }
inline void drawAt(const GraphicsContext& context, int x, int y)
{ drawAt(context, Point<int>(x, y)); };
{ drawAt(context, Point<int>(x, y)); }

/**
Constructor using raw image data, specifying an OpenGL image format.
@@ -297,4 +218,4 @@ typedef ImageBaseSwitch<OpenGLImage> OpenGLImageSwitch;

END_NAMESPACE_DGL

#endif
#endif // DGL_OPENGL_HPP_INCLUDED

+ 1
- 0
dgl/StandaloneWindow.hpp View File

@@ -71,6 +71,7 @@ public:
bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs = 0)
{ return Window::addIdleCallback(callback, timerFrequencyInMs); }
bool removeIdleCallback(IdleCallback* callback) { return Window::removeIdleCallback(callback); }
Application& getApp() const noexcept { return Window::getApp(); }
const GraphicsContext& getGraphicsContext() const noexcept { return Window::getGraphicsContext(); }
double getScaleFactor() const noexcept { return Window::getScaleFactor(); }
void setGeometryConstraints(uint minimumWidth, uint minimumHeight,


+ 1
- 1
dgl/SubWidget.hpp View File

@@ -47,7 +47,7 @@ public:
/**
Destructor.
*/
virtual ~SubWidget();
~SubWidget() override;

/**
Check if this widget contains the point defined by @a x and @a y.


+ 8
- 3
dgl/TopLevelWidget.hpp View File

@@ -54,7 +54,7 @@ public:
/**
Destructor.
*/
virtual ~TopLevelWidget();
~TopLevelWidget() override;

/**
Get the application associated with this top-level widget's window.
@@ -101,13 +101,17 @@ public:
void repaint(const Rectangle<uint>& rect) noexcept;

// TODO group stuff after here, convenience functions present in Window class
const void* getClipboard(size_t& dataSize);
bool setClipboard(const char* mimeType, const void* data, size_t dataSize);
bool setCursor(MouseCursor cursor);
bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs = 0);
bool removeIdleCallback(IdleCallback* callback);
double getScaleFactor() const noexcept;
void setGeometryConstraints(uint minimumWidth,
uint minimumHeight,
bool keepAspectRatio = false,
bool automaticallyScale = false);
bool automaticallyScale = false,
bool resizeNowIfAutoScaling = true);

DISTRHO_DEPRECATED_BY("getApp()")
Application& getParentApp() const noexcept { return getApp(); }
@@ -117,7 +121,6 @@ public:

protected:
bool onKeyboard(const KeyboardEvent&) override;
bool onSpecial(const SpecialEvent&) override;
bool onCharacterInput(const CharacterInputEvent&) override;
bool onMouse(const MouseEvent&) override;
bool onMotion(const MotionEvent&) override;
@@ -130,6 +133,8 @@ private:
#ifdef DISTRHO_DEFINES_H_INCLUDED
friend class DISTRHO_NAMESPACE::UI;
#endif
/** @internal */
virtual void requestSizeChange(uint width, uint height);

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TopLevelWidget)
};


+ 54
- 49
dgl/Widget.hpp View File

@@ -56,17 +56,16 @@ public:
/**
Base event data.
These are the fields present on all Widget events.

@a mod Currently active keyboard modifiers, @see Modifier.
@a mod Event flags, @see Flag.
@a time Event timestamp (if any).
*/
struct BaseEvent {
/** Currently active keyboard modifiers. @see Modifier */
uint mod;
/** Event flags. @see EventFlag */
uint flags;
/** Event timestamp (if any). */
uint time;

/** Constructor */
/** Constructor for default/null values */
BaseEvent() noexcept : mod(0x0), flags(0x0), time(0) {}
/** Destuctor */
virtual ~BaseEvent() noexcept {}
@@ -86,17 +85,17 @@ public:
Alternatively, the raw @a keycode can be used to work directly with physical keys,
but note that this value is not portable and differs between platforms and hardware.

@a press True if the key was pressed, false if released.
@a key Unicode point of the key pressed.
@a keycode Raw keycode.
@see onKeyboard
*/
struct KeyboardEvent : BaseEvent {
/** True if the key was pressed, false if released. */
bool press;
/** Unicode point of the key pressed. */
uint key;
/** Raw keycode. */
uint keycode;

/** Constructor */
/** Constructor for default/null values */
KeyboardEvent() noexcept
: BaseEvent(),
press(false),
@@ -107,18 +106,14 @@ public:
/**
Special keyboard event.

This event allows the use of keys that do not have unicode points.
Note that some are non-printable keys.

@a press True if the key was pressed, false if released.
@a key The key pressed.
@see onSpecial
DEPRECATED This used to be part of DPF due to pugl, but now deprecated and simply non-functional.
All events go through KeyboardEvent or CharacterInputEvent, use those instead.
*/
struct SpecialEvent : BaseEvent {
struct DISTRHO_DEPRECATED_BY("KeyboardEvent") SpecialEvent : BaseEvent {
bool press;
Key key;
Key key;

/** Constructor */
/** Constructor for default/null values */
SpecialEvent() noexcept
: BaseEvent(),
press(false),
@@ -135,17 +130,17 @@ public:
so there is not necessarily a direct correspondence between text events and physical key presses.
For example, with some input methods a sequence of several key presses will generate a single character.

@a keycode Raw key code.
@a character Unicode character code.
@a string UTF-8 string.
@see onCharacterInput
*/
struct CharacterInputEvent : BaseEvent {
/** Raw key code. */
uint keycode;
/** Unicode character code. */
uint character;
/** UTF-8 string. */
char string[8];

/** Constructor */
/** Constructor for default/null values */
CharacterInputEvent() noexcept
: BaseEvent(),
keycode(0),
@@ -159,20 +154,19 @@ public:

/**
Mouse press or release event.

@a button The button number starting from 1 (1 = left, 2 = middle, 3 = right).
@a press True if the button was pressed, false if released.
@a pos The widget-relative coordinates of the pointer.
@a absolutePos The absolute coordinates of the pointer.
@see onMouse
*/
struct MouseEvent : BaseEvent {
/** The button number starting from 1. @see MouseButton */
uint button;
/** True if the button was pressed, false if released. */
bool press;
/** The widget-relative coordinates of the pointer. */
Point<double> pos;
/** The absolute coordinates of the pointer. */
Point<double> absolutePos;

/** Constructor */
/** Constructor for default/null values */
MouseEvent() noexcept
: BaseEvent(),
button(0),
@@ -183,16 +177,15 @@ public:

/**
Mouse motion event.

@a pos The widget-relative coordinates of the pointer.
@a absolutePos The absolute coordinates of the pointer.
@see onMotion
*/
struct MotionEvent : BaseEvent {
/** The widget-relative coordinates of the pointer. */
Point<double> pos;
/** The absolute coordinates of the pointer. */
Point<double> absolutePos;

/** Constructor */
/** Constructor for default/null values */
MotionEvent() noexcept
: BaseEvent(),
pos(0.0, 0.0),
@@ -208,19 +201,19 @@ public:
Some systems and devices support finer resolution and/or higher values for fast scrolls,
so programs should handle any value gracefully.

@a pos The widget-relative coordinates of the pointer.
@a absolutePos The absolute coordinates of the pointer.
@a delta The scroll distance.
@a direction The direction of the scroll or "smooth".
@see onScroll
*/
struct ScrollEvent : BaseEvent {
/** The widget-relative coordinates of the pointer. */
Point<double> pos;
/** The absolute coordinates of the pointer. */
Point<double> absolutePos;
/** The scroll distance. */
Point<double> delta;
/** The direction of the scroll or "smooth". */
ScrollDirection direction;

/** Constructor */
/** Constructor for default/null values */
ScrollEvent() noexcept
: BaseEvent(),
pos(0.0, 0.0),
@@ -231,15 +224,15 @@ public:

/**
Resize event.
@a size The new widget size.
@a oldSize The previous size, may be null.
@see onResize
*/
struct ResizeEvent {
/** The new widget size. */
Size<uint> size;
/** The previous size, can be null. */
Size<uint> oldSize;

/** Constructor */
/** Constructor for default/null values */
ResizeEvent() noexcept
: size(0, 0),
oldSize(0, 0) {}
@@ -247,15 +240,15 @@ public:

/**
Widget position changed event.
@a pos The new absolute position of the widget.
@a oldPos The previous absolute position of the widget.
@see onPositionChanged
*/
struct PositionChangedEvent {
/** The new absolute position of the widget. */
Point<int> pos;
/** The previous absolute position of the widget. */
Point<int> oldPos;

/** Constructor */
/** Constructor for default/null values */
PositionChangedEvent() noexcept
: pos(0, 0),
oldPos(0, 0) {}
@@ -398,12 +391,6 @@ protected:
*/
virtual bool onKeyboard(const KeyboardEvent&);

/**
A function called when a special key is pressed or released.
@return True to stop event propagation, false otherwise.
*/
virtual bool onSpecial(const SpecialEvent&);

/**
A function called when an UTF-8 character is received.
@return True to stop event propagation, false otherwise.
@@ -433,6 +420,24 @@ protected:
*/
virtual void onResize(const ResizeEvent&);

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

private:
struct PrivateData;
PrivateData* const pData;


+ 131
- 58
dgl/Window.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
@@ -19,6 +19,18 @@

#include "Geometry.hpp"

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

#include <vector>

#ifdef DISTRHO_NAMESPACE
START_NAMESPACE_DISTRHO
class PluginWindow;
END_NAMESPACE_DISTRHO
#endif

START_NAMESPACE_DGL

class Application;
@@ -47,60 +59,11 @@ class TopLevelWidget;

...
*/
class Window
class DISTRHO_API Window
{
struct PrivateData;

public:
#ifndef DGL_FILE_BROWSER_DISABLED
/**
File browser options.
@see Window::openFileBrowser
*/
struct FileBrowserOptions {
/**
File browser button state.
This allows to customize the behaviour of the file browse dialog buttons.
Note these are merely hints, not all systems support them.
*/
enum ButtonState {
kButtonInvisible,
kButtonVisibleUnchecked,
kButtonVisibleChecked,
};

/** Start directory, uses current working directory if null */
const char* startDir;
/** File browser dialog window title, uses "FileBrowser" if null */
const char* title;
// TODO file filter

/**
File browser buttons.
*/
struct Buttons {
/** Whether to list all files vs only those with matching file extension */
ButtonState listAllFiles;
/** Whether to show hidden files */
ButtonState showHidden;
/** Whether to show list of places (bookmarks) */
ButtonState showPlaces;

/** Constructor for default values */
Buttons()
: listAllFiles(kButtonVisibleChecked),
showHidden(kButtonVisibleUnchecked),
showPlaces(kButtonVisibleChecked) {}
} buttons;

/** Constructor for default values */
FileBrowserOptions()
: startDir(nullptr),
title(nullptr),
buttons() {}
};
#endif // DGL_FILE_BROWSER_DISABLED

/**
Window graphics context as a scoped struct.
This class gives graphics context drawing time to a window's widgets.
@@ -157,10 +120,10 @@ public:
explicit Window(Application& app);

/**
Constructor for a modal window, by having another window as its parent.
Constructor for a modal window, by having another window as its transient parent.
The Application instance must be the same between the 2 windows.
*/
explicit Window(Application& app, Window& parent);
explicit Window(Application& app, Window& transientParentWindow);

/**
Constructor for an embed Window without known size,
@@ -245,6 +208,41 @@ public:
*/
void setResizable(bool resizable);

/**
Get X offset, typically 0.
*/
int getOffsetX() const noexcept;

/**
Get Y offset, typically 0.
*/
int getOffsetY() const noexcept;

/**
Get offset.
*/
Point<int> getOffset() const noexcept;

/**
Set X offset.
*/
void setOffsetX(int x);

/**
Set Y offset.
*/
void setOffsetY(int y);

/**
Set offset using @a x and @a y values.
*/
void setOffset(int x, int y);

/**
Set offset.
*/
void setOffset(const Point<int>& offset);

/**
Get width.
*/
@@ -302,6 +300,39 @@ public:
*/
void setIgnoringKeyRepeat(bool ignore) noexcept;

/**
Get the clipboard contents.

This gets the system clipboard contents,
which may have been set with setClipboard() or copied from another application.

Returns the clipboard contents, or null.

@note By default only "text/plain" mimetype is supported and returned.
Override onClipboardDataOffer for supporting other types.
*/
const void* getClipboard(size_t& dataSize);

/**
Set the clipboard contents.

This sets the system clipboard contents,
which can be retrieved with getClipboard() or pasted into other applications.

If using a string, the use of a null terminator is required (and must be part of dataSize).@n
The MIME type of the data "text/plain" is assumed if null is used.
*/
bool setClipboard(const char* mimeType, const void* data, size_t dataSize);

/**
Set the mouse cursor.

This changes the system cursor that is displayed when the pointer is inside the window.
May fail if setting the cursor is not supported on this system,
for example if compiled on X11 without Xcursor support.
*/
bool setCursor(MouseCursor cursor);

/**
Add a callback function to be triggered on every idle cycle or on a specific timer frequency.
You can add more than one, and remove them at anytime with removeIdleCallback().
@@ -361,7 +392,7 @@ public:

#ifndef DGL_FILE_BROWSER_DISABLED
/**
Open a file browser dialog with this window as parent.
Open a file browser dialog with this window as transient parent.
A few options can be specified to setup the dialog.

If a path is selected, onFileSelected() will be called with the user chosen path.
@@ -369,7 +400,7 @@ public:

This function does not block the event loop.
*/
bool openFileBrowser(const FileBrowserOptions& options);
bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions());
#endif

/**
@@ -382,6 +413,13 @@ public:
*/
void repaint(const Rectangle<uint>& rect) noexcept;

/**
Render this window's content into a picture file, specified by @a filename.
Window must be visible and on screen.
Written picture format is PPM.
*/
void renderToPicture(const char* filename);

/**
Run this window as a modal, blocking input events from the parent.
Only valid for windows that have been created with another window as parent (as passed in the constructor).
@@ -389,13 +427,28 @@ public:
*/
void runAsModal(bool blockWait = false);

/**
Get the geometry constraints set for the Window.
@see setGeometryConstraints
*/
Size<uint> getGeometryConstraints(bool& keepAspectRatio);

/**
Set geometry constraints for the Window when resized by the user, and optionally scale contents automatically.
*/
void setGeometryConstraints(uint minimumWidth,
uint minimumHeight,
bool keepAspectRatio = false,
bool automaticallyScale = false);
bool automaticallyScale = false,
bool resizeNowIfAutoScaling = true);

/**
Set the transient parent of the window.

Set this for transient children like dialogs, to have them properly associated with their parent window.
This should be not be called for embed windows, or after making the window visible.
*/
void setTransientParent(uintptr_t transientParentWindowHandle);

/** DEPRECATED Use isIgnoringKeyRepeat(). */
DISTRHO_DEPRECATED_BY("isIgnoringKeyRepeat()")
@@ -410,6 +463,23 @@ public:
inline void exec(bool blockWait = false) { runAsModal(blockWait); }

protected:
/**
Get the types available for the data in a clipboard.
Must only be called within the context of onClipboardDataOffer.
*/
std::vector<ClipboardDataOffer> getClipboardDataOfferTypes();

/**
A function called when clipboard has data present, possibly with several datatypes.
While handling this event, the data types can be investigated with getClipboardDataOfferTypes() to decide whether to accept the offer.

Reimplement and return a non-zero id to accept the clipboard data offer for a particular type.
Applications must ignore any type they do not recognize.

The default implementation accepts the "text/plain" mimetype.
*/
virtual uint32_t onClipboardDataOffer();

/**
A function called when the window is attempted to be closed.
Returning true closes the window, which is the default behaviour.
@@ -459,8 +529,10 @@ protected:
private:
PrivateData* const pData;
friend class Application;
friend class PluginWindow;
friend class TopLevelWidget;
#ifdef DISTRHO_NAMESPACE
friend class DISTRHO_NAMESPACE::PluginWindow;
#endif

/** @internal */
explicit Window(Application& app,
@@ -469,9 +541,10 @@ private:
uint height,
double scaleFactor,
bool resizable,
bool isVST3,
bool doPostInit);

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Window);
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Window)
};

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


+ 33
- 1
dgl/src/Application.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
@@ -16,10 +16,23 @@

#include "ApplicationPrivateData.hpp"

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

START_NAMESPACE_DGL

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

#ifdef __EMSCRIPTEN__
static void app_idle(void* const app)
{
static_cast<Application*>(app)->idle();
}
#endif

Application::Application(const bool isStandalone)
: pData(new PrivateData(isStandalone)) {}

@@ -37,8 +50,22 @@ void Application::exec(const uint idleTimeInMs)
{
DISTRHO_SAFE_ASSERT_RETURN(pData->isStandalone,);

#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);
#endif
}

void Application::quit()
@@ -56,6 +83,11 @@ bool Application::isStandalone() const noexcept
return pData->isStandalone;
}

double Application::getTime() const
{
return pData->getTime();
}

void Application::addIdleCallback(IdleCallback* const callback)
{
DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,)


+ 20
- 4
dgl/src/ApplicationPrivateData.cpp View File

@@ -45,6 +45,13 @@ static bool isThisTheMainThread(const d_ThreadHandle mainThreadHandle) noexcept

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

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

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

Application::PrivateData::PrivateData(const bool standalone)
: world(puglNewWorld(standalone ? PUGL_PROGRAM : PUGL_MODULE,
standalone ? PUGL_WORLD_THREADS : 0x0)),
@@ -60,11 +67,8 @@ Application::PrivateData::PrivateData(const bool standalone)
DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,);

puglSetWorldHandle(world, this);
#ifndef __EMSCRIPTEN__
puglSetClassName(world, DISTRHO_MACRO_AS_STRING(DGL_NAMESPACE));

#ifdef DISTRHO_OS_MAC
if (standalone)
puglMacOSActivateApp();
#endif
}

@@ -118,6 +122,11 @@ void Application::PrivateData::idle(const uint timeoutInMs)
puglUpdate(world, timeoutInSeconds);
}

triggerIdleCallbacks();
}

void Application::PrivateData::triggerIdleCallbacks()
{
for (std::list<IdleCallback*>::iterator it = idleCallbacks.begin(), ite = idleCallbacks.end(); it != ite; ++it)
{
IdleCallback* const idleCallback(*it);
@@ -147,6 +156,13 @@ void Application::PrivateData::quit()
#endif
}

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

return puglGetTime(world);
}

void Application::PrivateData::setClassName(const char* const name)
{
DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,);


+ 15
- 0
dgl/src/ApplicationPrivateData.hpp View File

@@ -22,6 +22,9 @@
#include <list>

#ifdef DISTRHO_OS_WINDOWS
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <winsock2.h>
# include <windows.h>
typedef HANDLE d_ThreadHandle;
@@ -30,12 +33,18 @@ typedef HANDLE d_ThreadHandle;
typedef pthread_t d_ThreadHandle;
#endif

#ifdef DISTRHO_OS_MAC
typedef struct PuglWorldImpl PuglWorld;
#endif

START_NAMESPACE_DGL

class Window;

#ifndef DISTRHO_OS_MAC
typedef struct PuglWorldImpl PuglWorld;
#endif

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

struct Application::PrivateData {
@@ -84,10 +93,16 @@ struct Application::PrivateData {
/** Run Pugl world update for @a timeoutInMs, and then each idle callback in order of registration. */
void idle(uint timeoutInMs);

/** Run each idle callback without updating pugl world. */
void triggerIdleCallbacks();

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

/** Get time via pugl */
double getTime() const;

/** Set pugl world class name. */
void setClassName(const char* name);



+ 10
- 3
dgl/src/Cairo.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>
* Copyright (C) 2019-2021 Jean Pierre Cimalando <jp-dev@inbox.ru>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
@@ -384,8 +384,8 @@ void CairoImage::loadFromMemory(const char* const rdata, const Size<uint>& s, co

cairo_surface_t* const newsurface = cairo_image_surface_create_for_data(newdata, cairoformat, width, height, stride);
DISTRHO_SAFE_ASSERT_RETURN(newsurface != nullptr,);
DISTRHO_SAFE_ASSERT_RETURN(s.getWidth() == cairo_image_surface_get_width(newsurface),);
DISTRHO_SAFE_ASSERT_RETURN(s.getHeight() == cairo_image_surface_get_height(newsurface),);
DISTRHO_SAFE_ASSERT_RETURN(static_cast<int>(s.getWidth()) == cairo_image_surface_get_width(newsurface),);
DISTRHO_SAFE_ASSERT_RETURN(static_cast<int>(s.getHeight()) == cairo_image_surface_get_height(newsurface),);

cairo_surface_destroy(surface);

@@ -808,6 +808,13 @@ void TopLevelWidget::PrivateData::display()

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

void Window::PrivateData::renderToPicture(const char*, const GraphicsContext&, uint, uint)
{
notImplemented("Window::PrivateData::renderToPicture");
}

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

const GraphicsContext& Window::PrivateData::getGraphicsContext() const noexcept
{
GraphicsContext& context((GraphicsContext&)graphicsContext);


+ 2
- 2
dgl/src/Color.cpp View File

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

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



+ 5
- 3
dgl/src/Geometry.cpp View File

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

#ifdef _MSC_VER
// instantiated template classes whose methods are defined elsewhere
# pragma warning(disable:4661)
# pragma warning(disable:4661) /* instantiated template classes whose methods are defined elsewhere */
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wconversion"
#endif

#include "../Geometry.hpp"


+ 4
- 4
dgl/src/ImageBaseWidgets.cpp View File

@@ -22,8 +22,8 @@ START_NAMESPACE_DGL
// --------------------------------------------------------------------------------------------------------------------

template <class ImageType>
ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(Window& parentWindow, const ImageType& image)
: StandaloneWindow(parentWindow.getApp(), parentWindow),
ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(Window& transientParentWindow, const ImageType& image)
: StandaloneWindow(transientParentWindow.getApp(), transientParentWindow),
img(image)
{
setResizable(false);
@@ -39,8 +39,8 @@ ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(Window& parentWindow, cons
}

template <class ImageType>
ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(TopLevelWidget* const parentTopLevelWidget, const ImageType& image)
: StandaloneWindow(parentTopLevelWidget->getApp(), parentTopLevelWidget->getWindow()),
ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(TopLevelWidget* const topLevelWidget, const ImageType& image)
: StandaloneWindow(topLevelWidget->getApp(), topLevelWidget->getWindow()),
img(image)
{
setResizable(false);


+ 105
- 17
dgl/src/NanoVG.cpp View File

@@ -54,6 +54,18 @@ DGL_EXT(PFNGLUNIFORM4FVPROC, glUniform4fv)
DGL_EXT(PFNGLUSEPROGRAMPROC, glUseProgram)
DGL_EXT(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer)
DGL_EXT(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate)
# ifdef DGL_USE_NANOVG_FBO
DGL_EXT(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus)
DGL_EXT(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer)
DGL_EXT(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer)
DGL_EXT(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers)
DGL_EXT(PFNGLDELETERENDERBUFFERSPROC, glDeleteRenderbuffers)
DGL_EXT(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D)
DGL_EXT(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer)
DGL_EXT(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers)
DGL_EXT(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers)
DGL_EXT(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage)
# endif
# ifdef DGL_USE_OPENGL3
DGL_EXT(PFNGLBINDBUFFERRANGEPROC, glBindBufferRange)
DGL_EXT(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray)
@@ -70,13 +82,15 @@ DGL_EXT(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding)
// Include NanoVG OpenGL implementation

//#define STB_IMAGE_STATIC
#ifdef DGL_USE_OPENGL3
#if defined(DGL_USE_GLES2)
# define NANOVG_GLES2_IMPLEMENTATION
#elif defined(DGL_USE_OPENGL3)
# define NANOVG_GL3_IMPLEMENTATION
#else
# define NANOVG_GL2_IMPLEMENTATION
#endif

#if defined(DISTRHO_OS_MAC) && defined(NANOVG_GL3_IMPLEMENTATION)
#if defined(DISTRHO_OS_MAC) && defined(NANOVG_GL2_IMPLEMENTATION)
# define glBindVertexArray glBindVertexArrayAPPLE
# define glDeleteVertexArrays glDeleteVertexArraysAPPLE
# define glGenVertexArrays glGenVertexArraysAPPLE
@@ -84,29 +98,38 @@ DGL_EXT(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding)

#include "nanovg/nanovg_gl.h"

#ifdef DGL_USE_NANOVG_FBO
# define NANOVG_FBO_VALID 1
# include "nanovg/nanovg_gl_utils.h"
#endif

#if defined(NANOVG_GL2)
# define nvgCreateGL nvgCreateGL2
# define nvgCreateGLfn nvgCreateGL2
# define nvgDeleteGL nvgDeleteGL2
# define nvglCreateImageFromHandle nvglCreateImageFromHandleGL2
# define nvglImageHandle nvglImageHandleGL2
#elif defined(NANOVG_GL3)
# define nvgCreateGL nvgCreateGL3
# define nvgCreateGLfn nvgCreateGL3
# define nvgDeleteGL nvgDeleteGL3
# define nvglCreateImageFromHandle nvglCreateImageFromHandleGL3
# define nvglImageHandle nvglImageHandleGL3
#elif defined(NANOVG_GLES2)
# define nvgCreateGL nvgCreateGLES2
# define nvgCreateGLfn nvgCreateGLES2
# define nvgDeleteGL nvgDeleteGLES2
# define nvglCreateImageFromHandle nvglCreateImageFromHandleGLES2
# define nvglImageHandle nvglImageHandleGLES2
#elif defined(NANOVG_GLES3)
# define nvgCreateGL nvgCreateGLES3
# define nvgCreateGLfn nvgCreateGLES3
# define nvgDeleteGL nvgDeleteGLES3
# define nvglCreateImageFromHandle nvglCreateImageFromHandleGLES3
# define nvglImageHandle nvglImageHandleGLES3
#endif

static NVGcontext* nvgCreateGL_helper(int flags)
// -----------------------------------------------------------------------

START_NAMESPACE_DGL

NVGcontext* nvgCreateGL(int flags)
{
#if defined(DISTRHO_OS_WINDOWS)
# if defined(__GNUC__) && (__GNUC__ >= 9)
@@ -117,6 +140,11 @@ static NVGcontext* nvgCreateGL_helper(int flags)
# define DGL_EXT(PROC, func) \
if (needsInit) func = (PROC) wglGetProcAddress ( #func ); \
DISTRHO_SAFE_ASSERT_RETURN(func != nullptr, nullptr);
# define DGL_EXT2(PROC, func, fallback) \
if (needsInit) { \
func = (PROC) wglGetProcAddress ( #func ); \
if (func == nullptr) func = (PROC) wglGetProcAddress ( #fallback ); \
} DISTRHO_SAFE_ASSERT_RETURN(func != nullptr, nullptr);
DGL_EXT(PFNGLACTIVETEXTUREPROC, glActiveTexture)
DGL_EXT(PFNGLATTACHSHADERPROC, glAttachShader)
DGL_EXT(PFNGLBINDATTRIBLOCATIONPROC, glBindAttribLocation)
@@ -145,6 +173,18 @@ DGL_EXT(PFNGLUNIFORM4FVPROC, glUniform4fv)
DGL_EXT(PFNGLUSEPROGRAMPROC, glUseProgram)
DGL_EXT(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer)
DGL_EXT(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate)
# ifdef DGL_USE_NANOVG_FBO
DGL_EXT(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus)
DGL_EXT2(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer, glBindFramebufferEXT)
DGL_EXT2(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer, glBindRenderbufferEXT)
DGL_EXT2(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers, glDeleteFramebuffersEXT)
DGL_EXT2(PFNGLDELETERENDERBUFFERSPROC, glDeleteRenderbuffers, glDeleteRenderbuffersEXT)
DGL_EXT2(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D, glFramebufferTexture2DEXT)
DGL_EXT2(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer, glFramebufferRenderbufferEXT)
DGL_EXT2(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers, glGenFramebuffersEXT)
DGL_EXT2(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers, glGenRenderbuffersEXT)
DGL_EXT2(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage, glRenderbufferStorageEXT)
# endif
# ifdef DGL_USE_OPENGL3
DGL_EXT(PFNGLBINDBUFFERRANGEPROC, glBindBufferRange)
DGL_EXT(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray)
@@ -155,18 +195,15 @@ DGL_EXT(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays)
DGL_EXT(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding)
# endif
# undef DGL_EXT
# undef DGL_EXT2
needsInit = false;
# if defined(__GNUC__) && (__GNUC__ >= 9)
# pragma GCC diagnostic pop
# endif
#endif
return nvgCreateGL(flags);
return nvgCreateGLfn(flags);
}

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

START_NAMESPACE_DGL

// -----------------------------------------------------------------------
// DGL Color class conversion

@@ -215,6 +252,7 @@ NanoImage& NanoImage::operator=(const Handle& handle)

fHandle.context = handle.context;
fHandle.imageId = handle.imageId;
_updateSize();

return *this;
}
@@ -282,13 +320,16 @@ NanoVG::Paint::operator NVGpaint() const noexcept
// NanoVG

NanoVG::NanoVG(int flags)
: fContext(nvgCreateGL_helper(flags)),
: fContext(nvgCreateGL(flags)),
fInFrame(false),
fIsSubWidget(false) {}
fIsSubWidget(false)
{
DISTRHO_CUSTOM_SAFE_ASSERT("Failed to create NanoVG context, expect a black screen", fContext != nullptr);
}

NanoVG::~NanoVG()
{
DISTRHO_SAFE_ASSERT(! fInFrame);
DISTRHO_CUSTOM_SAFE_ASSERT("Destroying NanoVG context with still active frame", ! fInFrame);

if (fContext != nullptr && ! fIsSubWidget)
nvgDeleteGL(fContext);
@@ -483,6 +524,12 @@ void NanoVG::globalAlpha(float alpha)
nvgGlobalAlpha(fContext, alpha);
}

void NanoVG::globalTint(Color tint)
{
if (fContext != nullptr)
nvgGlobalTint(fContext, tint);
}

// -----------------------------------------------------------------------
// Transforms

@@ -631,6 +678,45 @@ NanoImage::Handle NanoVG::createImageFromMemory(uchar* data, uint dataSize, int
return NanoImage::Handle(fContext, nvgCreateImageMem(fContext, imageFlags, data,static_cast<int>(dataSize)));
}

NanoImage::Handle NanoVG::createImageFromRawMemory(uint w, uint h, const uchar* data,
ImageFlags imageFlags, ImageFormat format)
{
return createImageFromRawMemory(w, h, data, static_cast<int>(imageFlags), format);
}

NanoImage::Handle NanoVG::createImageFromRawMemory(uint w, uint h, const uchar* data,
int imageFlags, ImageFormat format)
{
if (fContext == nullptr) return NanoImage::Handle();
DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, NanoImage::Handle());

NVGtexture nvgformat;
switch (format)
{
case kImageFormatGrayscale:
nvgformat = NVG_TEXTURE_ALPHA;
break;
case kImageFormatBGR:
nvgformat = NVG_TEXTURE_BGR;
break;
case kImageFormatBGRA:
nvgformat = NVG_TEXTURE_BGRA;
break;
case kImageFormatRGB:
nvgformat = NVG_TEXTURE_RGB;
break;
case kImageFormatRGBA:
nvgformat = NVG_TEXTURE_RGBA;
break;
default:
return NanoImage::Handle();
}

return NanoImage::Handle(fContext, nvgCreateImageRaw(fContext,
static_cast<int>(w),
static_cast<int>(h), imageFlags, nvgformat, data));
}

NanoImage::Handle NanoVG::createImageFromRGBA(uint w, uint h, const uchar* data, ImageFlags imageFlags)
{
return createImageFromRGBA(w, h, data, static_cast<int>(imageFlags));
@@ -646,12 +732,14 @@ NanoImage::Handle NanoVG::createImageFromRGBA(uint w, uint h, const uchar* data,
static_cast<int>(h), imageFlags, data));
}

NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, ImageFlags imageFlags, bool deleteTexture)
NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h,
ImageFlags imageFlags, bool deleteTexture)
{
return createImageFromTextureHandle(textureId, w, h, static_cast<int>(imageFlags), deleteTexture);
}

NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, int imageFlags, bool deleteTexture)
NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h,
int imageFlags, bool deleteTexture)
{
if (fContext == nullptr) return NanoImage::Handle();
DISTRHO_SAFE_ASSERT_RETURN(textureId != 0, NanoImage::Handle());


+ 131
- 0
dgl/src/OpenGL.cpp View File

@@ -33,20 +33,48 @@

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)
static void notImplemented(const char* const name)
{
d_stderr2("OpenGL3 function not implemented: %s", name);
}
#else
# define DGL_USE_COMPAT_OPENGL
#endif

// -----------------------------------------------------------------------
// Color

void Color::setFor(const GraphicsContext&, const bool includeAlpha)
{
#ifdef DGL_USE_COMPAT_OPENGL
if (includeAlpha)
glColor4f(red, green, blue, alpha);
else
glColor3f(red, green, blue);
#else
notImplemented("Color::setFor");
// unused
(void)includeAlpha;
#endif
}

// -----------------------------------------------------------------------
// Line

#ifdef DGL_USE_COMPAT_OPENGL
template<typename T>
static void drawLine(const Point<T>& posStart, const Point<T>& posEnd)
{
@@ -61,21 +89,30 @@ static void drawLine(const Point<T>& posStart, const Point<T>& posEnd)

glEnd();
}
#endif

template<typename T>
void Line<T>::draw(const GraphicsContext&, const T width)
{
#ifdef DGL_USE_COMPAT_OPENGL
DISTRHO_SAFE_ASSERT_RETURN(width != 0,);

glLineWidth(static_cast<GLfloat>(width));
drawLine<T>(posStart, posEnd);
#else
notImplemented("Line::draw");
#endif
}

// deprecated calls
template<typename T>
void Line<T>::draw()
{
#ifdef DGL_USE_COMPAT_OPENGL
drawLine<T>(posStart, posEnd);
#else
notImplemented("Line::draw");
#endif
}

template class Line<double>;
@@ -88,6 +125,7 @@ template class Line<ushort>;
// -----------------------------------------------------------------------
// Circle

#ifdef DGL_USE_COMPAT_OPENGL
template<typename T>
static void drawCircle(const Point<T>& pos,
const uint numSegments,
@@ -115,11 +153,16 @@ static void drawCircle(const Point<T>& pos,

glEnd();
}
#endif

template<typename T>
void Circle<T>::draw(const GraphicsContext&)
{
#ifdef DGL_USE_COMPAT_OPENGL
drawCircle<T>(fPos, fNumSegments, fSize, fSin, fCos, false);
#else
notImplemented("Circle::draw");
#endif
}

template<typename T>
@@ -128,20 +171,32 @@ void Circle<T>::drawOutline(const GraphicsContext&, const T lineWidth)
DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,);

glLineWidth(static_cast<GLfloat>(lineWidth));
#ifdef DGL_USE_COMPAT_OPENGL
drawCircle<T>(fPos, fNumSegments, fSize, fSin, fCos, true);
#else
notImplemented("Circle::drawOutline");
#endif
}

// deprecated calls
template<typename T>
void Circle<T>::draw()
{
#ifdef DGL_USE_COMPAT_OPENGL
drawCircle<T>(fPos, fNumSegments, fSize, fSin, fCos, false);
#else
notImplemented("Circle::draw");
#endif
}

template<typename T>
void Circle<T>::drawOutline()
{
#ifdef DGL_USE_COMPAT_OPENGL
drawCircle<T>(fPos, fNumSegments, fSize, fSin, fCos, true);
#else
notImplemented("Circle::drawOutline");
#endif
}

template class Circle<double>;
@@ -154,6 +209,7 @@ template class Circle<ushort>;
// -----------------------------------------------------------------------
// Triangle

#ifdef DGL_USE_COMPAT_OPENGL
template<typename T>
static void drawTriangle(const Point<T>& pos1,
const Point<T>& pos2,
@@ -172,11 +228,16 @@ static void drawTriangle(const Point<T>& pos1,

glEnd();
}
#endif

template<typename T>
void Triangle<T>::draw(const GraphicsContext&)
{
#ifdef DGL_USE_COMPAT_OPENGL
drawTriangle<T>(pos1, pos2, pos3, false);
#else
notImplemented("Triangle::draw");
#endif
}

template<typename T>
@@ -185,20 +246,32 @@ void Triangle<T>::drawOutline(const GraphicsContext&, const T lineWidth)
DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,);

glLineWidth(static_cast<GLfloat>(lineWidth));
#ifdef DGL_USE_COMPAT_OPENGL
drawTriangle<T>(pos1, pos2, pos3, true);
#else
notImplemented("Triangle::drawOutline");
#endif
}

// deprecated calls
template<typename T>
void Triangle<T>::draw()
{
#ifdef DGL_USE_COMPAT_OPENGL
drawTriangle<T>(pos1, pos2, pos3, false);
#else
notImplemented("Triangle::draw");
#endif
}

template<typename T>
void Triangle<T>::drawOutline()
{
#ifdef DGL_USE_COMPAT_OPENGL
drawTriangle<T>(pos1, pos2, pos3, true);
#else
notImplemented("Triangle::drawOutline");
#endif
}

template class Triangle<double>;
@@ -211,6 +284,7 @@ template class Triangle<ushort>;
// -----------------------------------------------------------------------
// Rectangle

#ifdef DGL_USE_COMPAT_OPENGL
template<typename T>
static void drawRectangle(const Rectangle<T>& rect, const bool outline)
{
@@ -239,11 +313,16 @@ static void drawRectangle(const Rectangle<T>& rect, const bool outline)

glEnd();
}
#endif

template<typename T>
void Rectangle<T>::draw(const GraphicsContext&)
{
#ifdef DGL_USE_COMPAT_OPENGL
drawRectangle<T>(*this, false);
#else
notImplemented("Rectangle::draw");
#endif
}

template<typename T>
@@ -252,20 +331,32 @@ void Rectangle<T>::drawOutline(const GraphicsContext&, const T lineWidth)
DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,);

glLineWidth(static_cast<GLfloat>(lineWidth));
#ifdef DGL_USE_COMPAT_OPENGL
drawRectangle<T>(*this, true);
#else
notImplemented("Rectangle::drawOutline");
#endif
}

// deprecated calls
template<typename T>
void Rectangle<T>::draw()
{
#ifdef DGL_USE_COMPAT_OPENGL
drawRectangle<T>(*this, false);
#else
notImplemented("Rectangle::draw");
#endif
}

template<typename T>
void Rectangle<T>::drawOutline()
{
#ifdef DGL_USE_COMPAT_OPENGL
drawRectangle<T>(*this, true);
#else
notImplemented("Rectangle::drawOutline");
#endif
}

template class Rectangle<double>;
@@ -316,11 +407,14 @@ static void drawOpenGLImage(const OpenGLImage& image, const Point<int>& pos, con
setupCalled = true;
}

#ifdef DGL_USE_COMPAT_OPENGL
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
#endif

glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);

#ifdef DGL_USE_COMPAT_OPENGL
glBegin(GL_QUADS);

{
@@ -343,6 +437,7 @@ static void drawOpenGLImage(const OpenGLImage& image, const Point<int>& pos, con
}

glEnd();
#endif

glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
@@ -533,17 +628,23 @@ void ImageBaseKnob<OpenGLImage>::onDisplay()

if (pData->rotationAngle != 0)
{
#ifdef DGL_USE_COMPAT_OPENGL
glPushMatrix();
#endif

const int w2 = w/2;
const int h2 = h/2;

#ifdef DGL_USE_COMPAT_OPENGL
glTranslatef(static_cast<float>(w2), static_cast<float>(h2), 0.0f);
glRotatef(normValue*static_cast<float>(pData->rotationAngle), 0.0f, 0.0f, 1.0f);
#endif

Rectangle<int>(-w2, -h2, w, h).draw(context);

#ifdef DGL_USE_COMPAT_OPENGL
glPopMatrix();
#endif
}
else
{
@@ -667,6 +768,36 @@ void TopLevelWidget::PrivateData::display()

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

void Window::PrivateData::renderToPicture(const char* const filename,
const GraphicsContext&,
const uint width,
const uint height)
{
FILE* const f = fopen(filename, "w");
DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);

GLubyte* const pixels = new GLubyte[width * height * 3 * sizeof(GLubyte)];

glFlush();
glReadPixels(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height), GL_RGB, GL_UNSIGNED_BYTE, pixels);

fprintf(f, "P3\n%d %d\n255\n", width, height);
for (uint y = 0; y < height; y++)
{
for (uint i, x = 0; x < width; x++)
{
i = 3 * ((height - y - 1) * width + x);
fprintf(f, "%3d %3d %3d ", pixels[i], pixels[i+1], pixels[i+2]);
}
fprintf(f, "\n");
}

delete[] pixels;
fclose(f);
}

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

const GraphicsContext& Window::PrivateData::getGraphicsContext() const noexcept
{
return (const GraphicsContext&)graphicsContext;


+ 15
- 4
dgl/src/SubWidget.cpp View File

@@ -34,7 +34,9 @@ SubWidget::~SubWidget()
template<typename T>
bool SubWidget::contains(const T x, const T y) const noexcept
{
return Rectangle<double>(0, 0, getWidth()-pData->margin.getX(), getHeight()-pData->margin.getY()).contains(x, y);
return Rectangle<double>(0, 0,
static_cast<double>(getWidth()),
static_cast<double>(getHeight())).contains(x, y);
}

template<typename T>
@@ -65,9 +67,18 @@ Rectangle<int> SubWidget::getAbsoluteArea() const noexcept

Rectangle<uint> SubWidget::getConstrainedAbsoluteArea() const noexcept
{
return Rectangle<uint>(static_cast<uint>(std::max(0, getAbsoluteX())),
static_cast<uint>(std::max(0, getAbsoluteY())),
getSize());
const int x = getAbsoluteX();
const int y = getAbsoluteY();

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

const int xOffset = std::min(0, x);
const int yOffset = std::min(0, y);
const int width = std::max(0, static_cast<int>(getWidth()) + xOffset);
const int height = std::max(0, static_cast<int>(getHeight()) + yOffset);

return Rectangle<uint>(0, 0, static_cast<uint>(width), static_cast<uint>(height));
}

void SubWidget::setAbsoluteX(const int x) noexcept


+ 35
- 14
dgl/src/TopLevelWidget.cpp View File

@@ -60,6 +60,21 @@ void TopLevelWidget::setSize(const Size<uint>& size)
pData->window.setSize(size);
}

const void* TopLevelWidget::getClipboard(size_t& dataSize)
{
return pData->window.getClipboard(dataSize);
}

bool TopLevelWidget::setClipboard(const char* const mimeType, const void* const data, const size_t dataSize)
{
return pData->window.setClipboard(mimeType, data, dataSize);
}

bool TopLevelWidget::setCursor(const MouseCursor cursor)
{
return pData->window.setCursor(cursor);
}

bool TopLevelWidget::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs)
{
return pData->window.addIdleCallback(callback, timerFrequencyInMs);
@@ -88,41 +103,47 @@ void TopLevelWidget::repaint(const Rectangle<uint>& rect) noexcept
void TopLevelWidget::setGeometryConstraints(const uint minimumWidth,
const uint minimumHeight,
const bool keepAspectRatio,
const bool automaticallyScale)
const bool automaticallyScale,
const bool resizeNowIfAutoScaling)
{
pData->window.setGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio, automaticallyScale);
pData->window.setGeometryConstraints(minimumWidth,
minimumHeight,
keepAspectRatio,
automaticallyScale,
resizeNowIfAutoScaling);
}

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

bool TopLevelWidget::onKeyboard(const KeyboardEvent&)
bool TopLevelWidget::onKeyboard(const KeyboardEvent& ev)
{
return false;
return pData->keyboardEvent(ev);
}

bool TopLevelWidget::onSpecial(const SpecialEvent&)
bool TopLevelWidget::onCharacterInput(const CharacterInputEvent& ev)
{
return false;
return pData->characterInputEvent(ev);
}

bool TopLevelWidget::onCharacterInput(const CharacterInputEvent&)
bool TopLevelWidget::onMouse(const MouseEvent& ev)
{
return false;
return pData->mouseEvent(ev);
}

bool TopLevelWidget::onMouse(const MouseEvent&)
bool TopLevelWidget::onMotion(const MotionEvent& ev)
{
return false;
return pData->motionEvent(ev);
}

bool TopLevelWidget::onMotion(const MotionEvent&)
bool TopLevelWidget::onScroll(const ScrollEvent& ev)
{
return false;
return pData->scrollEvent(ev);
}

bool TopLevelWidget::onScroll(const ScrollEvent&)
// --------------------------------------------------------------------------------------------------------------------

void TopLevelWidget::requestSizeChange(uint, uint)
{
return false;
}

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


+ 0
- 34
dgl/src/TopLevelWidgetPrivateData.cpp View File

@@ -42,38 +42,16 @@ bool TopLevelWidget::PrivateData::keyboardEvent(const KeyboardEvent& ev)
if (! selfw->pData->visible)
return false;

// give top-level widget chance to catch this event first
if (self->onKeyboard(ev))
return true;

// propagate event to all subwidgets recursively
return selfw->pData->giveKeyboardEventForSubWidgets(ev);
}

bool TopLevelWidget::PrivateData::specialEvent(const SpecialEvent& ev)
{
// ignore event if we are not visible
if (! selfw->pData->visible)
return false;

// give top-level widget chance to catch this event first
if (self->onSpecial(ev))
return true;

// propagate event to all subwidgets recursively
return selfw->pData->giveSpecialEventForSubWidgets(ev);
}

bool TopLevelWidget::PrivateData::characterInputEvent(const CharacterInputEvent& ev)
{
// ignore event if we are not visible
if (! selfw->pData->visible)
return false;

// give top-level widget chance to catch this event first
if (self->onCharacterInput(ev))
return true;

// propagate event to all subwidgets recursively
return selfw->pData->giveCharacterInputEventForSubWidgets(ev);
}
@@ -96,10 +74,6 @@ bool TopLevelWidget::PrivateData::mouseEvent(const MouseEvent& ev)
rev.absolutePos.setY(ev.absolutePos.getY() / autoScaleFactor);
}

// give top-level widget chance to catch this event first
if (self->onMouse(ev))
return true;

// propagate event to all subwidgets recursively
return selfw->pData->giveMouseEventForSubWidgets(rev);
}
@@ -122,10 +96,6 @@ bool TopLevelWidget::PrivateData::motionEvent(const MotionEvent& ev)
rev.absolutePos.setY(ev.absolutePos.getY() / autoScaleFactor);
}

// give top-level widget chance to catch this event first
if (self->onMotion(ev))
return true;

// propagate event to all subwidgets recursively
return selfw->pData->giveMotionEventForSubWidgets(rev);
}
@@ -150,10 +120,6 @@ bool TopLevelWidget::PrivateData::scrollEvent(const ScrollEvent& ev)
rev.delta.setY(ev.delta.getY() / autoScaleFactor);
}

// give top-level widget chance to catch this event first
if (self->onScroll(ev))
return true;

// propagate event to all subwidgets recursively
return selfw->pData->giveScrollEventForSubWidgets(rev);
}


+ 1
- 2
dgl/src/TopLevelWidgetPrivateData.hpp View File

@@ -30,11 +30,10 @@ struct TopLevelWidget::PrivateData {
Widget* const selfw;
Window& window;

explicit PrivateData(TopLevelWidget* const s, Window& w);
explicit PrivateData(TopLevelWidget* self, Window& window);
~PrivateData();
void display();
bool keyboardEvent(const KeyboardEvent& ev);
bool specialEvent(const SpecialEvent& ev);
bool characterInputEvent(const CharacterInputEvent& ev);
bool mouseEvent(const MouseEvent& ev);
bool motionEvent(const MotionEvent& ev);


+ 7
- 0
dgl/src/Vulkan.cpp View File

@@ -231,6 +231,13 @@ void TopLevelWidget::PrivateData::display()

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

void Window::PrivateData::renderToPicture(const char*, const GraphicsContext&, uint, uint)
{
notImplemented("Window::PrivateData::renderToPicture");
}

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

const GraphicsContext& Window::PrivateData::getGraphicsContext() const noexcept
{
return (const GraphicsContext&)graphicsContext;


+ 0
- 5
dgl/src/Widget.cpp View File

@@ -167,11 +167,6 @@ bool Widget::onKeyboard(const KeyboardEvent& ev)
return pData->giveKeyboardEventForSubWidgets(ev);
}

bool Widget::onSpecial(const SpecialEvent& ev)
{
return pData->giveSpecialEventForSubWidgets(ev);
}

bool Widget::onCharacterInput(const CharacterInputEvent& ev)
{
return pData->giveCharacterInputEventForSubWidgets(ev);


+ 12
- 39
dgl/src/WidgetPrivateData.cpp View File

@@ -87,24 +87,6 @@ bool Widget::PrivateData::giveKeyboardEventForSubWidgets(const KeyboardEvent& ev
return false;
}

bool Widget::PrivateData::giveSpecialEventForSubWidgets(const SpecialEvent& ev)
{
if (! visible)
return false;
if (subWidgets.size() == 0)
return false;

FOR_EACH_SUBWIDGET_INV(rit)
{
SubWidget* const widget(*rit);

if (widget->isVisible() && widget->onSpecial(ev))
return true;
}

return false;
}

bool Widget::PrivateData::giveCharacterInputEventForSubWidgets(const CharacterInputEvent& ev)
{
if (! visible)
@@ -130,18 +112,15 @@ bool Widget::PrivateData::giveMouseEventForSubWidgets(MouseEvent& ev)
if (subWidgets.size() == 0)
return false;

double x = ev.absolutePos.getX();
double y = ev.absolutePos.getY();
const double x = ev.absolutePos.getX();
const double y = ev.absolutePos.getY();

if (SubWidget* const selfw = dynamic_cast<SubWidget*>(self))
{
if (selfw->pData->needsViewportScaling)
{
x -= selfw->getAbsoluteX();
y -= selfw->getAbsoluteY();

ev.absolutePos.setX(x);
ev.absolutePos.setY(y);
ev.absolutePos.setX(x - selfw->getAbsoluteX() + selfw->getMargin().getX());
ev.absolutePos.setY(y - selfw->getAbsoluteY() + selfw->getMargin().getY());
}
}

@@ -169,18 +148,15 @@ bool Widget::PrivateData::giveMotionEventForSubWidgets(MotionEvent& ev)
if (subWidgets.size() == 0)
return false;

double x = ev.absolutePos.getX();
double y = ev.absolutePos.getY();
const double x = ev.absolutePos.getX();
const double y = ev.absolutePos.getY();

if (SubWidget* const selfw = dynamic_cast<SubWidget*>(self))
{
if (selfw->pData->needsViewportScaling)
{
x -= selfw->getAbsoluteX();
y -= selfw->getAbsoluteY();

ev.absolutePos.setX(x);
ev.absolutePos.setY(y);
ev.absolutePos.setX(x - selfw->getAbsoluteX() + selfw->getMargin().getX());
ev.absolutePos.setY(y - selfw->getAbsoluteY() + selfw->getMargin().getY());
}
}

@@ -208,18 +184,15 @@ bool Widget::PrivateData::giveScrollEventForSubWidgets(ScrollEvent& ev)
if (subWidgets.size() == 0)
return false;

double x = ev.absolutePos.getX();
double y = ev.absolutePos.getY();
const double x = ev.absolutePos.getX();
const double y = ev.absolutePos.getY();

if (SubWidget* const selfw = dynamic_cast<SubWidget*>(self))
{
if (selfw->pData->needsViewportScaling)
{
x -= selfw->getAbsoluteX();
y -= selfw->getAbsoluteY();

ev.absolutePos.setX(x);
ev.absolutePos.setY(y);
ev.absolutePos.setX(x - selfw->getAbsoluteX() + selfw->getMargin().getX());
ev.absolutePos.setY(y - selfw->getAbsoluteY() + selfw->getMargin().getY());
}
}



+ 0
- 1
dgl/src/WidgetPrivateData.hpp View File

@@ -44,7 +44,6 @@ struct Widget::PrivateData {
void displaySubWidgets(uint width, uint height, double autoScaleFactor);

bool giveKeyboardEventForSubWidgets(const KeyboardEvent& ev);
bool giveSpecialEventForSubWidgets(const SpecialEvent& ev);
bool giveCharacterInputEventForSubWidgets(const CharacterInputEvent& ev);
bool giveMouseEventForSubWidgets(MouseEvent& ev);
bool giveMotionEventForSubWidgets(MotionEvent& ev);


+ 151
- 45
dgl/src/Window.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
@@ -15,6 +15,7 @@
*/

#include "WindowPrivateData.hpp"
#include "../TopLevelWidget.hpp"

#include "pugl.hpp"

@@ -66,8 +67,8 @@ Window::Window(Application& app)
pData->initPost();
}

Window::Window(Application& app, Window& parent)
: pData(new PrivateData(app, this, parent.pData))
Window::Window(Application& app, Window& transientParentWindow)
: pData(new PrivateData(app, this, transientParentWindow.pData))
{
pData->initPost();
}
@@ -87,7 +88,7 @@ Window::Window(Application& app,
const uint height,
const double scaleFactor,
const bool resizable)
: pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable))
: pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, false))
{
pData->initPost();
}
@@ -98,8 +99,9 @@ Window::Window(Application& app,
const uint height,
const double scaleFactor,
const bool resizable,
const bool isVST3,
const bool doPostInit)
: pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable))
: pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, isVST3))
{
if (doPostInit)
pData->initPost();
@@ -153,6 +155,48 @@ void Window::setResizable(const bool resizable)
pData->setResizable(resizable);
}

int Window::getOffsetX() const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0);

return puglGetFrame(pData->view).x;
}

int Window::getOffsetY() const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0);

return puglGetFrame(pData->view).y;
}

Point<int> Window::getOffset() const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, Point<int>());

const PuglRect rect = puglGetFrame(pData->view);
return Point<int>(rect.x, rect.y);
}

void Window::setOffsetX(const int x)
{
setOffset(x, getOffsetY());
}

void Window::setOffsetY(const int y)
{
setOffset(getOffsetX(), y);
}

void Window::setOffset(const int x, const int y)
{
puglSetPosition(pData->view, x, y);
}

void Window::setOffset(const Point<int>& offset)
{
setOffset(offset.getX(), offset.getY());
}

uint Window::getWidth() const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0);
@@ -199,8 +243,14 @@ void Window::setSize(uint width, uint height)
if (pData->isEmbed)
{
const double scaleFactor = pData->scaleFactor;
const uint minWidth = static_cast<uint>(pData->minWidth * scaleFactor + 0.5);
const uint minHeight = static_cast<uint>(pData->minHeight * scaleFactor + 0.5);
uint minWidth = pData->minWidth;
uint minHeight = pData->minHeight;

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

// handle geometry constraints here
if (width < minWidth)
@@ -220,15 +270,27 @@ void Window::setSize(uint width, uint height)
{
// fix width
if (reqRatio > ratio)
width = height * ratio;
width = static_cast<uint>(height * ratio + 0.5);
// fix height
else
height = width / ratio;
height = static_cast<uint>(static_cast<double>(width) / ratio + 0.5);
}
}
}

puglSetWindowSize(pData->view, width, height);
if (pData->usesSizeRequest)
{
DISTRHO_SAFE_ASSERT_RETURN(pData->topLevelWidgets.size() != 0,);

TopLevelWidget* const topLevelWidget = pData->topLevelWidgets.front();
DISTRHO_SAFE_ASSERT_RETURN(topLevelWidget != nullptr,);

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

void Window::setSize(const Size<uint>& size)
@@ -257,6 +319,21 @@ void Window::setIgnoringKeyRepeat(const bool ignore) noexcept
puglSetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT, ignore);
}

const void* Window::getClipboard(size_t& dataSize)
{
return pData->getClipboard(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;
}

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

bool Window::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs)
{
DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr, false)
@@ -285,7 +362,7 @@ const GraphicsContext& Window::getGraphicsContext() const noexcept

uintptr_t Window::getNativeWindowHandle() const noexcept
{
return puglGetNativeWindow(pData->view);
return puglGetNativeView(pData->view);
}

double Window::getScaleFactor() const noexcept
@@ -319,10 +396,10 @@ void Window::repaint(const Rectangle<uint>& rect) noexcept
return;

PuglRect prect = {
static_cast<double>(rect.getX()),
static_cast<double>(rect.getY()),
static_cast<double>(rect.getWidth()),
static_cast<double>(rect.getHeight()),
static_cast<PuglCoord>(rect.getX()),
static_cast<PuglCoord>(rect.getY()),
static_cast<PuglSpan>(rect.getWidth()),
static_cast<PuglSpan>(rect.getHeight()),
};
if (pData->autoScaling)
{
@@ -336,15 +413,27 @@ void Window::repaint(const Rectangle<uint>& rect) noexcept
puglPostRedisplayRect(pData->view, prect);
}

void Window::renderToPicture(const char* const filename)
{
pData->filenameToRenderInto = strdup(filename);
}

void Window::runAsModal(bool blockWait)
{
pData->runAsModal(blockWait);
}

void Window::setGeometryConstraints(const uint minimumWidth,
const uint minimumHeight,
Size<uint> Window::getGeometryConstraints(bool& keepAspectRatio)
{
keepAspectRatio = pData->keepAspectRatio;
return Size<uint>(pData->minWidth, pData->minHeight);
}

void Window::setGeometryConstraints(uint minimumWidth,
uint minimumHeight,
const bool keepAspectRatio,
const bool automaticallyScale)
const bool automaticallyScale,
const bool resizeNowIfAutoScaling)
{
DISTRHO_SAFE_ASSERT_RETURN(minimumWidth > 0,);
DISTRHO_SAFE_ASSERT_RETURN(minimumHeight > 0,);
@@ -359,12 +448,15 @@ void Window::setGeometryConstraints(const uint minimumWidth,

const double scaleFactor = pData->scaleFactor;

puglSetGeometryConstraints(pData->view,
static_cast<uint>(minimumWidth * scaleFactor + 0.5),
static_cast<uint>(minimumHeight * scaleFactor + 0.5),
keepAspectRatio);
if (automaticallyScale && scaleFactor != 1.0)
{
minimumWidth *= scaleFactor;
minimumHeight *= scaleFactor;
}

if (scaleFactor != 1.0)
puglSetGeometryConstraints(pData->view, minimumWidth, minimumHeight, keepAspectRatio);

if (scaleFactor != 1.0 && automaticallyScale && resizeNowIfAutoScaling)
{
const Size<uint> size(getSize());

@@ -373,50 +465,64 @@ void Window::setGeometryConstraints(const uint minimumWidth,
}
}

bool Window::onClose()
void Window::setTransientParent(const uintptr_t transientParentWindowHandle)
{
return true;
puglSetTransientParent(pData->view, transientParentWindowHandle);
}

void Window::onFocus(bool, CrossingMode)
std::vector<ClipboardDataOffer> Window::getClipboardDataOfferTypes()
{
std::vector<ClipboardDataOffer> offerTypes;

if (const uint32_t numTypes = puglGetNumClipboardTypes(pData->view))
{
offerTypes.reserve(numTypes);

for (uint32_t i=0; i<numTypes; ++i)
{
const ClipboardDataOffer offer = { i + 1, puglGetClipboardType(pData->view, i) };
offerTypes.push_back(offer);
}
}

return offerTypes;
}

void Window::onReshape(uint, uint)
uint32_t Window::onClipboardDataOffer()
{
puglFallbackOnResize(pData->view);
std::vector<ClipboardDataOffer> offers(getClipboardDataOfferTypes());

for (std::vector<ClipboardDataOffer>::iterator it=offers.begin(), end=offers.end(); it != end;++it)
{
const ClipboardDataOffer offer = *it;
if (std::strcmp(offer.type, "text/plain") == 0)
return offer.id;
}

return 0;
}

void Window::onScaleFactorChanged(double)
bool Window::onClose()
{
return true;
}

#ifndef DGL_FILE_BROWSER_DISABLED
void Window::onFileSelected(const char*)
void Window::onFocus(bool, CrossingMode)
{
}
#endif

#if 0
void Window::setTransientWinId(const uintptr_t winId)
void Window::onReshape(uint, uint)
{
puglSetTransientFor(pData->view, winId);
puglFallbackOnResize(pData->view);
}

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

bool Window::handlePluginKeyboard(const bool press, const uint key)
void Window::onScaleFactorChanged(double)
{
// TODO
return false;
// return pData->handlePluginKeyboard(press, key);
}

bool Window::handlePluginSpecial(const bool press, const Key key)
#ifndef DGL_FILE_BROWSER_DISABLED
void Window::onFileSelected(const char*)
{
// TODO
return false;
// return pData->handlePluginSpecial(press, key);
}
#endif



+ 259
- 266
dgl/src/WindowPrivateData.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
@@ -19,26 +19,19 @@

#include "pugl.hpp"

#include "../../distrho/extra/String.hpp"

#ifdef DISTRHO_OS_WINDOWS
# include <direct.h>
# include <winsock2.h>
# include <windows.h>
# include <vector>
#else
# include <unistd.h>
#endif

#define DGL_DEBUG_EVENTS
// #define DGL_DEBUG_EVENTS

#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
# include <cinttypes>
# ifdef DISTRHO_PROPER_CPP11_SUPPORT
# include <cinttypes>
# else
# include <inttypes.h>
# endif
#endif

START_NAMESPACE_DGL

#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
#ifdef DGL_DEBUG_EVENTS
# define DGL_DBG(msg) std::fprintf(stderr, "%s", msg);
# define DGL_DBGp(...) std::fprintf(stderr, __VA_ARGS__);
# define DGL_DBGF std::fflush(stderr);
@@ -59,44 +52,69 @@ START_NAMESPACE_DGL

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

#ifdef DISTRHO_OS_WINDOWS
// static pointer used for direct comparisons
static const char* const kWin32SelectedFileCancelled = "__dpf_cancelled__";
#endif

static double getDesktopScaleFactor(const PuglView* const view)
static double getScaleFactorFromParent(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 puglGetDesktopScaleFactor(view);
return puglGetScaleFactorFromParent(view);

return 1.0;
}

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

if (PuglView* const view = puglNewView(world))
{
puglSetTransientParent(view, puglGetNativeView(transientParentView));
return view;
}

return nullptr;
}

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

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

return nullptr;
}

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

Window::PrivateData::PrivateData(Application& a, Window* const s)
: app(a),
appData(a.pData),
self(s),
view(puglNewView(appData->world)),
transientParentView(nullptr),
view(appData->world != nullptr ? puglNewView(appData->world) : nullptr),
topLevelWidgets(),
isClosed(true),
isVisible(false),
isEmbed(false),
scaleFactor(getDesktopScaleFactor(view)),
usesSizeRequest(false),
scaleFactor(getScaleFactorFromParent(view)),
autoScaling(false),
autoScaleFactor(1.0),
minWidth(0),
minHeight(0),
keepAspectRatio(false),
ignoreIdleCallbacks(false),
#ifdef DISTRHO_OS_WINDOWS
win32SelectedFile(nullptr),
waitingForClipboardData(false),
waitingForClipboardEvents(false),
clipboardTypeId(0),
filenameToRenderInto(nullptr),
#ifndef DGL_FILE_BROWSER_DISABLED
fileBrowserHandle(nullptr),
#endif
modal()
{
@@ -107,12 +125,12 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c
: app(a),
appData(a.pData),
self(s),
view(puglNewView(appData->world)),
transientParentView(ppData->view),
view(puglNewViewWithTransientParent(appData->world, ppData->view)),
topLevelWidgets(),
isClosed(true),
isVisible(false),
isEmbed(false),
usesSizeRequest(false),
scaleFactor(ppData->scaleFactor),
autoScaling(false),
autoScaleFactor(1.0),
@@ -120,13 +138,15 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c
minHeight(0),
keepAspectRatio(false),
ignoreIdleCallbacks(false),
#ifdef DISTRHO_OS_WINDOWS
win32SelectedFile(nullptr),
waitingForClipboardData(false),
waitingForClipboardEvents(false),
clipboardTypeId(0),
filenameToRenderInto(nullptr),
#ifndef DGL_FILE_BROWSER_DISABLED
fileBrowserHandle(nullptr),
#endif
modal(ppData)
{
puglSetTransientFor(view, puglGetNativeWindow(transientParentView));

initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false);
}

@@ -136,52 +156,57 @@ Window::PrivateData::PrivateData(Application& a, Window* const s,
: app(a),
appData(a.pData),
self(s),
view(puglNewView(appData->world)),
transientParentView(nullptr),
view(puglNewViewWithParentWindow(appData->world, parentWindowHandle)),
topLevelWidgets(),
isClosed(parentWindowHandle == 0),
isVisible(parentWindowHandle != 0),
isEmbed(parentWindowHandle != 0),
scaleFactor(scale != 0.0 ? scale : getDesktopScaleFactor(view)),
usesSizeRequest(false),
scaleFactor(scale != 0.0 ? scale : getScaleFactorFromParent(view)),
autoScaling(false),
autoScaleFactor(1.0),
minWidth(0),
minHeight(0),
keepAspectRatio(false),
ignoreIdleCallbacks(false),
#ifdef DISTRHO_OS_WINDOWS
win32SelectedFile(nullptr),
waitingForClipboardData(false),
waitingForClipboardEvents(false),
clipboardTypeId(0),
filenameToRenderInto(nullptr),
#ifndef DGL_FILE_BROWSER_DISABLED
fileBrowserHandle(nullptr),
#endif
modal()
{
if (isEmbed)
puglSetParentWindow(view, parentWindowHandle);

initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, resizable);
}

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 double scale, const bool resizable, const bool isVST3)
: app(a),
appData(a.pData),
self(s),
view(appData->world != nullptr ? puglNewView(appData->world) : nullptr),
transientParentView(nullptr),
view(puglNewViewWithParentWindow(appData->world, parentWindowHandle)),
topLevelWidgets(),
isClosed(parentWindowHandle == 0),
isVisible(parentWindowHandle != 0 && view != nullptr),
isEmbed(parentWindowHandle != 0),
scaleFactor(scale != 0.0 ? scale : getDesktopScaleFactor(view)),
usesSizeRequest(isVST3),
scaleFactor(scale != 0.0 ? scale : getScaleFactorFromParent(view)),
autoScaling(false),
autoScaleFactor(1.0),
minWidth(0),
minHeight(0),
keepAspectRatio(false),
ignoreIdleCallbacks(false),
#ifdef DISTRHO_OS_WINDOWS
win32SelectedFile(nullptr),
waitingForClipboardData(false),
waitingForClipboardEvents(false),
clipboardTypeId(0),
filenameToRenderInto(nullptr),
#ifndef DGL_FILE_BROWSER_DISABLED
fileBrowserHandle(nullptr),
#endif
modal()
{
@@ -195,14 +220,16 @@ Window::PrivateData::~PrivateData()
{
appData->idleCallbacks.remove(this);
appData->windows.remove(self);
std::free(filenameToRenderInto);

if (view == nullptr)
return;

if (isEmbed)
{
#ifdef HAVE_X11
sofdFileDialogClose();
#ifndef DGL_FILE_BROWSER_DISABLED
if (fileBrowserHandle != nullptr)
fileBrowserClose(fileBrowserHandle);
#endif
puglHide(view);
appData->oneWindowClosed();
@@ -210,11 +237,6 @@ Window::PrivateData::~PrivateData()
isVisible = false;
}

#ifdef DISTRHO_OS_WINDOWS
if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled)
std::free(const_cast<char*>(win32SelectedFile));
#endif

puglFreeView(view);
}

@@ -233,21 +255,33 @@ void Window::PrivateData::initPre(const uint width, const uint height, const boo
}

puglSetMatchingBackendForCurrentBuild(view);

puglClearMinSize(view);
puglSetWindowSize(view, width, height);

puglSetHandle(view, this);

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

#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);
}

bool Window::PrivateData::initPost()
@@ -313,8 +347,8 @@ void Window::PrivateData::show()
appData->oneWindowShown();

// FIXME
PuglRect rect = puglGetFrame(view);
puglSetWindowSize(view, static_cast<uint>(rect.width), static_cast<uint>(rect.height));
// PuglRect rect = puglGetFrame(view);
// puglSetWindowSize(view, static_cast<uint>(rect.width), static_cast<uint>(rect.height));

#if defined(DISTRHO_OS_WINDOWS)
puglWin32ShowCentered(view);
@@ -354,9 +388,14 @@ void Window::PrivateData::hide()
if (modal.enabled)
stopModal();

#ifdef HAVE_X11
sofdFileDialogClose();
#ifndef DGL_FILE_BROWSER_DISABLED
if (fileBrowserHandle != nullptr)
{
fileBrowserClose(fileBrowserHandle);
fileBrowserHandle = nullptr;
}
#endif

puglHide(view);

isVisible = false;
@@ -372,11 +411,7 @@ void Window::PrivateData::focus()
if (! isEmbed)
puglRaiseWindow(view);

#ifdef HAVE_X11
puglX11GrabFocus(view);
#else
puglGrabFocus(view);
#endif
}

// -----------------------------------------------------------------------
@@ -387,10 +422,7 @@ void Window::PrivateData::setResizable(const bool resizable)

DGL_DBG("Window setResizable called\n");

puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE);
#ifdef DISTRHO_OS_WINDOWS
puglWin32SetWindowResizable(view, resizable);
#endif
puglSetResizable(view, resizable);
}

// -----------------------------------------------------------------------
@@ -398,20 +430,12 @@ void Window::PrivateData::setResizable(const bool resizable)
void Window::PrivateData::idleCallback()
{
#ifndef DGL_FILE_BROWSER_DISABLED
# ifdef DISTRHO_OS_WINDOWS
if (const char* path = win32SelectedFile)
if (fileBrowserHandle != nullptr && fileBrowserIdle(fileBrowserHandle))
{
win32SelectedFile = nullptr;
if (path == kWin32SelectedFileCancelled)
path = nullptr;
self->onFileSelected(path);
std::free(const_cast<char*>(path));
self->onFileSelected(fileBrowserGetPath(fileBrowserHandle));
fileBrowserClose(fileBrowserHandle);
fileBrowserHandle = nullptr;
}
# endif
# ifdef HAVE_X11
if (sofdFileDialogIdle(view))
self->onFileSelected(sofdFileDialogGetPath());
# endif
#endif
}

@@ -451,164 +475,23 @@ bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback)
// -----------------------------------------------------------------------
// file handling

bool Window::PrivateData::openFileBrowser(const Window::FileBrowserOptions& options)
bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options)
{
using DISTRHO_NAMESPACE::String;

// --------------------------------------------------------------------------
// configure start dir

// TODO: get abspath if needed
// TODO: cross-platform

String startDir(options.startDir);

if (startDir.isEmpty())
{
// TESTING verify this whole thing...
# ifdef DISTRHO_OS_WINDOWS
if (char* const cwd = _getcwd(nullptr, 0))
{
startDir = cwd;
std::free(cwd);
}
# else
if (char* const cwd = getcwd(nullptr, 0))
{
startDir = cwd;
std::free(cwd);
}
# endif
}

DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), false);

if (! startDir.endsWith(DISTRHO_OS_SEP))
startDir += DISTRHO_OS_SEP_STR;

// --------------------------------------------------------------------------
// configure title

String title(options.title);

if (title.isEmpty())
{
title = puglGetWindowTitle(view);

if (title.isEmpty())
title = "FileBrowser";
}
if (fileBrowserHandle != nullptr)
fileBrowserClose(fileBrowserHandle);

// --------------------------------------------------------------------------
// show
FileBrowserOptions options2 = options;

# ifdef DISTRHO_OS_MAC
uint flags = 0x0;
if (options2.title == nullptr)
options2.title = puglGetWindowTitle(view);

if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
flags |= 0x001;
else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked)
flags |= 0x002;
fileBrowserHandle = fileBrowserCreate(isEmbed,
puglGetNativeView(view),
autoScaling ? autoScaleFactor : scaleFactor,
options2);

if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
flags |= 0x010;
else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked)
flags |= 0x020;

if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked)
flags |= 0x100;
else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked)
flags |= 0x200;

return puglMacOSFilePanelOpen(view, startDir, title, flags, openPanelCallback);
# endif

# ifdef DISTRHO_OS_WINDOWS
// the old and compatible dialog API
OPENFILENAMEW ofn;
memset(&ofn, 0, sizeof(ofn));
if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled)
std::free(const_cast<char*>(win32SelectedFile));
win32SelectedFile = nullptr;

ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = (HWND)puglGetNativeWindow(view);

// set start directory in UTF-16 encoding
std::vector<WCHAR> startDirW;
startDirW.resize(startDir.length() + 1);
if (MultiByteToWideChar(CP_UTF8, 0, startDir.buffer(), -1, startDirW.data(), startDirW.size()))
ofn.lpstrInitialDir = startDirW.data();

// set title in UTF-16 encoding
std::vector<WCHAR> titleW;
titleW.resize(title.length() + 1);
if (MultiByteToWideChar(CP_UTF8, 0, title.buffer(), -1, titleW.data(), titleW.size()))
ofn.lpstrTitle = titleW.data();

// prepare a buffer to receive the result
std::vector<WCHAR> fileNameW(32768); // the Unicode maximum
ofn.lpstrFile = fileNameW.data();
ofn.nMaxFile = (DWORD)fileNameW.size();

// flags
ofn.Flags = OFN_PATHMUSTEXIST;
if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
ofn.Flags |= OFN_FORCESHOWHIDDEN;
if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible)
ofn.FlagsEx |= OFN_EX_NOPLACESBAR;

// TODO synchronous only, can't do better with WinAPI native dialogs.
// threading might work, if someone is motivated to risk it.
if (GetOpenFileNameW(&ofn))
{
// back to UTF-8
std::vector<char> fileNameA(4 * 32768);
if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1,
fileNameA.data(), (int)fileNameA.size(),
nullptr, nullptr))
{
// handle it during the next idle cycle (fake async)
win32SelectedFile = strdup(fileNameA.data());
}
}

if (win32SelectedFile == nullptr)
win32SelectedFile = kWin32SelectedFileCancelled;

return true;
# endif

# ifdef HAVE_X11
uint flags = 0x0;

if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
flags |= 0x001;
else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked)
flags |= 0x002;

if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
flags |= 0x010;
else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked)
flags |= 0x020;

if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked)
flags |= 0x100;
else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked)
flags |= 0x200;

return sofdFileDialogShow(view, startDir, title, flags, autoScaling ? autoScaleFactor : scaleFactor);
# endif

return false;
}

# ifdef DISTRHO_OS_MAC
void Window::PrivateData::openPanelCallback(PuglView* const view, const char* const path)
{
((Window::PrivateData*)puglGetHandle(view))->self->onFileSelected(path);
return fileBrowserHandle != nullptr;
}
# endif
#endif // ! DGL_FILE_BROWSER_DISABLED

// -----------------------------------------------------------------------
@@ -731,7 +614,7 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh

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

puglOnDisplayPrepare(view);

@@ -743,6 +626,14 @@ void Window::PrivateData::onPuglExpose()
if (widget->isVisible())
widget->pData->display();
}

if (char* const filename = filenameToRenderInto)
{
const PuglRect rect = puglGetFrame(view);
filenameToRenderInto = nullptr;
renderToPicture(filename, getGraphicsContext(), static_cast<uint>(rect.width), static_cast<uint>(rect.height));
std::free(filename);
}
#endif
}

@@ -801,15 +692,15 @@ void Window::PrivateData::onPuglKey(const Widget::KeyboardEvent& ev)
{
TopLevelWidget* const widget(*rit);

if (widget->isVisible() && widget->pData->keyboardEvent(ev))
if (widget->isVisible() && widget->onKeyboard(ev))
break;
}
#endif
}

void Window::PrivateData::onPuglSpecial(const Widget::SpecialEvent& ev)
void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev)
{
DGL_DBGp("onPuglSpecial : %i %u\n", ev.press, ev.key);
DGL_DBGp("onPuglText : %u %u %s\n", ev.keycode, ev.character, ev.string);

if (modal.child != nullptr)
return modal.child->focus();
@@ -819,15 +710,15 @@ void Window::PrivateData::onPuglSpecial(const Widget::SpecialEvent& ev)
{
TopLevelWidget* const widget(*rit);

if (widget->isVisible() && widget->pData->specialEvent(ev))
if (widget->isVisible() && widget->onCharacterInput(ev))
break;
}
#endif
}

void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev)
void Window::PrivateData::onPuglMouse(const Widget::MouseEvent& ev)
{
DGL_DBGp("onPuglText : %u %u %s\n", ev.keycode, ev.character, ev.string);
DGL_DBGp("onPuglMouse : %i %i %f %f\n", ev.button, ev.press, ev.pos.getX(), ev.pos.getY());

if (modal.child != nullptr)
return modal.child->focus();
@@ -837,15 +728,15 @@ void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev)
{
TopLevelWidget* const widget(*rit);

if (widget->isVisible() && widget->pData->characterInputEvent(ev))
if (widget->isVisible() && widget->onMouse(ev))
break;
}
#endif
}

void Window::PrivateData::onPuglMouse(const Widget::MouseEvent& ev)
void Window::PrivateData::onPuglMotion(const Widget::MotionEvent& ev)
{
DGL_DBGp("onPuglMouse : %i %i %f %f\n", ev.button, ev.press, ev.pos.getX(), ev.pos.getY());
DGL_DBGp("onPuglMotion : %f %f\n", ev.pos.getX(), ev.pos.getY());

if (modal.child != nullptr)
return modal.child->focus();
@@ -855,15 +746,15 @@ void Window::PrivateData::onPuglMouse(const Widget::MouseEvent& ev)
{
TopLevelWidget* const widget(*rit);

if (widget->isVisible() && widget->pData->mouseEvent(ev))
if (widget->isVisible() && widget->onMotion(ev))
break;
}
#endif
}

void Window::PrivateData::onPuglMotion(const Widget::MotionEvent& ev)
void Window::PrivateData::onPuglScroll(const Widget::ScrollEvent& ev)
{
DGL_DBGp("onPuglMotion : %f %f\n", ev.pos.getX(), ev.pos.getY());
DGL_DBGp("onPuglScroll : %f %f %f %f\n", ev.pos.getX(), ev.pos.getY(), ev.delta.getX(), ev.delta.getY());

if (modal.child != nullptr)
return modal.child->focus();
@@ -873,28 +764,82 @@ void Window::PrivateData::onPuglMotion(const Widget::MotionEvent& ev)
{
TopLevelWidget* const widget(*rit);

if (widget->isVisible() && widget->pData->motionEvent(ev))
if (widget->isVisible() && widget->onScroll(ev))
break;
}
#endif
}

void Window::PrivateData::onPuglScroll(const Widget::ScrollEvent& ev)
const void* Window::PrivateData::getClipboard(size_t& dataSize)
{
DGL_DBGp("onPuglScroll : %f %f %f %f\n", ev.pos.getX(), ev.pos.getY(), ev.delta.getX(), ev.delta.getY());
clipboardTypeId = 0;
waitingForClipboardData = true,
waitingForClipboardEvents = true;

if (modal.child != nullptr)
return modal.child->focus();
// begin clipboard dance here
if (puglPaste(view) != PUGL_SUCCESS)
{
dataSize = 0;
waitingForClipboardEvents = false;
return nullptr;
}

#ifndef DPF_TEST_WINDOW_CPP
FOR_EACH_TOP_LEVEL_WIDGET_INV(rit)
#ifdef DGL_USING_X11
// wait for type request, clipboardTypeId must be != 0 to be valid
int retry = static_cast<int>(2 / 0.03);
while (clipboardTypeId == 0 && waitingForClipboardData && --retry >= 0)
{
TopLevelWidget* const widget(*rit);
if (puglX11UpdateWithoutExposures(appData->world) != PUGL_SUCCESS)
break;
}
#endif

if (clipboardTypeId == 0)
{
dataSize = 0;
waitingForClipboardEvents = false;
return nullptr;
}

if (widget->isVisible() && widget->pData->scrollEvent(ev))
#ifdef DGL_USING_X11
// wait for actual data (assumes offer was accepted)
retry = static_cast<int>(2 / 0.03);
while (waitingForClipboardData && --retry >= 0)
{
if (puglX11UpdateWithoutExposures(appData->world) != PUGL_SUCCESS)
break;
}
#endif
#endif

if (clipboardTypeId == 0)
{
dataSize = 0;
waitingForClipboardEvents = false;
return nullptr;
}

waitingForClipboardEvents = false;
return puglGetClipboard(view, clipboardTypeId - 1, &dataSize);
}

uint32_t Window::PrivateData::onClipboardDataOffer()
{
DGL_DBG("onClipboardDataOffer\n");

if ((clipboardTypeId = self->onClipboardDataOffer()) != 0)
return clipboardTypeId;

// stop waiting for data, it was rejected
waitingForClipboardData = false;
return 0;
}

void Window::PrivateData::onClipboardData(const uint32_t typeId)
{
if (clipboardTypeId != typeId)
clipboardTypeId = 0;

waitingForClipboardData = false;
}

#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
@@ -905,9 +850,41 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu
{
Window::PrivateData* const pData = (Window::PrivateData*)puglGetHandle(view);
#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
printEvent(event, "pugl event: ", true);
if (event->type != PUGL_TIMER) {
printEvent(event, "pugl event: ", true);
}
#endif

if (pData->waitingForClipboardEvents)
{
switch (event->type)
{
case PUGL_UPDATE:
case PUGL_EXPOSE:
case PUGL_FOCUS_IN:
case PUGL_FOCUS_OUT:
case PUGL_KEY_PRESS:
case PUGL_KEY_RELEASE:
case PUGL_TEXT:
case PUGL_POINTER_IN:
case PUGL_POINTER_OUT:
case PUGL_BUTTON_PRESS:
case PUGL_BUTTON_RELEASE:
case PUGL_MOTION:
case PUGL_SCROLL:
case PUGL_TIMER:
case PUGL_LOOP_ENTER:
case PUGL_LOOP_LEAVE:
return PUGL_SUCCESS;
case PUGL_DATA_OFFER:
case PUGL_DATA:
break;
default:
d_stdout("Got event %d while waitingForClipboardEvents", event->type);
break;
}
}

switch (event->type)
{
///< No event
@@ -916,10 +893,10 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu

///< View created, a #PuglEventCreate
case PUGL_CREATE:
#ifdef HAVE_X11
#ifdef DGL_USING_X11
if (! pData->isEmbed)
puglX11SetWindowTypeAndPID(view);
#endif
puglX11SetWindowTypeAndPID(view, pData->appData->isStandalone);
#endif
break;

///< View destroyed, a #PuglEventDestroy
@@ -969,7 +946,6 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu
case PUGL_KEY_RELEASE:
{
// unused x, y, xRoot, yRoot (double)
// TODO special keys?
Widget::KeyboardEvent ev;
ev.mod = event->key.state;
ev.flags = event->key.flags;
@@ -977,8 +953,14 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu
ev.press = event->type == PUGL_KEY_PRESS;
ev.key = event->key.key;
ev.keycode = event->key.keycode;
if ((ev.mod & kModifierShift) != 0 && ev.key >= 'a' && ev.key <= 'z')
ev.key -= 'a' - 'A'; // a-z -> A-Z

// keyboard events must always be lowercase
if (ev.key >= 'A' && ev.key <= 'Z')
{
ev.key += 'a' - 'A'; // A-Z -> a-z
ev.mod |= kModifierShift;
}

pData->onPuglKey(ev);
break;
}
@@ -1014,7 +996,7 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu
ev.mod = event->button.state;
ev.flags = event->button.flags;
ev.time = static_cast<uint>(event->button.time * 1000.0 + 0.5);
ev.button = event->button.button;
ev.button = event->button.button + 1;
ev.press = event->type == PUGL_BUTTON_PRESS;
ev.pos = Point<double>(event->button.x, event->button.y);
ev.absolutePos = ev.pos;
@@ -1067,6 +1049,17 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu
///< Recursive loop left, a #PuglEventLoopLeave
case PUGL_LOOP_LEAVE:
break;

///< Data offered from clipboard, a #PuglDataOfferEvent
case PUGL_DATA_OFFER:
if (const uint32_t offerTypeId = pData->onClipboardDataOffer())
puglAcceptOffer(view, &event->offer, offerTypeId - 1);
break;

///< Data available from clipboard, a #PuglDataEvent
case PUGL_DATA:
pData->onClipboardData(event->data.typeIndex + 1);
break;
}

return PUGL_SUCCESS;


+ 26
- 133
dgl/src/WindowPrivateData.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
@@ -44,9 +44,6 @@ struct Window::PrivateData : IdleCallback {
/** Pugl view instance. */
PuglView* view;

/** Pugl view instance of the transient parent window. */
PuglView* const transientParentView;

/** Reserved space for graphics context. */
mutable uint8_t graphicsContext[sizeof(void*)];

@@ -63,6 +60,9 @@ 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 */
const bool usesSizeRequest;

/** Scale factor to report to widgets on request, purely informational. */
double scaleFactor;

@@ -77,9 +77,19 @@ struct Window::PrivateData : IdleCallback {
/** Whether to ignore idle callback requests, useful for temporary windows. */
bool ignoreIdleCallbacks;

#ifdef DISTRHO_OS_WINDOWS
/** Selected file for openFileBrowser on windows, stored for fake async operation. */
const char* win32SelectedFile;
/** Whether we are waiting to receive clipboard data, ignoring some events in the process. */
bool waitingForClipboardData;
bool waitingForClipboardEvents;

/** The type id returned by the last onClipboardDataOffer call. */
uint32_t clipboardTypeId;

/** Render to a picture file when non-null, automatically free+unset after saving. */
char* filenameToRenderInto;

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

/** Modal window setup. */
@@ -121,7 +131,7 @@ 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);
uint width, uint height, double scaling, bool resizable, bool isVST3);

/** Destructor. */
~PrivateData() override;
@@ -156,12 +166,11 @@ struct Window::PrivateData : IdleCallback {

#ifndef DGL_FILE_BROWSER_DISABLED
// file handling
bool openFileBrowser(const Window::FileBrowserOptions& options);
# ifdef DISTRHO_OS_MAC
static void openPanelCallback(PuglView* view, const char* path);
# endif
bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options);
#endif

static void renderToPicture(const char* filename, const GraphicsContext& context, uint width, uint height);

// modal handling
void startModal();
void stopModal();
@@ -173,12 +182,16 @@ struct Window::PrivateData : IdleCallback {
void onPuglClose();
void onPuglFocus(bool focus, CrossingMode mode);
void onPuglKey(const Widget::KeyboardEvent& ev);
void onPuglSpecial(const Widget::SpecialEvent& ev);
void onPuglText(const Widget::CharacterInputEvent& ev);
void onPuglMouse(const Widget::MouseEvent& ev);
void onPuglMotion(const Widget::MotionEvent& ev);
void onPuglScroll(const Widget::ScrollEvent& ev);

// clipboard related handling
const void* getClipboard(size_t& dataSize);
uint32_t onClipboardDataOffer();
void onClipboardData(uint32_t typeId);

// Pugl event handling entry point
static PuglStatus puglEventCallback(PuglView* view, const PuglEvent* event);

@@ -189,124 +202,4 @@ struct Window::PrivateData : IdleCallback {

END_NAMESPACE_DGL

#if 0
// #if defined(DISTRHO_OS_HAIKU)
// BApplication* bApplication;
// BView* bView;
// BWindow* bWindow;
#if defined(DISTRHO_OS_MAC)
// NSView<PuglGenericView>* mView;
// id mWindow;
// id mParentWindow;
# ifndef DGL_FILE_BROWSER_DISABLED
NSOpenPanel* fOpenFilePanel;
id fFilePanelDelegate;
# endif
#elif defined(DISTRHO_OS_WINDOWS)
// HWND hwnd;
// HWND hwndParent;
# ifndef DGL_FILE_BROWSER_DISABLED
String fSelectedFile;
# endif
#endif
#endif

#if 0
// -----------------------------------------------------------------------
// Window Private

struct Window::PrivateData {
// -------------------------------------------------------------------

bool handlePluginSpecial(const bool press, const Key key)
{
DBGp("PUGL: handlePluginSpecial : %i %i\n", press, key);

if (fModal.childFocus != nullptr)
{
fModal.childFocus->focus();
return true;
}

int mods = 0x0;

switch (key)
{
case kKeyShift:
mods |= kModifierShift;
break;
case kKeyControl:
mods |= kModifierControl;
break;
case kKeyAlt:
mods |= kModifierAlt;
break;
default:
break;
}

if (mods != 0x0)
{
if (press)
fView->mods |= mods;
else
fView->mods &= ~(mods);
}

Widget::SpecialEvent ev;
ev.press = press;
ev.key = key;
ev.mod = static_cast<Modifier>(fView->mods);
ev.time = 0;

FOR_EACH_WIDGET_INV(rit)
{
Widget* const widget(*rit);

if (widget->isVisible() && widget->onSpecial(ev))
return true;
}

return false;
}

#if defined(DISTRHO_OS_MAC) && !defined(DGL_FILE_BROWSER_DISABLED)
static void openPanelDidEnd(NSOpenPanel* panel, int returnCode, void *userData)
{
PrivateData* pData = (PrivateData*)userData;

if (returnCode == NSOKButton)
{
NSArray* urls = [panel URLs];
NSURL* fileUrl = nullptr;

for (NSUInteger i = 0, n = [urls count]; i < n && !fileUrl; ++i)
{
NSURL* url = (NSURL*)[urls objectAtIndex:i];
if ([url isFileURL])
fileUrl = url;
}

if (fileUrl)
{
PuglView* view = pData->fView;
if (view->fileSelectedFunc)
{
const char* fileName = [fileUrl.path UTF8String];
view->fileSelectedFunc(view, fileName);
}
}
}

[pData->fOpenFilePanel release];
pData->fOpenFilePanel = nullptr;
}
#endif

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

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData)
};
#endif

#endif // DGL_WINDOW_PRIVATE_DATA_HPP_INCLUDED

+ 154
- 143
dgl/src/nanovg/fontstash.h View File

@@ -157,31 +157,170 @@ struct FONSttFontImpl {
};
typedef struct FONSttFontImpl FONSttFontImpl;

static FT_Library ftLibrary;
#else

#define STB_TRUETYPE_IMPLEMENTATION
static void* fons__tmpalloc(size_t size, void* up);
static void fons__tmpfree(void* ptr, void* up);
#define STBTT_malloc(x,u) fons__tmpalloc(x,u)
#define STBTT_free(x,u) fons__tmpfree(x,u)
#include "stb_truetype.h"

struct FONSttFontImpl {
stbtt_fontinfo font;
};
typedef struct FONSttFontImpl FONSttFontImpl;

#endif

#ifndef FONS_SCRATCH_BUF_SIZE
# define FONS_SCRATCH_BUF_SIZE 96000
#endif
#ifndef FONS_HASH_LUT_SIZE
# define FONS_HASH_LUT_SIZE 256
#endif
#ifndef FONS_INIT_FONTS
# define FONS_INIT_FONTS 4
#endif
#ifndef FONS_INIT_GLYPHS
# define FONS_INIT_GLYPHS 256
#endif
#ifndef FONS_INIT_ATLAS_NODES
# define FONS_INIT_ATLAS_NODES 256
#endif
#ifndef FONS_VERTEX_COUNT
# define FONS_VERTEX_COUNT 1024
#endif
#ifndef FONS_MAX_STATES
# define FONS_MAX_STATES 20
#endif
#ifndef FONS_MAX_FALLBACKS
# define FONS_MAX_FALLBACKS 20
#endif

static unsigned int fons__hashint(unsigned int a)
{
a += ~(a<<15);
a ^= (a>>10);
a += (a<<3);
a ^= (a>>6);
a += ~(a<<11);
a ^= (a>>16);
return a;
}

static int fons__mini(int a, int b)
{
return a < b ? a : b;
}

static int fons__maxi(int a, int b)
{
return a > b ? a : b;
}

struct FONSglyph
{
unsigned int codepoint;
int index;
int next;
short size, blur;
short x0,y0,x1,y1;
short xadv,xoff,yoff;
};
typedef struct FONSglyph FONSglyph;

struct FONSfont
{
FONSttFontImpl font;
char name[64];
unsigned char* data;
int dataSize;
unsigned char freeData;
float ascender;
float descender;
float lineh;
FONSglyph* glyphs;
int cglyphs;
int nglyphs;
int lut[FONS_HASH_LUT_SIZE];
int fallbacks[FONS_MAX_FALLBACKS];
int nfallbacks;
};
typedef struct FONSfont FONSfont;

struct FONSstate
{
int font;
int align;
float size;
unsigned int color;
float blur;
float spacing;
};
typedef struct FONSstate FONSstate;

struct FONSatlasNode {
short x, y, width;
};
typedef struct FONSatlasNode FONSatlasNode;

struct FONSatlas
{
int width, height;
FONSatlasNode* nodes;
int nnodes;
int cnodes;
};
typedef struct FONSatlas FONSatlas;

struct FONScontext
{
FONSparams params;
float itw,ith;
unsigned char* texData;
int dirtyRect[4];
FONSfont** fonts;
FONSatlas* atlas;
int cfonts;
int nfonts;
float verts[FONS_VERTEX_COUNT*2];
float tcoords[FONS_VERTEX_COUNT*2];
unsigned int colors[FONS_VERTEX_COUNT];
int nverts;
unsigned char* scratch;
int nscratch;
FONSstate states[FONS_MAX_STATES];
int nstates;
void (*handleError)(void* uptr, int error, int val);
void* errorUptr;
#ifdef FONS_USE_FREETYPE
FT_Library ftLibrary;
#endif
};

#ifdef FONS_USE_FREETYPE

int fons__tt_init(FONScontext *context)
{
FT_Error ftError;
FONS_NOTUSED(context);
ftError = FT_Init_FreeType(&ftLibrary);
ftError = FT_Init_FreeType(&context->ftLibrary);
return ftError == 0;
}

int fons__tt_done(FONScontext *context)
{
FT_Error ftError;
FONS_NOTUSED(context);
ftError = FT_Done_FreeType(ftLibrary);
ftError = FT_Done_FreeType(context->ftLibrary);
return ftError == 0;
}

int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize, int fontIndex)
{
FT_Error ftError;
FONS_NOTUSED(context);

//font->font.userdata = stash;
ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, fontIndex, &font->font);
ftError = FT_New_Memory_Face(context->ftLibrary, (const FT_Byte*)data, dataSize, fontIndex, &font->font);
return ftError == 0;
}

@@ -269,18 +408,6 @@ int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2)

#else

#define STB_TRUETYPE_IMPLEMENTATION
static void* fons__tmpalloc(size_t size, void* up);
static void fons__tmpfree(void* ptr, void* up);
#define STBTT_malloc(x,u) fons__tmpalloc(x,u)
#define STBTT_free(x,u) fons__tmpfree(x,u)
#include "stb_truetype.h"

struct FONSttFontImpl {
stbtt_fontinfo font;
};
typedef struct FONSttFontImpl FONSttFontImpl;

int fons__tt_init(FONScontext *context)
{
FONS_NOTUSED(context);
@@ -350,129 +477,6 @@ int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2)

#endif

#ifndef FONS_SCRATCH_BUF_SIZE
# define FONS_SCRATCH_BUF_SIZE 96000
#endif
#ifndef FONS_HASH_LUT_SIZE
# define FONS_HASH_LUT_SIZE 256
#endif
#ifndef FONS_INIT_FONTS
# define FONS_INIT_FONTS 4
#endif
#ifndef FONS_INIT_GLYPHS
# define FONS_INIT_GLYPHS 256
#endif
#ifndef FONS_INIT_ATLAS_NODES
# define FONS_INIT_ATLAS_NODES 256
#endif
#ifndef FONS_VERTEX_COUNT
# define FONS_VERTEX_COUNT 1024
#endif
#ifndef FONS_MAX_STATES
# define FONS_MAX_STATES 20
#endif
#ifndef FONS_MAX_FALLBACKS
# define FONS_MAX_FALLBACKS 20
#endif

static unsigned int fons__hashint(unsigned int a)
{
a += ~(a<<15);
a ^= (a>>10);
a += (a<<3);
a ^= (a>>6);
a += ~(a<<11);
a ^= (a>>16);
return a;
}

static int fons__mini(int a, int b)
{
return a < b ? a : b;
}

static int fons__maxi(int a, int b)
{
return a > b ? a : b;
}

struct FONSglyph
{
unsigned int codepoint;
int index;
int next;
short size, blur;
short x0,y0,x1,y1;
short xadv,xoff,yoff;
};
typedef struct FONSglyph FONSglyph;

struct FONSfont
{
FONSttFontImpl font;
char name[64];
unsigned char* data;
int dataSize;
unsigned char freeData;
float ascender;
float descender;
float lineh;
FONSglyph* glyphs;
int cglyphs;
int nglyphs;
int lut[FONS_HASH_LUT_SIZE];
int fallbacks[FONS_MAX_FALLBACKS];
int nfallbacks;
};
typedef struct FONSfont FONSfont;

struct FONSstate
{
int font;
int align;
float size;
unsigned int color;
float blur;
float spacing;
};
typedef struct FONSstate FONSstate;

struct FONSatlasNode {
short x, y, width;
};
typedef struct FONSatlasNode FONSatlasNode;

struct FONSatlas
{
int width, height;
FONSatlasNode* nodes;
int nnodes;
int cnodes;
};
typedef struct FONSatlas FONSatlas;

struct FONScontext
{
FONSparams params;
float itw,ith;
unsigned char* texData;
int dirtyRect[4];
FONSfont** fonts;
FONSatlas* atlas;
int cfonts;
int nfonts;
float verts[FONS_VERTEX_COUNT*2];
float tcoords[FONS_VERTEX_COUNT*2];
unsigned int colors[FONS_VERTEX_COUNT];
int nverts;
unsigned char* scratch;
int nscratch;
FONSstate states[FONS_MAX_STATES];
int nstates;
void (*handleError)(void* uptr, int error, int val);
void* errorUptr;
};

#ifdef STB_TRUETYPE_IMPLEMENTATION

static void* fons__tmpalloc(size_t size, void* up)
@@ -907,6 +911,8 @@ static int fons__allocFont(FONScontext* stash)
stash->fonts = (FONSfont**)realloc(stash->fonts, sizeof(FONSfont*) * stash->cfonts);
if (stash->fonts == NULL)
return -1;
for (int i=stash->nfonts; i<stash->cfonts; ++i)
stash->fonts[i] = NULL;
}
font = (FONSfont*)malloc(sizeof(FONSfont));
if (font == NULL) goto error;
@@ -961,7 +967,10 @@ int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, in

int idx = fons__allocFont(stash);
if (idx == FONS_INVALID)
{
if (freeData && data) free(data);
return FONS_INVALID;
}

font = stash->fonts[idx];

@@ -1015,6 +1024,8 @@ static FONSglyph* fons__allocGlyph(FONSfont* font)
font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2;
font->glyphs = (FONSglyph*)realloc(font->glyphs, sizeof(FONSglyph) * font->cglyphs);
if (font->glyphs == NULL) return NULL;
for (int i=font->nglyphs; i<font->cglyphs; ++i)
memset(&font->glyphs[i], 0, sizeof(*font->glyphs));
}
font->nglyphs++;
return &font->glyphs[font->nglyphs-1];
@@ -1680,8 +1691,8 @@ void fonsDeleteInternal(FONScontext* stash)
if (stash->fonts) free(stash->fonts);
if (stash->texData) free(stash->texData);
if (stash->scratch) free(stash->scratch);
free(stash);
fons__tt_done(stash);
free(stash);
}

void fonsSetErrorCallback(FONScontext* stash, void (*callback)(void* uptr, int error, int val), void* uptr)


+ 215
- 117
dgl/src/nanovg/nanovg.c View File

@@ -24,8 +24,21 @@
#include "nanovg.h"
#define FONTSTASH_IMPLEMENTATION
#include "fontstash.h"

#ifndef NVG_NO_STB
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#endif

#ifdef NVG_DISABLE_SKIPPING_WHITESPACE
#define NVG_SKIPPED_CHAR NVG_SPACE
#else
#define NVG_SKIPPED_CHAR NVG_CHAR
#endif

#ifndef NVG_FONT_TEXTURE_FLAGS
#define NVG_FONT_TEXTURE_FLAGS 0
#endif

#ifdef _MSC_VER
#pragma warning(disable: 4100) // unreferenced formal parameter
@@ -74,7 +87,7 @@ struct NVGstate {
float miterLimit;
int lineJoin;
int lineCap;
float alpha;
NVGcolor tint;
float xform[6];
NVGscissor scissor;
float fontSize;
@@ -109,6 +122,14 @@ struct NVGpathCache {
};
typedef struct NVGpathCache NVGpathCache;

struct NVGfontContext { // Fontstash context plus font images; shared between shared NanoVG contexts.
int refCount;
struct FONScontext* fs;
int fontImages[NVG_MAX_FONTIMAGES];
int fontImageIdx;
};
typedef struct NVGfontContext NVGfontContext;

struct NVGcontext {
NVGparams params;
float* commands;
@@ -122,9 +143,7 @@ struct NVGcontext {
float distTol;
float fringeWidth;
float devicePxRatio;
struct FONScontext* fs;
int fontImages[NVG_MAX_FONTIMAGES];
int fontImageIdx;
NVGfontContext* fontContext;
int drawCallCount;
int fillTriCount;
int strokeTriCount;
@@ -283,7 +302,7 @@ static NVGstate* nvg__getState(NVGcontext* ctx)
return &ctx->states[ctx->nstates-1];
}

NVGcontext* nvgCreateInternal(NVGparams* params)
NVGcontext* nvgCreateInternal(NVGparams* params, NVGcontext* other) // Share the fonts and images of 'other' if it's non-NULL.
{
FONSparams fontParams;
NVGcontext* ctx = (NVGcontext*)malloc(sizeof(NVGcontext));
@@ -292,8 +311,16 @@ NVGcontext* nvgCreateInternal(NVGparams* params)
memset(ctx, 0, sizeof(NVGcontext));

ctx->params = *params;
for (i = 0; i < NVG_MAX_FONTIMAGES; i++)
ctx->fontImages[i] = 0;
if (other) {
ctx->fontContext = other->fontContext;
ctx->fontContext->refCount++;
} else {
ctx->fontContext = (NVGfontContext*)malloc(sizeof(NVGfontContext));
if (ctx->fontContext == NULL) goto error;
for (i = 0; i < NVG_MAX_FONTIMAGES; i++)
ctx->fontContext->fontImages[i] = 0;
ctx->fontContext->refCount = 1;
}

ctx->commands = (float*)malloc(sizeof(float)*NVG_INIT_COMMANDS_SIZE);
if (!ctx->commands) goto error;
@@ -308,25 +335,32 @@ NVGcontext* nvgCreateInternal(NVGparams* params)

nvg__setDevicePixelRatio(ctx, 1.0f);

if (ctx->params.renderCreate(ctx->params.userPtr) == 0) goto error;
if (ctx->params.renderCreate(ctx->params.userPtr, other ? other->params.userPtr : NULL) == 0) goto error;

// Init font rendering
memset(&fontParams, 0, sizeof(fontParams));
fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
fontParams.flags = FONS_ZERO_TOPLEFT;
fontParams.renderCreate = NULL;
fontParams.renderUpdate = NULL;
fontParams.renderDraw = NULL;
fontParams.renderDelete = NULL;
fontParams.userPtr = NULL;
ctx->fs = fonsCreateInternal(&fontParams);
if (ctx->fs == NULL) goto error;

// Create font texture
ctx->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, fontParams.width, fontParams.height, 0, NULL);
if (ctx->fontImages[0] == 0) goto error;
ctx->fontImageIdx = 0;
if (!other) {
memset(&fontParams, 0, sizeof(fontParams));
fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
fontParams.flags = FONS_ZERO_TOPLEFT;
fontParams.renderCreate = NULL;
fontParams.renderUpdate = NULL;
fontParams.renderDraw = NULL;
fontParams.renderDelete = NULL;
fontParams.userPtr = NULL;
ctx->fontContext->fs = fonsCreateInternal(&fontParams);
if (ctx->fontContext->fs == NULL) goto error;

// Create font texture
ctx->fontContext->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr,
NVG_TEXTURE_ALPHA,
fontParams.width,
fontParams.height,
NVG_FONT_TEXTURE_FLAGS,
NULL);
if (ctx->fontContext->fontImages[0] == 0) goto error;
ctx->fontContext->fontImageIdx = 0;
}

return ctx;

@@ -347,14 +381,18 @@ void nvgDeleteInternal(NVGcontext* ctx)
if (ctx->commands != NULL) free(ctx->commands);
if (ctx->cache != NULL) nvg__deletePathCache(ctx->cache);

if (ctx->fs)
fonsDeleteInternal(ctx->fs);
if (ctx->fontContext != NULL && --ctx->fontContext->refCount == 0) {
if (ctx->fontContext->fs)
fonsDeleteInternal(ctx->fontContext->fs);

for (i = 0; i < NVG_MAX_FONTIMAGES; i++) {
if (ctx->fontImages[i] != 0) {
nvgDeleteImage(ctx, ctx->fontImages[i]);
ctx->fontImages[i] = 0;
for (i = 0; i < NVG_MAX_FONTIMAGES; i++) {
if (ctx->fontContext->fontImages[i] != 0) {
nvgDeleteImage(ctx, ctx->fontContext->fontImages[i]);
ctx->fontContext->fontImages[i] = 0;
}
}

free(ctx->fontContext);
}

if (ctx->params.renderDelete != NULL)
@@ -391,30 +429,30 @@ void nvgCancelFrame(NVGcontext* ctx)
void nvgEndFrame(NVGcontext* ctx)
{
ctx->params.renderFlush(ctx->params.userPtr);
if (ctx->fontImageIdx != 0) {
int fontImage = ctx->fontImages[ctx->fontImageIdx];
if (ctx->fontContext->fontImageIdx != 0) {
int fontImage = ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx];
int i, j, iw, ih;
// delete images that smaller than current one
if (fontImage == 0)
return;
nvgImageSize(ctx, fontImage, &iw, &ih);
for (i = j = 0; i < ctx->fontImageIdx; i++) {
if (ctx->fontImages[i] != 0) {
for (i = j = 0; i < ctx->fontContext->fontImageIdx; i++) {
if (ctx->fontContext->fontImages[i] != 0) {
int nw, nh;
nvgImageSize(ctx, ctx->fontImages[i], &nw, &nh);
nvgImageSize(ctx, ctx->fontContext->fontImages[i], &nw, &nh);
if (nw < iw || nh < ih)
nvgDeleteImage(ctx, ctx->fontImages[i]);
nvgDeleteImage(ctx, ctx->fontContext->fontImages[i]);
else
ctx->fontImages[j++] = ctx->fontImages[i];
ctx->fontContext->fontImages[j++] = ctx->fontContext->fontImages[i];
}
}
// make current font image to first
ctx->fontImages[j++] = ctx->fontImages[0];
ctx->fontImages[0] = fontImage;
ctx->fontImageIdx = 0;
ctx->fontContext->fontImages[j++] = ctx->fontContext->fontImages[0];
ctx->fontContext->fontImages[0] = fontImage;
ctx->fontContext->fontImageIdx = 0;
// clear all images after j
for (i = j; i < NVG_MAX_FONTIMAGES; i++)
ctx->fontImages[i] = 0;
ctx->fontContext->fontImages[i] = 0;
}
}

@@ -651,7 +689,7 @@ void nvgReset(NVGcontext* ctx)
state->miterLimit = 10.0f;
state->lineCap = NVG_BUTT;
state->lineJoin = NVG_MITER;
state->alpha = 1.0f;
state->tint = nvgRGBAf(1, 1, 1, 1);
nvgTransformIdentity(state->xform);

state->scissor.extent[0] = -1.0f;
@@ -699,7 +737,33 @@ void nvgLineJoin(NVGcontext* ctx, int join)
void nvgGlobalAlpha(NVGcontext* ctx, float alpha)
{
NVGstate* state = nvg__getState(ctx);
state->alpha = alpha;
state->tint.a = alpha;
}

void nvgGlobalTint(NVGcontext* ctx, NVGcolor tint)
{
NVGstate* state = nvg__getState(ctx);
state->tint = tint;
}

NVGcolor nvgGetGlobalTint(NVGcontext* ctx)
{
NVGstate* state = nvg__getState(ctx);
return state->tint;
}

void nvgAlpha(NVGcontext* ctx, float alpha)
{
NVGstate* state = nvg__getState(ctx);
state->tint.a *= alpha;
}

void nvgTint(NVGcontext* ctx, NVGcolor tint)
{
NVGstate* state = nvg__getState(ctx);
int i;
for (i = 0; i < 4; i++)
state->tint.rgba[i] *= tint.rgba[i];
}

void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f)
@@ -788,6 +852,7 @@ void nvgFillPaint(NVGcontext* ctx, NVGpaint paint)
nvgTransformMultiply(state->fill.xform, state->xform);
}

#ifndef NVG_NO_STB
int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags)
{
int w, h, n, image;
@@ -816,10 +881,16 @@ int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int
stbi_image_free(img);
return image;
}
#endif

int nvgCreateImageRaw(NVGcontext* ctx, int w, int h, int imageFlags, NVGtexture format, const unsigned char* data)
{
return ctx->params.renderCreateTexture(ctx->params.userPtr, format, w, h, imageFlags, data);
}

int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data)
{
return ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_RGBA, w, h, imageFlags, data);
return nvgCreateImageRaw(ctx, w, h, imageFlags, NVG_TEXTURE_RGBA, data);
}

void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data)
@@ -2229,9 +2300,11 @@ void nvgFill(NVGcontext* ctx)
else
nvg__expandFill(ctx, 0.0f, NVG_MITER, 2.4f);

// Apply global alpha
fillPaint.innerColor.a *= state->alpha;
fillPaint.outerColor.a *= state->alpha;
// Apply global tint
for (i = 0; i < 4; i++) {
fillPaint.innerColor.rgba[i] *= state->tint.rgba[i];
fillPaint.outerColor.rgba[i] *= state->tint.rgba[i];
}

ctx->params.renderFill(ctx->params.userPtr, &fillPaint, state->compositeOperation, &state->scissor, ctx->fringeWidth,
ctx->cache->bounds, ctx->cache->paths, ctx->cache->npaths);
@@ -2264,9 +2337,11 @@ void nvgStroke(NVGcontext* ctx)
strokeWidth = ctx->fringeWidth;
}

// Apply global alpha
strokePaint.innerColor.a *= state->alpha;
strokePaint.outerColor.a *= state->alpha;
// Apply global tint
for (i = 0; i < 4; i++) {
strokePaint.innerColor.rgba[i] *= state->tint.rgba[i];
strokePaint.outerColor.rgba[i] *= state->tint.rgba[i];
}

nvg__flattenPaths(ctx);

@@ -2289,35 +2364,35 @@ void nvgStroke(NVGcontext* ctx)
// Add fonts
int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename)
{
return fonsAddFont(ctx->fs, name, filename, 0);
return fonsAddFont(ctx->fontContext->fs, name, filename, 0);
}

int nvgCreateFontAtIndex(NVGcontext* ctx, const char* name, const char* filename, const int fontIndex)
{
return fonsAddFont(ctx->fs, name, filename, fontIndex);
return fonsAddFont(ctx->fontContext->fs, name, filename, fontIndex);
}

int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData)
{
return fonsAddFontMem(ctx->fs, name, data, ndata, freeData, 0);
return fonsAddFontMem(ctx->fontContext->fs, name, data, ndata, freeData, 0);
}

int nvgCreateFontMemAtIndex(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData, const int fontIndex)
{
return fonsAddFontMem(ctx->fs, name, data, ndata, freeData, fontIndex);
return fonsAddFontMem(ctx->fontContext->fs, name, data, ndata, freeData, fontIndex);
}

int nvgFindFont(NVGcontext* ctx, const char* name)
{
if (name == NULL) return -1;
return fonsGetFontByName(ctx->fs, name);
return fonsGetFontByName(ctx->fontContext->fs, name);
}


int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont)
{
if(baseFont == -1 || fallbackFont == -1) return 0;
return fonsAddFallbackFont(ctx->fs, baseFont, fallbackFont);
return fonsAddFallbackFont(ctx->fontContext->fs, baseFont, fallbackFont);
}

int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont)
@@ -2327,7 +2402,7 @@ int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallba

void nvgResetFallbackFontsId(NVGcontext* ctx, int baseFont)
{
fonsResetFallbackFont(ctx->fs, baseFont);
fonsResetFallbackFont(ctx->fontContext->fs, baseFont);
}

void nvgResetFallbackFonts(NVGcontext* ctx, const char* baseFont)
@@ -2375,7 +2450,7 @@ void nvgFontFaceId(NVGcontext* ctx, int font)
void nvgFontFace(NVGcontext* ctx, const char* font)
{
NVGstate* state = nvg__getState(ctx);
state->fontId = fonsGetFontByName(ctx->fs, font);
state->fontId = fonsGetFontByName(ctx->fontContext->fs, font);
}

static float nvg__quantize(float a, float d)
@@ -2392,12 +2467,12 @@ static void nvg__flushTextTexture(NVGcontext* ctx)
{
int dirty[4];

if (fonsValidateTexture(ctx->fs, dirty)) {
int fontImage = ctx->fontImages[ctx->fontImageIdx];
if (fonsValidateTexture(ctx->fontContext->fs, dirty)) {
int fontImage = ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx];
// Update texture
if (fontImage != 0) {
int iw, ih;
const unsigned char* data = fonsGetTextureData(ctx->fs, &iw, &ih);
const unsigned char* data = fonsGetTextureData(ctx->fontContext->fs, &iw, &ih);
int x = dirty[0];
int y = dirty[1];
int w = dirty[2] - dirty[0];
@@ -2411,37 +2486,42 @@ static int nvg__allocTextAtlas(NVGcontext* ctx)
{
int iw, ih;
nvg__flushTextTexture(ctx);
if (ctx->fontImageIdx >= NVG_MAX_FONTIMAGES-1)
if (ctx->fontContext->fontImageIdx >= NVG_MAX_FONTIMAGES-1)
return 0;
// if next fontImage already have a texture
if (ctx->fontImages[ctx->fontImageIdx+1] != 0)
nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx+1], &iw, &ih);
if (ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx+1] != 0)
nvgImageSize(ctx, ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx+1], &iw, &ih);
else { // calculate the new font image size and create it.
nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx], &iw, &ih);
nvgImageSize(ctx, ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx], &iw, &ih);
if (iw > ih)
ih *= 2;
else
iw *= 2;
if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE)
iw = ih = NVG_MAX_FONTIMAGE_SIZE;
ctx->fontImages[ctx->fontImageIdx+1] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, iw, ih, 0, NULL);
ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx+1]
= ctx->params.renderCreateTexture(ctx->params.userPtr,
NVG_TEXTURE_ALPHA, iw, ih, NVG_FONT_TEXTURE_FLAGS, NULL);
}
++ctx->fontImageIdx;
fonsResetAtlas(ctx->fs, iw, ih);
++ctx->fontContext->fontImageIdx;
fonsResetAtlas(ctx->fontContext->fs, iw, ih);
return 1;
}

static void nvg__renderText(NVGcontext* ctx, NVGvertex* verts, int nverts)
{
int i;
NVGstate* state = nvg__getState(ctx);
NVGpaint paint = state->fill;

// Render triangles.
paint.image = ctx->fontImages[ctx->fontImageIdx];
paint.image = ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx];

// Apply global alpha
paint.innerColor.a *= state->alpha;
paint.outerColor.a *= state->alpha;
// Apply global tint
for (i = 0; i < 4; i++) {
paint.innerColor.rgba[i] *= state->tint.rgba[i];
paint.outerColor.rgba[i] *= state->tint.rgba[i];
}

ctx->params.renderTriangles(ctx->params.userPtr, &paint, state->compositeOperation, &state->scissor, verts, nverts, ctx->fringeWidth);

@@ -2449,6 +2529,12 @@ static void nvg__renderText(NVGcontext* ctx, NVGvertex* verts, int nverts)
ctx->textTriCount += nverts/3;
}

static int nvg__isTransformFlipped(const float *xform)
{
float det = xform[0] * xform[3] - xform[2] * xform[1];
return( det < 0);
}

float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end)
{
NVGstate* state = nvg__getState(ctx);
@@ -2459,25 +2545,26 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char*
float invscale = 1.0f / scale;
int cverts = 0;
int nverts = 0;
int isFlipped = nvg__isTransformFlipped(state->xform);

if (end == NULL)
end = string + strlen(string);

if (state->fontId == FONS_INVALID) return x;

fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);
fonsSetSize(ctx->fontContext->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fontContext->fs, state->textAlign);
fonsSetFont(ctx->fontContext->fs, state->fontId);

cverts = nvg__maxi(2, (int)(end - string)) * 6; // conservative estimate.
verts = nvg__allocTempVerts(ctx, cverts);
if (verts == NULL) return x;

fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_REQUIRED);
fonsTextIterInit(ctx->fontContext->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_REQUIRED);
prevIter = iter;
while (fonsTextIterNext(ctx->fs, &iter, &q)) {
while (fonsTextIterNext(ctx->fontContext->fs, &iter, &q)) {
float c[4*2];
if (iter.prevGlyphIndex == -1) { // can not retrieve glyph?
if (nverts != 0) {
@@ -2487,11 +2574,17 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char*
if (!nvg__allocTextAtlas(ctx))
break; // no memory :(
iter = prevIter;
fonsTextIterNext(ctx->fs, &iter, &q); // try again
fonsTextIterNext(ctx->fontContext->fs, &iter, &q); // try again
if (iter.prevGlyphIndex == -1) // still can not find glyph?
break;
}
prevIter = iter;
if(isFlipped) {
float tmp;

tmp = q.y0; q.y0 = q.y1; q.y1 = tmp;
tmp = q.t0; q.t0 = q.t1; q.t1 = tmp;
}
// Transform corners.
nvgTransformPoint(&c[0],&c[1], state->xform, q.x0*invscale, q.y0*invscale);
nvgTransformPoint(&c[2],&c[3], state->xform, q.x1*invscale, q.y0*invscale);
@@ -2499,6 +2592,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++;
@@ -2566,18 +2664,18 @@ int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string,
if (string == end)
return 0;

fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);
fonsSetSize(ctx->fontContext->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fontContext->fs, state->textAlign);
fonsSetFont(ctx->fontContext->fs, state->fontId);

fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_OPTIONAL);
fonsTextIterInit(ctx->fontContext->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_OPTIONAL);
prevIter = iter;
while (fonsTextIterNext(ctx->fs, &iter, &q)) {
while (fonsTextIterNext(ctx->fontContext->fs, &iter, &q)) {
if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph?
iter = prevIter;
fonsTextIterNext(ctx->fs, &iter, &q); // try again
fonsTextIterNext(ctx->fontContext->fs, &iter, &q); // try again
}
prevIter = iter;
positions[npos].str = iter.str;
@@ -2630,20 +2728,20 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa

if (string == end) return 0;

fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);
fonsSetSize(ctx->fontContext->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fontContext->fs, state->textAlign);
fonsSetFont(ctx->fontContext->fs, state->fontId);

breakRowWidth *= scale;

fonsTextIterInit(ctx->fs, &iter, 0, 0, string, end, FONS_GLYPH_BITMAP_OPTIONAL);
fonsTextIterInit(ctx->fontContext->fs, &iter, 0, 0, string, end, FONS_GLYPH_BITMAP_OPTIONAL);
prevIter = iter;
while (fonsTextIterNext(ctx->fs, &iter, &q)) {
while (fonsTextIterNext(ctx->fontContext->fs, &iter, &q)) {
if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph?
iter = prevIter;
fonsTextIterNext(ctx->fs, &iter, &q); // try again
fonsTextIterNext(ctx->fontContext->fs, &iter, &q); // try again
}
prevIter = iter;
switch (iter.codepoint) {
@@ -2699,7 +2797,7 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa
} else {
if (rowStart == NULL) {
// Skip white space until the beginning of the line
if (type == NVG_CHAR || type == NVG_CJK_CHAR) {
if (type == NVG_CHAR || type == NVG_CJK_CHAR || type == NVG_SKIPPED_CHAR) {
// The current char is the row so far
rowStartX = iter.x;
rowStart = iter.str;
@@ -2719,7 +2817,7 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa
float nextWidth = iter.nextx - rowStartX;

// track last non-white space character
if (type == NVG_CHAR || type == NVG_CJK_CHAR) {
if (type == NVG_CHAR || type == NVG_CJK_CHAR || type == NVG_SKIPPED_CHAR) {
rowEnd = iter.next;
rowWidth = iter.nextx - rowStartX;
rowMaxX = q.x1 - rowStartX;
@@ -2814,16 +2912,16 @@ float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const

if (state->fontId == FONS_INVALID) return 0;

fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);
fonsSetSize(ctx->fontContext->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fontContext->fs, state->textAlign);
fonsSetFont(ctx->fontContext->fs, state->fontId);

width = fonsTextBounds(ctx->fs, x*scale, y*scale, string, end, bounds);
width = fonsTextBounds(ctx->fontContext->fs, x*scale, y*scale, string, end, bounds);
if (bounds != NULL) {
// Use line bounds for height.
fonsLineBounds(ctx->fs, y*scale, &bounds[1], &bounds[3]);
fonsLineBounds(ctx->fontContext->fs, y*scale, &bounds[1], &bounds[3]);
bounds[0] *= invscale;
bounds[1] *= invscale;
bounds[2] *= invscale;
@@ -2858,12 +2956,12 @@ void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, co
minx = maxx = x;
miny = maxy = y;

fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);
fonsLineBounds(ctx->fs, 0, &rminy, &rmaxy);
fonsSetSize(ctx->fontContext->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fontContext->fs, state->textAlign);
fonsSetFont(ctx->fontContext->fs, state->fontId);
fonsLineBounds(ctx->fontContext->fs, 0, &rminy, &rmaxy);
rminy *= invscale;
rmaxy *= invscale;

@@ -2909,13 +3007,13 @@ void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* l

if (state->fontId == FONS_INVALID) return;

fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);
fonsSetSize(ctx->fontContext->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale);
fonsSetAlign(ctx->fontContext->fs, state->textAlign);
fonsSetFont(ctx->fontContext->fs, state->fontId);

fonsVertMetrics(ctx->fs, ascender, descender, lineh);
fonsVertMetrics(ctx->fontContext->fs, ascender, descender, lineh);
if (ascender != NULL)
*ascender *= invscale;
if (descender != NULL)


+ 19
- 8
dgl/src/nanovg/nanovg.h View File

@@ -144,6 +144,14 @@ enum NVGimageFlags {
NVG_IMAGE_NEAREST = 1<<5, // Image interpolation is Nearest instead Linear
};

enum NVGtexture {
NVG_TEXTURE_ALPHA,
NVG_TEXTURE_BGR,
NVG_TEXTURE_BGRA,
NVG_TEXTURE_RGB,
NVG_TEXTURE_RGBA,
};

// Begin drawing a new frame
// Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame()
// nvgBeginFrame() defines the size of the window to render to in relation currently
@@ -271,6 +279,10 @@ void nvgLineJoin(NVGcontext* ctx, int join);
// Sets the transparency applied to all rendered shapes.
// Already transparent paths will get proportionally more transparent as well.
void nvgGlobalAlpha(NVGcontext* ctx, float alpha);
void nvgGlobalTint(NVGcontext* ctx, NVGcolor tint);
NVGcolor nvgGetGlobalTint(NVGcontext* ctx);
void nvgAlpha(NVGcontext* ctx, float alpha);
void nvgTint(NVGcontext* ctx, NVGcolor tint);

//
// Transforms
@@ -375,6 +387,10 @@ int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags);
// Returns handle to the image.
int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata);

// Creates image from specified image data and texture format.
// Returns handle to the image.
int nvgCreateImageRaw(NVGcontext* ctx, int w, int h, int imageFlags, enum NVGtexture format, const unsigned char* data);

// Creates image from specified image data.
// Returns handle to the image.
int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data);
@@ -414,7 +430,7 @@ NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h,
NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr,
NVGcolor icol, NVGcolor ocol);

// Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern,
// Creates and returns an image pattern. Parameters (ox,oy) specify the left-top location of the image pattern,
// (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render.
// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey,
@@ -627,11 +643,6 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa
//
// Internal Render API
//
enum NVGtexture {
NVG_TEXTURE_ALPHA = 0x01,
NVG_TEXTURE_RGBA = 0x02,
};

struct NVGscissor {
float xform[6];
float extent[2];
@@ -660,7 +671,7 @@ typedef struct NVGpath NVGpath;
struct NVGparams {
void* userPtr;
int edgeAntiAlias;
int (*renderCreate)(void* uptr);
int (*renderCreate)(void* uptr, void* otherUptr);
int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data);
int (*renderDeleteTexture)(void* uptr, int image);
int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data);
@@ -676,7 +687,7 @@ struct NVGparams {
typedef struct NVGparams NVGparams;

// Constructor and destructor, called by the render back-end.
NVGcontext* nvgCreateInternal(NVGparams* params);
NVGcontext* nvgCreateInternal(NVGparams* params, NVGcontext* other);
void nvgDeleteInternal(NVGcontext* ctx);

NVGparams* nvgInternalParams(NVGcontext* ctx);


+ 200
- 41
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
@@ -40,9 +62,7 @@ enum NVGcreateFlags {
#elif defined NANOVG_GL3_IMPLEMENTATION
# define NANOVG_GL3 1
# define NANOVG_GL_IMPLEMENTATION 1
# ifndef __APPLE__
# define NANOVG_GL_USE_UNIFORMBUFFER 1
# endif
# define NANOVG_GL_USE_UNIFORMBUFFER 1
#elif defined NANOVG_GLES2_IMPLEMENTATION
# define NANOVG_GLES2 1
# define NANOVG_GL_IMPLEMENTATION 1
@@ -59,6 +79,7 @@ enum NVGcreateFlags {
#if defined NANOVG_GL2

NVGcontext* nvgCreateGL2(int flags);
NVGcontext* nvgCreateSharedGL2(NVGcontext* other, int flags);
void nvgDeleteGL2(NVGcontext* ctx);

int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags);
@@ -69,6 +90,7 @@ GLuint nvglImageHandleGL2(NVGcontext* ctx, int image);
#if defined NANOVG_GL3

NVGcontext* nvgCreateGL3(int flags);
NVGcontext* nvgCreateSharedGL3(NVGcontext* other, int flags);
void nvgDeleteGL3(NVGcontext* ctx);

int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags);
@@ -79,6 +101,7 @@ GLuint nvglImageHandleGL3(NVGcontext* ctx, int image);
#if defined NANOVG_GLES2

NVGcontext* nvgCreateGLES2(int flags);
NVGcontext* nvgCreateSharedGLES2(NVGcontext* other, int flags);
void nvgDeleteGLES2(NVGcontext* ctx);

int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags);
@@ -89,6 +112,7 @@ GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image);
#if defined NANOVG_GLES3

NVGcontext* nvgCreateGLES3(int flags);
NVGcontext* nvgCreateSharedGLES3(NVGcontext* other, int flags);
void nvgDeleteGLES3(NVGcontext* ctx);

int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags);
@@ -149,6 +173,9 @@ struct GLNVGtexture {
int width, height;
int type;
int flags;
#if defined NANOVG_GLES2
unsigned char* data;
#endif
};
typedef struct GLNVGtexture GLNVGtexture;

@@ -230,13 +257,19 @@ struct GLNVGfragUniforms {
};
typedef struct GLNVGfragUniforms GLNVGfragUniforms;

struct GLNVGcontext {
GLNVGshader shader;
struct GLNVGtextureContext { // Textures; shared between shared NanoVG contexts.
int refCount;
GLNVGtexture* textures;
float view[2];
int ntextures;
int ctextures;
int textureId;
};
typedef struct GLNVGtextureContext GLNVGtextureContext;

struct GLNVGcontext {
GLNVGshader shader;
GLNVGtextureContext* textureContext;
float view[2];
GLuint vertBuf;
#if defined NANOVG_GL3
GLuint vertArr;
@@ -352,26 +385,26 @@ static GLNVGtexture* glnvg__allocTexture(GLNVGcontext* gl)
GLNVGtexture* tex = NULL;
int i;

for (i = 0; i < gl->ntextures; i++) {
if (gl->textures[i].id == 0) {
tex = &gl->textures[i];
for (i = 0; i < gl->textureContext->ntextures; i++) {
if (gl->textureContext->textures[i].id == 0) {
tex = &gl->textureContext->textures[i];
break;
}
}
if (tex == NULL) {
if (gl->ntextures+1 > gl->ctextures) {
if (gl->textureContext->ntextures+1 > gl->textureContext->ctextures) {
GLNVGtexture* textures;
int ctextures = glnvg__maxi(gl->ntextures+1, 4) + gl->ctextures/2; // 1.5x Overallocate
textures = (GLNVGtexture*)realloc(gl->textures, sizeof(GLNVGtexture)*ctextures);
int ctextures = glnvg__maxi(gl->textureContext->ntextures+1, 4) + gl->textureContext->ctextures/2; // 1.5x Overallocate
textures = (GLNVGtexture*)realloc(gl->textureContext->textures, sizeof(GLNVGtexture)*ctextures);
if (textures == NULL) return NULL;
gl->textures = textures;
gl->ctextures = ctextures;
gl->textureContext->textures = textures;
gl->textureContext->ctextures = ctextures;
}
tex = &gl->textures[gl->ntextures++];
tex = &gl->textureContext->textures[gl->textureContext->ntextures++];
}

memset(tex, 0, sizeof(*tex));
tex->id = ++gl->textureId;
tex->id = ++gl->textureContext->textureId;

return tex;
}
@@ -379,20 +412,25 @@ static GLNVGtexture* glnvg__allocTexture(GLNVGcontext* gl)
static GLNVGtexture* glnvg__findTexture(GLNVGcontext* gl, int id)
{
int i;
for (i = 0; i < gl->ntextures; i++)
if (gl->textures[i].id == id)
return &gl->textures[i];
for (i = 0; i < gl->textureContext->ntextures; i++)
if (gl->textureContext->textures[i].id == id)
return &gl->textureContext->textures[i];
return NULL;
}

static int glnvg__deleteTexture(GLNVGcontext* gl, int id)
{
int i;
for (i = 0; i < gl->ntextures; i++) {
if (gl->textures[i].id == id) {
if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0)
glDeleteTextures(1, &gl->textures[i].tex);
memset(&gl->textures[i], 0, sizeof(gl->textures[i]));
for (i = 0; i < gl->textureContext->ntextures; i++) {
if (gl->textureContext->textures[i].id == id) {
if (gl->textureContext->textures[i].tex != 0 && (gl->textureContext->textures[i].flags & NVG_IMAGE_NODELETE) == 0)
{
glDeleteTextures(1, &gl->textureContext->textures[i].tex);
#if defined NANOVG_GLES2
free(gl->textureContext->textures[i].data);
#endif
}
memset(&gl->textureContext->textures[i], 0, sizeof(gl->textureContext->textures[i]));
return 1;
}
}
@@ -506,9 +544,20 @@ static void glnvg__getUniforms(GLNVGshader* shader)

static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data);

static int glnvg__renderCreate(void* uptr)
static int glnvg__renderCreate(void* uptr, void* otherUptr) // Share the textures of GLNVGcontext 'otherUptr' if it's non-NULL.
{
GLNVGcontext* gl = (GLNVGcontext*)uptr;

if (otherUptr) {
GLNVGcontext* other = (GLNVGcontext*)otherUptr;
gl->textureContext = other->textureContext;
gl->textureContext->refCount++;
} else {
gl->textureContext = (GLNVGtextureContext*)malloc(sizeof(GLNVGtextureContext));
memset(gl->textureContext, 0, sizeof(GLNVGtextureContext));
gl->textureContext->refCount = 1;
}

int align = 4;

// TODO: mediump float may not be enough for GLES2 in iOS.
@@ -734,7 +783,7 @@ static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int im
}
// No mips.
if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) {
printf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h);
printf("Mip-maps is not supported for non power-of-two textures (%d x %d)\n", w, h);
imageFlags &= ~NVG_IMAGE_GENERATE_MIPMAPS;
}
}
@@ -761,9 +810,48 @@ static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int im
}
#endif

if (type == NVG_TEXTURE_RGBA)
switch (type)
{
case NVG_TEXTURE_BGR:
#if NANOVG_GLES2
// GLES2 cannot handle GL_BGR, do local conversion to GL_RGB
tex->data = (uint8_t*)malloc(sizeof(uint8_t) * 3 * w * h);
for (uint32_t i=0; i<w*h; ++i)
{
tex->data[i*3+0] = data[i*3+2];
tex->data[i*3+1] = data[i*3+1];
tex->data[i*3+2] = data[i*3+0];
}
data = tex->data;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
#else
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_BGR, GL_UNSIGNED_BYTE, data);
#endif
break;
case NVG_TEXTURE_BGRA:
#if NANOVG_GLES2
// GLES2 cannot handle GL_BGRA, do local conversion to GL_RGBA
tex->data = (uint8_t*)malloc(sizeof(uint8_t) * 4 * w * h);
for (uint32_t i=0; i<w*h; ++i)
{
tex->data[i*3+0] = data[i*3+3];
tex->data[i*3+1] = data[i*3+2];
tex->data[i*3+2] = data[i*3+1];
tex->data[i*3+3] = data[i*3+0];
}
data = tex->data;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, data);
#else
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, data);
#endif
break;
case NVG_TEXTURE_RGB:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
break;
case NVG_TEXTURE_RGBA:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
else
break;
default:
#if defined(NANOVG_GLES2) || defined (NANOVG_GL2)
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
#elif defined(NANOVG_GLES3)
@@ -771,6 +859,8 @@ static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int im
#else
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data);
#endif
break;
}

if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) {
if (imageFlags & NVG_IMAGE_NEAREST) {
@@ -845,22 +935,50 @@ static int glnvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w
glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
#else
// No support for all of skip, need to update a whole row at a time.
if (tex->type == NVG_TEXTURE_RGBA)
switch (tex->type)
{
case NVG_TEXTURE_BGR:
data += y*tex->width*3;
break;
case NVG_TEXTURE_BGRA:
data += y*tex->width*4;
else
break;
case NVG_TEXTURE_RGB:
data += y*tex->width*3;
break;
case NVG_TEXTURE_RGBA:
data += y*tex->width*4;
break;
default:
data += y*tex->width;
break;
}
x = 0;
w = tex->width;
#endif

if (tex->type == NVG_TEXTURE_RGBA)
switch (tex->type)
{
case NVG_TEXTURE_BGR:
glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_BGR, GL_UNSIGNED_BYTE, data);
break;
case NVG_TEXTURE_BGRA:
glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_BGRA, GL_UNSIGNED_BYTE, data);
break;
case NVG_TEXTURE_RGB:
glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGB, GL_UNSIGNED_BYTE, data);
break;
case NVG_TEXTURE_RGBA:
glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGBA, GL_UNSIGNED_BYTE, data);
else
break;
default:
#if defined(NANOVG_GLES2) || defined(NANOVG_GL2)
glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
#else
glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RED, GL_UNSIGNED_BYTE, data);
#endif
break;
}

glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
#ifndef NANOVG_GLES2
@@ -956,15 +1074,31 @@ static int glnvg__convertPaint(GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGpai
frag->type = NSVG_SHADER_FILLIMG;

#if NANOVG_GL_USE_UNIFORMBUFFER
if (tex->type == NVG_TEXTURE_RGBA)
switch (tex->type)
{
case NVG_TEXTURE_BGR:
case NVG_TEXTURE_BGRA:
case NVG_TEXTURE_RGB:
case NVG_TEXTURE_RGBA:
frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1;
else
break;
default:
frag->texType = 2;
break;
}
#else
if (tex->type == NVG_TEXTURE_RGBA)
switch (tex->type)
{
case NVG_TEXTURE_BGR:
case NVG_TEXTURE_BGRA:
case NVG_TEXTURE_RGB:
case NVG_TEXTURE_RGBA:
frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0.0f : 1.0f;
else
break;
default:
frag->texType = 2.0f;
break;
}
#endif
// printf("frag->texType = %d\n", frag->texType);
} else {
@@ -1547,11 +1681,14 @@ static void glnvg__renderDelete(void* uptr)
if (gl->vertBuf != 0)
glDeleteBuffers(1, &gl->vertBuf);

for (i = 0; i < gl->ntextures; i++) {
if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0)
glDeleteTextures(1, &gl->textures[i].tex);
if (gl->textureContext != NULL && --gl->textureContext->refCount == 0) {
for (i = 0; i < gl->textureContext->ntextures; i++) {
if (gl->textureContext->textures[i].tex != 0 && (gl->textureContext->textures[i].flags & NVG_IMAGE_NODELETE) == 0)
glDeleteTextures(1, &gl->textureContext->textures[i].tex);
}
free(gl->textureContext->textures);
free(gl->textureContext);
}
free(gl->textures);

free(gl->paths);
free(gl->verts);
@@ -1571,6 +1708,28 @@ NVGcontext* nvgCreateGLES2(int flags)
#elif defined NANOVG_GLES3
NVGcontext* nvgCreateGLES3(int flags)
#endif
{
#if defined NANOVG_GL2
return nvgCreateSharedGL2(NULL, flags);
#elif defined NANOVG_GL3
return nvgCreateSharedGL3(NULL, flags);
#elif defined NANOVG_GLES2
return nvgCreateSharedGLES2(NULL, flags);
#elif defined NANOVG_GLES3
return nvgCreateSharedGLES3(NULL, flags);
#endif
}

// Share the fonts and textures of 'other' if it's non-NULL.
#if defined NANOVG_GL2
NVGcontext* nvgCreateSharedGL2(NVGcontext* other, int flags)
#elif defined NANOVG_GL3
NVGcontext* nvgCreateSharedGL3(NVGcontext* other, int flags)
#elif defined NANOVG_GLES2
NVGcontext* nvgCreateSharedGLES2(NVGcontext* other, int flags)
#elif defined NANOVG_GLES3
NVGcontext* nvgCreateSharedGLES3(NVGcontext* other, int flags)
#endif
{
NVGparams params;
NVGcontext* ctx = NULL;
@@ -1596,7 +1755,7 @@ NVGcontext* nvgCreateGLES3(int flags)

gl->flags = flags;

ctx = nvgCreateInternal(&params);
ctx = nvgCreateInternal(&params, other);
if (ctx == NULL) goto error;

return ctx;


+ 725
- 1767
dgl/src/nanovg/stb_image.h
File diff suppressed because it is too large
View File


+ 1
- 1
dgl/src/pugl-upstream

@@ -1 +1 @@
Subproject commit 0fdc19059de5214973ae6ec0d775470f94ceb1c9
Subproject commit 09afe84fcaa67adba7a168b8490046dab6ecf67d

+ 245
- 380
dgl/src/pugl.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
@@ -16,20 +16,32 @@

#include "pugl.hpp"

// --------------------------------------------------------------------------------------------------------------------
// include base headers

#ifdef DGL_CAIRO
# include <cairo.h>
#endif
#ifdef DGL_OPENGL
# include "../OpenGL-include.hpp"
#endif
#ifdef DGL_VULKAN
# include <vulkan/vulkan_core.h>
#endif

/* we will include all header files used in pugl in their C++ friendly form, then pugl stuff in custom namespace */
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <ctime>

#if defined(DISTRHO_OS_HAIKU)
#elif defined(DISTRHO_OS_MAC)
#if defined(DISTRHO_OS_MAC)
# import <Cocoa/Cocoa.h>
# include <dlfcn.h>
# include <mach/mach_time.h>
# ifdef DGL_CAIRO
# include <cairo.h>
# include <cairo-quartz.h>
# endif
# ifdef DGL_OPENGL
@@ -37,15 +49,20 @@
# endif
# ifdef DGL_VULKAN
# import <QuartzCore/CAMetalLayer.h>
# include <vulkan/vulkan_core.h>
# include <vulkan/vulkan_macos.h>
# endif
#elif defined(DISTRHO_OS_WASM)
# include <emscripten/emscripten.h>
# include <emscripten/html5.h>
# ifdef DGL_OPENGL
# include <EGL/egl.h>
# endif
#elif defined(DISTRHO_OS_WINDOWS)
# include <wctype.h>
# include <winsock2.h>
# include <windows.h>
# include <windowsx.h>
# ifdef DGL_CAIRO
# include <cairo.h>
# include <cairo-win32.h>
# endif
# ifdef DGL_OPENGL
@@ -55,10 +72,12 @@
# include <vulkan/vulkan.h>
# include <vulkan/vulkan_win32.h>
# endif
#else
#elif defined(HAVE_X11)
# include <dlfcn.h>
# include <limits.h>
# include <unistd.h>
# include <sys/select.h>
# include <sys/time.h>
// # include <sys/time.h>
# include <X11/X.h>
# include <X11/Xatom.h>
# include <X11/Xlib.h>
@@ -67,7 +86,7 @@
# include <X11/keysym.h>
# ifdef HAVE_XCURSOR
# include <X11/Xcursor/Xcursor.h>
# include <X11/cursorfont.h>
// # include <X11/cursorfont.h>
# endif
# ifdef HAVE_XRANDR
# include <X11/extensions/Xrandr.h>
@@ -77,23 +96,24 @@
# include <X11/extensions/syncconst.h>
# endif
# ifdef DGL_CAIRO
# include <cairo.h>
# include <cairo-xlib.h>
# endif
# ifdef DGL_OPENGL
# include <GL/gl.h>
# include <GL/glx.h>
# endif
# ifdef DGL_VULKAN
# include <vulkan/vulkan_core.h>
# include <vulkan/vulkan_xlib.h>
# endif
#endif

#ifdef HAVE_X11
# define DBLCLKTME 400
# include "sofd/libsofd.h"
# include "sofd/libsofd.c"
#ifndef DGL_FILE_BROWSER_DISABLED
# 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

#ifndef DISTRHO_OS_MAC
@@ -102,17 +122,17 @@ START_NAMESPACE_DGL

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

#if defined(DISTRHO_OS_HAIKU)
#elif defined(DISTRHO_OS_MAC)
#if 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 PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglStubView)
# define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWrapperView)
# define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWindow)
# endif
# ifndef __MAC_10_9
# define NSModalResponseOK NSOKButton
# 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)
# endif
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -128,6 +148,12 @@ START_NAMESPACE_DGL
# import "pugl-upstream/src/mac_vulkan.m"
# endif
# pragma clang diagnostic pop
#elif defined(DISTRHO_OS_WASM)
# include "pugl-upstream/src/wasm.c"
# include "pugl-upstream/src/wasm_stub.c"
# ifdef DGL_OPENGL
# include "pugl-upstream/src/wasm_gl.c"
# endif
#elif defined(DISTRHO_OS_WINDOWS)
# include "pugl-upstream/src/win.c"
# include "pugl-upstream/src/win_stub.c"
@@ -140,7 +166,7 @@ START_NAMESPACE_DGL
# ifdef DGL_VULKAN
# include "pugl-upstream/src/win_vulkan.c"
# endif
#else
#elif defined(HAVE_X11)
# include "pugl-upstream/src/x11.c"
# include "pugl-upstream/src/x11_stub.c"
# ifdef DGL_CAIRO
@@ -154,119 +180,41 @@ START_NAMESPACE_DGL
# endif
#endif

#include "pugl-upstream/src/implementation.c"
#include "pugl-upstream/src/common.c"
#include "pugl-upstream/src/internal.c"

// --------------------------------------------------------------------------------------------------------------------
// expose backend enter
// DGL specific, expose backend enter

bool puglBackendEnter(PuglView* const view)
{
return view->backend->enter(view, NULL) == PUGL_SUCCESS;
return view->backend->enter(view, nullptr) == PUGL_SUCCESS;
}

// --------------------------------------------------------------------------------------------------------------------
// expose backend leave
// DGL specific, expose backend leave

void puglBackendLeave(PuglView* const view)
bool puglBackendLeave(PuglView* const view)
{
view->backend->leave(view, NULL);
return view->backend->leave(view, nullptr) == PUGL_SUCCESS;
}

// --------------------------------------------------------------------------------------------------------------------
// clear minimum size to 0

void puglClearMinSize(PuglView* const view)
{
view->minWidth = 0;
view->minHeight = 0;
}
// DGL specific, assigns backend that matches current DGL build

// --------------------------------------------------------------------------------------------------------------------
// missing in pugl, directly returns transient parent

PuglNativeView puglGetTransientParent(const PuglView* const view)
{
return view->transientParent;
}

// --------------------------------------------------------------------------------------------------------------------
// missing in pugl, directly returns title char* pointer

const char* puglGetWindowTitle(const PuglView* const view)
{
return view->title;
}

// --------------------------------------------------------------------------------------------------------------------
// get global scale factor

double puglGetDesktopScaleFactor(const PuglView* const view)
void puglSetMatchingBackendForCurrentBuild(PuglView* const view)
{
#if defined(DISTRHO_OS_MAC)
if (NSWindow* const window = view->impl->window ? view->impl->window
: [view->impl->wrapperView window])
return [window screen].backingScaleFactor;
return [NSScreen mainScreen].backingScaleFactor;
#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
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

DWORD dpiAware = 0;
if (GetProcessDpiAwareness && GetScaleFactorForMonitor
&& GetProcessDpiAwareness(NULL, &dpiAware) == 0 && dpiAware != 0)
{
const HMONITOR hMon = MonitorFromWindow(view->impl->hwnd, MONITOR_DEFAULTTOPRIMARY);

DWORD scaleFactor = 0;
if (GetScaleFactorForMonitor(hMon, &scaleFactor) == 0 && scaleFactor != 0)
{
FreeLibrary(Shcore);
return static_cast<double>(scaleFactor) / 100.0;
}
}

FreeLibrary(Shcore);
}
#elif defined(HAVE_X11)
XrmInitialize();

if (char* const rms = XResourceManagerString(view->world->impl->display))
{
if (const XrmDatabase sdb = XrmGetStringDatabase(rms))
{
char* type = nullptr;
XrmValue ret;

if (XrmGetResource(sdb, "Xft.dpi", "String", &type, &ret)
&& ret.addr != nullptr
&& type != nullptr
&& std::strncmp("String", type, 6) == 0)
{
if (const double dpi = std::atof(ret.addr))
return dpi / 96;
}
}
}
#else
// unused
(void)view;
#ifdef DGL_CAIRO
puglSetBackend(view, puglCairoBackend());
#endif

return 1.0;
#ifdef DGL_OPENGL
puglSetBackend(view, puglGlBackend());
#endif
#ifdef DGL_VULKAN
puglSetBackend(view, puglVulkanBackend());
#endif
if (view->backend == nullptr)
puglSetBackend(view, puglStubBackend());
}

// --------------------------------------------------------------------------------------------------------------------
@@ -274,141 +222,173 @@ double puglGetDesktopScaleFactor(const PuglView* const view)

void puglRaiseWindow(PuglView* const view)
{
#if defined(DISTRHO_OS_HAIKU)
// nothing here yet
#elif defined(DISTRHO_OS_MAC)
#if defined(DISTRHO_OS_MAC)
if (NSWindow* const window = view->impl->window ? view->impl->window
: [view->impl->wrapperView window])
[window orderFrontRegardless];
#elif defined(DISTRHO_OS_WASM)
// nothing
#elif defined(DISTRHO_OS_WINDOWS)
SetForegroundWindow(view->impl->hwnd);
SetActiveWindow(view->impl->hwnd);
#else
XRaiseWindow(view->impl->display, view->impl->win);
#elif defined(HAVE_X11)
XRaiseWindow(view->world->impl->display, view->impl->win);
#endif
}

// --------------------------------------------------------------------------------------------------------------------
// set backend that matches current build
// get scale factor from parent window if possible, fallback to puglGetScaleFactor

void puglSetMatchingBackendForCurrentBuild(PuglView* const view)
double puglGetScaleFactorFromParent(const PuglView* const view)
{
#ifdef DGL_CAIRO
puglSetBackend(view, puglCairoBackend());
#endif
#ifdef DGL_OPENGL
puglSetBackend(view, puglGlBackend());
#endif
#ifdef DGL_VULKAN
puglSetBackend(view, puglVulkanBackend());
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
if (view->backend == nullptr)
puglSetBackend(view, puglStubBackend());
}

// --------------------------------------------------------------------------------------------------------------------
// Combine puglSetMinSize and puglSetAspectRatio
// 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->minWidth = (int)width;
view->minHeight = (int)height;

if (aspect) {
view->minAspectX = (int)width;
view->minAspectY = (int)height;
view->maxAspectX = (int)width;
view->maxAspectY = (int)height;
view->sizeHints[PUGL_MIN_SIZE].width = width;
view->sizeHints[PUGL_MIN_SIZE].height = height;

if (aspect)
{
view->sizeHints[PUGL_FIXED_ASPECT].width = width;
view->sizeHints[PUGL_FIXED_ASPECT].height = height;
}

#if defined(DISTRHO_OS_HAIKU)
// nothing?
#elif defined(DISTRHO_OS_MAC)
/*
#if defined(DISTRHO_OS_MAC)
if (view->impl->window)
{
[view->impl->window setContentMinSize:sizePoints(view, view->minWidth, view->minHeight)];
PuglStatus status;

if ((status = updateSizeHint(view, PUGL_MIN_SIZE)) != PUGL_SUCCESS)
return status;

if (aspect)
[view->impl->window setContentAspectRatio:sizePoints(view, view->minAspectX, view->minAspectY)];
if (aspect && (status = updateSizeHint(view, PUGL_FIXED_ASPECT)) != PUGL_SUCCESS)
return status;
}
*/
puglSetMinSize(view, width, height);
puglSetAspectRatio(view, width, height, width, height);
#elif defined(DISTRHO_OS_WASM)
// nothing
#elif defined(DISTRHO_OS_WINDOWS)
// nothing
#else
#elif defined(HAVE_X11)
if (const PuglStatus status = updateSizeHints(view))
return status;

XFlush(view->impl->display);
XFlush(view->world->impl->display);
#endif

return PUGL_SUCCESS;
}

// --------------------------------------------------------------------------------------------------------------------
// set window size with default size and without changing frame x/y position
// set view as resizable (or not) during runtime

PuglStatus puglSetWindowSize(PuglView* const view, const uint width, const uint height)
void puglSetResizable(PuglView* const view, const bool resizable)
{
view->defaultWidth = width;
view->defaultHeight = height;
puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE);

#if defined(DISTRHO_OS_HAIKU) || defined(DISTRHO_OS_MAC)
// replace the 2 views setFrame with setFrameSize
const PuglRect frame = { view->frame.x, view->frame.y, (double)width, (double)height };
PuglInternals* const impl = view->impl;
#if defined(DISTRHO_OS_MAC)
if (PuglWindow* const window = view->impl->window)
{
const uint style = (NSClosableWindowMask | NSTitledWindowMask | NSMiniaturizableWindowMask)
| (resizable ? NSResizableWindowMask : 0x0);
[window setStyleMask:style];
}
// FIXME use [view setAutoresizingMask:NSViewNotSizable] ?
#elif defined(DISTRHO_OS_WASM)
// nothing
#elif defined(DISTRHO_OS_WINDOWS)
if (const HWND hwnd = view->impl->hwnd)
{
const uint winFlags = resizable ? GetWindowLong(hwnd, GWL_STYLE) | (WS_SIZEBOX | WS_MAXIMIZEBOX)
: GetWindowLong(hwnd, GWL_STYLE) & ~(WS_SIZEBOX | WS_MAXIMIZEBOX);
SetWindowLong(hwnd, GWL_STYLE, winFlags);
}
#elif defined(HAVE_X11)
updateSizeHints(view);
#endif
}

// Update view frame to exactly the requested frame in Pugl coordinates
view->frame = frame;
// --------------------------------------------------------------------------------------------------------------------
// set window size while also changing default

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 (impl->window)

if (PuglWindow* const window = view->impl->window)
{
// Resize window to fit new content rect
const NSRect screenPt = rectToScreen(viewScreen(view), framePt);
const NSRect winFrame = [impl->window frameRectForContentRect:screenPt];

[impl->window setFrame:winFrame display:NO];
const NSRect winFrame = [window frameRectForContentRect:screenPt];
[window setFrame:winFrame display:NO];
}

// Resize views
const NSSize sizePx = NSMakeSize(frame.width, frame.height);
const NSSize sizePt = [impl->drawView convertSizeFromBacking:sizePx];

[impl->wrapperView setFrameSize:(impl->window ? sizePt : framePt.size)];
[impl->wrapperView setFrameSize:sizePt];
[impl->drawView setFrameSize:sizePt];
#elif defined(DISTRHO_OS_WASM)
d_stdout("className is %s", view->world->className);
emscripten_set_canvas_element_size(view->world->className, width, height);
#elif defined(DISTRHO_OS_WINDOWS)
// matches upstream pugl, except we add SWP_NOMOVE flag
if (view->impl->hwnd)
// matches upstream pugl, except we re-enter context after resize
if (const HWND hwnd = view->impl->hwnd)
{
const PuglRect frame = view->frame;

RECT rect = { (long)frame.x,
(long)frame.y,
(long)frame.x + (long)frame.width,
(long)frame.y + (long)frame.height };

AdjustWindowRectEx(&rect, puglWinGetWindowFlags(view), FALSE, puglWinGetWindowExFlags(view));

if (! SetWindowPos(view->impl->hwnd,
HWND_TOP,
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER))
const RECT rect = adjustedWindowRect(view, view->frame.x, view->frame.y,
static_cast<long>(width), static_cast<long>(height));

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;

// make sure to return context back to ourselves
puglBackendEnter(view);
}
#else
// matches upstream pugl, except we use XResizeWindow instead of XMoveResizeWindow
if (view->impl->win)
#elif defined(HAVE_X11)
// matches upstream pugl, all in one
if (const Window window = view->impl->win)
{
Display* const display = view->impl->display;
Display* const display = view->world->impl->display;

if (! XResizeWindow(display, view->impl->win, width, height))
if (! XResizeWindow(display, window, width, height))
return PUGL_UNKNOWN_ERROR;

if (const PuglStatus status = updateSizeHints(view))
@@ -418,8 +398,6 @@ PuglStatus puglSetWindowSize(PuglView* const view, const uint width, const uint
}
#endif

view->frame.width = width;
view->frame.height = height;
return PUGL_SUCCESS;
}

@@ -430,7 +408,9 @@ void puglOnDisplayPrepare(PuglView*)
{
#ifdef DGL_OPENGL
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
# ifndef DGL_USE_GLES
glLoadIdentity();
# endif
#endif
}

@@ -442,24 +422,26 @@ void puglFallbackOnResize(PuglView* const view)
#ifdef DGL_OPENGL
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
# ifndef DGL_USE_GLES
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));
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
# else
glViewport(0, 0, static_cast<GLsizei>(view->frame.width), static_cast<GLsizei>(view->frame.height));
# endif
#else
return;
// unused
(void)view;
#endif
}

#ifdef DISTRHO_OS_MAC
// --------------------------------------------------------------------------------------------------------------------
// macOS specific, allow standalone window to gain focus

void puglMacOSActivateApp()
{
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
}
#if defined(DISTRHO_OS_MAC)

// --------------------------------------------------------------------------------------------------------------------
// macOS specific, add another view's window as child
@@ -517,8 +499,8 @@ void puglMacOSShowCentered(PuglView* const view)
const NSRect ourFrame = [view->impl->window frame];
const NSRect transientFrame = [transientWindow frame];

const int x = transientFrame.origin.x + transientFrame.size.width / 2 - ourFrame.size.width / 2;
const int y = transientFrame.origin.y + transientFrame.size.height / 2 + ourFrame.size.height / 2;
const int x = transientFrame.origin.x + (transientFrame.size.width - ourFrame.size.width) / 2;
const int y = transientFrame.origin.y + (transientFrame.size.height - ourFrame.size.height) / 2;

[view->impl->window setFrameTopLeftPoint:NSMakePoint(x, y)];
}
@@ -529,53 +511,9 @@ void puglMacOSShowCentered(PuglView* const view)
}

// --------------------------------------------------------------------------------------------------------------------
// macOS specific, setup file browser dialog

bool puglMacOSFilePanelOpen(PuglView* const view,
const char* const startDir, const char* const title, const uint flags,
openPanelCallback callback)
{
PuglInternals* impl = view->impl;

NSOpenPanel* const panel = [NSOpenPanel openPanel];

[panel setAllowsMultipleSelection:NO];
[panel setCanChooseFiles:YES];
[panel setCanChooseDirectories:NO];
[panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]];

// TODO file filter using allowedContentTypes: [UTType]

if (flags & 0x001)
[panel setAllowsOtherFileTypes:YES];
if (flags & 0x010)
[panel setShowsHiddenFiles:YES];

NSString* titleString = [[NSString alloc]
initWithBytes:title
length:strlen(title)
encoding:NSUTF8StringEncoding];
[panel setTitle:titleString];

[panel beginSheetModalForWindow:(impl->window ? impl->window : [view->impl->wrapperView window])
completionHandler:^(NSInteger result)
{
if (result == NSModalResponseOK && [[panel URL] isFileURL])
{
NSString* const path = [[panel URL] path];
callback(view, [path UTF8String]);
}
else
{
callback(view, nullptr);
}
}];

return true;
}
#endif
#elif defined(DISTRHO_OS_WINDOWS)

#ifdef DISTRHO_OS_WINDOWS
// --------------------------------------------------------------------------------------------------------------------
// win32 specific, call ShowWindow with SW_RESTORE

@@ -609,165 +547,92 @@ void puglWin32ShowCentered(PuglView* const view)
}
else
{
ShowWindow(impl->hwnd, SW_SHOWNORMAL);
}
#ifdef DGL_WINDOWS_ICON_ID
WNDCLASSEX wClass;
std::memset(&wClass, 0, sizeof(wClass));

SetFocus(impl->hwnd);
}
const HINSTANCE hInstance = GetModuleHandle(nullptr);

// --------------------------------------------------------------------------------------------------------------------
// win32 specific, set or unset WS_SIZEBOX style flag

void puglWin32SetWindowResizable(PuglView* const view, const bool resizable)
{
PuglInternals* impl = view->impl;
DISTRHO_SAFE_ASSERT_RETURN(impl->hwnd != nullptr,);

const int winFlags = resizable ? GetWindowLong(impl->hwnd, GWL_STYLE) | WS_SIZEBOX
: GetWindowLong(impl->hwnd, GWL_STYLE) & ~WS_SIZEBOX;
SetWindowLong(impl->hwnd, GWL_STYLE, winFlags);
}
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

#ifdef HAVE_X11
// --------------------------------------------------------------------------------------------------------------------
// X11 specific, safer way to grab focus

PuglStatus puglX11GrabFocus(const PuglView* const view)
{
const PuglInternals* const impl = view->impl;

XWindowAttributes wa;
std::memset(&wa, 0, sizeof(wa));

DISTRHO_SAFE_ASSERT_RETURN(XGetWindowAttributes(impl->display, impl->win, &wa), PUGL_UNKNOWN_ERROR);
MONITORINFO mInfo;
std::memset(&mInfo, 0, sizeof(mInfo));
mInfo.cbSize = sizeof(mInfo);

if (wa.map_state == IsViewable)
{
XRaiseWindow(impl->display, impl->win);
XSetInputFocus(impl->display, impl->win, RevertToPointerRoot, CurrentTime);
XSync(impl->display, False);
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,
0, 0, SWP_SHOWWINDOW|SWP_NOSIZE);
else
ShowWindow(impl->hwnd, SW_NORMAL);
}

return PUGL_SUCCESS;
SetFocus(impl->hwnd);
}

// --------------------------------------------------------------------------------------------------------------------
// X11 specific, set dialog window type and pid hints

void puglX11SetWindowTypeAndPID(const PuglView* const view)
{
const PuglInternals* const impl = view->impl;
#elif defined(DISTRHO_OS_WASM)

const pid_t pid = getpid();
const Atom _nwp = XInternAtom(impl->display, "_NET_WM_PID", False);
XChangeProperty(impl->display, impl->win, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);

const Atom _wt = XInternAtom(impl->display, "_NET_WM_WINDOW_TYPE", False);

// Setting the window to both dialog and normal will produce a decorated floating dialog
// Order is important: DIALOG needs to come before NORMAL
const Atom _wts[2] = {
XInternAtom(impl->display, "_NET_WM_WINDOW_TYPE_DIALOG", False),
XInternAtom(impl->display, "_NET_WM_WINDOW_TYPE_NORMAL", False)
};

XChangeProperty(impl->display, impl->win, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, 2);
}
// nothing here yet

// --------------------------------------------------------------------------------------------------------------------
// X11 specific stuff for sofd

static Display* sofd_display;
static char* sofd_filename;

// --------------------------------------------------------------------------------------------------------------------
// X11 specific, show file dialog via sofd
#elif defined(HAVE_X11)

bool sofdFileDialogShow(PuglView* const view,
const char* const startDir, const char* const title,
const uint flags, const double scaleFactor)
PuglStatus puglX11UpdateWithoutExposures(PuglWorld* const world)
{
// only one possible at a time
DISTRHO_SAFE_ASSERT_RETURN(sofd_display == nullptr, false);
const bool wasDispatchingEvents = world->impl->dispatchingEvents;
world->impl->dispatchingEvents = true;
PuglStatus st = PUGL_SUCCESS;

sofd_display = XOpenDisplay(nullptr);
DISTRHO_SAFE_ASSERT_RETURN(sofd_display != nullptr, false);
const double startTime = puglGetTime(world);
const double endTime = startTime + 0.03;

DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, false);
DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, title) == 0, false);

x_fib_cfg_buttons(3, flags & 0x001 ? 1 : flags & 0x002 ? 0 : -1);
x_fib_cfg_buttons(1, flags & 0x010 ? 1 : flags & 0x020 ? 0 : -1);
x_fib_cfg_buttons(2, flags & 0x100 ? 1 : flags & 0x200 ? 0 : -1);
for (double t = startTime; !st && t < endTime; t = puglGetTime(world))
{
pollX11Socket(world, endTime - t);
st = dispatchX11Events(world);
}

return (x_fib_show(sofd_display, view->impl->win, 0, 0, scaleFactor + 0.5) == 0);
world->impl->dispatchingEvents = wasDispatchingEvents;
return st;
}

// --------------------------------------------------------------------------------------------------------------------
// X11 specific, idle sofd file dialog, returns true if dialog was closed (with or without a file selection)
// X11 specific, set dialog window type and pid hints

bool sofdFileDialogIdle(PuglView* const view)
void puglX11SetWindowTypeAndPID(const PuglView* const view, const bool isStandalone)
{
if (sofd_display == nullptr)
return false;
const PuglInternals* const impl = view->impl;
Display* const display = view->world->impl->display;

XEvent event;
while (XPending(sofd_display) > 0)
{
XNextEvent(sofd_display, &event);

if (x_fib_handle_events(sofd_display, &event) == 0)
continue;

if (sofd_filename != nullptr)
std::free(sofd_filename);

if (x_fib_status() > 0)
sofd_filename = x_fib_filename();
else
sofd_filename = nullptr;
const pid_t pid = getpid();
const Atom _nwp = XInternAtom(display, "_NET_WM_PID", False);
XChangeProperty(display, impl->win, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1);

x_fib_close(sofd_display);
XCloseDisplay(sofd_display);
sofd_display = nullptr;
return true;
}
const Atom _wt = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);

return false;
}
Atom _wts[2];
int numAtoms = 0;

// --------------------------------------------------------------------------------------------------------------------
// X11 specific, close sofd file dialog
if (! isStandalone)
_wts[numAtoms++] = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False);

void sofdFileDialogClose()
{
if (sofd_display != nullptr)
{
x_fib_close(sofd_display);
XCloseDisplay(sofd_display);
sofd_display = nullptr;
}
_wts[numAtoms++] = XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", False);

if (sofd_filename != nullptr)
{
std::free(sofd_filename);
sofd_filename = nullptr;
}
XChangeProperty(display, impl->win, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, numAtoms);
}

// --------------------------------------------------------------------------------------------------------------------
// X11 specific, get path chosen via sofd file dialog

const char* sofdFileDialogGetPath()
{
return sofd_filename;
}
#endif

// --------------------------------------------------------------------------------------------------------------------
#endif // HAVE_X11

#ifndef DISTRHO_OS_MAC
END_NAMESPACE_DGL


+ 39
- 90
dgl/src/pugl.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
@@ -19,7 +19,7 @@

#include "../Base.hpp"

/* we will include all header files used in pugl 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,136 +29,85 @@
# include <stdint.h>
#endif

// hidden api
#define PUGL_API
#define PUGL_DISABLE_DEPRECATED
#define PUGL_NO_INCLUDE_GL_H
#define PUGL_NO_INCLUDE_GLU_H

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

#ifndef DISTRHO_OS_MAC
START_NAMESPACE_DGL
#else
USE_NAMESPACE_DGL
#endif

#include "pugl-upstream/include/pugl/pugl.h"

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

PUGL_BEGIN_DECLS

// expose backend enter
PUGL_API bool
puglBackendEnter(PuglView* view);

// expose backend leave
PUGL_API void
puglBackendLeave(PuglView* view);
// DGL specific, expose backend enter
bool puglBackendEnter(PuglView* view);

// clear minimum size to 0
PUGL_API void
puglClearMinSize(PuglView* view);
// DGL specific, expose backend leave
bool puglBackendLeave(PuglView* view);

// missing in pugl, directly returns transient parent
PUGL_API PuglNativeView
puglGetTransientParent(const PuglView* view);

// missing in pugl, directly returns title char* pointer
PUGL_API const char*
puglGetWindowTitle(const PuglView* view);

// get global scale factor
PUGL_API double
puglGetDesktopScaleFactor(const PuglView* view);
// DGL specific, assigns backend that matches current DGL build
void puglSetMatchingBackendForCurrentBuild(PuglView* view);

// bring view window into the foreground, aka "raise" window
PUGL_API void
puglRaiseWindow(PuglView* view);
void puglRaiseWindow(PuglView* view);

// DGL specific, assigns backend that matches current DGL build
PUGL_API void
puglSetMatchingBackendForCurrentBuild(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);

// Combine puglSetMinSize and puglSetAspectRatio
PUGL_API PuglStatus
puglSetGeometryConstraints(PuglView* view, unsigned int width, unsigned int height, bool aspect);
// set view as resizable (or not) during runtime
void puglSetResizable(PuglView* view, bool resizable);

// set window size with default size and without changing frame x/y position
PUGL_API PuglStatus
puglSetWindowSize(PuglView* view, unsigned int width, unsigned int height);
// set window size while also changing default
PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height);

// DGL specific, build-specific drawing prepare
PUGL_API void
puglOnDisplayPrepare(PuglView* view);
void puglOnDisplayPrepare(PuglView* view);

// DGL specific, build-specific fallback resize
PUGL_API void
puglFallbackOnResize(PuglView* view);
void puglFallbackOnResize(PuglView* view);

#ifdef DISTRHO_OS_MAC
// macOS specific, allow standalone window to gain focus
PUGL_API void
puglMacOSActivateApp();
#if defined(DISTRHO_OS_MAC)

// macOS specific, add another view's window as child
PUGL_API PuglStatus
puglMacOSAddChildWindow(PuglView* view, PuglView* child);
PuglStatus puglMacOSAddChildWindow(PuglView* view, PuglView* child);

// macOS specific, remove another view's window as child
PUGL_API PuglStatus
puglMacOSRemoveChildWindow(PuglView* view, PuglView* child);
PuglStatus puglMacOSRemoveChildWindow(PuglView* view, PuglView* child);

// macOS specific, center view based on parent coordinates (if there is one)
PUGL_API void
puglMacOSShowCentered(PuglView* view);
void puglMacOSShowCentered(PuglView* view);

// macOS specific, setup file browser dialog
typedef void (*openPanelCallback)(PuglView* view, const char* path);
bool puglMacOSFilePanelOpen(PuglView* view, const char* startDir, const char* title, uint flags, openPanelCallback callback);
#endif
#elif defined(DISTRHO_OS_WASM)

#ifdef DISTRHO_OS_WINDOWS
// win32 specific, call ShowWindow with SW_RESTORE
PUGL_API void
puglWin32RestoreWindow(PuglView* view);
// nothing here yet

// win32 specific, center view based on parent coordinates (if there is one)
PUGL_API void
puglWin32ShowCentered(PuglView* view);
#elif defined(DISTRHO_OS_WINDOWS)

// win32 specific, set or unset WS_SIZEBOX style flag
PUGL_API void
puglWin32SetWindowResizable(PuglView* view, bool resizable);
#endif
// win32 specific, call ShowWindow with SW_RESTORE
void puglWin32RestoreWindow(PuglView* view);

#ifdef HAVE_X11
// X11 specific, safer way to grab focus
PUGL_API PuglStatus
puglX11GrabFocus(const PuglView* view);
// win32 specific, center view based on parent coordinates (if there is one)
void puglWin32ShowCentered(PuglView* view);

// X11 specific, set dialog window type and pid hints
PUGL_API void
puglX11SetWindowTypeAndPID(const PuglView* view);
#elif defined(HAVE_X11)

// X11 specific, show file dialog via sofd
PUGL_API bool
sofdFileDialogShow(PuglView* view, const char* startDir, const char* title, uint flags, double scaleFactor);
#define DGL_USING_X11

// X11 specific, idle sofd file dialog, returns true if dialog was closed (with or without a file selection)
PUGL_API bool
sofdFileDialogIdle(PuglView* const view);
// X11 specific, update world without triggering exposure evente
PuglStatus puglX11UpdateWithoutExposures(PuglWorld* world);

// X11 specific, close sofd file dialog
PUGL_API void
sofdFileDialogClose();
// X11 specific, set dialog window type and pid hints
void puglX11SetWindowTypeAndPID(const PuglView* view, bool isStandalone);

// X11 specific, get path chosen via sofd file dialog
PUGL_API const char*
sofdFileDialogGetPath();
#endif

PUGL_END_DECLS

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

#ifndef DISTRHO_OS_MAC


+ 268
- 35
distrho/DistrhoInfo.hpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -31,23 +31,39 @@ START_NAMESPACE_DISTRHO
It allows developers to create plugins with custom UIs using a simple C++ API.@n
The framework facilitates exporting various different plugin formats from the same code-base.

DPF can build for LADSPA, DSSI, LV2 and VST2 formats.@n
DPF can build for LADSPA, DSSI, LV2, VST2 and VST3 formats.@n
A JACK/Standalone mode is also available, allowing you to quickly test plugins.

@section Macros
You start by creating a "DistrhoPluginInfo.h" file describing the plugin via macros, see @ref PluginMacros.@n
This file is included in the main DPF code to select which features to activate for each plugin format.
This file is included during compilation of the main DPF code to select which features to activate for each plugin format.

For example, a plugin (with %UI) that use states will require LV2 hosts to support Atom and Worker extensions for
message passing from the %UI to the plugin.@n
message passing from the %UI to the (DSP) plugin.@n
If your plugin does not make use of states, the Worker extension is not set as a required feature.

@section Plugin
The next step is to create your plugin code by subclassing DPF's Plugin class.@n
You need to pass the number of parameters in the constructor and also the number of programs and states, if any.

Here's an example of an audio plugin that simply mutes the host output:
Do note all of DPF code is within its own C++ namespace (@b DISTRHO for DSP/plugin stuff, @b DGL for UI stuff).@n
You can use @ref START_NAMESPACE_DISTRHO / @ref END_NAMESPACE_DISTRHO combo around your code, or globally set @ref USE_NAMESPACE_DISTRHO.@n
These are defined as compiler macros so that you can override the namespace name during build. When in doubt, just follow the examples.

@section Examples
Let's begin with some examples.@n
Here is one of a stereo audio plugin that simply mutes the host output:
@code
/* Make DPF related classes available for us to use without any extra namespace references */
USE_NAMESPACE_DISTRHO;

/**
Our custom plugin class.
Subclassing `Plugin` from DPF is how this all works.

By default, only information-related functions and `run` are pure virtual (that is, must be reimplemented).
When enabling certain features (such as programs or states, more on that below), a few extra functions also need to be reimplemented.
*/
class MutePlugin : public Plugin
{
public:
@@ -106,19 +122,11 @@ START_NAMESPACE_DISTRHO
return d_cconst('M', 'u', 't', 'e');
}

/* ----------------------------------------------------------------------------------------
* This example has no parameters, so skip parameter stuff */

void initParameter(uint32_t, Parameter&) override {}
float getParameterValue(uint32_t) const override { return 0.0f; }
void setParameterValue(uint32_t, float) override {}

/* ----------------------------------------------------------------------------------------
* Audio/MIDI Processing */

/**
Run/process function for plugins without MIDI input.
NOTE: Some parameters might be null if there are no audio inputs or outputs.
*/
void run(const float**, float** outputs, uint32_t frames) override
{
@@ -130,11 +138,20 @@ START_NAMESPACE_DISTRHO
std::memset(outL, 0, sizeof(float)*frames);
std::memset(outR, 0, sizeof(float)*frames);
}

};

/**
Create an instance of the Plugin class.
This is the entry point for DPF plugins.
DPF will call this to either create an instance of your plugin for the host or to fetch some initial information for internal caching.
*/
Plugin* createPlugin()
{
return new MutePlugin();
}
@endcode

See the Plugin class for more information and to understand what each function does.
See the Plugin class for more information.

@section Parameters
A plugin is nothing without parameters.@n
@@ -142,12 +159,12 @@ START_NAMESPACE_DISTRHO
They have hints to describe how they behave plus a name and a symbol identifying them.@n
Parameters also have 'ranges' – a minimum, maximum and default value.

Input parameters are "read-only": the plugin can read them but not change them.
(the exception being when changing programs, more on that below)@n
Input parameters are by default "read-only": the plugin can read them but not change them.
(there are exceptions and possibly a request to the host to change values, more on that below)@n
It's the host responsibility to save, restore and set input parameters.

Output parameters can be changed at anytime by the plugin.@n
The host will simply read their values and not change them.
The host will simply read their values and never change them.

Here's an example of an audio plugin that has 1 input parameter:
@code
@@ -204,7 +221,7 @@ START_NAMESPACE_DISTRHO
{
// we only have one parameter so we can skip checking the index

parameter.hints = kParameterIsAutomable;
parameter.hints = kParameterIsAutomatable;
parameter.name = "Gain";
parameter.symbol = "gain";
parameter.ranges.min = 0.0f;
@@ -257,8 +274,8 @@ START_NAMESPACE_DISTRHO
See the Parameter struct for more information about parameters.

@section Programs
Programs in DPF refer to plugin-side presets (usually called "factory presets"),
an initial set of presets provided by plugin authors included in the actual plugin.
Programs in DPF refer to plugin-side presets (usually called "factory presets").@n
This is meant as an initial set of presets provided by plugin authors included in the actual plugin.

To use programs you must first enable them by setting @ref DISTRHO_PLUGIN_WANT_PROGRAMS to 1 in your DistrhoPluginInfo.h file.@n
When enabled you'll need to override 2 new function in your plugin code,
@@ -314,7 +331,7 @@ START_NAMESPACE_DISTRHO
*/
void initParameter(uint32_t index, Parameter& parameter) override
{
parameter.hints = kParameterIsAutomable;
parameter.hints = kParameterIsAutomatable;
parameter.ranges.min = 0.0f;
parameter.ranges.max = 2.0f;
parameter.ranges.def = 1.0f;
@@ -338,12 +355,9 @@ START_NAMESPACE_DISTRHO
*/
void initProgramName(uint32_t index, String& programName)
{
switch(index)
{
case 0:
programName = "Default";
break;
}
// we only have one program so we can skip checking the index

programName = "Default";
}

/* ----------------------------------------------------------------------------------------
@@ -384,13 +398,10 @@ START_NAMESPACE_DISTRHO
*/
void loadProgram(uint32_t index)
{
switch(index)
{
case 0:
fGainL = 1.0f;
fGainR = 1.0f;
break;
}
// same as before, ignore index check

fGainL = 1.0f;
fGainR = 1.0f;
}

/* ----------------------------------------------------------------------------------------
@@ -507,6 +518,20 @@ START_NAMESPACE_DISTRHO
*/
#define DISTRHO_PLUGIN_IS_SYNTH 1

/**
Request the minimum buffer size for the input and output event ports.@n
Currently only used in LV2, with a default value of 2048 if unset.
*/
#define DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE 2048

/**
Whether the plugin has an LV2 modgui.

This will simply add a "rdfs:seeAlso <modgui.ttl>" on the LV2 manifest.@n
It is up to you to create this file.
*/
#define DISTRHO_PLUGIN_USES_MODGUI 0

/**
Enable direct access between the %UI and plugin code.
@see UI::getPluginInstancePointer()
@@ -613,8 +638,216 @@ START_NAMESPACE_DISTRHO
*/
#define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#UI"

/**
Custom LV2 category for the plugin.@n
This can be one of the following values:

- lv2:Plugin
- lv2:AllpassPlugin
- lv2:AmplifierPlugin
- lv2:AnalyserPlugin
- lv2:BandpassPlugin
- lv2:ChorusPlugin
- lv2:CombPlugin
- lv2:CompressorPlugin
- lv2:ConstantPlugin
- lv2:ConverterPlugin
- lv2:DelayPlugin
- lv2:DistortionPlugin
- lv2:DynamicsPlugin
- lv2:EQPlugin
- lv2:EnvelopePlugin
- lv2:ExpanderPlugin
- lv2:FilterPlugin
- lv2:FlangerPlugin
- lv2:FunctionPlugin
- lv2:GatePlugin
- lv2:GeneratorPlugin
- lv2:HighpassPlugin
- lv2:InstrumentPlugin
- lv2:LimiterPlugin
- lv2:LowpassPlugin
- lv2:MIDIPlugin
- lv2:MixerPlugin
- lv2:ModulatorPlugin
- lv2:MultiEQPlugin
- lv2:OscillatorPlugin
- lv2:ParaEQPlugin
- lv2:PhaserPlugin
- lv2:PitchPlugin
- lv2:ReverbPlugin
- lv2:SimulatorPlugin
- lv2:SpatialPlugin
- lv2:SpectralPlugin
- lv2:UtilityPlugin
- lv2:WaveshaperPlugin

See http://lv2plug.in/ns/lv2core for more information.
*/
#define DISTRHO_PLUGIN_LV2_CATEGORY "lv2:Plugin"

/**
Custom VST3 categories for the plugin.@n
This is a list of categories, separated by a @c |.

Each effect category can be one of the following values:

- Fx
- Fx|Ambisonics
- Fx|Analyzer
- Fx|Delay
- Fx|Distortion
- Fx|Dynamics
- Fx|EQ
- Fx|Filter
- Fx|Instrument
- Fx|Instrument|External
- Fx|Spatial
- Fx|Generator
- Fx|Mastering
- Fx|Modulation
- Fx|Network
- Fx|Pitch Shift
- Fx|Restoration
- Fx|Reverb
- Fx|Surround
- Fx|Tools

Each instrument category can be one of the following values:

- Instrument
- Instrument|Drum
- Instrument|External
- Instrument|Piano
- Instrument|Sampler
- Instrument|Synth
- Instrument|Synth|Sampler

@note DPF will automatically set Mono and Stereo categories when appropriate.
*/
#define DISTRHO_PLUGIN_VST3_CATEGORIES "Fx"

/** @} */

/* ------------------------------------------------------------------------------------------------------------
* Plugin Macros */

/**
@defgroup ExtraPluginMacros Extra Plugin Macros

C Macros to customize DPF behaviour.

These are macros that do not set plugin features or information, but instead change DPF internals.
They are all optional.

Unless stated otherwise, values are assumed to be a simple/empty define.
@{
*/

/**
Whether to enable runtime plugin tests.@n
This will check, during initialization of the plugin, if parameters, programs and states are setup properly.@n
Useful to enable as part of CI, can safely be skipped.@n
Under DPF makefiles this can be enabled by using `make DPF_RUNTIME_TESTING=true`.

@note Some checks are only available with the GCC compiler,
for detecting if a virtual function has been reimplemented.
*/
#define DPF_RUNTIME_TESTING

/**
Whether to show parameter outputs in the VST2 plugins.@n
This is disabled (unset) by default, as the VST2 format has no notion of read-only parameters.
*/
#define DPF_VST_SHOW_PARAMETER_OUTPUTS

/**
Disable all file browser related code.@n
Must be set as compiler macro when building DGL. (e.g. `CXXFLAGS="-DDGL_FILE_BROWSER_DISABLED"`)
*/
#define DGL_FILE_BROWSER_DISABLED

/**
Disable resource files, like internally used fonts.@n
Must be set as compiler macro when building DGL. (e.g. `CXXFLAGS="-DDGL_NO_SHARED_RESOURCES"`)
*/
#define DGL_NO_SHARED_RESOURCES

/**
Whether to use OpenGL3 instead of the default OpenGL2 compatility profile.
Under DPF makefiles this can be enabled by using `make USE_OPENGL3=true` on the dgl build step.

@note This is experimental and incomplete, contributions are welcome and appreciated.
*/
#define DGL_USE_OPENGL3

/**
Whether to use the GPLv2+ vestige header instead of the official Steinberg VST2 SDK.@n
This is a boolean, and enabled (set to 1) by default.@n
Set this to 0 in order to create non-GPL binaries.
(but then at your own discretion in regards to Steinberg licensing)@n
When set to 0, DPF will import the VST2 definitions from `"vst/aeffectx.h"` (not shipped with DPF).
*/
#define VESTIGE_HEADER 1

/** @} */

/* ------------------------------------------------------------------------------------------------------------
* Namespace Macros */

/**
@defgroup NamespaceMacros Namespace Macros

C Macros to use and customize DPF namespaces.

These are macros that serve as helpers around C++ namespaces, and also as a way to set custom namespaces during a build.
@{
*/

/**
Compiler macro that sets the C++ namespace for DPF plugins.@n
If unset during build, it will use the name @b DISTRHO by default.

Unless you know exactly what you are doing, you do need to modify this value.@n
The only probable useful case for customizing it is if you are building a big collection of very similar DPF-based plugins in your application.@n
For example, having 2 different versions of the same plugin that should behave differently but still exist within the same binary.

On macOS (where due to Objective-C restrictions all code that interacts with Cocoa needs to be in a flat namespace),
DPF will automatically use the plugin name as prefix to flat namespace functions in order to avoid conflicts.

So, basically, it is DPF's job to make sure plugin binaries are 100% usable as-is.@n
You typically do not need to care about this at all.
*/
#define DISTRHO_NAMESPACE DISTRHO

/**
Compiler macro that begins the C++ namespace for @b DISTRHO, as needed for (the DSP side of) plugins.@n
All classes in DPF are within this namespace except for UI/graphics stuff.
@see END_NAMESPACE_DISTRHO
*/
#define START_NAMESPACE_DISTRHO namespace DISTRHO_NAMESPACE {

/**
Close the namespace previously started by @ref START_NAMESPACE_DISTRHO.@n
This doesn't really need to be a macro, it is just prettier/more consistent that way.
*/
#define END_NAMESPACE_DISTRHO }

/**
Make the @b DISTRHO namespace available in the current function scope.@n
This is not set by default in order to avoid conflicts with commonly used names such as "Parameter" and "Plugin".
*/
#define USE_NAMESPACE_DISTRHO using namespace DISTRHO_NAMESPACE;

/* TODO
*
* DISTRHO_MACRO_AS_STRING_VALUE
* DISTRHO_MACRO_AS_STRING
* DISTRHO_PROPER_CPP11_SUPPORT
* DONT_SET_USING_DISTRHO_NAMESPACE
*
*/

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

END_NAMESPACE_DISTRHO


+ 155
- 21
distrho/DistrhoPlugin.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
@@ -40,7 +40,9 @@ START_NAMESPACE_DISTRHO
static const uint32_t kAudioPortIsCV = 0x1;

/**
Audio port should be used as sidechan (LV2 only).
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;

@@ -84,10 +86,14 @@ static const uint32_t kCVPortHasScaledRange = 0x80;
*/

/**
Parameter is automable (real-time safe).
Parameter is automatable (real-time safe).
@see Plugin::setParameterValue(uint32_t, float)
*/
static const uint32_t kParameterIsAutomable = 0x01;
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
@@ -129,6 +135,52 @@ 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 */

@@ -561,7 +613,7 @@ struct Parameter {
case kParameterDesignationNull:
break;
case kParameterDesignationBypass:
hints = kParameterIsAutomable|kParameterIsBoolean|kParameterIsInteger;
hints = kParameterIsAutomatable|kParameterIsBoolean|kParameterIsInteger;
name = "Bypass";
shortName = "Bypass";
symbol = "dpf_bypass";
@@ -584,6 +636,9 @@ struct Parameter {
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.

@@ -610,6 +665,49 @@ struct PortGroup {
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.
*/
@@ -632,6 +730,9 @@ struct MidiEvent {
/**
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;
@@ -652,6 +753,8 @@ struct TimePosition {

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

@@ -832,6 +935,22 @@ public:
*/
double getSampleRate() const noexcept;

/**
Get the bundle path where the plugin resides.
Can return null if the plugin is not available in a bundle (if it is a single binary).
@see getBinaryFilename
@see getResourcePath
*/
const char* getBundlePath() const noexcept;

/**
Check if this plugin instance is a "dummy" one used for plugin meta-data/information export.@n
When true no processing will be done, the plugin is created only to extract information.@n
In DPF, LADSPA/DSSI, VST2 and VST3 formats create one global instance per plugin binary
while LV2 creates one when generating turtle meta-data.
*/
bool isDummyInstance() const noexcept;

#if DISTRHO_PLUGIN_WANT_TIMEPOS
/**
Get the current host transport time position.@n
@@ -878,6 +997,19 @@ public:
bool requestParameterValueChange(uint32_t index, float value) noexcept;
#endif

#if DISTRHO_PLUGIN_WANT_STATE
/**
Set state value and notify the host about the change.@n
This function will call `setState()` and also trigger an update on the UI side as necessary.@n
It must not be called during run.@n
The state must be host readable.
@note this function does nothing on DSSI plugin format, as DSSI only supports UI->DSP messages.

TODO API under construction
*/
bool updateStateValue(const char* key, const char* value) noexcept;
#endif

protected:
/* --------------------------------------------------------------------------------------------------------
* Information */
@@ -943,7 +1075,7 @@ protected:
Initialize the parameter @a index.@n
This function will be called once, shortly after the plugin is created.
*/
virtual void initParameter(uint32_t index, Parameter& parameter) = 0;
virtual void initParameter(uint32_t index, Parameter& parameter);

/**
Initialize the port group @a groupId.@n
@@ -963,18 +1095,17 @@ protected:

#if DISTRHO_PLUGIN_WANT_STATE
/**
Set the state key and default value of @a index.@n
Initialize the state @a index.@n
This function will be called once, shortly after the plugin is created.@n
Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled.
*/
virtual void initState(uint32_t index, String& stateKey, String& defaultStateValue) = 0;
#endif
virtual void initState(uint32_t index, State& state);

#if DISTRHO_PLUGIN_WANT_STATEFILES
/**
TODO API under construction
*/
virtual bool isStateFile(uint32_t index) = 0;
DISTRHO_DEPRECATED_BY("initState(uint32_t,State&)")
virtual void initState(uint32_t, String&, String&) {}
DISTRHO_DEPRECATED_BY("initState(uint32_t,State&)")
virtual bool isStateFile(uint32_t) { return false; }
#endif

/* --------------------------------------------------------------------------------------------------------
@@ -984,15 +1115,15 @@ protected:
Get the current value of a parameter.@n
The host may call this function from any context, including realtime processing.
*/
virtual float getParameterValue(uint32_t index) const = 0;
virtual float getParameterValue(uint32_t index) const;

/**
Change a parameter value.@n
The host may call this function from any context, including realtime processing.@n
When a parameter is marked as automable, you must ensure no non-realtime operations are performed.
When a parameter is marked as automatable, you must ensure no non-realtime operations are performed.
@note This function will only be called for parameter inputs.
*/
virtual void setParameterValue(uint32_t index, float value) = 0;
virtual void setParameterValue(uint32_t index, float value);

#if DISTRHO_PLUGIN_WANT_PROGRAMS
/**
@@ -1000,7 +1131,7 @@ protected:
The host may call this function from any context, including realtime processing.@n
Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_PROGRAMS is enabled.
*/
virtual void loadProgram(uint32_t index) = 0;
virtual void loadProgram(uint32_t index);
#endif

#if DISTRHO_PLUGIN_WANT_FULL_STATE
@@ -1010,7 +1141,7 @@ protected:
Must be implemented by your plugin class if DISTRHO_PLUGIN_WANT_FULL_STATE is enabled.
@note The use of this function breaks compatibility with the DSSI format.
*/
virtual String getState(const char* key) const = 0;
virtual String getState(const char* key) const;
#endif

#if DISTRHO_PLUGIN_WANT_STATE
@@ -1018,7 +1149,7 @@ protected:
Change an internal state @a key to @a value.@n
Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled.
*/
virtual void setState(const char* key, const char* value) = 0;
virtual void setState(const char* key, const char* value);
#endif

/* --------------------------------------------------------------------------------------------------------
@@ -1089,7 +1220,10 @@ private:
*/

/**
TODO.
Create an instance of the Plugin class.@n
This is the entry point for DPF plugins.@n
DPF will call this to either create an instance of your plugin for the host
or to fetch some initial information for internal caching.
*/
extern Plugin* createPlugin();



+ 15
- 1
distrho/DistrhoPluginMain.cpp View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2016 Filipe Coelho <falktx@falktx.com>
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -29,6 +29,20 @@
# include "src/DistrhoPluginVST2.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_VST3)
# include "src/DistrhoPluginVST3.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(); }
#elif defined(DISTRHO_PLUGIN_TARGET_STATIC)
START_NAMESPACE_DISTRHO
Plugin* createStaticPlugin() { return createPlugin(); }
END_NAMESPACE_DISTRHO
#else
# error unsupported format
#endif

#if defined(DISTRHO_PLUGIN_TARGET_JACK)
# define DISTRHO_IS_STANDALONE 1
#else
# define DISTRHO_IS_STANDALONE 0
#endif
#include "src/DistrhoUtils.cpp"

+ 61
- 5
distrho/DistrhoPluginUtils.hpp View File

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

START_NAMESPACE_DISTRHO

// -----------------------------------------------------------------------------------------------------------
/* ------------------------------------------------------------------------------------------------------------
* Plugin related utilities */

/**
@defgroup PluginRelatedUtilities Plugin related utilities

@{
*/

/**
Get the absolute filename of the plugin DSP/UI binary.@n
Under certain systems or plugin formats the binary will be inside the plugin bundle.@n
Also, in some formats or setups, the DSP and UI binaries are in different files.
*/
const char* getBinaryFilename();

/**
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 string is purely informational and must not be used to tweak plugin behaviour.

@note DO NOT CHANGE PLUGIN BEHAVIOUR BASED ON PLUGIN FORMAT.
*/
const char* getPluginFormatName() noexcept;

/**
Get the path to where resources are stored within the plugin bundle.@n
Requires a valid plugin bundle path.

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:

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

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
rather than only shipping with the binary (e.g. <myplugin.vst>/myplugin.dll)
*/
const char* getResourcePath(const char* bundlePath) noexcept;

/** @} */

/* ------------------------------------------------------------------------------------------------------------
* Plugin helper classes */

/**
@defgroup PluginHelperClasses Plugin helper classes

@{
*/

#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
/**
Handy class to help keep audio buffer in sync with incoming MIDI events.
To use it, create a local variable (on the stack) and call nextEvent() until it returns false.
@@ -44,11 +97,11 @@ START_NAMESPACE_DISTRHO

Some important notes when using this class:
1. MidiEvent::frame retains its original value, but it is useless, do not use it.
2. The class variables names are be the same as the default ones in the run function.
2. The class variable names are the same as the default ones in the run function.
Keep that in mind and try to avoid typos. :)
*/
class AudioMidiSyncHelper {
public:
struct AudioMidiSyncHelper
{
/** Parameters from the run function, adjusted for event sync */
float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS];
uint32_t frames;
@@ -151,6 +204,9 @@ private:
uint32_t remainingMidiEventCount;
uint32_t totalFramesUsed;
};
#endif

/** @} */

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



+ 99
- 0
distrho/DistrhoStandaloneUtils.hpp View File

@@ -0,0 +1,99 @@
/*
* DISTRHO Plugin Framework (DPF)
* 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
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef DISTRHO_STANDALONE_UTILS_HPP_INCLUDED
#define DISTRHO_STANDALONE_UTILS_HPP_INCLUDED

#include "src/DistrhoDefines.h"

START_NAMESPACE_DISTRHO

/* ------------------------------------------------------------------------------------------------------------
* Standalone plugin related utilities */

/**
@defgroup StandalonePluginRelatedUtilities Plugin related utilities

When the plugin is running as standalone and JACK is not available, a native audio handling is in place.
It is a very simple handling, auto-connecting to the default audio interface for outputs.

!!EXPERIMENTAL!!

Still under development and testing.

@{
*/

/**
Check if the current standalone is using native audio methods.
If this function returns false, you MUST NOT use any other function from this group.
*/
bool isUsingNativeAudio() noexcept;

/**
Check if the current standalone supports audio input.
*/
bool supportsAudioInput();

/**
Check if the current standalone supports dynamic buffer size changes.
*/
bool supportsBufferSizeChanges();

/**
Check if the current standalone supports MIDI.
*/
bool supportsMIDI();

/**
Check if the current standalone has audio input enabled.
*/
bool isAudioInputEnabled();

/**
Check if the current standalone has MIDI enabled.
*/
bool isMIDIEnabled();

/**
Get the current buffer size.
*/
uint getBufferSize();

/**
Request permissions to use audio input.
Only valid to call if audio input is supported but not currently enabled.
*/
bool requestAudioInput();

/**
Request change to a new buffer size.
*/
bool requestBufferSizeChange(uint newBufferSize);

/**
Request permissions to use MIDI.
Only valid to call if MIDI is supported but not currently enabled.
*/
bool requestMIDI();

/** @} */

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

END_NAMESPACE_DISTRHO

#endif // DISTRHO_STANDALONE_UTILS_HPP_INCLUDED

+ 57
- 13
distrho/DistrhoUI.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
@@ -48,12 +48,14 @@ typedef DGL_NAMESPACE::NanoTopLevelWidget UIWidget;
typedef DGL_NAMESPACE::TopLevelWidget UIWidget;
#endif

START_NAMESPACE_DGL
class PluginWindow;
END_NAMESPACE_DGL
#if DISTRHO_UI_FILE_BROWSER
# include "extra/FileBrowserDialog.hpp"
#endif

START_NAMESPACE_DISTRHO

class PluginWindow;

/* ------------------------------------------------------------------------------------------------------------
* DPF UI */

@@ -80,12 +82,12 @@ public:
It assumes aspect ratio is meant to be kept.
Manually call setGeometryConstraints instead if keeping UI aspect ratio is not required.
*/
UI(uint width = 0, uint height = 0, bool automaticallyScale = false);
UI(uint width = 0, uint height = 0, bool automaticallyScaleAndSetAsMinimumSize = false);

/**
Destructor.
*/
virtual ~UI();
~UI() override;

/* --------------------------------------------------------------------------------------------------------
* Host state */
@@ -131,6 +133,13 @@ public:
*/
double getSampleRate() const noexcept;

/**
Get the bundle path where the UI resides.@n
Can return null if the UI is not available in a bundle (if it is a single binary).
@see getBinaryFilename
*/
const char* getBundlePath() const noexcept;

/**
editParameter.

@@ -153,9 +162,7 @@ public:
@TODO Document this.
*/
void setState(const char* key, const char* value);
#endif

#if DISTRHO_PLUGIN_WANT_STATEFILES
/**
Request a new file from the host, matching the properties of a state key.@n
This will use the native host file browser if available, otherwise a DPF built-in file browser is used.@n
@@ -176,6 +183,22 @@ public:
void sendNote(uint8_t channel, uint8_t note, uint8_t velocity);
#endif

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

If a path is selected, onFileSelected() will be called with the user chosen path.
If the user cancels or does not pick a file, onFileSelected() will be called with nullptr as filename.

This function does not block the event loop.

@note This is exactly the same API as provided by the Window class,
but redeclared here so that non-embed/DGL based UIs can still use file browser related functions.
*/
bool openFileBrowser(const DISTRHO_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions());
#endif

#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
/* --------------------------------------------------------------------------------------------------------
* Direct DSP access - DO NOT USE THIS UNLESS STRICTLY NECESSARY!! */
@@ -271,6 +294,23 @@ 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.
*/
std::vector<DGL_NAMESPACE::ClipboardDataOffer> getClipboardDataOfferTypes();

/**
Window clipboard data offer function, called when clipboard has data present, possibly with several datatypes.
While handling this event, the data types can be investigated with getClipboardDataOfferTypes() to decide whether to accept the offer.

Reimplement and return a non-zero id to accept the clipboard data offer for a particular type.
UIs must ignore any type they do not recognize.

The default implementation accepts the "text/plain" mimetype.
*/
virtual uint32_t uiClipboardDataOffer();

/**
Windows focus function, called when the window gains or loses the keyboard focus.
This function is for plugin UIs to be able to override Window::onFocus(bool, CrossingMode).
@@ -290,8 +330,9 @@ protected:
The most common exception is custom OpenGL setup, but only really needed for custom OpenGL drawing code.
*/
virtual void uiReshape(uint width, uint height);
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI

# ifndef DGL_FILE_BROWSER_DISABLED
#if DISTRHO_UI_FILE_BROWSER
/**
Window file selected function, called when a path is selected by the user, as triggered by openFileBrowser().
This function is for plugin UIs to be able to override Window::onFileSelected(const char*).
@@ -299,11 +340,10 @@ protected:
This action happens after the user confirms the action, so the file browser dialog will be closed at this point.
The default implementation does nothing.

If you need to use files as plugin state, please setup and use DISTRHO_PLUGIN_WANT_STATEFILES instead.
If you need to use files as plugin state, please setup and use states with kStateIsFilenamePath instead.
*/
virtual void uiFileBrowserSelected(const char* filename);
# endif
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#endif

/* --------------------------------------------------------------------------------------------------------
* UI Resize Handling, internal */
@@ -329,8 +369,12 @@ protected:
private:
struct PrivateData;
PrivateData* const uiData;
friend class DGL_NAMESPACE::PluginWindow;
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)
};


+ 12
- 1
distrho/DistrhoUIMain.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
@@ -27,7 +27,18 @@
#elif defined(DISTRHO_PLUGIN_TARGET_VST2)
// nothing
#elif defined(DISTRHO_PLUGIN_TARGET_VST3)
# include "src/DistrhoUIVST3.cpp"
#elif defined(DISTRHO_PLUGIN_TARGET_SHARED) || defined(DISTRHO_PLUGIN_TARGET_STATIC)
// nothing
#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)
# ifdef DISTRHO_PLUGIN_TARGET_DSSI
# define DISTRHO_IS_STANDALONE 1
# else
# define DISTRHO_IS_STANDALONE 0
# endif
# include "src/DistrhoUtils.cpp"
#endif

+ 19
- 24
distrho/DistrhoUI_macOS.mm 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
@@ -14,18 +14,28 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef PUGL_NAMESPACE
# error PUGL_NAMESPACE must be set when compiling this file
#endif
// A few utils declared in DistrhoUI.cpp but defined here because they use Obj-C

#include "src/DistrhoPluginChecks.h"
#include "src/DistrhoDefines.h"

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#import <Cocoa/Cocoa.h>
#include <algorithm>
#include <cmath>
#if DISTRHO_UI_FILE_BROWSER || DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# import <Cocoa/Cocoa.h>
#endif

#if DISTRHO_UI_FILE_BROWSER
# define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED
# define FILE_BROWSER_DIALOG_NAMESPACE DISTRHO_NAMESPACE
# define FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE
START_NAMESPACE_DISTRHO
# include "extra/FileBrowserDialogImpl.hpp"
END_NAMESPACE_DISTRHO
# include "extra/FileBrowserDialogImpl.cpp"
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# include <algorithm>
# include <cmath>
START_NAMESPACE_DISTRHO
double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
{
@@ -40,19 +50,4 @@ double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
return [NSScreen mainScreen].backingScaleFactor;
}
END_NAMESPACE_DISTRHO
#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#include "../dgl/Base.hpp"

#define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, SEP, PUGL_NS, INTERFACE) DGL_NS ## SEP ## PUGL_NS ## SEP ## INTERFACE
#define DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NS, PUGL_NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, _, PUGL_NS, INTERFACE)

#define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, CairoView)
#define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, OpenGLView)
#define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, StubView)
#define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, VulkanView)
#define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, Window)
#define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WindowDelegate)
#define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WrapperView)

#import "src/pugl.mm"
#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#endif

+ 72
- 47
distrho/DistrhoUtils.hpp View File

@@ -58,11 +58,18 @@ inline float round(float __x)
#define DISTRHO_MACRO_AS_STRING_VALUE(MACRO) #MACRO
#define DISTRHO_MACRO_AS_STRING(MACRO) DISTRHO_MACRO_AS_STRING_VALUE(MACRO)

// -----------------------------------------------------------------------
// misc functions
/* ------------------------------------------------------------------------------------------------------------
* misc functions */

/*
* Return a 64-bit number from 4 8-bit numbers.
/**
@defgroup MiscellaneousFunctions Miscellaneous functions

@{
*/

/**
Return a 32-bit number from 4 8-bit numbers.@n
The return type is a int64_t for better compatibility with plugin formats that use such numbers.
*/
static inline constexpr
int64_t d_cconst(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_t d) noexcept
@@ -70,8 +77,8 @@ 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 an hexadecimal representation of a MAJ.MIN.MICRO version number.
/**
Return an hexadecimal representation of a MAJ.MIN.MICRO version number.
*/
static inline constexpr
uint32_t d_version(const uint8_t major, const uint8_t minor, const uint8_t micro) noexcept
@@ -79,18 +86,26 @@ uint32_t d_version(const uint8_t major, const uint8_t minor, const uint8_t micro
return uint32_t(major << 16) | uint32_t(minor << 8) | (micro << 0);
}

/*
* Dummy function.
/**
Dummy, no-op function.
*/
static inline
void d_pass() noexcept {}

// -----------------------------------------------------------------------
// string print functions
/** @} */

/*
* Print a string to stdout with newline (gray color).
* Does nothing if DEBUG is not defined.
/* ------------------------------------------------------------------------------------------------------------
* string print functions */

/**
@defgroup StringPrintFunctions String print functions

@{
*/

/**
Print a string to stdout with newline (gray color).
Does nothing if DEBUG is not defined.
*/
#ifndef DEBUG
# define d_debug(...)
@@ -109,8 +124,8 @@ void d_debug(const char* const fmt, ...) noexcept
}
#endif

/*
* Print a string to stdout with newline.
/**
Print a string to stdout with newline.
*/
static inline
void d_stdout(const char* const fmt, ...) noexcept
@@ -124,8 +139,8 @@ void d_stdout(const char* const fmt, ...) noexcept
} catch (...) {}
}

/*
* Print a string to stderr with newline.
/**
Print a string to stderr with newline.
*/
static inline
void d_stderr(const char* const fmt, ...) noexcept
@@ -139,8 +154,8 @@ void d_stderr(const char* const fmt, ...) noexcept
} catch (...) {}
}

/*
* Print a string to stderr with newline (red color).
/**
Print a string to stderr with newline (red color).
*/
static inline
void d_stderr2(const char* const fmt, ...) noexcept
@@ -155,8 +170,8 @@ void d_stderr2(const char* const fmt, ...) noexcept
} catch (...) {}
}

/*
* Print a safe assertion error message.
/**
Print a safe assertion error message.
*/
static inline
void d_safe_assert(const char* const assertion, const char* const file, const int line) noexcept
@@ -164,8 +179,8 @@ void d_safe_assert(const char* const assertion, const char* const file, const in
d_stderr2("assertion failure: \"%s\" in file %s, line %i", assertion, file, line);
}

/*
* Print a safe assertion error message, with 1 extra signed integer value.
/**
Print a safe assertion error message, with 1 extra signed integer value.
*/
static inline
void d_safe_assert_int(const char* const assertion, const char* const file,
@@ -174,8 +189,8 @@ void d_safe_assert_int(const char* const assertion, const char* const file,
d_stderr2("assertion failure: \"%s\" in file %s, line %i, value %i", assertion, file, line, value);
}

/*
* Print a safe assertion error message, with 1 extra unsigned integer value.
/**
Print a safe assertion error message, with 1 extra unsigned integer value.
*/
static inline
void d_safe_assert_uint(const char* const assertion, const char* const file,
@@ -184,8 +199,8 @@ void d_safe_assert_uint(const char* const assertion, const char* const file,
d_stderr2("assertion failure: \"%s\" in file %s, line %i, value %u", assertion, file, line, value);
}

/*
* Print a safe assertion error message, with 2 extra signed integer values.
/**
Print a safe assertion error message, with 2 extra signed integer values.
*/
static inline
void d_safe_assert_int2(const char* const assertion, const char* const file,
@@ -194,8 +209,8 @@ void d_safe_assert_int2(const char* const assertion, const char* const file,
d_stderr2("assertion failure: \"%s\" in file %s, line %i, v1 %i, v2 %i", assertion, file, line, v1, v2);
}

/*
* Print a safe assertion error message, with 2 extra unsigned integer values.
/**
Print a safe assertion error message, with 2 extra unsigned integer values.
*/
static inline
void d_safe_assert_uint2(const char* const assertion, const char* const file,
@@ -204,8 +219,8 @@ void d_safe_assert_uint2(const char* const assertion, const char* const file,
d_stderr2("assertion failure: \"%s\" in file %s, line %i, v1 %u, v2 %u", assertion, file, line, v1, v2);
}

/*
* Print a safe assertion error message, with a custom error message.
/**
Print a safe assertion error message, with a custom error message.
*/
static inline
void d_custom_safe_assert(const char* const message, const char* const assertion, const char* const file,
@@ -214,8 +229,8 @@ void d_custom_safe_assert(const char* const message, const char* const assertion
d_stderr2("assertion failure: %s, condition \"%s\" in file %s, line %i", message, assertion, file, line);
}

/*
* Print a safe exception error message.
/**
Print a safe exception error message.
*/
static inline
void d_safe_exception(const char* const exception, const char* const file, const int line) noexcept
@@ -223,12 +238,20 @@ void d_safe_exception(const char* const exception, const char* const file, const
d_stderr2("exception caught: \"%s\" in file %s, line %i", exception, file, line);
}

// -----------------------------------------------------------------------
// math functions
/** @} */

/*
* Safely compare two floating point numbers.
* Returns true if they match.
/* ------------------------------------------------------------------------------------------------------------
* math functions */

/**
@defgroup MathFunctions Math related functions

@{
*/

/**
Safely compare two floating point numbers.
Returns true if they match.
*/
template<typename T>
static inline
@@ -237,9 +260,9 @@ bool d_isEqual(const T& v1, const T& v2)
return std::abs(v1-v2) < std::numeric_limits<T>::epsilon();
}

/*
* Safely compare two floating point numbers.
* Returns true if they don't match.
/**
Safely compare two floating point numbers.
Returns true if they don't match.
*/
template<typename T>
static inline
@@ -248,8 +271,8 @@ bool d_isNotEqual(const T& v1, const T& v2)
return std::abs(v1-v2) >= std::numeric_limits<T>::epsilon();
}

/*
* Safely check if a floating point number is zero.
/**
Safely check if a floating point number is zero.
*/
template<typename T>
static inline
@@ -258,8 +281,8 @@ bool d_isZero(const T& value)
return std::abs(value) < std::numeric_limits<T>::epsilon();
}

/*
* Safely check if a floating point number is not zero.
/**
Safely check if a floating point number is not zero.
*/
template<typename T>
static inline
@@ -268,8 +291,8 @@ bool d_isNotZero(const T& value)
return std::abs(value) >= std::numeric_limits<T>::epsilon();
}

/*
* Get next power of 2.
/**
Get next power of 2.
*/
static inline
uint32_t d_nextPowerOf2(uint32_t size) noexcept
@@ -286,6 +309,8 @@ uint32_t d_nextPowerOf2(uint32_t size) noexcept
return ++size;
}

/** @} */

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

#ifndef DONT_SET_USING_DISTRHO_NAMESPACE


+ 28
- 9
distrho/extra/ExternalWindow.hpp View File

@@ -295,7 +295,8 @@ public:
*/
void setSize(uint width, uint height)
{
DISTRHO_SAFE_ASSERT_UINT2_RETURN(width > 1 && height > 1, width, height,);
DISTRHO_SAFE_ASSERT_UINT_RETURN(width > 1, width,);
DISTRHO_SAFE_ASSERT_UINT_RETURN(height > 1, height,);

if (pData.width == width && pData.height == height)
return;
@@ -314,10 +315,24 @@ public:
{
if (pData.title == title)
return;

pData.title = title;
titleChanged(title);
}

/**
Set geometry constraints for the Window when resized by the user.
*/
void setGeometryConstraints(uint minimumWidth, uint minimumHeight, bool keepAspectRatio = false)
{
DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumWidth > 0, minimumWidth,);
DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumHeight > 0, minimumHeight,);

pData.minWidth = minimumWidth;
pData.minHeight = minimumHeight;
pData.keepAspectRatio = keepAspectRatio;
}

/* --------------------------------------------------------------------------------------------------------
* TopLevelWidget-like calls - actions called by the host */

@@ -339,6 +354,7 @@ public:
{
if (pData.visible == visible)
return;

pData.visible = visible;
visibilityChanged(visible);
}
@@ -351,6 +367,7 @@ public:
{
if (pData.transientWinId == winId)
return;

pData.transientWinId = winId;
transientParentWindowChanged(winId);
}
@@ -388,39 +405,35 @@ protected:
A callback for when the window size changes.
@note WIP this might need to get fed back into the host somehow.
*/
virtual void sizeChanged(uint width, uint height)
virtual void sizeChanged(uint /* width */, uint /* height */)
{
// unused, meant for custom implementations
return; (void)width; (void)height;
}

/**
A callback for when the window title changes.
@note WIP this might need to get fed back into the host somehow.
*/
virtual void titleChanged(const char* title)
virtual void titleChanged(const char* /* title */)
{
// unused, meant for custom implementations
return; (void)title;
}

/**
A callback for when the window visibility changes.
@note WIP this might need to get fed back into the host somehow.
*/
virtual void visibilityChanged(bool visible)
virtual void visibilityChanged(bool /* visible */)
{
// unused, meant for custom implementations
return; (void)visible;
}

/**
A callback for when the transient parent window changes.
*/
virtual void transientParentWindowChanged(uintptr_t winId)
virtual void transientParentWindowChanged(uintptr_t /* winId */)
{
// unused, meant for custom implementations
return; (void)winId;
}

private:
@@ -533,6 +546,9 @@ private:
uint height;
double scaleFactor;
String title;
uint minWidth;
uint minHeight;
bool keepAspectRatio;
bool isQuitting;
bool isStandalone;
bool visible;
@@ -544,6 +560,9 @@ private:
height(1),
scaleFactor(1.0),
title(),
minWidth(0),
minHeight(0),
keepAspectRatio(false),
isQuitting(false),
isStandalone(false),
visible(false) {}


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

@@ -0,0 +1,28 @@
/*
* DISTRHO Plugin Framework (DPF)
* 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
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED
#define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED

#include "../DistrhoUtils.hpp"

START_NAMESPACE_DISTRHO

#include "FileBrowserDialogImpl.hpp"

END_NAMESPACE_DISTRHO

#endif // DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED

+ 844
- 0
distrho/extra/FileBrowserDialogImpl.cpp View File

@@ -0,0 +1,844 @@
/*
* DISTRHO Plugin Framework (DPF)
* 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
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#if !defined(DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED) && !defined(DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED)
# error bad include
#endif
#if !defined(FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE) && !defined(FILE_BROWSER_DIALOG_DGL_NAMESPACE)
# error bad usage
#endif

#include "ScopedPointer.hpp"
#include "String.hpp"

#ifdef DISTRHO_OS_MAC
# import <Cocoa/Cocoa.h>
#endif
#ifdef DISTRHO_OS_WASM
# include <emscripten/emscripten.h>
#endif
#ifdef DISTRHO_OS_WINDOWS
# include <direct.h>
# include <process.h>
# include <winsock2.h>
# include <windows.h>
# include <commdlg.h>
# include <vector>
#else
# include <unistd.h>
#endif
#ifdef HAVE_DBUS
# include <dbus/dbus.h>
#endif
#ifdef HAVE_X11
# define DBLCLKTME 400
# include "sofd/libsofd.h"
# include "sofd/libsofd.c"
#endif

#ifdef FILE_BROWSER_DIALOG_DGL_NAMESPACE
START_NAMESPACE_DGL
using DISTRHO_NAMESPACE::ScopedPointer;
using DISTRHO_NAMESPACE::String;
#else
START_NAMESPACE_DISTRHO
#endif

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

// static pointer used for signal null/none action taken
static const char* const kSelectedFileCancelled = "__dpf_cancelled__";

#ifdef HAVE_DBUS
static constexpr bool isHexChar(const char c) noexcept
{
return c >= '0' && c <= 'f' && (c <= '9' || (c >= 'A' && c <= 'F') || c >= 'a');
}

static constexpr int toHexChar(const char c) noexcept
{
return c >= '0' && c <= '9' ? c - '0' : (c >= 'A' && c <= 'F' ? c - 'A' : c - 'a') + 10;
}
#endif

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

#ifdef DISTRHO_OS_WASM
# define DISTRHO_WASM_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION
# define DISTRHO_WASM_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_WASM_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION)
# define DISTRHO_WASM_NAMESPACE_HELPER(NS) #NS
# define DISTRHO_WASM_NAMESPACE(NS) DISTRHO_WASM_NAMESPACE_HELPER(NS)
# define fileBrowserSetPathNamespaced DISTRHO_WASM_NAMESPACE_MACRO(FILE_BROWSER_DIALOG_NAMESPACE, fileBrowserSetPath)
# define fileBrowserSetPathFuncName DISTRHO_WASM_NAMESPACE(FILE_BROWSER_DIALOG_NAMESPACE) "_fileBrowserSetPath"

// FIXME use world class name as prefix
static bool openWebBrowserFileDialog(const char* const funcname, void* const handle)
{
const char* const nameprefix = DISTRHO_WASM_NAMESPACE(FILE_BROWSER_DIALOG_NAMESPACE);

return EM_ASM_INT({
var canvasFileObjName = UTF8ToString($0) + "_file_open";
var canvasFileOpenElem = document.getElementById(canvasFileObjName);

var jsfuncname = UTF8ToString($1);
var jsfunc = Module.cwrap(jsfuncname, 'null', ['number', 'string']);

if (canvasFileOpenElem) {
document.body.removeChild(canvasFileOpenElem);
}

canvasFileOpenElem = document.createElement('input');
canvasFileOpenElem.type = 'file';
canvasFileOpenElem.id = canvasFileObjName;
canvasFileOpenElem.style.display = 'none';
document.body.appendChild(canvasFileOpenElem);

canvasFileOpenElem.onchange = function(e) {
if (!canvasFileOpenElem.files) {
jsfunc($2, "");
return;
}

var file = canvasFileOpenElem.files[0];
var filename = '/' + file.name;
var reader = new FileReader();

reader.onloadend = function(e) {
var content = new Uint8Array(reader.result);
Module.FS.writeFile(filename, content);
jsfunc($2, filename);
};

reader.readAsArrayBuffer(file);
};

canvasFileOpenElem.click();
return 1;
}, nameprefix, funcname, handle) != 0;
}

static bool downloadWebBrowserFile(const char* const filename)
{
const char* const nameprefix = DISTRHO_WASM_NAMESPACE(FILE_BROWSER_DIALOG_NAMESPACE);

return EM_ASM_INT({
var canvasFileObjName = UTF8ToString($0) + "_file_save";
var jsfilename = UTF8ToString($1);

var canvasFileSaveElem = document.getElementById(canvasFileObjName);
if (canvasFileSaveElem) {
// only 1 file save allowed at once
console.warn("One file save operation already in progress, refusing to open another");
return 0;
}

canvasFileSaveElem = document.createElement('a');
canvasFileSaveElem.download = jsfilename;
canvasFileSaveElem.id = canvasFileObjName;
canvasFileSaveElem.style.display = 'none';
document.body.appendChild(canvasFileSaveElem);

var content = Module.FS.readFile('/' + jsfilename);
canvasFileSaveElem.href = URL.createObjectURL(new Blob([content]));
canvasFileSaveElem.click();

setTimeout(function() {
URL.revokeObjectURL(canvasFileSaveElem.href);
document.body.removeChild(canvasFileSaveElem);
}, 2000);
return 1;
}, nameprefix, filename) != 0;
}
#endif

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

struct FileBrowserData {
const char* selectedFile;

#ifdef DISTRHO_OS_MAC
NSSavePanel* nsBasePanel;
NSOpenPanel* nsOpenPanel;
#endif
#ifdef HAVE_DBUS
DBusConnection* dbuscon;
#endif
#ifdef HAVE_X11
Display* x11display;
#endif

#ifdef DISTRHO_OS_WASM
char* defaultName;
bool saving;
#endif

#ifdef DISTRHO_OS_WINDOWS
OPENFILENAMEW ofn;
volatile bool threadCancelled;
uintptr_t threadHandle;
std::vector<WCHAR> fileNameW;
std::vector<WCHAR> startDirW;
std::vector<WCHAR> titleW;
const bool saving;
bool isEmbed;

FileBrowserData(const bool save)
: selectedFile(nullptr),
threadCancelled(false),
threadHandle(0),
fileNameW(32768),
saving(save),
isEmbed(false)
{
std::memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.lpstrFile = fileNameW.data();
ofn.nMaxFile = (DWORD)fileNameW.size();
}

~FileBrowserData()
{
if (cancelAndStop())
free();
}

void setupAndStart(const bool embed,
const char* const startDir,
const char* const windowTitle,
const uintptr_t winId,
const FileBrowserOptions options)
{
isEmbed = embed;

ofn.hwndOwner = (HWND)winId;

ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
ofn.Flags |= OFN_FORCESHOWHIDDEN;

ofn.FlagsEx = 0x0;
if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible)
ofn.FlagsEx |= OFN_EX_NOPLACESBAR;

startDirW.resize(std::strlen(startDir) + 1);
if (MultiByteToWideChar(CP_UTF8, 0, startDir, -1, startDirW.data(), static_cast<int>(startDirW.size())))
ofn.lpstrInitialDir = startDirW.data();

titleW.resize(std::strlen(windowTitle) + 1);
if (MultiByteToWideChar(CP_UTF8, 0, windowTitle, -1, titleW.data(), static_cast<int>(titleW.size())))
ofn.lpstrTitle = titleW.data();

uint threadId;
threadCancelled = false;
threadHandle = _beginthreadex(nullptr, 0, _run, this, 0, &threadId);
}

bool cancelAndStop()
{
threadCancelled = true;

if (threadHandle == 0)
return true;

// if previous dialog running, carefully close its window
const HWND owner = isEmbed ? GetParent(ofn.hwndOwner) : ofn.hwndOwner;

if (owner != nullptr && owner != INVALID_HANDLE_VALUE)
{
const HWND window = GetWindow(owner, GW_HWNDFIRST);

if (window != nullptr && window != INVALID_HANDLE_VALUE)
{
SendMessage(window, WM_SYSCOMMAND, SC_CLOSE, 0);
SendMessage(window, WM_CLOSE, 0, 0);
WaitForSingleObject((HANDLE)threadHandle, 5000);
}
}

if (threadHandle == 0)
return true;

// not good if thread still running, but let's close the handle anyway
CloseHandle((HANDLE)threadHandle);
threadHandle = 0;
return false;
}

void run()
{
const char* nextFile = nullptr;

if (saving ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn))
{
if (threadCancelled)
{
threadHandle = 0;
return;
}

// back to UTF-8
std::vector<char> fileNameA(4 * 32768);
if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1,
fileNameA.data(), (int)fileNameA.size(),
nullptr, nullptr))
{
nextFile = strdup(fileNameA.data());
}
}

if (threadCancelled)
{
threadHandle = 0;
return;
}

if (nextFile == nullptr)
nextFile = kSelectedFileCancelled;

selectedFile = nextFile;
threadHandle = 0;
}

static unsigned __stdcall _run(void* const arg)
{
// CoInitializeEx(nullptr, COINIT_MULTITHREADED);
static_cast<FileBrowserData*>(arg)->run();
// CoUninitialize();
_endthreadex(0);
return 0;
}
#else // DISTRHO_OS_WINDOWS
FileBrowserData(const bool save)
: selectedFile(nullptr)
{
#ifdef DISTRHO_OS_MAC
if (save)
{
nsOpenPanel = nullptr;
nsBasePanel = [[NSSavePanel savePanel]retain];
}
else
{
nsOpenPanel = [[NSOpenPanel openPanel]retain];
nsBasePanel = nsOpenPanel;
}
#endif
#ifdef DISTRHO_OS_WASM
defaultName = nullptr;
saving = save;
#endif
#ifdef HAVE_DBUS
if ((dbuscon = dbus_bus_get(DBUS_BUS_SESSION, nullptr)) != nullptr)
dbus_connection_set_exit_on_disconnect(dbuscon, false);
#endif
#ifdef HAVE_X11
x11display = XOpenDisplay(nullptr);
#endif

// maybe unused
return; (void)save;
}

~FileBrowserData()
{
#ifdef DISTRHO_OS_MAC
[nsBasePanel release];
#endif
#ifdef DISTRHO_OS_WASM
std::free(defaultName);
#endif
#ifdef HAVE_DBUS
if (dbuscon != nullptr)
dbus_connection_unref(dbuscon);
#endif
#ifdef HAVE_X11
if (x11display != nullptr)
XCloseDisplay(x11display);
#endif

free();
}
#endif

void free()
{
if (selectedFile == nullptr)
return;

if (selectedFile == kSelectedFileCancelled || std::strcmp(selectedFile, kSelectedFileCancelled) == 0)
{
selectedFile = nullptr;
return;
}

std::free(const_cast<char*>(selectedFile));
selectedFile = nullptr;
}
};

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

#ifdef DISTRHO_OS_WASM
extern "C" {
EMSCRIPTEN_KEEPALIVE
void fileBrowserSetPathNamespaced(FileBrowserHandle handle, const char* filename)
{
handle->free();

if (filename != nullptr && filename[0] != '\0')
handle->selectedFile = strdup(filename);
else
handle->selectedFile = kSelectedFileCancelled;
}
}
#endif

FileBrowserHandle fileBrowserCreate(const bool isEmbed,
const uintptr_t windowId,
const double scaleFactor,
const FileBrowserOptions& options)
{
String startDir(options.startDir);

if (startDir.isEmpty())
{
#ifdef DISTRHO_OS_WINDOWS
if (char* const cwd = _getcwd(nullptr, 0))
#else
if (char* const cwd = getcwd(nullptr, 0))
#endif
{
startDir = cwd;
std::free(cwd);
}
}

DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), nullptr);

if (! startDir.endsWith(DISTRHO_OS_SEP))
startDir += DISTRHO_OS_SEP_STR;

String windowTitle(options.title);

if (windowTitle.isEmpty())
windowTitle = "FileBrowser";

ScopedPointer<FileBrowserData> handle(new FileBrowserData(options.saving));

#ifdef DISTRHO_OS_MAC
# if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
// unsupported
d_stderr2("fileBrowserCreate is unsupported on macos < 10.8");
return nullptr;
# else
NSSavePanel* const nsBasePanel = handle->nsBasePanel;
DISTRHO_SAFE_ASSERT_RETURN(nsBasePanel != nullptr, nullptr);

if (! options.saving)
{
NSOpenPanel* const nsOpenPanel = handle->nsOpenPanel;
DISTRHO_SAFE_ASSERT_RETURN(nsOpenPanel != nullptr, nullptr);

[nsOpenPanel setAllowsMultipleSelection:NO];
[nsOpenPanel setCanChooseDirectories:NO];
[nsOpenPanel setCanChooseFiles:YES];
}

[nsBasePanel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]];

// TODO file filter using allowedContentTypes: [UTType]

if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
[nsBasePanel setAllowsOtherFileTypes:YES];
if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
[nsBasePanel setShowsHiddenFiles:YES];

NSString* const titleString = [[NSString alloc]
initWithBytes:windowTitle
length:strlen(windowTitle)
encoding:NSUTF8StringEncoding];
[nsBasePanel setTitle:titleString];

FileBrowserData* const handleptr = handle.get();

dispatch_async(dispatch_get_main_queue(), ^
{
[nsBasePanel beginSheetModalForWindow:[(NSView*)windowId window]
completionHandler:^(NSModalResponse result)
{
if (result == NSModalResponseOK && [[nsBasePanel URL] isFileURL])
{
NSString* const path = [[nsBasePanel URL] path];
handleptr->selectedFile = strdup([path UTF8String]);
}
else
{
handleptr->selectedFile = kSelectedFileCancelled;
}
}];
});
# endif
#endif

#ifdef DISTRHO_OS_WASM
if (options.saving)
{
const size_t len = options.defaultName != nullptr ? strlen(options.defaultName) : 0;
DISTRHO_SAFE_ASSERT_RETURN(len != 0, nullptr);

char* const filename = static_cast<char*>(malloc(len + 2));
filename[0] = '/';
std::memcpy(filename + 1, options.defaultName, len + 1);

handle->defaultName = strdup(options.defaultName);
handle->selectedFile = filename;
return handle.release();
}

const char* const funcname = fileBrowserSetPathFuncName;
if (openWebBrowserFileDialog(funcname, handle.get()))
return handle.release();

return nullptr;
#endif

#ifdef DISTRHO_OS_WINDOWS
handle->setupAndStart(isEmbed, startDir, windowTitle, windowId, options);
#endif

#ifdef HAVE_DBUS
// optional, can be null
DBusConnection* const dbuscon = handle->dbuscon;

// https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.portal.FileChooser
if (dbuscon != nullptr)
{
// if this is the first time we are calling into DBus, check if things are working
static bool checkAvailable = !dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr);

if (checkAvailable)
{
checkAvailable = false;

if (DBusMessage* const msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.FileChooser",
"version"))
{
if (DBusMessage* const reply = dbus_connection_send_with_reply_and_block(dbuscon, msg, 250, nullptr))
dbus_message_unref(reply);

dbus_message_unref(msg);
}
}

// Any subsquent calls should have this DBus service active
if (dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr))
{
if (DBusMessage* const msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.FileChooser",
options.saving ? "SaveFile" : "OpenFile"))
{
#ifdef HAVE_X11
char windowIdStr[32];
memset(windowIdStr, 0, sizeof(windowIdStr));
snprintf(windowIdStr, sizeof(windowIdStr)-1, "x11:%llx", (ulonglong)windowId);
const char* windowIdStrPtr = windowIdStr;
#endif

dbus_message_append_args(msg,
#ifdef HAVE_X11
DBUS_TYPE_STRING, &windowIdStrPtr,
#endif
DBUS_TYPE_STRING, &windowTitle,
DBUS_TYPE_INVALID);

DBusMessageIter iter, array;
dbus_message_iter_init_append(msg, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array);

{
DBusMessageIter dict, variant, variantArray;
const char* const current_folder_key = "current_folder";
const char* const current_folder_val = startDir.buffer();

dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, nullptr, &dict);
dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &current_folder_key);
dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "ay", &variant);
dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "y", &variantArray);
dbus_message_iter_append_fixed_array(&variantArray, DBUS_TYPE_BYTE,
&current_folder_val, startDir.length()+1);
dbus_message_iter_close_container(&variant, &variantArray);
dbus_message_iter_close_container(&dict, &variant);
dbus_message_iter_close_container(&array, &dict);
}

dbus_message_iter_close_container(&iter, &array);

dbus_connection_send(dbuscon, msg, nullptr);

dbus_message_unref(msg);
return handle.release();
}
}
}
#endif

#ifdef HAVE_X11
Display* const x11display = handle->x11display;
DISTRHO_SAFE_ASSERT_RETURN(x11display != nullptr, nullptr);

// unsupported at the moment
if (options.saving)
return nullptr;

DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, nullptr);
DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, windowTitle) == 0, nullptr);

const int button1 = options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked ? 1
: options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
const int button2 = options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked ? 1
: options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;
const int button3 = options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked ? 1
: options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1;

x_fib_cfg_buttons(1, button1);
x_fib_cfg_buttons(2, button2);
x_fib_cfg_buttons(3, button3);

if (x_fib_show(x11display, windowId, 0, 0, scaleFactor + 0.5) != 0)
return nullptr;
#endif

return handle.release();

// might be unused
(void)isEmbed;
(void)windowId;
(void)scaleFactor;
}

// --------------------------------------------------------------------------------------------------------------------
// returns true if dialog was closed (with or without a file selection)

bool fileBrowserIdle(const FileBrowserHandle handle)
{
#ifdef HAVE_DBUS
if (DBusConnection* dbuscon = handle->dbuscon)
{
while (dbus_connection_dispatch(dbuscon) == DBUS_DISPATCH_DATA_REMAINS) {}
dbus_connection_read_write_dispatch(dbuscon, 0);

if (DBusMessage* const message = dbus_connection_pop_message(dbuscon))
{
const char* const interface = dbus_message_get_interface(message);
const char* const member = dbus_message_get_member(message);

if (interface != nullptr && std::strcmp(interface, "org.freedesktop.portal.Request") == 0
&& member != nullptr && std::strcmp(member, "Response") == 0)
{
do {
DBusMessageIter iter;
dbus_message_iter_init(message, &iter);

// starts with uint32 for return/exit code
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32);

uint32_t ret = 1;
dbus_message_iter_get_basic(&iter, &ret);

if (ret != 0)
break;

// next must be array
dbus_message_iter_next(&iter);
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY);

// open dict array
DBusMessageIter dictArray;
dbus_message_iter_recurse(&iter, &dictArray);
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY);

// open containing dict
DBusMessageIter dict;
dbus_message_iter_recurse(&dictArray, &dict);

// look for dict with string "uris"
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING);

const char* key = nullptr;
dbus_message_iter_get_basic(&dict, &key);
DISTRHO_SAFE_ASSERT_BREAK(key != nullptr);

// keep going until we find it
while (std::strcmp(key, "uris") != 0)
{
key = nullptr;
dbus_message_iter_next(&dictArray);
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY);

dbus_message_iter_recurse(&dictArray, &dict);
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING);

dbus_message_iter_get_basic(&dict, &key);
DISTRHO_SAFE_ASSERT_BREAK(key != nullptr);
}

if (key == nullptr)
break;

// then comes variant
dbus_message_iter_next(&dict);
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_VARIANT);

DBusMessageIter variant;
dbus_message_iter_recurse(&dict, &variant);
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_ARRAY);

// open variant array (variant type is string)
DBusMessageIter variantArray;
dbus_message_iter_recurse(&variant, &variantArray);
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variantArray) == DBUS_TYPE_STRING);

const char* value = nullptr;
dbus_message_iter_get_basic(&variantArray, &value);

// and finally we have our dear value, just make sure it is local
DISTRHO_SAFE_ASSERT_BREAK(value != nullptr);

if (const char* const localvalue = std::strstr(value, "file:///"))
{
if (char* const decodedvalue = strdup(localvalue + 7))
{
for (char* s = decodedvalue; (s = std::strchr(s, '%')) != nullptr; ++s)
{
if (! isHexChar(s[1]) || ! isHexChar(s[2]))
continue;

const int decodedNum = toHexChar(s[1]) * 0x10 + toHexChar(s[2]);

char replacementChar;
switch (decodedNum)
{
case 0x20: replacementChar = ' '; break;
case 0x22: replacementChar = '\"'; break;
case 0x23: replacementChar = '#'; break;
case 0x25: replacementChar = '%'; break;
case 0x3c: replacementChar = '<'; break;
case 0x3e: replacementChar = '>'; break;
case 0x5b: replacementChar = '['; break;
case 0x5c: replacementChar = '\\'; break;
case 0x5d: replacementChar = ']'; break;
case 0x5e: replacementChar = '^'; break;
case 0x60: replacementChar = '`'; break;
case 0x7b: replacementChar = '{'; break;
case 0x7c: replacementChar = '|'; break;
case 0x7d: replacementChar = '}'; break;
case 0x7e: replacementChar = '~'; break;
default: continue;
}

s[0] = replacementChar;
std::memmove(s + 1, s + 3, std::strlen(s) - 2);
}

handle->selectedFile = decodedvalue;
}
}

} while(false);

if (handle->selectedFile == nullptr)
handle->selectedFile = kSelectedFileCancelled;
}
}
}
#endif

#ifdef HAVE_X11
Display* const x11display = handle->x11display;

if (x11display == nullptr)
return false;

XEvent event;
while (XPending(x11display) > 0)
{
XNextEvent(x11display, &event);

if (x_fib_handle_events(x11display, &event) == 0)
continue;

if (x_fib_status() > 0)
handle->selectedFile = x_fib_filename();
else
handle->selectedFile = kSelectedFileCancelled;

x_fib_close(x11display);
XCloseDisplay(x11display);
handle->x11display = nullptr;
break;
}
#endif

return handle->selectedFile != nullptr;
}

// --------------------------------------------------------------------------------------------------------------------
// close sofd file dialog

void fileBrowserClose(const FileBrowserHandle handle)
{
#ifdef DISTRHO_OS_WASM
if (handle->saving && fileBrowserGetPath(handle) != nullptr)
downloadWebBrowserFile(handle->defaultName);
#endif

#ifdef HAVE_X11
if (Display* const x11display = handle->x11display)
x_fib_close(x11display);
#endif

delete handle;
}

// --------------------------------------------------------------------------------------------------------------------
// get path chosen via sofd file dialog

const char* fileBrowserGetPath(const FileBrowserHandle handle)
{
if (const char* const selectedFile = handle->selectedFile)
if (selectedFile != kSelectedFileCancelled && std::strcmp(selectedFile, kSelectedFileCancelled) != 0)
return selectedFile;

return nullptr;
}

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

#ifdef FILE_BROWSER_DIALOG_DGL_NAMESPACE
END_NAMESPACE_DGL
#else
END_NAMESPACE_DISTRHO
#endif

#undef FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE
#undef FILE_BROWSER_DIALOG_DGL_NAMESPACE
#undef FILE_BROWSER_DIALOG_NAMESPACE

#undef fileBrowserSetPathNamespaced
#undef fileBrowserSetPathFuncName

+ 121
- 0
distrho/extra/FileBrowserDialogImpl.hpp View File

@@ -0,0 +1,121 @@
/*
* DISTRHO Plugin Framework (DPF)
* 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
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#if !defined(DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED) && !defined(DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED)
# error bad include
#endif

// --------------------------------------------------------------------------------------------------------------------
// File Browser Dialog stuff

struct FileBrowserData;
typedef FileBrowserData* FileBrowserHandle;

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

/**
File browser options, for customizing the file browser dialog.@n
By default the file browser dialog will be work as "open file" in the current working directory.
*/
struct FileBrowserOptions {
/** Whether we are saving, opening files otherwise (default) */
bool saving;

/** Default filename when saving, required in some platforms (basename without path separators) */
const char* defaultName;

/** Start directory, uses current working directory if null */
const char* startDir;

/** File browser dialog window title, uses "FileBrowser" if null */
const char* title;

// TODO file filter

/**
File browser button state.
This allows to customize the behaviour of the file browse dialog buttons.
Note these are merely hints, not all systems support them.
*/
enum ButtonState {
kButtonInvisible,
kButtonVisibleUnchecked,
kButtonVisibleChecked,
};

/**
File browser buttons.
*/
struct Buttons {
/** Whether to list all files vs only those with matching file extension */
ButtonState listAllFiles;
/** Whether to show hidden files */
ButtonState showHidden;
/** Whether to show list of places (bookmarks) */
ButtonState showPlaces;

/** Constructor for default values */
Buttons()
: listAllFiles(kButtonVisibleChecked),
showHidden(kButtonVisibleUnchecked),
showPlaces(kButtonVisibleChecked) {}
} buttons;

/** Constructor for default values */
FileBrowserOptions()
: saving(false),
defaultName(nullptr),
startDir(nullptr),
title(nullptr),
buttons() {}
};

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

/**
Create a new file browser dialog.

@p isEmbed: Whether the window this dialog belongs to is an embed/child window (needed to close dialog on Windows)
@p windowId: The native window id to attach this dialog to as transient parent (X11 Window, HWND or NSView*)
@p scaleFactor: Scale factor to use (only used on X11)
@p options: Extra options, optional
By default the file browser dialog will be work as "open file" in the current working directory.
*/
FileBrowserHandle fileBrowserCreate(bool isEmbed,
uintptr_t windowId,
double scaleFactor,
const FileBrowserOptions& options = FileBrowserOptions());

/**
Idle the file browser dialog handle.@n
Returns true if dialog was closed (with or without a file selection),
in which case the handle must not be used afterwards.
You can then call fileBrowserGetPath to know the selected file (or null if cancelled).
*/
bool fileBrowserIdle(const FileBrowserHandle handle);

/**
Close the file browser dialog, handle must not be used afterwards.
*/
void fileBrowserClose(const FileBrowserHandle handle);

/**
Get the path chosen by the user or null.@n
Should only be called after fileBrowserIdle returns true.
*/
const char* fileBrowserGetPath(const FileBrowserHandle handle);

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

+ 19
- 1
distrho/extra/LeakDetector.hpp View File

@@ -23,7 +23,25 @@ START_NAMESPACE_DISTRHO

// -----------------------------------------------------------------------
// The following code was based from juce-core LeakDetector class
// Copyright (C) 2013 Raw Material Software Ltd.

/**
Copyright (C) 2013 Raw Material Software Ltd.

Permission is granted to use this software under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license/

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
OF THIS SOFTWARE.
*/

/** A good old-fashioned C macro concatenation helper.
This combines two items (which may themselves be macros) into a single string,


+ 23
- 2
distrho/extra/RingBuffer.hpp View File

@@ -203,11 +203,23 @@ public:
/*
* Get the size of the data available to read.
*/
uint32_t getAvailableDataSize() const noexcept
uint32_t getReadableDataSize() const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, 0);

const uint32_t wrap((buffer->tail > buffer->wrtn) ? 0 : buffer->size);
const uint32_t wrap = buffer->head > buffer->tail ? 0 : buffer->size;

return wrap + buffer->head - buffer->tail;
}

/*
* Get the size of the data available to write.
*/
uint32_t getWritableDataSize() const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, 0);

const uint32_t wrap = (buffer->tail > buffer->wrtn) ? 0 : buffer->size;

return wrap + buffer->tail - buffer->wrtn;
}
@@ -724,6 +736,15 @@ public:
heapBuffer.size = 0;
}

void copyFromAndClearOther(HeapRingBuffer& other)
{
DISTRHO_SAFE_ASSERT_RETURN(other.heapBuffer.size == heapBuffer.size,);

std::memcpy(&heapBuffer, &other.heapBuffer, sizeof(HeapBuffer) - sizeof(uint8_t*));
std::memcpy(heapBuffer.buf, other.heapBuffer.buf, sizeof(uint8_t) * heapBuffer.size);
other.clearData();
}

private:
/** The heap buffer used for this class. */
HeapBuffer heapBuffer;


+ 251
- 0
distrho/extra/Runner.hpp View File

@@ -0,0 +1,251 @@
/*
* DISTRHO Plugin Framework (DPF)
* 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
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef DISTRHO_RUNNER_HPP_INCLUDED
#define DISTRHO_RUNNER_HPP_INCLUDED

#include "../DistrhoUtils.hpp"

#ifndef DISTRHO_OS_WASM
# include "Thread.hpp"
#else
# include "String.hpp"
# include <emscripten/html5.h>
#endif

START_NAMESPACE_DISTRHO

#ifdef DISTRHO_RUNNER_INDIRECT_WASM_CALLS
long d_emscripten_set_interval(void (*)(void*), double, void*);
void d_emscripten_clear_interval(long);
#else
# define d_emscripten_set_interval emscripten_set_interval
# define d_emscripten_clear_interval emscripten_clear_interval
#endif

// -------------------------------------------------------------------------------------------------------------------
// Runner class

/**
Runner class for DPF.

This is a handy class that handles "idle" time in either background or main thread,
whichever is more suitable to the target platform.
Typically background threads on desktop platforms, main thread on web.

A single function is expected to be implemented by subclasses,
which directly allows it to stop the runner by returning false.

You can use it for quick operations that do not need to be handled in the main thread if possible.
The target is to spread out execution over many runs, instead of spending a lot of time on a single task.
*/
class Runner
{
protected:
/*
* Constructor.
*/
Runner(const char* const runnerName = nullptr) noexcept
#ifndef DISTRHO_OS_WASM
: fRunnerThread(this, runnerName),
fTimeInterval(0)
#else
: fRunnerName(runnerName),
fIntervalId(0)
#endif
{
}

/*
* Destructor.
*/
virtual ~Runner() /*noexcept*/
{
DISTRHO_SAFE_ASSERT(! isRunnerActive());

stopRunner();
}

/*
* Virtual function to be implemented by the subclass.
* Return true to keep running, false to stop execution.
*/
virtual bool run() = 0;

/*
* Check if the runner should stop.
* To be called from inside the runner to know if a stop request has been made.
*/
bool shouldRunnerStop() const noexcept
{
#ifndef DISTRHO_OS_WASM
return fRunnerThread.shouldThreadExit();
#else
return fIntervalId == 0;
#endif
}

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

public:
/*
* Check if the runner is active.
*/
bool isRunnerActive() noexcept
{
#ifndef DISTRHO_OS_WASM
return fRunnerThread.isThreadRunning();
#else
return fIntervalId != 0;
#endif
}

/*
* Start the thread.
*/
bool startRunner(const uint timeIntervalMilliseconds = 0) noexcept
{
#ifndef DISTRHO_OS_WASM
DISTRHO_SAFE_ASSERT_RETURN(!fRunnerThread.isThreadRunning(), false);
fTimeInterval = timeIntervalMilliseconds;
return fRunnerThread.startThread();
#else
DISTRHO_SAFE_ASSERT_RETURN(fIntervalId == 0, false);
fIntervalId = d_emscripten_set_interval(_entryPoint, timeIntervalMilliseconds, this);
return true;
#endif
}

/*
* Stop the runner.
* This will signal the runner to stop if active, and wait until it finishes.
*/
bool stopRunner() noexcept
{
#ifndef DISTRHO_OS_WASM
return fRunnerThread.stopThread(-1);
#else
signalRunnerShouldStop();
return true;
#endif
}

/*
* Tell the runner to stop as soon as possible.
*/
void signalRunnerShouldStop() noexcept
{
#ifndef DISTRHO_OS_WASM
fRunnerThread.signalThreadShouldExit();
#else
if (fIntervalId != 0)
{
d_emscripten_clear_interval(fIntervalId);
fIntervalId = 0;
}
#endif
}

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

/*
* Returns the name of the runner.
* This is the name that gets set in the constructor.
*/
const String& getRunnerName() const noexcept
{
#ifndef DISTRHO_OS_WASM
return fRunnerThread.getThreadName();
#else
return fRunnerName;
#endif
}

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

private:
#ifndef DISTRHO_OS_WASM
class RunnerThread : public Thread
{
Runner* const runner;

public:
RunnerThread(Runner* const r, const char* const rn)
: Thread(rn),
runner(r) {}

protected:
void run() override
{
const uint timeInterval = runner->fTimeInterval;

while (!shouldThreadExit())
{
bool stillRunning = false;

try {
stillRunning = runner->run();
} catch(...) {}

if (stillRunning && !shouldThreadExit())
{
if (timeInterval != 0)
d_msleep(timeInterval);

// FIXME
// pthread_yield();
continue;
}

break;
}
}
} fRunnerThread;

uint fTimeInterval;
#else
const String fRunnerName;
long fIntervalId;

void _runEntryPoint() noexcept
{
bool stillRunning = false;

try {
stillRunning = run();
} catch(...) {}

if (fIntervalId != 0 && !stillRunning)
{
d_emscripten_clear_interval(fIntervalId);
fIntervalId = 0;
}
}

static void _entryPoint(void* const userData) noexcept
{
static_cast<Runner*>(userData)->_runEntryPoint();
}
#endif

DISTRHO_DECLARE_NON_COPYABLE(Runner)
};

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

END_NAMESPACE_DISTRHO

#endif // DISTRHO_RUNNER_HPP_INCLUDED

+ 19
- 1
distrho/extra/ScopedPointer.hpp View File

@@ -25,7 +25,25 @@ START_NAMESPACE_DISTRHO

// -----------------------------------------------------------------------
// The following code was based from juce-core ScopedPointer class
// Copyright (C) 2013 Raw Material Software Ltd.

/**
Copyright (C) 2013 Raw Material Software Ltd.

Permission is granted to use this software under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license/

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
OF THIS SOFTWARE.
*/

//==============================================================================
/**


+ 38
- 2
distrho/extra/String.hpp View File

@@ -516,7 +516,7 @@ public:
*/
String& replace(const char before, const char after) noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(before != '\0' && after != '\0', *this);
DISTRHO_SAFE_ASSERT_RETURN(before != '\0' /* && after != '\0' */, *this);

for (std::size_t i=0; i < fBufferLen; ++i)
{
@@ -618,6 +618,36 @@ public:
return *this;
}

/*
* Create a new string where all non-basic characters are converted to '_'.
* @see toBasic()
*/
String asBasic() const noexcept
{
String s(*this);
return s.toBasic();
}

/*
* Create a new string where all ascii characters are converted lowercase.
* @see toLower()
*/
String asLower() const noexcept
{
String s(*this);
return s.toLower();
}

/*
* Create a new string where all ascii characters are converted to uppercase.
* @see toUpper()
*/
String asUpper() const noexcept
{
String s(*this);
return s.toUpper();
}

/*
* Direct access to the string buffer (read-only).
*/
@@ -831,7 +861,7 @@ public:
std::memcpy(newBuf, fBuffer, fBufferLen);
std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1);

return String(newBuf);
return String(newBuf, false);
}

String operator+(const String& str) noexcept
@@ -839,6 +869,12 @@ public:
return operator+(str.fBuffer);
}

// needed for std::map compatibility
bool operator<(const String& str) const noexcept
{
return std::strcmp(fBuffer, str.fBuffer) < 0;
}

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

private:


+ 4
- 0
distrho/extra/Thread.hpp View File

@@ -25,6 +25,10 @@
# include <sys/prctl.h>
#endif

#ifdef DISTRHO_OS_WASM
# error Threads do not work under wasm!
#endif

START_NAMESPACE_DISTRHO

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


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

@@ -338,7 +338,6 @@ const char *x_fib_recent_file(const char *appname) {
}

#ifdef HAVE_X11
#include <mntent.h>
#include <dirent.h>

#include <X11/Xlib.h>
@@ -347,6 +346,11 @@ const char *x_fib_recent_file(const char *appname) {
#include <X11/keysym.h>
#include <X11/Xos.h>

#if defined(__linux__) || defined(__linux)
#define HAVE_MNTENT
#include <mntent.h>
#endif

#ifndef MIN
#define MIN(A,B) ( (A) < (B) ? (A) : (B) )
#endif
@@ -495,7 +499,9 @@ static int query_font_geometry (Display *dpy, GC gc, const char *txt, int *w, in
if (h) *h = text_structure.ascent + text_structure.descent;
if (a) *a = text_structure.ascent;
if (d) *d = text_structure.descent;
#ifndef DISTRHO_OS_HAIKU // FIXME
XFreeFontInfo (NULL, fontinfo, 1);
#endif
return 0;
}

@@ -1766,6 +1772,7 @@ static int parse_gtk_bookmarks (Display *dpy, const char *fn) {
return found;
}

#ifdef HAVE_MNTENT
static const char *ignore_mountpoints[] = {
"/bin", "/boot", "/dev", "/etc",
"/lib", "/live", "/mnt", "/opt",
@@ -1840,6 +1847,7 @@ static int read_mtab (Display *dpy, const char *mtab) {
fclose (mt);
return found;
}
#endif

static void populate_places (Display *dpy) {
char tmp[1024];
@@ -1868,9 +1876,11 @@ static void populate_places (Display *dpy) {
parse_gtk_bookmarks (dpy, _fib_cfg_custom_places);
}

#ifdef HAVE_MNTENT
if (read_mtab (dpy, "/proc/mounts") < 1) {
read_mtab (dpy, "/etc/mtab");
}
#endif

int parsed_bookmarks = 0;
if (!parsed_bookmarks && getenv ("HOME")) {

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


+ 50
- 29
distrho/src/DistrhoDefines.h View File

@@ -27,10 +27,12 @@

/* Check OS */
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
# define DISTRHO_API
# define DISTRHO_PLUGIN_EXPORT extern "C" __declspec (dllexport)
# define DISTRHO_OS_WINDOWS 1
# define DISTRHO_DLL_EXTENSION "dll"
#else
# define DISTRHO_API
# define DISTRHO_PLUGIN_EXPORT extern "C" __attribute__ ((visibility("default")))
# if defined(__APPLE__)
# define DISTRHO_OS_MAC 1
@@ -43,6 +45,8 @@
# define DISTRHO_OS_BSD 1
# elif defined(__GNU__)
# define DISTRHO_OS_GNU_HURD 1
# elif defined(__EMSCRIPTEN__)
# define DISTRHO_OS_WASM 1
# endif
#endif

@@ -71,6 +75,13 @@
# define nullptr NULL
#endif

/* Define unlikely */
#ifdef __GNUC__
# define unlikely(x) __builtin_expect(x,0)
#else
# define unlikely(x) x
#endif

/* Define DISTRHO_DEPRECATED */
#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 480
# define DISTRHO_DEPRECATED __attribute__((deprecated))
@@ -81,49 +92,49 @@
#endif

/* Define DISTRHO_DEPRECATED_BY */
#if defined(__clang__) && defined(DISTRHO_PROPER_CPP11_SUPPORT)
#if defined(__clang__) && (__clang_major__ * 100 + __clang_minor__) >= 502
# define DISTRHO_DEPRECATED_BY(other) __attribute__((deprecated("", other)))
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 480
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408
# define DISTRHO_DEPRECATED_BY(other) __attribute__((deprecated("Use " other)))
#else
# define DISTRHO_DEPRECATED_BY(other) DISTRHO_DEPRECATED
#endif

/* Define DISTRHO_SAFE_ASSERT* */
#define DISTRHO_SAFE_ASSERT(cond) if (! (cond)) d_safe_assert (#cond, __FILE__, __LINE__);
#define DISTRHO_SAFE_ASSERT_INT(cond, value) if (! (cond)) d_safe_assert_int (#cond, __FILE__, __LINE__, static_cast<int>(value));
#define DISTRHO_SAFE_ASSERT_INT2(cond, v1, v2) if (! (cond)) d_safe_assert_int2 (#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2));
#define DISTRHO_SAFE_ASSERT_UINT(cond, value) if (! (cond)) d_safe_assert_uint (#cond, __FILE__, __LINE__, static_cast<uint>(value));
#define DISTRHO_SAFE_ASSERT_UINT2(cond, v1, v2) if (! (cond)) d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2));
#define DISTRHO_SAFE_ASSERT(cond) if (unlikely(!(cond))) d_safe_assert (#cond, __FILE__, __LINE__);
#define DISTRHO_SAFE_ASSERT_INT(cond, value) if (unlikely(!(cond))) d_safe_assert_int (#cond, __FILE__, __LINE__, static_cast<int>(value));
#define DISTRHO_SAFE_ASSERT_INT2(cond, v1, v2) if (unlikely(!(cond))) d_safe_assert_int2 (#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2));
#define DISTRHO_SAFE_ASSERT_UINT(cond, value) if (unlikely(!(cond))) d_safe_assert_uint (#cond, __FILE__, __LINE__, static_cast<uint>(value));
#define DISTRHO_SAFE_ASSERT_UINT2(cond, v1, v2) if (unlikely(!(cond))) d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2));

#define DISTRHO_SAFE_ASSERT_BREAK(cond) if (! (cond)) { d_safe_assert(#cond, __FILE__, __LINE__); break; }
#define DISTRHO_SAFE_ASSERT_CONTINUE(cond) if (! (cond)) { d_safe_assert(#cond, __FILE__, __LINE__); continue; }
#define DISTRHO_SAFE_ASSERT_RETURN(cond, ret) if (! (cond)) { d_safe_assert(#cond, __FILE__, __LINE__); return ret; }
#define DISTRHO_SAFE_ASSERT_BREAK(cond) if (unlikely(!(cond))) { d_safe_assert(#cond, __FILE__, __LINE__); break; }
#define DISTRHO_SAFE_ASSERT_CONTINUE(cond) if (unlikely(!(cond))) { d_safe_assert(#cond, __FILE__, __LINE__); continue; }
#define DISTRHO_SAFE_ASSERT_RETURN(cond, ret) if (unlikely(!(cond))) { d_safe_assert(#cond, __FILE__, __LINE__); return ret; }

#define DISTRHO_CUSTOM_SAFE_ASSERT(msg, cond) if (! (cond)) d_custom_safe_assert(msg, #cond, __FILE__, __LINE__);
#define DISTRHO_CUSTOM_SAFE_ASSERT_BREAK(msg, cond) if (! (cond)) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); break; }
#define DISTRHO_CUSTOM_SAFE_ASSERT_CONTINUE(msg, cond) if (! (cond)) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); continue; }
#define DISTRHO_CUSTOM_SAFE_ASSERT_RETURN(msg, cond, ret) if (! (cond)) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); return ret; }
#define DISTRHO_CUSTOM_SAFE_ASSERT(msg, cond) if (unlikely(!(cond))) d_custom_safe_assert(msg, #cond, __FILE__, __LINE__);
#define DISTRHO_CUSTOM_SAFE_ASSERT_BREAK(msg, cond) if (unlikely(!(cond))) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); break; }
#define DISTRHO_CUSTOM_SAFE_ASSERT_CONTINUE(msg, cond) if (unlikely(!(cond))) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); continue; }
#define DISTRHO_CUSTOM_SAFE_ASSERT_RETURN(msg, cond, ret) if (unlikely(!(cond))) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); return ret; }

#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_BREAK(msg, cond) if (! (cond)) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } break; }
#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_CONTINUE(msg, cond) if (! (cond)) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } continue; }
#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN(msg, cond, ret) if (! (cond)) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } return ret; }
#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_BREAK(msg, cond) if (unlikely(!(cond))) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } break; }
#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_CONTINUE(msg, cond) if (unlikely(!(cond))) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } continue; }
#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN(msg, cond, ret) if (unlikely(!(cond))) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } return ret; }

#define DISTRHO_SAFE_ASSERT_INT_BREAK(cond, value) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value); break; }
#define DISTRHO_SAFE_ASSERT_INT_CONTINUE(cond, value) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); continue; }
#define DISTRHO_SAFE_ASSERT_INT_RETURN(cond, value, ret) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); return ret; }
#define DISTRHO_SAFE_ASSERT_INT_BREAK(cond, value) if (unlikely(!(cond))) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); break; }
#define DISTRHO_SAFE_ASSERT_INT_CONTINUE(cond, value) if (unlikely(!(cond))) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); continue; }
#define DISTRHO_SAFE_ASSERT_INT_RETURN(cond, value, ret) if (unlikely(!(cond))) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); return ret; }

#define DISTRHO_SAFE_ASSERT_INT2_BREAK(cond, v1, v2) if (! (cond)) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); break; }
#define DISTRHO_SAFE_ASSERT_INT2_CONTINUE(cond, v1, v2) if (! (cond)) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); continue; }
#define DISTRHO_SAFE_ASSERT_INT2_RETURN(cond, v1, v2, ret) if (! (cond)) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); return ret; }
#define DISTRHO_SAFE_ASSERT_INT2_BREAK(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); break; }
#define DISTRHO_SAFE_ASSERT_INT2_CONTINUE(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); continue; }
#define DISTRHO_SAFE_ASSERT_INT2_RETURN(cond, v1, v2, ret) if (unlikely(!(cond))) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); return ret; }

#define DISTRHO_SAFE_ASSERT_UINT_BREAK(cond, value) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value); break; }
#define DISTRHO_SAFE_ASSERT_UINT_CONTINUE(cond, value) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); continue; }
#define DISTRHO_SAFE_ASSERT_UINT_RETURN(cond, value, ret) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); return ret; }
#define DISTRHO_SAFE_ASSERT_UINT_BREAK(cond, value) if (unlikely(!(cond))) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); break; }
#define DISTRHO_SAFE_ASSERT_UINT_CONTINUE(cond, value) if (unlikely(!(cond))) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); continue; }
#define DISTRHO_SAFE_ASSERT_UINT_RETURN(cond, value, ret) if (unlikely(!(cond))) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); return ret; }

#define DISTRHO_SAFE_ASSERT_UINT2_BREAK(cond, v1, v2) if (! (cond)) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); break; }
#define DISTRHO_SAFE_ASSERT_UINT2_CONTINUE(cond, v1, v2) if (! (cond)) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); continue; }
#define DISTRHO_SAFE_ASSERT_UINT2_RETURN(cond, v1, v2, ret) if (! (cond)) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); return ret; }
#define DISTRHO_SAFE_ASSERT_UINT2_BREAK(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); break; }
#define DISTRHO_SAFE_ASSERT_UINT2_CONTINUE(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); continue; }
#define DISTRHO_SAFE_ASSERT_UINT2_RETURN(cond, v1, v2, ret) if (unlikely(!(cond))) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); return ret; }

/* Define DISTRHO_SAFE_EXCEPTION */
#define DISTRHO_SAFE_EXCEPTION(msg) catch(...) { d_safe_exception(msg, __FILE__, __LINE__); }
@@ -193,11 +204,21 @@ private: \
# define DISTRHO_OS_SPLIT_STR ":"
#endif

/* MSVC warnings */
#ifdef _MSC_VER
# define strdup _strdup
# pragma warning(disable:4244) /* possible loss of data */
#endif

/* Useful macros */
#define ARRAY_SIZE(ARRAY) sizeof(ARRAY)/sizeof(ARRAY[0])

/* Useful typedefs */
typedef unsigned char uchar;
typedef unsigned short int ushort;
typedef unsigned int uint;
typedef unsigned long int ulong;
typedef unsigned long long int ulonglong;

/* Deprecated macros */
#define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) DISTRHO_DECLARE_NON_COPYABLE(ClassName)


+ 97
- 16
distrho/src/DistrhoPlugin.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
@@ -21,15 +21,17 @@ START_NAMESPACE_DISTRHO
/* ------------------------------------------------------------------------------------------------------------
* Static data, see DistrhoPluginInternal.hpp */

uint32_t d_lastBufferSize = 0;
double d_lastSampleRate = 0.0;
bool d_lastCanRequestParameterValueChanges = false;
uint32_t d_nextBufferSize = 0;
double d_nextSampleRate = 0.0;
const char* d_nextBundlePath = nullptr;
bool d_nextPluginIsDummy = false;
bool d_nextCanRequestParameterValueChanges = false;

/* ------------------------------------------------------------------------------------------------------------
* Static fallback data, see DistrhoPluginInternal.hpp */

const String PluginExporter::sFallbackString;
const AudioPort PluginExporter::sFallbackAudioPort;
/* */ AudioPortWithBusId PluginExporter::sFallbackAudioPort;
const ParameterRanges PluginExporter::sFallbackRanges;
const ParameterEnumerationValues PluginExporter::sFallbackEnumValues;
const PortGroupWithId PluginExporter::sFallbackPortGroup;
@@ -41,7 +43,13 @@ Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCou
: pData(new PrivateData())
{
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
pData->audioPorts = new AudioPort[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS];
pData->audioPorts = new AudioPortWithBusId[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS];
#endif

#ifdef DPF_ABORT_ON_ERROR
# define DPF_ABORT abort();
#else
# define DPF_ABORT
#endif

if (parameterCount > 0)
@@ -50,26 +58,29 @@ Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCou
pData->parameters = new Parameter[parameterCount];
}

#if DISTRHO_PLUGIN_WANT_PROGRAMS
if (programCount > 0)
{
#if DISTRHO_PLUGIN_WANT_PROGRAMS
pData->programCount = programCount;
pData->programNames = new String[programCount];
}
#else
DISTRHO_SAFE_ASSERT(programCount == 0);
d_stderr2("DPF warning: Plugins with programs must define `DISTRHO_PLUGIN_WANT_PROGRAMS` to 1");
DPF_ABORT
#endif
}

#if DISTRHO_PLUGIN_WANT_STATE
if (stateCount > 0)
{
pData->stateCount = stateCount;
pData->stateKeys = new String[stateCount];
pData->stateDefValues = new String[stateCount];
}
#if DISTRHO_PLUGIN_WANT_STATE
pData->stateCount = stateCount;
pData->states = new State[stateCount];
#else
DISTRHO_SAFE_ASSERT(stateCount == 0);
d_stderr2("DPF warning: Plugins with state must define `DISTRHO_PLUGIN_WANT_STATE` to 1");
DPF_ABORT
#endif
}

#undef DPF_ABORT
}

Plugin::~Plugin()
@@ -90,6 +101,16 @@ double Plugin::getSampleRate() const noexcept
return pData->sampleRate;
}

const char* Plugin::getBundlePath() const noexcept
{
return pData->bundlePath;
}

bool Plugin::isDummyInstance() const noexcept
{
return pData->isDummy;
}

#if DISTRHO_PLUGIN_WANT_TIMEPOS
const TimePosition& Plugin::getTimePosition() const noexcept
{
@@ -123,6 +144,13 @@ bool Plugin::requestParameterValueChange(const uint32_t index, const float value
}
#endif

#if DISTRHO_PLUGIN_WANT_STATE
bool Plugin::updateStateValue(const char* const key, const char* const value) noexcept
{
return pData->updateStateValueCallback(key, value);
}
#endif

/* ------------------------------------------------------------------------------------------------------------
* Init */

@@ -144,16 +172,69 @@ void Plugin::initAudioPort(bool input, uint32_t index, AudioPort& port)
}
}

void Plugin::initParameter(uint32_t, Parameter&) {}

void Plugin::initPortGroup(const uint32_t groupId, PortGroup& portGroup)
{
fillInPredefinedPortGroupData(groupId, portGroup);
}

#if DISTRHO_PLUGIN_WANT_PROGRAMS
void Plugin::initProgramName(uint32_t, String&) {}
#endif

#if DISTRHO_PLUGIN_WANT_STATE
void Plugin::initState(const uint32_t index, State& state)
{
uint hints = 0x0;
String stateKey, defaultStateValue;

#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
initState(index, stateKey, defaultStateValue);
if (isStateFile(index))
hints = kStateIsFilenamePath;
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic pop
#endif

state.hints = hints;
state.key = stateKey;
state.label = stateKey;
state.defaultValue = defaultStateValue;
}
#endif

/* ------------------------------------------------------------------------------------------------------------
* Init */

float Plugin::getParameterValue(uint32_t) const { return 0.0f; }
void Plugin::setParameterValue(uint32_t, float) {}

#if DISTRHO_PLUGIN_WANT_PROGRAMS
void Plugin::loadProgram(uint32_t) {}
#endif

#if DISTRHO_PLUGIN_WANT_FULL_STATE
String Plugin::getState(const char*) const { return String(); }
#endif

#if DISTRHO_PLUGIN_WANT_STATE
void Plugin::setState(const char*, const char*) {}
#endif

/* ------------------------------------------------------------------------------------------------------------
* Callbacks (optional) */

void Plugin::bufferSizeChanged(uint32_t) {}
void Plugin::sampleRateChanged(double) {}
void Plugin::sampleRateChanged(double) {}

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



+ 20
- 24
distrho/src/DistrhoPluginCarla.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
@@ -31,11 +31,13 @@
START_NAMESPACE_DISTRHO

#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
static const writeMidiFunc writeMidiCallback = nullptr;
static constexpr const writeMidiFunc writeMidiCallback = nullptr;
#endif
#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr;
static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr;
#endif
// TODO
static constexpr const updateStateValueFunc updateStateValueCallback = nullptr;

#if DISTRHO_PLUGIN_HAS_UI
// -----------------------------------------------------------------------
@@ -53,7 +55,12 @@ class UICarla
public:
UICarla(const NativeHostDescriptor* const host, PluginExporter* const plugin)
: fHost(host),
fUI(this, 0, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, plugin->getInstancePointer())
fUI(this, 0, plugin->getSampleRate(),
editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback,
nullptr, // window size
nullptr, // TODO file request
nullptr, // bundle path
plugin->getInstancePointer())
{
fUI.setWindowTitle(host->uiName);

@@ -75,7 +82,7 @@ public:

bool carla_idle()
{
return fUI.idle();
return fUI.plugin_idle();
}

void carla_setParameterValue(const uint32_t index, const float value)
@@ -129,11 +136,6 @@ protected:
}
#endif

void handleSetSize(const uint width, const uint height)
{
fUI.setWindowSize(width, height);
}

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

private:
@@ -172,11 +174,6 @@ private:
}
#endif

static void setSizeCallback(void* ptr, uint width, uint height)
{
handlePtr->handleSetSize(width, height);
}

#undef handlePtr

CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UICarla)
@@ -191,7 +188,7 @@ class PluginCarla : public NativePluginClass
public:
PluginCarla(const NativeHostDescriptor* const host)
: NativePluginClass(host),
fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback),
fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, updateStateValueCallback),
fScalePointsCache(nullptr)
{
#if DISTRHO_PLUGIN_HAS_UI
@@ -238,7 +235,7 @@ protected:
int nativeParamHints = ::NATIVE_PARAMETER_IS_ENABLED;
const uint32_t paramHints = fPlugin.getParameterHints(index);

if (paramHints & kParameterIsAutomable)
if (paramHints & kParameterIsAutomatable)
nativeParamHints |= ::NATIVE_PARAMETER_IS_AUTOMABLE;
if (paramHints & kParameterIsBoolean)
nativeParamHints |= ::NATIVE_PARAMETER_IS_BOOLEAN;
@@ -367,7 +364,8 @@ protected:
}

#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
void process(float** const inBuffer, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override
void process(const float* const* const inBuffer, float** const outBuffer, const uint32_t frames,
const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override
{
MidiEvent realMidiEvents[midiEventCount];

@@ -391,7 +389,8 @@ protected:
fPlugin.run(const_cast<const float**>(inBuffer), outBuffer, frames, realMidiEvents, midiEventCount);
}
#else
void process(float** const inBuffer, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override
void process(const float* const* const inBuffer, float** const outBuffer, const uint32_t frames,
const NativeMidiEvent* const, const uint32_t) override
{
fPlugin.run(const_cast<const float**>(inBuffer), outBuffer, frames);
}
@@ -498,10 +497,7 @@ private:
void createUiIfNeeded()
{
if (fUiPtr == nullptr)
{
d_lastUiSampleRate = getSampleRate();
fUiPtr = new UICarla(getHostHandle(), &fPlugin);
}
}
#endif

@@ -539,8 +535,8 @@ private:
public:
static NativePluginHandle _instantiate(const NativeHostDescriptor* host)
{
d_lastBufferSize = host->get_buffer_size(host->handle);
d_lastSampleRate = host->get_sample_rate(host->handle);
d_nextBufferSize = host->get_buffer_size(host->handle);
d_nextSampleRate = host->get_sample_rate(host->handle);
return new PluginCarla(host);
}



+ 34
- 18
distrho/src/DistrhoPluginChecks.h View File

@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2019 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
@@ -81,18 +81,23 @@
# define DISTRHO_PLUGIN_WANT_STATE 0
#endif

#ifndef DISTRHO_PLUGIN_WANT_STATEFILES
# define DISTRHO_PLUGIN_WANT_STATEFILES 0
#endif

#ifndef DISTRHO_PLUGIN_WANT_FULL_STATE
# define DISTRHO_PLUGIN_WANT_FULL_STATE 0
# define DISTRHO_PLUGIN_WANT_FULL_STATE_WAS_NOT_SET
#endif

#ifndef DISTRHO_PLUGIN_WANT_TIMEPOS
# define DISTRHO_PLUGIN_WANT_TIMEPOS 0
#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
#endif

#ifndef DISTRHO_UI_USER_RESIZABLE
# define DISTRHO_UI_USER_RESIZABLE 0
#endif
@@ -136,25 +141,37 @@
#endif

// -----------------------------------------------------------------------
// Enable state if plugin wants state files

#if DISTRHO_PLUGIN_WANT_STATEFILES && ! DISTRHO_PLUGIN_WANT_STATE
# undef DISTRHO_PLUGIN_WANT_STATE
# define DISTRHO_PLUGIN_WANT_STATE 1
// Enable state if plugin wants state files (deprecated)

#ifdef DISTRHO_PLUGIN_WANT_STATEFILES
# warning DISTRHO_PLUGIN_WANT_STATEFILES is deprecated
# undef DISTRHO_PLUGIN_WANT_STATEFILES
# if ! DISTRHO_PLUGIN_WANT_STATE
# undef DISTRHO_PLUGIN_WANT_STATE
# define DISTRHO_PLUGIN_WANT_STATE 1
# endif
#endif

// -----------------------------------------------------------------------
// Enable full state if plugin exports presets

// FIXME
// #if DISTRHO_PLUGIN_WANT_PROGRAMS && DISTRHO_PLUGIN_WANT_STATE && ! DISTRHO_PLUGIN_WANT_FULL_STATE
// # warning Plugins with programs and state need to implement full state API
// # undef DISTRHO_PLUGIN_WANT_FULL_STATE
// # define DISTRHO_PLUGIN_WANT_FULL_STATE 1
// #endif
#if DISTRHO_PLUGIN_WANT_PROGRAMS && DISTRHO_PLUGIN_WANT_STATE && defined(DISTRHO_PLUGIN_WANT_FULL_STATE_WAS_NOT_SET)
# warning Plugins with programs and state should implement full state API too
# undef DISTRHO_PLUGIN_WANT_FULL_STATE
# define DISTRHO_PLUGIN_WANT_FULL_STATE 1
#endif

// -----------------------------------------------------------------------
// Disable UI if DGL or External UI is not available
// Disable file browser if using external UI

#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
#endif

// -----------------------------------------------------------------------
// Disable UI if DGL or external UI is not available

#if (defined(DGL_CAIRO) && ! defined(HAVE_CAIRO)) || (defined(DGL_OPENGL) && ! defined(HAVE_OPENGL))
# undef DISTRHO_PLUGIN_HAS_EMBED_UI
@@ -173,7 +190,6 @@
# error DISTRHO_UI_IS_STANDALONE must not be defined
#endif


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

#endif // DISTRHO_PLUGIN_CHECKS_H_INCLUDED

+ 205
- 39
distrho/src/DistrhoPluginInternal.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
@@ -19,6 +19,10 @@

#include "../DistrhoPlugin.hpp"

#ifdef DISTRHO_PLUGIN_TARGET_VST3
# include "DistrhoPluginVST.hpp"
#endif

#include <set>

START_NAMESPACE_DISTRHO
@@ -31,19 +35,30 @@ static const uint32_t kMaxMidiEvents = 512;
// -----------------------------------------------------------------------
// Static data, see DistrhoPlugin.cpp

extern uint32_t d_lastBufferSize;
extern double d_lastSampleRate;
extern bool d_lastCanRequestParameterValueChanges;
extern uint32_t d_nextBufferSize;
extern double d_nextSampleRate;
extern const char* d_nextBundlePath;
extern bool d_nextPluginIsDummy;
extern bool d_nextCanRequestParameterValueChanges;

// -----------------------------------------------------------------------
// DSP callbacks

typedef bool (*writeMidiFunc) (void* ptr, const MidiEvent& midiEvent);
typedef bool (*requestParameterValueChangeFunc) (void* ptr, uint32_t index, float value);
typedef bool (*updateStateValueFunc) (void* ptr, const char* key, const char* value);

// -----------------------------------------------------------------------
// Helpers

struct AudioPortWithBusId : AudioPort {
uint32_t busId;

AudioPortWithBusId()
: AudioPort(),
busId(0) {}
};

struct PortGroupWithId : PortGroup {
uint32_t groupId;

@@ -75,10 +90,12 @@ static void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& por
// Plugin private data

struct Plugin::PrivateData {
const bool canRequestParameterValueChanges;
const bool isDummy;
bool isProcessing;

#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
AudioPort* audioPorts;
AudioPortWithBusId* audioPorts;
#endif

uint32_t parameterCount;
@@ -95,8 +112,7 @@ struct Plugin::PrivateData {

#if DISTRHO_PLUGIN_WANT_STATE
uint32_t stateCount;
String* stateKeys;
String* stateDefValues;
State* states;
#endif

#if DISTRHO_PLUGIN_WANT_LATENCY
@@ -111,13 +127,16 @@ struct Plugin::PrivateData {
void* callbacksPtr;
writeMidiFunc writeMidiCallbackFunc;
requestParameterValueChangeFunc requestParameterValueChangeCallbackFunc;
updateStateValueFunc updateStateValueCallbackFunc;

uint32_t bufferSize;
double sampleRate;
bool canRequestParameterValueChanges;
char* bundlePath;

PrivateData() noexcept
: isProcessing(false),
: canRequestParameterValueChanges(d_nextCanRequestParameterValueChanges),
isDummy(d_nextPluginIsDummy),
isProcessing(false),
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
audioPorts(nullptr),
#endif
@@ -132,8 +151,7 @@ struct Plugin::PrivateData {
#endif
#if DISTRHO_PLUGIN_WANT_STATE
stateCount(0),
stateKeys(nullptr),
stateDefValues(nullptr),
states(nullptr),
#endif
#if DISTRHO_PLUGIN_WANT_LATENCY
latency(0),
@@ -141,9 +159,10 @@ struct Plugin::PrivateData {
callbacksPtr(nullptr),
writeMidiCallbackFunc(nullptr),
requestParameterValueChangeCallbackFunc(nullptr),
bufferSize(d_lastBufferSize),
sampleRate(d_lastSampleRate),
canRequestParameterValueChanges(d_lastCanRequestParameterValueChanges)
updateStateValueCallbackFunc(nullptr),
bufferSize(d_nextBufferSize),
sampleRate(d_nextSampleRate),
bundlePath(d_nextBundlePath != nullptr ? strdup(d_nextBundlePath) : nullptr)
{
DISTRHO_SAFE_ASSERT(bufferSize != 0);
DISTRHO_SAFE_ASSERT(d_isNotZero(sampleRate));
@@ -156,12 +175,16 @@ struct Plugin::PrivateData {
#endif

#ifdef DISTRHO_PLUGIN_TARGET_LV2
# if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
# if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_STATE || DISTRHO_PLUGIN_WANT_TIMEPOS)
parameterOffset += 1;
# endif
# if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)
parameterOffset += 1;
# endif
#endif

#ifdef DISTRHO_PLUGIN_TARGET_VST3
parameterOffset += kVst3InternalParameterCount;
#endif
}

@@ -196,18 +219,18 @@ struct Plugin::PrivateData {
#endif

#if DISTRHO_PLUGIN_WANT_STATE
if (stateKeys != nullptr)
if (states != nullptr)
{
delete[] stateKeys;
stateKeys = nullptr;
delete[] states;
states = nullptr;
}
#endif

if (stateDefValues != nullptr)
if (bundlePath != nullptr)
{
delete[] stateDefValues;
stateDefValues = nullptr;
std::free(bundlePath);
bundlePath = nullptr;
}
#endif
}

#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
@@ -229,6 +252,17 @@ struct Plugin::PrivateData {
return false;
}
#endif

#if DISTRHO_PLUGIN_WANT_STATE
bool updateStateValueCallback(const char* const key, const char* const value)
{
d_stdout("updateStateValueCallback %p", updateStateValueCallbackFunc);
if (updateStateValueCallbackFunc != nullptr)
return updateStateValueCallbackFunc(callbacksPtr, key, value);

return false;
}
#endif
};

// -----------------------------------------------------------------------
@@ -239,7 +273,8 @@ class PluginExporter
public:
PluginExporter(void* const callbacksPtr,
const writeMidiFunc writeMidiCall,
const requestParameterValueChangeFunc requestParameterValueChangeCall)
const requestParameterValueChangeFunc requestParameterValueChangeCall,
const updateStateValueFunc updateStateValueCall)
: fPlugin(createPlugin()),
fData((fPlugin != nullptr) ? fPlugin->pData : nullptr),
fIsActive(false)
@@ -247,6 +282,79 @@ public:
DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,);
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,);

#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.
* This does not work on all compilers, but we use it purely as informational check anyway. */
if (fData->parameterCount != 0)
{
if ((void*)(fPlugin->*(&Plugin::initParameter)) == (void*)&Plugin::initParameter)
{
d_stderr2("DPF warning: Plugins with parameters must implement `initParameter`");
abort();
}
if ((void*)(fPlugin->*(&Plugin::getParameterValue)) == (void*)&Plugin::getParameterValue)
{
d_stderr2("DPF warning: Plugins with parameters must implement `getParameterValue`");
abort();
}
if ((void*)(fPlugin->*(&Plugin::setParameterValue)) == (void*)&Plugin::setParameterValue)
{
d_stderr2("DPF warning: Plugins with parameters must implement `setParameterValue`");
abort();
}
}

# if DISTRHO_PLUGIN_WANT_PROGRAMS
if (fData->programCount != 0)
{
if ((void*)(fPlugin->*(&Plugin::initProgramName)) == (void*)&Plugin::initProgramName)
{
d_stderr2("DPF warning: Plugins with programs must implement `initProgramName`");
abort();
}
if ((void*)(fPlugin->*(&Plugin::loadProgram)) == (void*)&Plugin::loadProgram)
{
d_stderr2("DPF warning: Plugins with programs must implement `loadProgram`");
abort();
}
}
# endif

# if DISTRHO_PLUGIN_WANT_STATE
if (fData->stateCount != 0)
{
if ((void*)(fPlugin->*(&Plugin::initState)) == (void*)&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

# if DISTRHO_PLUGIN_WANT_FULL_STATE
if (fData->stateCount != 0)
{
if ((void*)(fPlugin->*(&Plugin::getState)) == (void*)&Plugin::getState)
{
d_stderr2("DPF warning: Plugins with full state must implement `getState`");
abort();
}
}
else
{
d_stderr2("DPF warning: Plugins with full state must have at least 1 state");
abort();
}
# endif
#endif

#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
{
uint32_t j=0;
@@ -276,7 +384,7 @@ public:

portGroupIndices.erase(kPortGroupNone);

if (const size_t portGroupSize = portGroupIndices.size())
if (const uint32_t portGroupSize = static_cast<uint32_t>(portGroupIndices.size()))
{
fData->portGroups = new PortGroupWithId[portGroupSize];
fData->portGroupCount = portGroupSize;
@@ -302,12 +410,13 @@ public:

#if DISTRHO_PLUGIN_WANT_STATE
for (uint32_t i=0, count=fData->stateCount; i < count; ++i)
fPlugin->initState(i, fData->stateKeys[i], fData->stateDefValues[i]);
fPlugin->initState(i, fData->states[i]);
#endif

fData->callbacksPtr = callbacksPtr;
fData->writeMidiCallbackFunc = writeMidiCall;
fData->requestParameterValueChangeCallbackFunc = requestParameterValueChangeCall;
fData->updateStateValueCallbackFunc = updateStateValueCall;
}

~PluginExporter()
@@ -390,7 +499,7 @@ public:
#endif

#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
const AudioPort& getAudioPort(const bool input, const uint32_t index) const noexcept
AudioPortWithBusId& getAudioPort(const bool input, const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackAudioPort);

@@ -409,6 +518,41 @@ public:

return fData->audioPorts[index + (input ? 0 : DISTRHO_PLUGIN_NUM_INPUTS)];
}

uint32_t getAudioPortHints(const bool input, const uint32_t index) const noexcept
{
return getAudioPort(input, index).hints;
}
uint32_t getAudioPortCountWithGroupId(const bool input, const uint32_t groupId) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0);

uint32_t numPorts = 0;

if (input)
{
#if DISTRHO_PLUGIN_NUM_INPUTS > 0
for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
{
if (fData->audioPorts[i].groupId == groupId)
++numPorts;
}
#endif
}
else
{
#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
{
if (fData->audioPorts[i + DISTRHO_PLUGIN_NUM_INPUTS].groupId == groupId)
++numPorts;
}
#endif
}

return numPorts;
}
#endif

uint32_t getParameterCount() const noexcept
@@ -449,6 +593,11 @@ public:
return (getParameterHints(index) & kParameterIsOutput) != 0x0;
}

bool isParameterTrigger(const uint32_t index) const noexcept
{
return (getParameterHints(index) & kParameterIsTrigger) == kParameterIsTrigger;
}

bool isParameterOutputOrTrigger(const uint32_t index) const noexcept
{
const uint32_t hints = getParameterHints(index);
@@ -524,6 +673,14 @@ public:
return fData->parameters[index].groupId;
}

float getParameterDefault(const uint32_t index) const
{
DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, 0.0f);
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, 0.0f);

return fData->parameters[index].ranges.def;
}

float getParameterValue(const uint32_t index) const
{
DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, 0.0f);
@@ -608,31 +765,43 @@ public:
return fData->stateCount;
}

uint32_t getStateHints(const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, 0x0);

return fData->states[index].hints;
}

const String& getStateKey(const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString);

return fData->stateKeys[index];
return fData->states[index].key;
}

const String& getStateDefaultValue(const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString);

return fData->stateDefValues[index];
return fData->states[index].defaultValue;
}

# if DISTRHO_PLUGIN_WANT_STATEFILES
bool isStateFile(const uint32_t index) const
const String& getStateLabel(const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, false);
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString);

return fPlugin->isStateFile(index);
return fData->states[index].label;
}

const String& getStateDescription(const uint32_t index) const noexcept
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString);

return fData->states[index].description;
}
# endif

# if DISTRHO_PLUGIN_WANT_FULL_STATE
String getState(const char* key) const
String getStateValue(const char* const key) const
{
DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackString);
DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0', sFallbackString);
@@ -657,7 +826,7 @@ public:

for (uint32_t i=0; i < fData->stateCount; ++i)
{
if (fData->stateKeys[i] == key)
if (fData->states[i].key == key)
return true;
}

@@ -809,15 +978,12 @@ private:
// Static fallback data, see DistrhoPlugin.cpp

static const String sFallbackString;
static const AudioPort sFallbackAudioPort;
static /* */ AudioPortWithBusId sFallbackAudioPort;
static const ParameterRanges sFallbackRanges;
static const ParameterEnumerationValues sFallbackEnumValues;
static const PortGroupWithId sFallbackPortGroup;

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginExporter)
#ifndef DISTRHO_PLUGIN_TARGET_VST3 /* there is no way around this for VST3 */
DISTRHO_PREVENT_HEAP_ALLOCATION
#endif
};

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


+ 289
- 17
distrho/src/DistrhoPluginJACK.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>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -16,6 +16,10 @@

#include "DistrhoPluginInternal.hpp"

#if !defined(DISTRHO_OS_WINDOWS) && !defined(STATIC_BUILD)
# include "../DistrhoPluginUtils.hpp"
#endif

#if DISTRHO_PLUGIN_HAS_UI
# include "DistrhoUIInternal.hpp"
# include "../extra/RingBuffer.hpp"
@@ -23,11 +27,20 @@
# include "../extra/Sleep.hpp"
#endif

#ifdef DPF_RUNTIME_TESTING
# include "../extra/Thread.hpp"
#endif

#if defined(HAVE_JACK) && defined(STATIC_BUILD) && !defined(DISTRHO_OS_WASM)
# define JACKBRIDGE_DIRECT
#endif

#include "jackbridge/JackBridge.cpp"
#include "lv2/lv2.h"

#ifndef DISTRHO_OS_WINDOWS
# include <signal.h>
# include <unistd.h>
#endif

#ifndef JACK_METADATA_ORDER
@@ -110,12 +123,12 @@ class PluginJack
#endif
{
public:
PluginJack(jack_client_t* const client)
: fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback),
PluginJack(jack_client_t* const client, const uintptr_t winId)
: fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr),
#if DISTRHO_PLUGIN_HAS_UI
fUI(this,
0, // winId
d_lastSampleRate,
winId,
d_nextSampleRate,
nullptr, // edit param
setParameterValueCallback,
setStateCallback,
@@ -218,6 +231,9 @@ public:
#else
while (! gCloseSignalReceived)
d_sleep(1);

// unused
(void)winId;
#endif
}

@@ -422,7 +438,7 @@ protected:

for (uint32_t i=0; i < eventCount; ++i)
{
if (jackbridge_midi_event_get(&jevent, midiInBuf, i) != 0)
if (! jackbridge_midi_event_get(&jevent, midiInBuf, i))
break;

// Check if message is control change on channel 1
@@ -469,7 +485,7 @@ protected:
MidiEvent& midiEvent(midiEvents[midiEventCount++]);

midiEvent.frame = jevent.time;
midiEvent.size = jevent.size;
midiEvent.size = static_cast<uint32_t>(jevent.size);

if (midiEvent.size > MidiEvent::kDataSize)
midiEvent.dataExt = jevent.buffer;
@@ -735,7 +751,7 @@ private:
return jackbridge_midi_event_write(fPortMidiOutBuffer,
midiEvent.frame,
midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data,
midiEvent.size) == 0;
midiEvent.size);
}

static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent)
@@ -747,14 +763,183 @@ private:
#undef thisPtr
};

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

#ifdef DPF_RUNTIME_TESTING
class PluginProcessTestingThread : public Thread
{
PluginExporter& plugin;

public:
PluginProcessTestingThread(PluginExporter& p) : plugin(p) {}

protected:
void run() override
{
plugin.setBufferSize(256);
plugin.activate();

float buffer[256];
const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1];
float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1];
for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
inputs[i] = buffer;
for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
outputs[i] = buffer;

while (! shouldThreadExit())
{
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
plugin.run(inputs, outputs, 128, nullptr, 0);
#else
plugin.run(inputs, outputs, 128);
#endif
d_msleep(100);
}

plugin.deactivate();
}
};

bool runSelfTests()
{
// simple plugin creation first
{
d_nextBufferSize = 512;
d_nextSampleRate = 44100.0;
PluginExporter plugin(nullptr, nullptr, nullptr);
d_nextBufferSize = 0;
d_nextSampleRate = 0.0;
}

// keep values for all tests now
d_nextBufferSize = 512;
d_nextSampleRate = 44100.0;

// simple processing
{
PluginExporter plugin(nullptr, nullptr, nullptr);
plugin.activate();
plugin.deactivate();
plugin.setBufferSize(128);
plugin.setSampleRate(48000);
plugin.activate();

float buffer[128];
const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1];
float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1];
for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
inputs[i] = buffer;
for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
outputs[i] = buffer;

#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
plugin.run(inputs, outputs, 128, nullptr, 0);
#else
plugin.run(inputs, outputs, 128);
#endif

plugin.deactivate();
}

// multi-threaded processing with UI
{
PluginExporter pluginA(nullptr, nullptr, nullptr);
PluginExporter pluginB(nullptr, nullptr, nullptr);
PluginExporter pluginC(nullptr, nullptr, nullptr);
PluginProcessTestingThread procTestA(pluginA);
PluginProcessTestingThread procTestB(pluginB);
PluginProcessTestingThread procTestC(pluginC);
procTestA.startThread();
procTestB.startThread();
procTestC.startThread();

// wait 2s
d_sleep(2);

// stop the 2nd instance now
procTestB.stopThread(5000);

#if DISTRHO_PLUGIN_HAS_UI
// start UI in the middle of this
{
UIExporter uiA(nullptr, 0, pluginA.getSampleRate(),
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
pluginA.getInstancePointer(), 0.0);
UIExporter uiB(nullptr, 0, pluginA.getSampleRate(),
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
pluginB.getInstancePointer(), 0.0);
UIExporter uiC(nullptr, 0, pluginA.getSampleRate(),
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
pluginC.getInstancePointer(), 0.0);

// show UIs
uiB.showAndFocus();
uiA.showAndFocus();
uiC.showAndFocus();

// idle for 3s
for (int i=0; i<30; i++)
{
uiC.plugin_idle();
uiB.plugin_idle();
uiA.plugin_idle();
d_msleep(100);
}
}
#endif

procTestA.stopThread(5000);
procTestC.stopThread(5000);
}

return true;
}
#endif // DPF_RUNTIME_TESTING

END_NAMESPACE_DISTRHO

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

int main()
int main(int argc, char* argv[])
{
USE_NAMESPACE_DISTRHO;

#ifdef DPF_RUNTIME_TESTING
if (argc == 2 && std::strcmp(argv[1], "selftest") == 0)
return runSelfTests() ? 0 : 1;
#endif

#if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI
/* the code below is based on
* https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/
*/
bool hasConsole = false;

HANDLE consoleHandleOut, consoleHandleError;

if (AttachConsole(ATTACH_PARENT_PROCESS))
{
// Redirect unbuffered STDOUT to the console
consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (consoleHandleOut != INVALID_HANDLE_VALUE)
{
freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0);
}

// Redirect unbuffered STDERR to the console
consoleHandleError = GetStdHandle(STD_ERROR_HANDLE);
if (consoleHandleError != INVALID_HANDLE_VALUE)
{
freopen("CONOUT$", "w", stderr);
setvbuf(stderr, NULL, _IONBF, 0);
}

hasConsole = true;
}
#endif

jack_status_t status = jack_status_t(0x0);
jack_client_t* client = jackbridge_client_open(DISTRHO_PLUGIN_NAME, JackNoStartServer, &status);

@@ -792,25 +977,112 @@ int main()
if (errorString.isNotEmpty())
{
errorString[errorString.length()-2] = '.';
d_stderr("Failed to create jack client, reason was:\n%s", errorString.buffer());
d_stderr("Failed to create the JACK client, reason was:\n%s", errorString.buffer());
}
else
d_stderr("Failed to create jack client, cannot continue!");
d_stderr("Failed to create the JACK client, cannot continue!");

#if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI
// make sure message box is high-dpi aware
if (const HMODULE user32 = LoadLibrary("user32.dll"))
{
typedef BOOL(WINAPI* SPDA)(void);
#if defined(__GNUC__) && (__GNUC__ >= 9)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
const SPDA SetProcessDPIAware = (SPDA)GetProcAddress(user32, "SetProcessDPIAware");
#if defined(__GNUC__) && (__GNUC__ >= 9)
# pragma GCC diagnostic pop
#endif
if (SetProcessDPIAware)
SetProcessDPIAware();
FreeLibrary(user32);
}

const String win32error = "Failed to create JACK client, reason was:\n" + errorString;
MessageBoxA(nullptr, win32error.buffer(), "", MB_ICONERROR);
#endif

return 1;
}

USE_NAMESPACE_DISTRHO;

initSignalHandler();

d_lastBufferSize = jackbridge_get_buffer_size(client);
d_lastSampleRate = jackbridge_get_sample_rate(client);
d_lastCanRequestParameterValueChanges = true;
d_nextBufferSize = jackbridge_get_buffer_size(client);
d_nextSampleRate = jackbridge_get_sample_rate(client);
d_nextCanRequestParameterValueChanges = true;

#if !defined(DISTRHO_OS_WINDOWS) && !defined(STATIC_BUILD)
// find plugin bundle
static String bundlePath;
if (bundlePath.isEmpty())
{
String tmpPath(getBinaryFilename());
tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));
#ifdef DISTRHO_OS_MAC
if (tmpPath.endsWith("/MacOS"))
{
tmpPath.truncate(tmpPath.rfind('/'));
if (tmpPath.endsWith("/Contents"))
{
tmpPath.truncate(tmpPath.rfind('/'));
bundlePath = tmpPath;
d_nextBundlePath = bundlePath.buffer();
}
}
#else
if (access(tmpPath + DISTRHO_OS_SEP_STR "resources", F_OK) == 0)
{
bundlePath = tmpPath;
d_nextBundlePath = bundlePath.buffer();
}
#endif
}
#endif

uintptr_t winId = 0;
#if DISTRHO_PLUGIN_HAS_UI
if (argc == 3 && std::strcmp(argv[1], "embed") == 0)
winId = static_cast<uintptr_t>(std::atoll(argv[2]));
#endif

const PluginJack p(client, winId);

const PluginJack p(client);
#if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI
/* the code below is based on
* https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/
*/

// Send "enter" to release application from the console
// This is a hack, but if not used the console doesn't know the application has
// returned. The "enter" key only sent if the console window is in focus.
if (hasConsole && (GetConsoleWindow() == GetForegroundWindow() || SetFocus(GetConsoleWindow()) != nullptr))
{
INPUT ip;
// Set up a generic keyboard event.
ip.type = INPUT_KEYBOARD;
ip.ki.wScan = 0; // hardware scan code for key
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;

// Send the "Enter" key
ip.ki.wVk = 0x0D; // virtual-key code for the "Enter" key
ip.ki.dwFlags = 0; // 0 for key press
SendInput(1, &ip, sizeof(INPUT));

// Release the "Enter" key
ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
SendInput(1, &ip, sizeof(INPUT));
}
#endif

return 0;

#ifndef DPF_RUNTIME_TESTING
// unused
(void)argc; (void)argv;
#endif
}

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

+ 11
- 9
distrho/src/DistrhoPluginLADSPA+DSSI.cpp View File

@@ -50,7 +50,7 @@ class PluginLadspaDssi
{
public:
PluginLadspaDssi()
: fPlugin(nullptr, nullptr, nullptr),
: fPlugin(nullptr, nullptr, nullptr, nullptr),
fPortControls(nullptr),
fLastControlValues(nullptr)
{
@@ -418,9 +418,9 @@ private:

static LADSPA_Handle ladspa_instantiate(const LADSPA_Descriptor*, ulong sampleRate)
{
if (d_lastBufferSize == 0)
d_lastBufferSize = 2048;
d_lastSampleRate = sampleRate;
if (d_nextBufferSize == 0)
d_nextBufferSize = 2048;
d_nextSampleRate = sampleRate;

return new PluginLadspaDssi();
}
@@ -551,11 +551,13 @@ static const struct DescriptorInitializer
DescriptorInitializer()
{
// Create dummy plugin to get data from
d_lastBufferSize = 512;
d_lastSampleRate = 44100.0;
const PluginExporter plugin(nullptr, nullptr, nullptr);
d_lastBufferSize = 0;
d_lastSampleRate = 0.0;
d_nextBufferSize = 512;
d_nextSampleRate = 44100.0;
d_nextPluginIsDummy = true;
const PluginExporter plugin(nullptr, nullptr, nullptr, nullptr);
d_nextBufferSize = 0;
d_nextSampleRate = 0.0;
d_nextPluginIsDummy = false;

// Get port count, init
ulong port = 0;


+ 193
- 82
distrho/src/DistrhoPluginLV2.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
@@ -17,6 +17,7 @@
#include "DistrhoPluginInternal.hpp"

#include "lv2/atom.h"
#include "lv2/atom-forge.h"
#include "lv2/atom-util.h"
#include "lv2/buf-size.h"
#include "lv2/data-access.h"
@@ -37,10 +38,6 @@
# include "libmodla.h"
#endif

#ifdef noexcept
# undef noexcept
#endif

#include <map>

#ifndef DISTRHO_PLUGIN_URI
@@ -51,8 +48,8 @@
# define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:"
#endif

#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES)
#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI))
#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)

START_NAMESPACE_DISTRHO

@@ -65,6 +62,9 @@ static const writeMidiFunc writeMidiCallback = nullptr;
#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr;
#endif
#if ! DISTRHO_PLUGIN_WANT_STATE
static const updateStateValueFunc updateStateValueCallback = nullptr;
#endif

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

@@ -76,7 +76,7 @@ public:
const LV2_Worker_Schedule* const worker,
const LV2_ControlInputPort_Change_Request* const ctrlInPortChangeReq,
const bool usingNominal)
: fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback),
: fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, updateStateValueCallback),
fUsingNominal(usingNominal),
#ifdef DISTRHO_PLUGIN_LICENSED_FOR_MOD
fRunCount(0),
@@ -130,29 +130,29 @@ public:
#endif

#if DISTRHO_PLUGIN_WANT_STATE
std::memset(&fAtomForge, 0, sizeof(fAtomForge));
lv2_atom_forge_init(&fAtomForge, uridMap);

if (const uint32_t count = fPlugin.getStateCount())
{
fUrids = new LV2_URID[count];
fNeededUiSends = new bool[count];

for (uint32_t i=0; i < count; ++i)
{
fNeededUiSends[i] = false;

const String& dkey(fPlugin.getStateKey(i));
fStateMap[dkey] = fPlugin.getStateDefaultValue(i);
const String& statekey(fPlugin.getStateKey(i));
fStateMap[statekey] = fPlugin.getStateDefaultValue(i);

# if DISTRHO_PLUGIN_WANT_STATEFILES
if (fPlugin.isStateFile(i))
{
const String dpf_lv2_key(DISTRHO_PLUGIN_URI "#" + dkey);
const LV2_URID urid = uridMap->map(uridMap->handle, dpf_lv2_key.buffer());
fUridStateFileMap[urid] = dkey;
}
# endif
const String lv2key(DISTRHO_PLUGIN_URI "#" + statekey);
const LV2_URID urid = fUrids[i] = uridMap->map(uridMap->handle, lv2key.buffer());
fUridStateMap[urid] = statekey;
}
}
else
{
fUrids = nullptr;
fNeededUiSends = nullptr;
}
#else
@@ -187,6 +187,12 @@ public:
fNeededUiSends = nullptr;
}

if (fUrids != nullptr)
{
delete[] fUrids;
fUrids = nullptr;
}

fStateMap.clear();
#endif
}
@@ -548,13 +554,14 @@ public:
}
#endif

// check for messages from UI or files
#if DISTRHO_PLUGIN_WANT_STATE && (DISTRHO_PLUGIN_HAS_UI || DISTRHO_PLUGIN_WANT_STATEFILES)
// check for messages from UI or host
#if DISTRHO_PLUGIN_WANT_STATE
LV2_ATOM_SEQUENCE_FOREACH(fPortEventsIn, event)
{
if (event == nullptr)
break;

#if DISTRHO_PLUGIN_HAS_UI
if (event->body.type == fURIDs.dpfKeyValue)
{
const void* const data = (const void*)(event + 1);
@@ -563,7 +570,11 @@ public:
if (std::strcmp((const char*)data, "__dpf_ui_data__") == 0)
{
for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
{
if (fPlugin.getStateHints(i) & kStateIsOnlyForDSP)
continue;
fNeededUiSends[i] = true;
}
}
// no, send to DSP as usual
else if (fWorker != nullptr)
@@ -571,8 +582,9 @@ public:
fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body);
}
}
# if DISTRHO_PLUGIN_WANT_STATEFILES
else if (event->body.type == fURIDs.atomObject && fWorker != nullptr)
else
#endif
if (event->body.type == fURIDs.atomObject && fWorker != nullptr)
{
const LV2_Atom_Object* const object = (const LV2_Atom_Object*)&event->body;

@@ -581,12 +593,11 @@ public:
lv2_atom_object_get(object, fURIDs.patchProperty, &property, fURIDs.patchValue, &value, nullptr);

if (property != nullptr && property->type == fURIDs.atomURID &&
value != nullptr && value->type == fURIDs.atomPath)
value != nullptr && (value->type == fURIDs.atomPath || value->type == fURIDs.atomString))
{
fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body);
}
}
# endif
}
#endif

@@ -685,7 +696,7 @@ public:

updateParameterOutputsAndTriggers();

#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI
#if DISTRHO_PLUGIN_WANT_STATE
fEventsOutData.initIfNeeded(fURIDs.atomSequence);

LV2_Atom_Event* aev;
@@ -696,6 +707,16 @@ public:
if (! fNeededUiSends[i])
continue;

const uint32_t hints = fPlugin.getStateHints(i);

#if ! DISTRHO_PLUGIN_HAS_UI
if ((hints & kStateIsHostReadable) == 0x0)
{
fNeededUiSends[i] = false;
continue;
}
#endif

const String& curKey(fPlugin.getStateKey(i));

for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
@@ -707,30 +728,71 @@ public:

const String& value(cit->second);

// set msg size (key + value + separator + 2x null terminator)
const size_t msgSize = key.length()+value.length()+3;
// set msg size
uint32_t msgSize;

if (hints & kStateIsHostReadable)
{
// object, prop key, prop urid, value key, value
msgSize = sizeof(LV2_Atom_Object)
+ sizeof(LV2_Atom_Property_Body) * 4
+ sizeof(LV2_Atom_URID) * 3
+ sizeof(LV2_Atom_String)
+ value.length() + 1;
}
else
{
// key + value + 2x null terminator + separator
msgSize = static_cast<uint32_t>(key.length()+value.length())+3U;
}

if (sizeof(LV2_Atom_Event) + msgSize > capacity - fEventsOutData.offset)
{
d_stdout("Sending key '%s' to UI failed, out of space", key.buffer());
d_stdout("Sending key '%s' to UI failed, out of space (needs %u bytes)",
key.buffer(), msgSize);
break;
}

// put data
aev = (LV2_Atom_Event*)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, fEventsOutData.port) + fEventsOutData.offset);
aev->time.frames = 0;
aev->body.type = fURIDs.dpfKeyValue;
aev->body.size = msgSize;

uint8_t* const msgBuf = LV2_ATOM_BODY(&aev->body);
std::memset(msgBuf, 0, msgSize);
if (hints & kStateIsHostReadable)
{
uint8_t* const msgBuf = (uint8_t*)&aev->body;
LV2_Atom_Forge atomForge = fAtomForge;
lv2_atom_forge_set_buffer(&atomForge, msgBuf, msgSize);

// write key and value in atom buffer
std::memcpy(msgBuf, key.buffer(), key.length()+1);
std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1);
LV2_Atom_Forge_Frame forgeFrame;
lv2_atom_forge_object(&atomForge, &forgeFrame, 0, fURIDs.patchSet);

fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize));
lv2_atom_forge_key(&atomForge, fURIDs.patchProperty);
lv2_atom_forge_urid(&atomForge, fUrids[i]);

lv2_atom_forge_key(&atomForge, fURIDs.patchValue);
if ((hints & kStateIsFilenamePath) == kStateIsFilenamePath)
lv2_atom_forge_path(&atomForge, value.buffer(), static_cast<uint32_t>(value.length()+1));
else
lv2_atom_forge_string(&atomForge, value.buffer(), static_cast<uint32_t>(value.length()+1));

lv2_atom_forge_pop(&atomForge, &forgeFrame);

msgSize = ((LV2_Atom*)msgBuf)->size;
}
else
{
aev->body.type = fURIDs.dpfKeyValue;
aev->body.size = msgSize;

uint8_t* const msgBuf = LV2_ATOM_BODY(&aev->body);
std::memset(msgBuf, 0, msgSize);

// write key and value in atom buffer
std::memcpy(msgBuf, key.buffer(), key.length()+1);
std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1);
}

fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize));
fNeededUiSends[i] = false;
break;
}
@@ -838,7 +900,7 @@ public:
for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{
const String& key = cit->first;
fStateMap[key] = fPlugin.getState(key);
fStateMap[key] = fPlugin.getStateValue(key);
}
# endif
}
@@ -854,11 +916,11 @@ public:
for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{
const String& key = cit->first;
fStateMap[key] = fPlugin.getState(key);
fStateMap[key] = fPlugin.getStateValue(key);
}
# endif

String dpf_lv2_key;
String lv2key;
LV2_URID urid;

for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
@@ -872,30 +934,40 @@ public:
if (curKey != key)
continue;

const String& value(cit->second);
const uint32_t hints = fPlugin.getStateHints(i);

#if ! DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
// do not save UI-only messages if there is no UI available
if (hints & kStateIsOnlyForUI)
break;
#endif

# if DISTRHO_PLUGIN_WANT_STATEFILES
if (fPlugin.isStateFile(i))
if (hints & kStateIsHostReadable)
{
dpf_lv2_key = DISTRHO_PLUGIN_URI "#";
urid = fURIDs.atomPath;
lv2key = DISTRHO_PLUGIN_URI "#";
urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath
? fURIDs.atomPath
: fURIDs.atomString;
}
else
# endif
{
dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
urid = fURIDs.atomString;
}

dpf_lv2_key += key;
lv2key += key;

const String& value(cit->second);

// some hosts need +1 for the null terminator, even though the type is string
store(handle,
fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()),
fUridMap->map(fUridMap->handle, lv2key.buffer()),
value.buffer(),
value.length()+1,
urid,
LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE);

break;
}
}

@@ -907,33 +979,35 @@ public:
size_t size;
uint32_t type, flags;

String dpf_lv2_key;
String lv2key;
LV2_URID urid;

for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
{
const String& key(fPlugin.getStateKey(i));

# if DISTRHO_PLUGIN_WANT_STATEFILES
if (fPlugin.isStateFile(i))
const uint32_t hints = fPlugin.getStateHints(i);

if (hints & kStateIsHostReadable)
{
dpf_lv2_key = DISTRHO_PLUGIN_URI "#";
urid = fURIDs.atomPath;
lv2key = DISTRHO_PLUGIN_URI "#";
urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath
? fURIDs.atomPath
: fURIDs.atomString;
}
else
# endif
{
dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX;
urid = fURIDs.atomString;
}

dpf_lv2_key += key;
lv2key += key;

size = 0;
type = 0;
flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE;
const void* data = retrieve(handle,
fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()),
fUridMap->map(fUridMap->handle, lv2key.buffer()),
&size, &type, &flags);

if (data == nullptr || size == 0)
@@ -947,9 +1021,10 @@ public:

setState(key, value);

#if DISTRHO_LV2_USE_EVENTS_OUT
#if DISTRHO_PLUGIN_WANT_STATE
// signal msg needed for UI
fNeededUiSends[i] = true;
if ((hints & kStateIsOnlyForDSP) == 0x0)
fNeededUiSends[i] = true;
#endif
}

@@ -971,7 +1046,6 @@ public:
return LV2_WORKER_SUCCESS;
}

# if DISTRHO_PLUGIN_WANT_STATEFILES
if (eventBody->type == fURIDs.atomObject)
{
const LV2_Atom_Object* const object = (const LV2_Atom_Object*)eventBody;
@@ -982,7 +1056,8 @@ public:
DISTRHO_SAFE_ASSERT_RETURN(property != nullptr, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath, LV2_WORKER_ERR_UNKNOWN);
DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath ||
value->type == fURIDs.atomString, LV2_WORKER_ERR_UNKNOWN);

const LV2_URID urid = ((const LV2_Atom_URID*)property)->body;
const char* const filename = (const char*)(value + 1);
@@ -990,8 +1065,8 @@ public:
String key;

try {
key = fUridStateFileMap[urid];
} DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateFileMap[urid]", LV2_WORKER_ERR_UNKNOWN);
key = fUridStateMap[urid];
} DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateMap[urid]", LV2_WORKER_ERR_UNKNOWN);

setState(key, filename);

@@ -999,14 +1074,14 @@ public:
{
if (fPlugin.getStateKey(i) == key)
{
fNeededUiSends[i] = true;
if ((fPlugin.getStateHints(i) & kStateIsOnlyForDSP) == 0x0)
fNeededUiSends[i] = true;
break;
}
}

return LV2_WORKER_SUCCESS;
}
# endif

return LV2_WORKER_ERR_UNKNOWN;
}
@@ -1140,6 +1215,7 @@ private:
LV2_URID atomURID;
LV2_URID dpfKeyValue;
LV2_URID midiEvent;
LV2_URID patchSet;
LV2_URID patchProperty;
LV2_URID patchValue;
LV2_URID timePosition;
@@ -1166,6 +1242,7 @@ private:
atomURID(map(LV2_ATOM__URID)),
dpfKeyValue(map(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")),
midiEvent(map(LV2_MIDI__MidiEvent)),
patchSet(map(LV2_PATCH__Set)),
patchProperty(map(LV2_PATCH__property)),
patchValue(map(LV2_PATCH__value)),
timePosition(map(LV2_TIME__Position)),
@@ -1192,18 +1269,30 @@ private:
const LV2_Worker_Schedule* const fWorker;

#if DISTRHO_PLUGIN_WANT_STATE
LV2_Atom_Forge fAtomForge;
StringToStringMap fStateMap;
UridToStringMap fUridStateMap;
LV2_URID* fUrids;
bool* fNeededUiSends;

void setState(const char* const key, const char* const newValue)
{
fPlugin.setState(key, newValue);

// check if we want to save this key
if (! fPlugin.wantStateKey(key))
return;
// save this key if necessary
if (fPlugin.wantStateKey(key))
updateInternalState(key, newValue, false);
}

bool updateState(const char* const key, const char* const newValue)
{
fPlugin.setState(key, newValue);
return updateInternalState(key, newValue, true);
}

// check if key already exists
bool updateInternalState(const char* const key, const char* const newValue, const bool sendToUI)
{
// key must already exist
for (StringToStringMap::iterator it=fStateMap.begin(), ite=fStateMap.end(); it != ite; ++it)
{
const String& dkey(it->first);
@@ -1211,16 +1300,27 @@ private:
if (dkey == key)
{
it->second = newValue;
return;

if (sendToUI)
{
for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i)
{
if (fPlugin.getStateKey(i) == key)
{
if ((fPlugin.getStateHints(i) & kStateIsOnlyForDSP) == 0x0)
fNeededUiSends[i] = true;
break;
}
}
}

return true;
}
}

d_stderr("Failed to find plugin state with key \"%s\"", key);
return false;
}

# if DISTRHO_PLUGIN_WANT_STATEFILES
UridToStringMap fUridStateFileMap;
# endif
#endif

void updateParameterOutputsAndTriggers()
@@ -1261,6 +1361,13 @@ private:
}
#endif

#if DISTRHO_PLUGIN_WANT_STATE
static bool updateStateValueCallback(void* const ptr, const char* const key, const char* const value)
{
return ((PluginLv2*)ptr)->updateState(key, value);
}
#endif

#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
bool writeMidi(const MidiEvent& midiEvent)
{
@@ -1296,7 +1403,7 @@ private:

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

static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, const char*, const LV2_Feature* const* features)
static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, const char* bundlePath, const LV2_Feature* const* features)
{
const LV2_Options_Option* options = nullptr;
const LV2_URID_Map* uridMap = nullptr;
@@ -1339,7 +1446,7 @@ static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, cons
mod_license_check(features, DISTRHO_PLUGIN_URI);
#endif

d_lastBufferSize = 0;
d_nextBufferSize = 0;
bool usingNominal = false;

for (int i=0; options[i].key != 0; ++i)
@@ -1348,7 +1455,7 @@ static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, cons
{
if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Int))
{
d_lastBufferSize = *(const int*)options[i].value;
d_nextBufferSize = *(const int*)options[i].value;
usingNominal = true;
}
else
@@ -1361,7 +1468,7 @@ static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, cons
if (options[i].key == uridMap->map(uridMap->handle, LV2_BUF_SIZE__maxBlockLength))
{
if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Int))
d_lastBufferSize = *(const int*)options[i].value;
d_nextBufferSize = *(const int*)options[i].value;
else
d_stderr("Host provides maxBlockLength but has wrong value type");

@@ -1369,14 +1476,18 @@ static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, cons
}
}

if (d_lastBufferSize == 0)
if (d_nextBufferSize == 0)
{
d_stderr("Host does not provide nominalBlockLength or maxBlockLength options");
d_lastBufferSize = 2048;
d_nextBufferSize = 2048;
}

d_lastSampleRate = sampleRate;
d_lastCanRequestParameterValueChanges = ctrlInPortChangeReq != nullptr;
d_nextSampleRate = sampleRate;
d_nextBundlePath = bundlePath;
d_nextCanRequestParameterValueChanges = ctrlInPortChangeReq != nullptr;

if (std::getenv("RUNNING_UNDER_LV2LINT") != nullptr)
d_nextPluginIsDummy = true;

return new PluginLv2(sampleRate, uridMap, worker, ctrlInPortChangeReq, usingNominal);
}


+ 323
- 99
distrho/src/DistrhoPluginLV2export.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
@@ -15,6 +15,7 @@
*/

#include "DistrhoPluginInternal.hpp"
#include "../DistrhoPluginUtils.hpp"

#include "lv2/atom.h"
#include "lv2/buf-size.h"
@@ -41,6 +42,10 @@
# include "mod-license.h"
#endif

#ifndef DISTRHO_OS_WINDOWS
# include <unistd.h>
#endif

#include <fstream>
#include <iostream>

@@ -74,8 +79,8 @@
# define DISTRHO_LV2_UI_TYPE "UI"
#endif

#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES)
#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI))
#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE)
#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)

#define DISTRHO_BYPASS_PARAMETER_NAME "lv2_enabled"

@@ -147,9 +152,7 @@ static const char* const lv2ManifestUiOptionalFeatures[] =
"ui:parent",
"ui:touch",
#endif
#if DISTRHO_PLUGIN_WANT_STATEFILES
"ui:requestValue",
#endif
nullptr
};

@@ -223,12 +226,28 @@ void lv2_generate_ttl(const char* const basename)
{
USE_NAMESPACE_DISTRHO

String bundlePath(getBinaryFilename());
if (bundlePath.isNotEmpty())
{
bundlePath.truncate(bundlePath.rfind(DISTRHO_OS_SEP));
}
#ifndef DISTRHO_OS_WINDOWS
else if (char* const cwd = ::getcwd(nullptr, 0))
{
bundlePath = cwd;
std::free(cwd);
}
#endif
d_nextBundlePath = bundlePath.buffer();

// Dummy plugin to get data from
d_lastBufferSize = 512;
d_lastSampleRate = 44100.0;
PluginExporter plugin(nullptr, nullptr, nullptr);
d_lastBufferSize = 0;
d_lastSampleRate = 0.0;
d_nextBufferSize = 512;
d_nextSampleRate = 44100.0;
d_nextPluginIsDummy = true;
PluginExporter plugin(nullptr, nullptr, nullptr, nullptr);
d_nextBufferSize = 0;
d_nextSampleRate = 0.0;
d_nextPluginIsDummy = false;

const String pluginDLL(basename);
const String pluginTTL(pluginDLL + ".ttl");
@@ -332,6 +351,7 @@ void lv2_generate_ttl(const char* const basename)
pluginString += "@prefix doap: <http://usefulinc.com/ns/doap#> .\n";
pluginString += "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n";
pluginString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
pluginString += "@prefix midi: <" LV2_MIDI_PREFIX "> .\n";
pluginString += "@prefix mod: <http://moddevices.com/ns/mod#> .\n";
pluginString += "@prefix opts: <" LV2_OPTIONS_PREFIX "> .\n";
pluginString += "@prefix pg: <" LV2_PORT_GROUPS_PREFIX "> .\n";
@@ -341,38 +361,54 @@ void lv2_generate_ttl(const char* const basename)
#if DISTRHO_LV2_USE_EVENTS_IN || DISTRHO_LV2_USE_EVENTS_OUT
pluginString += "@prefix rsz: <" LV2_RESIZE_PORT_PREFIX "> .\n";
#endif
pluginString += "@prefix spdx: <http://spdx.org/rdf/terms#> .\n";
#if DISTRHO_PLUGIN_HAS_UI
pluginString += "@prefix ui: <" LV2_UI_PREFIX "> .\n";
#endif
pluginString += "@prefix unit: <" LV2_UNITS_PREFIX "> .\n";
pluginString += "\n";

#if DISTRHO_PLUGIN_WANT_STATEFILES
// define writable states as lv2 parameters
bool hasStateFiles = false;
#if DISTRHO_PLUGIN_WANT_STATE
bool hasHostVisibleState = false;

for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
{
if (! plugin.isStateFile(i))
const uint32_t hints = plugin.getStateHints(i);

if ((hints & kStateIsHostReadable) == 0x0)
continue;

const String& key(plugin.getStateKey(i));
pluginString += "<" DISTRHO_PLUGIN_URI "#" + key + ">\n";
pluginString += "<" DISTRHO_PLUGIN_URI "#" + plugin.getStateKey(i) + ">\n";
pluginString += " a lv2:Parameter ;\n";
pluginString += " rdfs:label \"" + key + "\" ;\n";
pluginString += " rdfs:range atom:Path .\n\n";
hasStateFiles = true;
pluginString += " rdfs:label \"" + plugin.getStateLabel(i) + "\" ;\n";

const String& comment(plugin.getStateDescription(i));

if (comment.isNotEmpty())
{
if (comment.contains('"') || comment.contains('\n'))
pluginString += " rdfs:comment \"\"\"" + comment + "\"\"\" ;\n";
else
pluginString += " rdfs:comment \"" + comment + "\" ;\n";
}

if ((hints & kStateIsFilenamePath) == kStateIsFilenamePath)
pluginString += " rdfs:range atom:Path .\n\n";
else
pluginString += " rdfs:range atom:String .\n\n";

hasHostVisibleState = true;
}
#endif

// plugin
pluginString += "<" DISTRHO_PLUGIN_URI ">\n";
#ifdef DISTRHO_PLUGIN_LV2_CATEGORY
pluginString += " a " DISTRHO_PLUGIN_LV2_CATEGORY ", lv2:Plugin ;\n";
pluginString += " a " DISTRHO_PLUGIN_LV2_CATEGORY ", lv2:Plugin, doap:Project ;\n";
#elif DISTRHO_PLUGIN_IS_SYNTH
pluginString += " a lv2:InstrumentPlugin, lv2:Plugin ;\n";
pluginString += " a lv2:InstrumentPlugin, lv2:Plugin, doap:Project ;\n";
#else
pluginString += " a lv2:Plugin ;\n";
pluginString += " a lv2:Plugin, doap:Project ;\n";
#endif
pluginString += "\n";

@@ -381,16 +417,22 @@ void lv2_generate_ttl(const char* const basename)
addAttribute(pluginString, "lv2:requiredFeature", lv2ManifestPluginRequiredFeatures, 4);
addAttribute(pluginString, "opts:supportedOption", lv2ManifestPluginSupportedOptions, 4);

#if DISTRHO_PLUGIN_WANT_STATEFILES
if (hasStateFiles)
#if DISTRHO_PLUGIN_WANT_STATE
if (hasHostVisibleState)
{
for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i)
{
if (! plugin.isStateFile(i))
const uint32_t hints = plugin.getStateHints(i);

if ((hints & kStateIsHostReadable) == 0x0)
continue;

const String& key(plugin.getStateKey(i));
pluginString += " patch:writable <" DISTRHO_PLUGIN_URI "#" + key + ">;\n";

if ((hints & kStateIsHostWritable) == kStateIsHostWritable)
pluginString += " patch:writable <" DISTRHO_PLUGIN_URI "#" + key + "> ;\n";
else
pluginString += " patch:readable <" DISTRHO_PLUGIN_URI "#" + key + "> ;\n";
}
pluginString += "\n";
}
@@ -430,20 +472,23 @@ void lv2_generate_ttl(const char* const basename)
if (port.hints & kAudioPortIsSidechain)
pluginString += " lv2:portProperty lv2:isSideChain;\n";

switch (port.groupId)
if (port.groupId != kPortGroupNone)
{
case kPortGroupNone:
break;
case kPortGroupMono:
pluginString += " pg:group pg:MonoGroup ;\n";
break;
case kPortGroupStereo:
pluginString += " pg:group pg:StereoGroup ;\n";
break;
default:
pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_"
+ plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n";
break;

switch (port.groupId)
{
case kPortGroupMono:
pluginString += " lv2:designation pg:center ;\n";
break;
case kPortGroupStereo:
if (i == 1)
pluginString += " lv2:designation pg:right ;\n";
else
pluginString += " lv2:designation pg:left ;\n";
break;
}
}

// set ranges
@@ -520,20 +565,23 @@ void lv2_generate_ttl(const char* const basename)
if (port.hints & kAudioPortIsSidechain)
pluginString += " lv2:portProperty lv2:isSideChain;\n";

switch (port.groupId)
if (port.groupId != kPortGroupNone)
{
case kPortGroupNone:
break;
case kPortGroupMono:
pluginString += " pg:group pg:MonoGroup ;\n";
break;
case kPortGroupStereo:
pluginString += " pg:group pg:StereoGroup ;\n";
break;
default:
pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_"
+ plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n";
break;

switch (port.groupId)
{
case kPortGroupMono:
pluginString += " lv2:designation pg:center ;\n";
break;
case kPortGroupStereo:
if (i == 1)
pluginString += " lv2:designation pg:right ;\n";
else
pluginString += " lv2:designation pg:left ;\n";
break;
}
}

// set ranges
@@ -594,13 +642,20 @@ void lv2_generate_ttl(const char* const basename)
pluginString += " rsz:minimumSize " + String(DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE) + " ;\n";
pluginString += " atom:bufferType atom:Sequence ;\n";
# if (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)
pluginString += " atom:supports <" LV2_ATOM__String "> ;\n";
pluginString += " atom:supports atom:String ;\n";
# endif
# if DISTRHO_PLUGIN_WANT_MIDI_INPUT
pluginString += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
pluginString += " atom:supports midi:MidiEvent ;\n";
# endif
# if DISTRHO_PLUGIN_WANT_TIMEPOS
pluginString += " atom:supports <" LV2_TIME__Position "> ;\n";
# endif
# if DISTRHO_PLUGIN_WANT_STATE
if (hasHostVisibleState)
{
pluginString += " atom:supports <" LV2_PATCH__Message "> ;\n";
pluginString += " lv2:designation lv2:control ;\n";
}
# endif
pluginString += " ] ;\n\n";
++portIndex;
@@ -615,10 +670,17 @@ void lv2_generate_ttl(const char* const basename)
pluginString += " rsz:minimumSize " + String(DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE) + " ;\n";
pluginString += " atom:bufferType atom:Sequence ;\n";
# if (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)
pluginString += " atom:supports <" LV2_ATOM__String "> ;\n";
pluginString += " atom:supports atom:String ;\n";
# endif
# if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
pluginString += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n";
pluginString += " atom:supports midi:MidiEvent ;\n";
# endif
# if DISTRHO_PLUGIN_WANT_STATE
if (hasHostVisibleState)
{
pluginString += " atom:supports <" LV2_PATCH__Message "> ;\n";
pluginString += " lv2:designation lv2:control ;\n";
}
# endif
pluginString += " ] ;\n\n";
++portIndex;
@@ -746,12 +808,22 @@ void lv2_generate_ttl(const char* const basename)
}

if (j+1 == enumValues.count)
pluginString += " ] ;\n\n";
pluginString += " ] ;\n";
else
pluginString += " ] ,\n";
}
}

// MIDI CC binding
if (const uint8_t midiCC = plugin.getParameterMidiCC(i))
{
char midiCCBuf[7];
snprintf(midiCCBuf, sizeof(midiCCBuf), "B0%02x00", midiCC);
pluginString += " midi:binding \"";
pluginString += midiCCBuf;
pluginString += "\"^^midi:MidiEvent ;\n";
}

// unit
const String& unit(plugin.getParameterUnit(i));

@@ -814,7 +886,7 @@ void lv2_generate_ttl(const char* const basename)
}

// hints
const uint32_t hints(plugin.getParameterHints(i));
const uint32_t hints = plugin.getParameterHints(i);

if (hints & kParameterIsBoolean)
{
@@ -826,32 +898,18 @@ void lv2_generate_ttl(const char* const basename)
pluginString += " lv2:portProperty lv2:integer ;\n";
if (hints & kParameterIsLogarithmic)
pluginString += " lv2:portProperty <" LV2_PORT_PROPS__logarithmic "> ;\n";
if ((hints & kParameterIsAutomable) == 0 && plugin.isParameterInput(i))
if ((hints & kParameterIsAutomatable) == 0 && plugin.isParameterInput(i))
{
pluginString += " lv2:portProperty <" LV2_PORT_PROPS__expensive "> ,\n";
pluginString += " <" LV2_KXSTUDIO_PROPERTIES__NonAutomable "> ;\n";
pluginString += " <" LV2_KXSTUDIO_PROPERTIES__NonAutomatable "> ;\n";
}

// TODO midiCC

// group
const uint32_t groupId = plugin.getParameterGroupId(i);

switch (groupId)
{
case kPortGroupNone:
break;
case kPortGroupMono:
pluginString += " pg:group pg:MonoGroup ;\n";
break;
case kPortGroupStereo:
pluginString += " pg:group pg:StereoGroup ;\n";
break;
default:
if (groupId != kPortGroupNone)
pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_"
+ plugin.getPortGroupSymbolForId(groupId) + "> ;\n";
break;
}

} // ! designated

@@ -895,13 +953,156 @@ void lv2_generate_ttl(const char* const basename)
{
const String license(plugin.getLicense());

// TODO always convert to URL, do best-guess based on known licenses
// Using URL as license
if (license.contains("://"))
{
pluginString += " doap:license <" + license + "> ;\n\n";
}
// String contaning quotes, use as-is
else if (license.contains('"'))
{
pluginString += " doap:license \"\"\"" + license + "\"\"\" ;\n\n";
}
// Regular license string, convert to URL as much as we can
else
pluginString += " doap:license \"" + license + "\" ;\n\n";
{
const String uplicense(license.asUpper());

// for reference, see https://spdx.org/licenses/

// common licenses
/**/ if (uplicense == "AGPL-1.0-ONLY" ||
uplicense == "AGPL1" ||
uplicense == "AGPLV1")
{
pluginString += " doap:license <http://spdx.org/licenses/AGPL-1.0-only.html> ;\n\n";
}
else if (uplicense == "AGPL-1.0-OR-LATER" ||
uplicense == "AGPL1+" ||
uplicense == "AGPLV1+")
{
pluginString += " doap:license <http://spdx.org/licenses/AGPL-1.0-or-later.html> ;\n\n";
}
else if (uplicense == "AGPL-3.0-ONLY" ||
uplicense == "AGPL3" ||
uplicense == "AGPLV3")
{
pluginString += " doap:license <http://spdx.org/licenses/AGPL-3.0-only.html> ;\n\n";
}
else if (uplicense == "AGPL-3.0-OR-LATER" ||
uplicense == "AGPL3+" ||
uplicense == "AGPLV3+")
{
pluginString += " doap:license <http://spdx.org/licenses/AGPL-3.0-or-later.html> ;\n\n";
}
else if (uplicense == "APACHE-2.0" ||
uplicense == "APACHE2" ||
uplicense == "APACHE-2")
{
pluginString += " doap:license <http://spdx.org/licenses/Apache-2.0.html> ;\n\n";
}
else if (uplicense == "BSD-2-CLAUSE" ||
uplicense == "BSD2" ||
uplicense == "BSD-2")
{
pluginString += " doap:license <http://spdx.org/licenses/BSD-2-Clause.html> ;\n\n";
}
else if (uplicense == "BSD-3-CLAUSE" ||
uplicense == "BSD3" ||
uplicense == "BSD-3")
{
pluginString += " doap:license <http://spdx.org/licenses/BSD-3-Clause.html> ;\n\n";
}
else if (uplicense == "GPL-2.0-ONLY" ||
uplicense == "GPL2" ||
uplicense == "GPLV2")
{
pluginString += " doap:license <http://spdx.org/licenses/GPL-2.0-only.html> ;\n\n";
}
else if (uplicense == "GPL-2.0-OR-LATER" ||
uplicense == "GPL2+" ||
uplicense == "GPLV2+" ||
uplicense == "GPLV2.0+" ||
uplicense == "GPL V2+")
{
pluginString += " doap:license <http://spdx.org/licenses/GPL-2.0-or-later.html> ;\n\n";
}
else if (uplicense == "GPL-3.0-ONLY" ||
uplicense == "GPL3" ||
uplicense == "GPLV3")
{
pluginString += " doap:license <http://spdx.org/licenses/GPL-3.0-only.html> ;\n\n";
}
else if (uplicense == "GPL-3.0-OR-LATER" ||
uplicense == "GPL3+" ||
uplicense == "GPLV3+" ||
uplicense == "GPLV3.0+" ||
uplicense == "GPL V3+")
{
pluginString += " doap:license <http://spdx.org/licenses/GPL-3.0-or-later.html> ;\n\n";
}
else if (uplicense == "ISC")
{
pluginString += " doap:license <http://spdx.org/licenses/ISC.html> ;\n\n";
}
else if (uplicense == "LGPL-2.0-ONLY" ||
uplicense == "LGPL2" ||
uplicense == "LGPLV2")
{
pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.0-only.html> ;\n\n";
}
else if (uplicense == "LGPL-2.0-OR-LATER" ||
uplicense == "LGPL2+" ||
uplicense == "LGPLV2+")
{
pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.0-or-later.html> ;\n\n";
}
else if (uplicense == "LGPL-2.1-ONLY" ||
uplicense == "LGPL2.1" ||
uplicense == "LGPLV2.1")
{
pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.1-only.html> ;\n\n";
}
else if (uplicense == "LGPL-2.1-OR-LATER" ||
uplicense == "LGPL2.1+" ||
uplicense == "LGPLV2.1+")
{
pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.1-or-later.html> ;\n\n";
}
else if (uplicense == "LGPL-3.0-ONLY" ||
uplicense == "LGPL3" ||
uplicense == "LGPLV3")
{
pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.0-only.html> ;\n\n";
}
else if (uplicense == "LGPL-3.0-OR-LATER" ||
uplicense == "LGPL3+" ||
uplicense == "LGPLV3+")
{
pluginString += " doap:license <http://spdx.org/licenses/LGPL-3.0-or-later.html> ;\n\n";
}
else if (uplicense == "MIT")
{
pluginString += " doap:license <http://spdx.org/licenses/MIT.html> ;\n\n";
}

// generic fallbacks
else if (uplicense.startsWith("GPL"))
{
pluginString += " doap:license <http://opensource.org/licenses/gpl-license> ;\n\n";
}
else if (uplicense.startsWith("LGPL"))
{
pluginString += " doap:license <http://opensource.org/licenses/lgpl-license> ;\n\n";
}

// unknown or not handled yet, log a warning
else
{
d_stderr("Unknown license string '%s'", license.buffer());
pluginString += " doap:license \"" + license + "\" ;\n\n";
}
}
}

// developer
@@ -926,8 +1127,8 @@ void lv2_generate_ttl(const char* const basename)
const uint32_t version(plugin.getVersion());

const uint32_t majorVersion = (version & 0xFF0000) >> 16;
const uint32_t microVersion = (version & 0x00FF00) >> 8;
/* */ uint32_t minorVersion = (version & 0x0000FF) >> 0;
/* */ uint32_t minorVersion = (version & 0x00FF00) >> 8;
const uint32_t microVersion = (version & 0x0000FF) >> 0;

// NOTE: LV2 ignores 'major' version and says 0 for minor is pre-release/unstable.
if (majorVersion > 0)
@@ -948,13 +1149,6 @@ void lv2_generate_ttl(const char* const basename)
DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.groupId != kPortGroupNone);
DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.symbol.isNotEmpty());

switch (portGroup.groupId)
{
case kPortGroupMono:
case kPortGroupStereo:
continue;
}

pluginString += "\n<" DISTRHO_PLUGIN_URI "#portGroup_" + portGroup.symbol + ">\n";
isInput = isOutput = false;

@@ -978,19 +1172,28 @@ void lv2_generate_ttl(const char* const basename)
}

pluginString += " a ";

if (isInput && !isOutput)
pluginString += "pg:InputGroup";
else if (isOutput && !isInput)
pluginString += "pg:OutputGroup";
else
pluginString += "pg:Group";

switch (portGroup.groupId)
{
case kPortGroupMono:
pluginString += " , pg:MonoGroup";
break;
case kPortGroupStereo:
pluginString += " , pg:StereoGroup";
break;
}

pluginString += " ;\n";

#if 0
pluginString += " rdfs:label \"" + portGroup.name + "\" ;\n";
#else
// pluginString += " rdfs:label \"" + portGroup.name + "\" ;\n";
pluginString += " lv2:name \"" + portGroup.name + "\" ;\n";
#endif
pluginString += " lv2:symbol \"" + portGroup.symbol + "\" .\n";
}
}
@@ -1037,7 +1240,10 @@ void lv2_generate_ttl(const char* const basename)
presetsString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n";
presetsString += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n";
# if DISTRHO_PLUGIN_WANT_STATE
presetsString += "@prefix owl: <http://www.w3.org/2002/07/owl#> .\n";
presetsString += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
presetsString += "@prefix state: <" LV2_STATE_PREFIX "> .\n";
presetsString += "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n";
# endif
presetsString += "\n";

@@ -1045,8 +1251,13 @@ void lv2_generate_ttl(const char* const basename)
const uint32_t numPrograms = plugin.getProgramCount();
# if DISTRHO_PLUGIN_WANT_FULL_STATE
const uint32_t numStates = plugin.getStateCount();
const bool valid = numParameters != 0 || numStates != 0;
# else
const bool valid = numParameters != 0;
# endif

DISTRHO_CUSTOM_SAFE_ASSERT_RETURN("Programs require parameters or full state", valid, presetsFile.close());

const String presetSeparator(std::strstr(DISTRHO_PLUGIN_URI, "#") != nullptr ? ":" : "#");

char strBuf[0xff+1];
@@ -1054,6 +1265,23 @@ void lv2_generate_ttl(const char* const basename)

String presetString;

# if DISTRHO_PLUGIN_WANT_FULL_STATE
for (uint32_t i=0; i<numStates; ++i)
{
if (plugin.getStateHints(i) & kStateIsHostReadable)
continue;

// readable states are defined as lv2 parameters.
// non-readable states have no definition, but one is needed for presets and ttl validation.
presetString = "<" DISTRHO_PLUGIN_LV2_STATE_PREFIX + plugin.getStateKey(i) + ">\n";
presetString += " a owl:DatatypeProperty ;\n";
presetString += " rdfs:label \"Plugin state key-value string pair\" ;\n";
presetString += " rdfs:domain state:State ;\n";
presetString += " rdfs:range xsd:string .\n\n";
presetsString += presetString;
}
# endif

for (uint32_t i=0; i<numPrograms; ++i)
{
std::snprintf(strBuf, 0xff, "%03i", i+1);
@@ -1062,25 +1290,21 @@ void lv2_generate_ttl(const char* const basename)

presetString = "<" DISTRHO_PLUGIN_URI + presetSeparator + "preset" + strBuf + ">\n";

# if DISTRHO_PLUGIN_WANT_FULL_STATE
if (numParameters == 0 && numStates == 0)
#else
if (numParameters == 0)
#endif
{
presetString += " .";
presetsString += presetString;
continue;
}

# if DISTRHO_PLUGIN_WANT_FULL_STATE
presetString += " state:state [\n";
for (uint32_t j=0; j<numStates; ++j)
{
const String key = plugin.getStateKey(j);
const String value = plugin.getState(key);
const String value = plugin.getStateValue(key);

presetString += " <";

if (plugin.getStateHints(i) & kStateIsHostReadable)
presetString += DISTRHO_PLUGIN_URI "#";
else
presetString += DISTRHO_PLUGIN_LV2_STATE_PREFIX;

presetString += " <" DISTRHO_PLUGIN_LV2_STATE_PREFIX + key + ">";
presetString += key + ">";

if (value.length() < 10)
presetString += " \"" + value + "\" ;\n";


+ 421
- 0
distrho/src/DistrhoPluginVST.hpp View File

@@ -0,0 +1,421 @@
/*
* DISTRHO Plugin Framework (DPF)
* 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
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef DISTRHO_PLUGIN_VST_HPP_INCLUDED
#define DISTRHO_PLUGIN_VST_HPP_INCLUDED

#include "DistrhoPluginChecks.h"
#include "../DistrhoUtils.hpp"

#include <algorithm>
#include <cmath>

#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI
# undef DISTRHO_PLUGIN_HAS_UI
# define DISTRHO_PLUGIN_HAS_UI 0
#endif

#if DISTRHO_PLUGIN_HAS_UI && ! defined(HAVE_DGL) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# undef DISTRHO_PLUGIN_HAS_UI
# define DISTRHO_PLUGIN_HAS_UI 0
#endif

#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# include "Base.hpp"
#endif

#if DISTRHO_PLUGIN_HAS_UI == 1 && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS == 0
# define DPF_VST3_USES_SEPARATE_CONTROLLER 1
#else
# define DPF_VST3_USES_SEPARATE_CONTROLLER 0
#endif

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

#ifdef DISTRHO_PROPER_CPP11_SUPPORT
# include <atomic>
#else
// quick and dirty std::atomic replacement for the things we need
namespace std {
struct atomic_int {
volatile int value;
explicit atomic_int(volatile int v) noexcept : value(v) {}
int operator++() volatile noexcept { return __atomic_add_fetch(&value, 1, __ATOMIC_RELAXED); }
int operator--() volatile noexcept { return __atomic_sub_fetch(&value, 1, __ATOMIC_RELAXED); }
operator int() volatile noexcept { return __atomic_load_n(&value, __ATOMIC_RELAXED); }
};
};
#endif

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

START_NAMESPACE_DISTRHO

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

enum Vst3InternalParameters {
#if DPF_VST3_USES_SEPARATE_CONTROLLER
kVst3InternalParameterBufferSize,
kVst3InternalParameterSampleRate,
#endif
#if DISTRHO_PLUGIN_WANT_LATENCY
kVst3InternalParameterLatency,
#endif
#if DISTRHO_PLUGIN_WANT_PROGRAMS
kVst3InternalParameterProgram,
#endif
kVst3InternalParameterBaseCount,
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
kVst3InternalParameterMidiCC_start = kVst3InternalParameterBaseCount,
kVst3InternalParameterMidiCC_end = kVst3InternalParameterMidiCC_start + 130*16,
kVst3InternalParameterCount = kVst3InternalParameterMidiCC_end
#else
kVst3InternalParameterCount = kVst3InternalParameterBaseCount
#endif
};

#if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS || DISTRHO_PLUGIN_WANT_MIDI_INPUT
# define DPF_VST3_HAS_INTERNAL_PARAMETERS 1
#else
# define DPF_VST3_HAS_INTERNAL_PARAMETERS 0
#endif

#if DPF_VST3_HAS_INTERNAL_PARAMETERS && DISTRHO_PLUGIN_WANT_MIDI_INPUT && \
!(DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS)
# define DPF_VST3_PURE_MIDI_INTERNAL_PARAMETERS 1
#else
# define DPF_VST3_PURE_MIDI_INTERNAL_PARAMETERS 0
#endif

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

static inline
bool strcmp_utf16(const int16_t* const str16, const char* const str8)
{
size_t i = 0;
for (; str8[i] != '\0'; ++i)
{
const uint8_t char8 = static_cast<uint8_t>(str8[i]);

// skip non-ascii chars, unsupported
if (char8 >= 0x80)
return false;

if (str16[i] != char8)
return false;
}

return str16[i] == str8[i];
}

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

static inline
size_t strlen_utf16(const int16_t* const str)
{
size_t i = 0;

while (str[i] != 0)
++i;

return i;
}

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

static inline
void 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';
}
}

static inline
void strncpy_utf8(char* const dst, const int16_t* const src, const size_t length)
{
DISTRHO_SAFE_ASSERT_RETURN(length > 0,);

if (const size_t len = std::min(strlen_utf16(src), length-1U))
{
for (size_t i=0; i<len; ++i)
{
// skip non-ascii chars, unsupported
if (src[i] >= 0x80)
continue;

dst[i] = src[i];
}
dst[len] = 0;
}
else
{
dst[0] = 0;
}
}

static inline
void strncpy_utf16(int16_t* 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))
{
for (size_t i=0; i<len; ++i)
{
// skip non-ascii chars, unsupported
if ((uint8_t)src[i] >= 0x80)
continue;

dst[i] = src[i];
}
dst[len] = 0;
}
else
{
dst[0] = 0;
}
}

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

template<typename T>
static 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';
}

template<typename T>
static void snprintf_utf16_t(int16_t* const dst, const T value, const char* const format, const size_t size)
{
DISTRHO_SAFE_ASSERT_RETURN(size > 0,);

char* const tmpbuf = (char*)std::malloc(size);
DISTRHO_SAFE_ASSERT_RETURN(tmpbuf != nullptr,);

std::snprintf(tmpbuf, size-1, format, value);
tmpbuf[size-1] = '\0';

strncpy_utf16(dst, tmpbuf, size);
std::free(tmpbuf);
}

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_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);
}

static inline
void snprintf_f32_utf16(int16_t* const dst, const float value, const size_t size)
{
return snprintf_utf16_t<float>(dst, value, "%f", size);
}

static inline
void snprintf_i32_utf16(int16_t* const dst, const int32_t value, const size_t size)
{
return snprintf_utf16_t<int32_t>(dst, value, "%d", size);
}

static inline
void snprintf_u32_utf16(int16_t* const dst, const uint32_t value, const size_t size)
{
return snprintf_utf16_t<uint32_t>(dst, value, "%u", size);
}

#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
// --------------------------------------------------------------------------------------------------------------------
// translate a vstgui-based key character and code to matching values used by DPF

static inline
uint translateVstKeyCode(bool& special, const int16_t keychar, const int16_t keycode) noexcept
{
using namespace DGL_NAMESPACE;

// special stuff first
special = true;
switch (keycode)
{
case 1: return kKeyBackspace;
// 2 \t (handled below)
// 3 clear
// 4 \r (handled below)
case 6: return kKeyEscape;
// 7 space (handled below)
// 8 next
// 17 select
// 18 print
// 19 \n (handled below)
// 20 snapshot
case 22: return kKeyDelete;
// 23 help
// 57 = (handled below)
// numpad stuff follows
// 24 0 (handled below)
// 25 1 (handled below)
// 26 2 (handled below)
// 27 3 (handled below)
// 28 4 (handled below)
// 29 5 (handled below)
// 30 6 (handled below)
// 31 7 (handled below)
// 32 8 (handled below)
// 33 9 (handled below)
// 34 * (handled below)
// 35 + (handled below)
// 36 separator
// 37 - (handled below)
// 38 . (handled below)
// 39 / (handled below)
// handle rest of special keys
/* these special keys are missing:
- kKeySuper
- kKeyCapsLock
- kKeyPrintScreen
*/
case 40: return kKeyF1;
case 41: return kKeyF2;
case 42: return kKeyF3;
case 43: return kKeyF4;
case 44: return kKeyF5;
case 45: return kKeyF6;
case 46: return kKeyF7;
case 47: return kKeyF8;
case 48: return kKeyF9;
case 49: return kKeyF10;
case 50: return kKeyF11;
case 51: return kKeyF12;
case 11: return kKeyLeft;
case 12: return kKeyUp;
case 13: return kKeyRight;
case 14: return kKeyDown;
case 15: return kKeyPageUp;
case 16: return kKeyPageDown;
case 10: return kKeyHome;
case 9: return kKeyEnd;
case 21: return kKeyInsert;
case 54: return kKeyShift;
case 55: return kKeyControl;
case 56: return kKeyAlt;
case 58: return kKeyMenu;
case 52: return kKeyNumLock;
case 53: return kKeyScrollLock;
case 5: return kKeyPause;
}

// regular keys next
special = false;
switch (keycode)
{
case 2: return '\t';
case 4: return '\r';
case 7: return ' ';
case 19: return '\n';
case 57: return '=';
case 24: return '0';
case 25: return '1';
case 26: return '2';
case 27: return '3';
case 28: return '4';
case 29: return '5';
case 30: return '6';
case 31: return '7';
case 32: return '8';
case 33: return '9';
case 34: return '*';
case 35: return '+';
case 37: return '-';
case 38: return '.';
case 39: return '/';
}

// fallback
return keychar;
}
#endif

// --------------------------------------------------------------------------------------------------------------------
// handy way to create a utf16 string from a utf8 one on the current function scope, used for message strings

struct ScopedUTF16String {
int16_t* str;
ScopedUTF16String(const char* const s) noexcept
: str(nullptr)
{
const size_t len = std::strlen(s);
str = static_cast<int16_t*>(std::malloc(sizeof(int16_t) * (len + 1)));
DISTRHO_SAFE_ASSERT_RETURN(str != nullptr,);
strncpy_utf16(str, s, len + 1);
}

~ScopedUTF16String() noexcept
{
std::free(str);
}

operator const int16_t*() const noexcept
{
return str;
}
};

// --------------------------------------------------------------------------------------------------------------------
// handy way to create a utf8 string from a utf16 one on the current function scope (limited to 128 chars)

struct ScopedUTF8String {
char str[128];

ScopedUTF8String(const int16_t* const s) noexcept
{
strncpy_utf8(str, s, 128);
}

operator const char*() const noexcept
{
return str;
}
};

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

END_NAMESPACE_DISTRHO

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

#endif // DISTRHO_PLUGIN_VST_HPP_INCLUDED

+ 139
- 204
distrho/src/DistrhoPluginVST2.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
@@ -15,17 +15,10 @@
*/

#include "DistrhoPluginInternal.hpp"
#include "DistrhoPluginVST.hpp"
#include "../DistrhoPluginUtils.hpp"
#include "../extra/ScopedSafeLocale.hpp"

#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI
# undef DISTRHO_PLUGIN_HAS_UI
# define DISTRHO_PLUGIN_HAS_UI 0
#endif

#if DISTRHO_PLUGIN_HAS_UI && ! defined(HAVE_DGL) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# undef DISTRHO_PLUGIN_HAS_UI
# define DISTRHO_PLUGIN_HAS_UI 0
#endif
#include "../extra/ScopedPointer.hpp"

#if DISTRHO_PLUGIN_HAS_UI
# include "DistrhoUIInternal.hpp"
@@ -44,6 +37,7 @@
#include <clocale>
#include <map>
#include <string>
#include <vector>

#if VESTIGE_HEADER
# include "vestige/vestige.h"
@@ -82,37 +76,6 @@ static const requestParameterValueChangeFunc requestParameterValueChangeCallback

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

void strncpy(char* const dst, const char* const src, const size_t size)
{
DISTRHO_SAFE_ASSERT_RETURN(size > 0,);

if (const size_t len = std::min(std::strlen(src), size-1U))
{
std::memcpy(dst, src, len);
dst[len] = '\0';
}
else
{
dst[0] = '\0';
}
}

void snprintf_param(char* const dst, const float value, const size_t size)
{
DISTRHO_SAFE_ASSERT_RETURN(size > 0,);
std::snprintf(dst, size-1, "%f", value);
dst[size-1] = '\0';
}

void snprintf_iparam(char* const dst, const int32_t value, const size_t size)
{
DISTRHO_SAFE_ASSERT_RETURN(size > 0,);
std::snprintf(dst, size-1, "%d", value);
dst[size-1] = '\0';
}

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

struct ParameterAndNotesHelper
{
float* parameterValues;
@@ -187,7 +150,7 @@ public:
sendNoteCallback,
setSizeCallback,
nullptr, // TODO file request
nullptr,
d_nextBundlePath,
plugin->getInstancePointer(),
scaleFactor)
# if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
@@ -254,86 +217,16 @@ public:
# endif

# if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
int handlePluginKeyEvent(const bool down, int32_t index, const intptr_t value)
int handlePluginKeyEvent(const bool down, const int32_t index, const intptr_t value)
{
d_stdout("handlePluginKeyEvent %i %i %li\n", down, index, (long int)value);

using namespace DGL_NAMESPACE;

int special = 0;
switch (value)
{
// convert some VST special values to normal keys
case 1: index = kKeyBackspace; break;
case 2: index = '\t'; break;
// 3 clear
case 4: index = '\r'; break;
case 6: index = kKeyEscape; break;
case 7: index = ' '; break;
// 8 next
// 17 select
// 18 print
case 19: index = '\n'; break;
// 20 snapshot
case 22: index = kKeyDelete; break;
// 23 help
case 57: index = '='; break;

// numpad stuff follows
case 24: index = '0'; break;
case 25: index = '1'; break;
case 26: index = '2'; break;
case 27: index = '3'; break;
case 28: index = '4'; break;
case 29: index = '5'; break;
case 30: index = '6'; break;
case 31: index = '7'; break;
case 32: index = '8'; break;
case 33: index = '9'; break;
case 34: index = '*'; break;
case 35: index = '+'; break;
// 36 separator
case 37: index = '-'; break;
case 38: index = '.'; break;
case 39: index = '/'; break;

// handle rest of special keys
/* these special keys are missing:
- kKeySuper
- kKeyCapsLock
- kKeyPrintScreen
*/
case 40: special = kKeyF1; break;
case 41: special = kKeyF2; break;
case 42: special = kKeyF3; break;
case 43: special = kKeyF4; break;
case 44: special = kKeyF5; break;
case 45: special = kKeyF6; break;
case 46: special = kKeyF7; break;
case 47: special = kKeyF8; break;
case 48: special = kKeyF9; break;
case 49: special = kKeyF10; break;
case 50: special = kKeyF11; break;
case 51: special = kKeyF12; break;
case 11: special = kKeyLeft; break;
case 12: special = kKeyUp; break;
case 13: special = kKeyRight; break;
case 14: special = kKeyDown; break;
case 15: special = kKeyPageUp; break;
case 16: special = kKeyPageDown; break;
case 10: special = kKeyHome; break;
case 9: special = kKeyEnd; break;
case 21: special = kKeyInsert; break;
case 54: special = kKeyShift; break;
case 55: special = kKeyControl; break;
case 56: special = kKeyAlt; break;
case 58: special = kKeyMenu; break;
case 52: special = kKeyNumLock; break;
case 53: special = kKeyScrollLock; break;
case 5: special = kKeyPause; break;
}
bool special;
const uint key = translateVstKeyCode(special, index, static_cast<int32_t>(value));

switch (special)
switch (key)
{
case kKeyShift:
if (down)
@@ -355,19 +248,9 @@ public:
break;
}

if (special != 0)
{
fUI.handlePluginSpecial(down, static_cast<Key>(special), fKeyboardModifiers);
return 1;
}

if (index > 0)
{
fUI.handlePluginKeyboard(down, static_cast<uint>(index), fKeyboardModifiers);
return 1;
}

return 0;
return fUI.handlePluginKeyboardVST(down, special, key,
value >= 0 ? static_cast<uint>(value) : 0,
fKeyboardModifiers) ? 1 : 0;
}
# endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI

@@ -486,11 +369,11 @@ class PluginVst : public ParameterAndNotesHelper
{
public:
PluginVst(const audioMasterCallback audioMaster, AEffect* const effect)
: fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback),
: fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr),
fAudioMaster(audioMaster),
fEffect(effect)
{
std::memset(fProgramName, 0, sizeof(char)*(32+1));
std::memset(fProgramName, 0, sizeof(fProgramName));
std::strcpy(fProgramName, "Default");

const uint32_t parameterCount = fPlugin.getParameterCount();
@@ -575,7 +458,7 @@ public:
case effSetProgramName:
if (char* const programName = (char*)ptr)
{
DISTRHO_NAMESPACE::strncpy(fProgramName, programName, 32);
strncpy(fProgramName, programName, 32);
return 1;
}
break;
@@ -583,7 +466,7 @@ public:
case effGetProgramName:
if (char* const programName = (char*)ptr)
{
DISTRHO_NAMESPACE::strncpy(programName, fProgramName, 24);
strncpy(programName, fProgramName, 24);
return 1;
}
break;
@@ -591,7 +474,7 @@ public:
case effGetProgramNameIndexed:
if (char* const programName = (char*)ptr)
{
DISTRHO_NAMESPACE::strncpy(programName, fProgramName, 24);
strncpy(programName, fProgramName, 24);
return 1;
}
break;
@@ -621,14 +504,14 @@ public:
if (d_isNotEqual(value, enumValues.values[i].value))
continue;

DISTRHO_NAMESPACE::strncpy((char*)ptr, enumValues.values[i].label.buffer(), 24);
strncpy((char*)ptr, enumValues.values[i].label.buffer(), 24);
return 1;
}

if (hints & kParameterIsInteger)
DISTRHO_NAMESPACE::snprintf_iparam((char*)ptr, (int32_t)value, 24);
snprintf_i32((char*)ptr, (int32_t)value, 24);
else
DISTRHO_NAMESPACE::snprintf_param((char*)ptr, value, 24);
snprintf_f32((char*)ptr, value, 24);

return 1;
}
@@ -693,7 +576,7 @@ public:
else
{
UIExporter tmpUI(nullptr, 0, fPlugin.getSampleRate(),
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, d_nextBundlePath,
fPlugin.getInstancePointer(), fLastScaleFactor);
fVstRect.right = tmpUI.getWidth();
fVstRect.bottom = tmpUI.getHeight();
@@ -725,7 +608,7 @@ public:
for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{
const String& key = cit->first;
fStateMap[key] = fPlugin.getState(key);
fStateMap[key] = fPlugin.getStateValue(key);
}
# endif

@@ -801,7 +684,7 @@ public:
for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit)
{
const String& key = cit->first;
fStateMap[key] = fPlugin.getState(key);
fStateMap[key] = fPlugin.getStateValue(key);
}
# endif

@@ -980,8 +863,8 @@ public:
{
const uint32_t hints(fPlugin.getParameterHints(index));

// must be automable, and not output
if ((hints & kParameterIsAutomable) != 0 && (hints & kParameterIsOutput) == 0)
// must be automatable, and not output
if ((hints & kParameterIsAutomatable) != 0 && (hints & kParameterIsOutput) == 0)
return 1;
}
break;
@@ -1020,6 +903,8 @@ public:
#else
return -1;
#endif
if (std::strcmp(canDo, "offline") == 0)
return -1;
}
break;

@@ -1055,7 +940,7 @@ public:

void vst_setParameter(const int32_t index, const float value)
{
const uint32_t hints(fPlugin.getParameterHints(index));
const uint32_t hints = fPlugin.getParameterHints(index);
const ParameterRanges& ranges(fPlugin.getParameterRanges(index));

// TODO figure out how to detect kVstParameterUsesIntegerMinMax host support, and skip normalization
@@ -1099,11 +984,10 @@ public:

if (const VstTimeInfo* const vstTimeInfo = (const VstTimeInfo*)hostCallback(audioMasterGetTime, 0, kWantVstTimeFlags))
{
fTimePosition.frame = vstTimeInfo->samplePos;
fTimePosition.playing = (vstTimeInfo->flags & kVstTransportPlaying);
fTimePosition.bbt.valid = ((vstTimeInfo->flags & kVstTempoValid) != 0 || (vstTimeInfo->flags & kVstTimeSigValid) != 0);
fTimePosition.frame = vstTimeInfo->samplePos;
fTimePosition.playing = vstTimeInfo->flags & kVstTransportPlaying;

// ticksPerBeat is not possible with VST
// ticksPerBeat is not possible with VST2
fTimePosition.bbt.ticksPerBeat = 1920.0;

if (vstTimeInfo->flags & kVstTempoValid)
@@ -1118,6 +1002,7 @@ public:
const double barBeats = (std::fmod(ppqPos, ppqPerBar) / ppqPerBar) * vstTimeInfo->timeSigNumerator;
const double rest = std::fmod(barBeats, 1.0);

fTimePosition.bbt.valid = true;
fTimePosition.bbt.bar = static_cast<int32_t>(ppqPos) / ppqPerBar + 1;
fTimePosition.bbt.beat = static_cast<int32_t>(barBeats - rest + 0.5) + 1;
fTimePosition.bbt.tick = rest * fTimePosition.bbt.ticksPerBeat;
@@ -1133,6 +1018,7 @@ public:
}
else
{
fTimePosition.bbt.valid = false;
fTimePosition.bbt.bar = 1;
fTimePosition.bbt.beat = 1;
fTimePosition.bbt.tick = 0.0;
@@ -1193,7 +1079,7 @@ private:
AEffect* const fEffect;

// Temporary data
char fProgramName[32+1];
char fProgramName[32];

#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
uint32_t fMidiEventCount;
@@ -1384,31 +1270,38 @@ struct VstObject {
#define vstObjectPtr (VstObject*)effect->object
#define pluginPtr (vstObjectPtr)->plugin

static ScopedPointer<PluginExporter> sPlugin;

static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt)
{
// first internal init
const bool doInternalInit = (opcode == -1729 && index == 0xdead && value == 0xf00d);

if (doInternalInit)
{
// set valid but dummy values
d_lastBufferSize = 512;
d_lastSampleRate = 44100.0;
d_lastCanRequestParameterValueChanges = true;
}

// Create dummy plugin to get data from
static PluginExporter plugin(nullptr, nullptr, nullptr);

if (doInternalInit)
if (doInternalInit || opcode == effOpen)
{
// unset
d_lastBufferSize = 0;
d_lastSampleRate = 0.0;
d_lastCanRequestParameterValueChanges = false;
if (sPlugin == nullptr)
{
// set valid but dummy values
d_nextBufferSize = 512;
d_nextSampleRate = 44100.0;
d_nextPluginIsDummy = true;
d_nextCanRequestParameterValueChanges = true;

// Create dummy plugin to get data from
sPlugin = new PluginExporter(nullptr, nullptr, nullptr, nullptr);

// unset
d_nextBufferSize = 0;
d_nextSampleRate = 0.0;
d_nextPluginIsDummy = false;
d_nextCanRequestParameterValueChanges = false;
}

*(PluginExporter**)ptr = &plugin;
return 0;
if (doInternalInit)
{
*(PluginExporter**)ptr = sPlugin.get();
return 0;
}
}

// handle base opcodes
@@ -1426,15 +1319,15 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t

audioMasterCallback audioMaster = (audioMasterCallback)obj->audioMaster;

d_lastBufferSize = audioMaster(effect, audioMasterGetBlockSize, 0, 0, nullptr, 0.0f);
d_lastSampleRate = audioMaster(effect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f);
d_lastCanRequestParameterValueChanges = true;
d_nextBufferSize = audioMaster(effect, audioMasterGetBlockSize, 0, 0, nullptr, 0.0f);
d_nextSampleRate = audioMaster(effect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f);
d_nextCanRequestParameterValueChanges = true;

// some hosts are not ready at this point or return 0 buffersize/samplerate
if (d_lastBufferSize == 0)
d_lastBufferSize = 2048;
if (d_lastSampleRate <= 0.0)
d_lastSampleRate = 44100.0;
if (d_nextBufferSize == 0)
d_nextBufferSize = 2048;
if (d_nextSampleRate <= 0.0)
d_nextSampleRate = 44100.0;

obj->plugin = new PluginVst(audioMaster, effect);
return 1;
@@ -1458,53 +1351,54 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t
delete obj;
#endif

sPlugin = nullptr;
return 1;
}
//delete effect;
return 0;

case effGetParamLabel:
if (ptr != nullptr && index < static_cast<int32_t>(plugin.getParameterCount()))
if (ptr != nullptr && index < static_cast<int32_t>(sPlugin->getParameterCount()))
{
DISTRHO_NAMESPACE::strncpy((char*)ptr, plugin.getParameterUnit(index), 8);
strncpy((char*)ptr, sPlugin->getParameterUnit(index), 8);
return 1;
}
return 0;

case effGetParamName:
if (ptr != nullptr && index < static_cast<int32_t>(plugin.getParameterCount()))
if (ptr != nullptr && index < static_cast<int32_t>(sPlugin->getParameterCount()))
{
const String& shortName(plugin.getParameterShortName(index));
const String& shortName(sPlugin->getParameterShortName(index));
if (shortName.isNotEmpty())
DISTRHO_NAMESPACE::strncpy((char*)ptr, shortName, 16);
strncpy((char*)ptr, shortName, 16);
else
DISTRHO_NAMESPACE::strncpy((char*)ptr, plugin.getParameterName(index), 16);
strncpy((char*)ptr, sPlugin->getParameterName(index), 16);
return 1;
}
return 0;

case effGetParameterProperties:
if (ptr != nullptr && index < static_cast<int32_t>(plugin.getParameterCount()))
if (ptr != nullptr && index < static_cast<int32_t>(sPlugin->getParameterCount()))
{
if (VstParameterProperties* const properties = (VstParameterProperties*)ptr)
{
memset(properties, 0, sizeof(VstParameterProperties));

// full name
DISTRHO_NAMESPACE::strncpy(properties->label,
plugin.getParameterName(index),
sizeof(properties->label));
strncpy(properties->label,
sPlugin->getParameterName(index),
sizeof(properties->label));

// short name
const String& shortName(plugin.getParameterShortName(index));
const String& shortName(sPlugin->getParameterShortName(index));

if (shortName.isNotEmpty())
DISTRHO_NAMESPACE::strncpy(properties->shortLabel,
plugin.getParameterShortName(index),
sizeof(properties->shortLabel));
strncpy(properties->shortLabel,
sPlugin->getParameterShortName(index),
sizeof(properties->shortLabel));

// parameter hints
const uint32_t hints = plugin.getParameterHints(index);
const uint32_t hints = sPlugin->getParameterHints(index);

if (hints & kParameterIsOutput)
return 1;
@@ -1516,7 +1410,7 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t

if (hints & kParameterIsInteger)
{
const ParameterRanges& ranges(plugin.getParameterRanges(index));
const ParameterRanges& ranges(sPlugin->getParameterRanges(index));
properties->flags |= kVstParameterUsesIntegerMinMax;
properties->minInteger = static_cast<int32_t>(ranges.min);
properties->maxInteger = static_cast<int32_t>(ranges.max);
@@ -1528,29 +1422,29 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t
}

// parameter group (category in vst)
const uint32_t groupId = plugin.getParameterGroupId(index);
const uint32_t groupId = sPlugin->getParameterGroupId(index);

if (groupId != kPortGroupNone)
{
// we can't use groupId directly, so use the index array where this group is stored in
for (uint32_t i=0, count=plugin.getPortGroupCount(); i < count; ++i)
for (uint32_t i=0, count=sPlugin->getPortGroupCount(); i < count; ++i)
{
const PortGroupWithId& portGroup(plugin.getPortGroupByIndex(i));
const PortGroupWithId& portGroup(sPlugin->getPortGroupByIndex(i));

if (portGroup.groupId == groupId)
{
properties->category = i + 1;
DISTRHO_NAMESPACE::strncpy(properties->categoryLabel,
portGroup.name.buffer(),
sizeof(properties->categoryLabel));
strncpy(properties->categoryLabel,
portGroup.name.buffer(),
sizeof(properties->categoryLabel));
break;
}
}

if (properties->category != 0)
{
for (uint32_t i=0, count=plugin.getParameterCount(); i < count; ++i)
if (plugin.getParameterGroupId(i) == groupId)
for (uint32_t i=0, count=sPlugin->getParameterCount(); i < count; ++i)
if (sPlugin->getParameterGroupId(i) == groupId)
++properties->numParametersInCategory;
}
}
@@ -1570,7 +1464,7 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t
case effGetEffectName:
if (char* const cptr = (char*)ptr)
{
DISTRHO_NAMESPACE::strncpy(cptr, plugin.getName(), 32);
strncpy(cptr, sPlugin->getName(), 32);
return 1;
}
return 0;
@@ -1578,7 +1472,7 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t
case effGetVendorString:
if (char* const cptr = (char*)ptr)
{
DISTRHO_NAMESPACE::strncpy(cptr, plugin.getMaker(), 32);
strncpy(cptr, sPlugin->getMaker(), 32);
return 1;
}
return 0;
@@ -1586,13 +1480,13 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t
case effGetProductString:
if (char* const cptr = (char*)ptr)
{
DISTRHO_NAMESPACE::strncpy(cptr, plugin.getLabel(), 32);
strncpy(cptr, sPlugin->getLabel(), 32);
return 1;
}
return 0;

case effGetVendorVersion:
return plugin.getVersion();
return sPlugin->getVersion();

case effGetVstVersion:
return kVstVersion;
@@ -1635,12 +1529,26 @@ static void vst_processReplacingCallback(AEffect* effect, float** inputs, float*
#undef validPlugin
#undef vstObjectPtr

static struct Cleanup {
std::vector<AEffect*> effects;

~Cleanup()
{
for (std::vector<AEffect*>::iterator it = effects.begin(), end = effects.end(); it != end; ++it)
{
AEffect* const effect = *it;
delete (VstObject*)effect->object;
delete effect;
}
}
} sCleanup;

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

END_NAMESPACE_DISTRHO

DISTRHO_PLUGIN_EXPORT
#if DISTRHO_OS_WINDOWS || DISTRHO_OS_MAC
#if DISTRHO_OS_MAC || DISTRHO_OS_WASM || DISTRHO_OS_WINDOWS
const AEffect* VSTPluginMain(audioMasterCallback audioMaster);
#else
const AEffect* VSTPluginMain(audioMasterCallback audioMaster) asm ("main");
@@ -1655,6 +1563,32 @@ const AEffect* VSTPluginMain(audioMasterCallback audioMaster)
if (audioMaster(nullptr, audioMasterVersion, 0, 0, nullptr, 0.0f) == 0)
return nullptr;

// find plugin bundle
static String bundlePath;
if (bundlePath.isEmpty())
{
String tmpPath(getBinaryFilename());
tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));
#ifdef DISTRHO_OS_MAC
if (tmpPath.endsWith("/MacOS"))
{
tmpPath.truncate(tmpPath.rfind('/'));
if (tmpPath.endsWith("/Contents"))
{
tmpPath.truncate(tmpPath.rfind('/'));
bundlePath = tmpPath;
d_nextBundlePath = bundlePath.buffer();
}
}
#else
if (tmpPath.endsWith(".vst"))
{
bundlePath = tmpPath;
d_nextBundlePath = bundlePath.buffer();
}
#endif
}

// first internal init
PluginExporter* plugin = nullptr;
vst_dispatcherCallback(nullptr, -1729, 0xdead, 0xf00d, &plugin, 0.0f);
@@ -1714,12 +1648,13 @@ const AEffect* VSTPluginMain(audioMasterCallback audioMaster)
effect->processReplacing = vst_processReplacingCallback;

// pointers
VstObject* const obj(new VstObject());
VstObject* const obj = new VstObject();
obj->audioMaster = audioMaster;
obj->plugin = nullptr;

// done
effect->object = obj;
sCleanup.effects.push_back(effect);

return effect;
}


+ 4738
- 190
distrho/src/DistrhoPluginVST3.cpp
File diff suppressed because it is too large
View File


+ 120
- 32
distrho/src/DistrhoUI.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
@@ -15,10 +15,46 @@
*/

#include "src/DistrhoPluginChecks.h"
#include "src/DistrhoDefines.h"

#include <cstddef>

#ifdef DISTRHO_PROPER_CPP11_SUPPORT
# include <cstdint>
#else
# include <stdint.h>
#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)
# define x_fib_add_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_add_recent)
# define x_fib_cfg_buttons DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_cfg_buttons)
# define x_fib_cfg_filter_callback DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_cfg_filter_callback)
# define x_fib_close DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_close)
# define x_fib_configure DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_configure)
# define x_fib_filename DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_filename)
# define x_fib_free_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_free_recent)
# define x_fib_handle_events DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_handle_events)
# define x_fib_load_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_load_recent)
# define x_fib_recent_at DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_at)
# define x_fib_recent_count DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_count)
# define x_fib_recent_file DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_file)
# define x_fib_save_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_save_recent)
# define x_fib_show DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_show)
# define x_fib_status DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_status)
# define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED
# define FILE_BROWSER_DIALOG_NAMESPACE DISTRHO_NAMESPACE
# define FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE
START_NAMESPACE_DISTRHO
# include "../extra/FileBrowserDialogImpl.hpp"
END_NAMESPACE_DISTRHO
# include "../extra/FileBrowserDialogImpl.cpp"
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# if defined(DISTRHO_OS_WINDOWS)
# define WIN32_LEAN_AND_MEAN
# include <winsock2.h>
# include <windows.h>
# elif defined(HAVE_X11)
# include <X11/Xresource.h>
@@ -32,14 +68,16 @@

START_NAMESPACE_DISTRHO

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
/* ------------------------------------------------------------------------------------------------------------
* 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;
const char* g_nextBundlePath = nullptr;
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
/* ------------------------------------------------------------------------------------------------------------
* get global scale factor */

@@ -71,22 +109,18 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle)
# endif

DWORD dpiAware = 0;
DWORD scaleFactor = 100;
if (GetProcessDpiAwareness && GetScaleFactorForMonitor
&& GetProcessDpiAwareness(NULL, &dpiAware) == 0 && dpiAware != 0)
&& GetProcessDpiAwareness(nullptr, &dpiAware) == 0 && dpiAware != 0)
{
const HMONITOR hMon = parentWindowHandle != 0
? MonitorFromWindow((HWND)parentWindowHandle, MONITOR_DEFAULTTOPRIMARY)
: MonitorFromPoint(POINT{0,0}, MONITOR_DEFAULTTOPRIMARY);

DWORD scaleFactor = 0;
if (GetScaleFactorForMonitor(hMon, &scaleFactor) == 0 && scaleFactor != 0)
{
FreeLibrary(Shcore);
return static_cast<double>(scaleFactor) / 100.0;
}
GetScaleFactorForMonitor(hMon, &scaleFactor);
}

FreeLibrary(Shcore);
return static_cast<double>(scaleFactor) / 100.0;
}
#elif defined(HAVE_X11)
::Display* const display = XOpenDisplay(nullptr);
@@ -94,28 +128,31 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle)

XrmInitialize();

double dpi = 96.0;
if (char* const rms = XResourceManagerString(display))
{
if (const XrmDatabase sdb = XrmGetStringDatabase(rms))
if (const XrmDatabase db = XrmGetStringDatabase(rms))
{
char* type = nullptr;
XrmValue ret;
XrmValue value = {};

if (XrmGetResource(sdb, "Xft.dpi", "String", &type, &ret)
&& ret.addr != nullptr
if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)
&& type != nullptr
&& std::strncmp("String", type, 6) == 0)
&& std::strcmp(type, "String") == 0
&& value.addr != nullptr)
{
if (const double dpi = std::atof(ret.addr))
{
XCloseDisplay(display);
return dpi / 96;
}
char* end = nullptr;
const double xftDpi = std::strtod(value.addr, &end);
if (xftDpi > 0.0 && xftDpi < HUGE_VAL)
dpi = xftDpi;
}

XrmDestroyDatabase(db);
}
}

XCloseDisplay(display);
return dpi / 96;
#endif

return 1.0;
@@ -164,21 +201,21 @@ UI::PrivateData::createNextWindow(UI* const ui, const uint width, const uint hei
/* ------------------------------------------------------------------------------------------------------------
* UI */

UI::UI(const uint width, const uint height, const bool automaticallyScale)
UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetAsMinimumSize)
: UIWidget(UI::PrivateData::createNextWindow(this, width, height)),
uiData(UI::PrivateData::s_nextPrivateData)
{
#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
if (width > 0 && height > 0)
if (width != 0 && height != 0)
{
Widget::setSize(width, height);

if (automaticallyScale)
setGeometryConstraints(width, height, true, true);
if (automaticallyScaleAndSetAsMinimumSize)
setGeometryConstraints(width, height, true, true, true);
}
#else
// unused
return; (void)automaticallyScale;
(void)automaticallyScaleAndSetAsMinimumSize;
#endif
}

@@ -217,6 +254,11 @@ double UI::getSampleRate() const noexcept
return uiData->sampleRate;
}

const char* UI::getBundlePath() const noexcept
{
return uiData->bundlePath;
}

void UI::editParameter(uint32_t index, bool started)
{
uiData->editParamCallback(index + uiData->parameterOffset, started);
@@ -234,7 +276,7 @@ void UI::setState(const char* key, const char* value)
}
#endif

#if DISTRHO_PLUGIN_WANT_STATEFILES
#if DISTRHO_PLUGIN_WANT_STATE
bool UI::requestStateFile(const char* key)
{
return uiData->fileRequestCallback(key);
@@ -248,6 +290,13 @@ void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity)
}
#endif

#if DISTRHO_UI_FILE_BROWSER
bool UI::openFileBrowser(const FileBrowserOptions& options)
{
return getWindow().openFileBrowser((DGL_NAMESPACE::FileBrowserOptions&)options);
}
#endif

#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
/* ------------------------------------------------------------------------------------------------------------
* Direct DSP access */
@@ -260,7 +309,7 @@ void* UI::getPluginInstancePointer() const noexcept

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
/* ------------------------------------------------------------------------------------------------------------
* External UI helpers */
* External UI helpers (static calls) */

const char* UI::getNextBundlePath() noexcept
{
@@ -295,6 +344,25 @@ void UI::uiScaleFactorChanged(double)
}

#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
std::vector<DGL_NAMESPACE::ClipboardDataOffer> UI::getClipboardDataOfferTypes()
{
return uiData->window->getClipboardDataOfferTypes();
}

uint32_t UI::uiClipboardDataOffer()
{
std::vector<DGL_NAMESPACE::ClipboardDataOffer> offers(uiData->window->getClipboardDataOfferTypes());

for (std::vector<DGL_NAMESPACE::ClipboardDataOffer>::iterator it=offers.begin(), end=offers.end(); it != end;++it)
{
const DGL_NAMESPACE::ClipboardDataOffer offer = *it;
if (std::strcmp(offer.type, "text/plain") == 0)
return offer.id;
}

return 0;
}

void UI::uiFocus(bool, DGL_NAMESPACE::CrossingMode)
{
}
@@ -304,13 +372,13 @@ void UI::uiReshape(uint, uint)
// NOTE this must be the same as Window::onReshape
pData->fallbackOnResize();
}
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI

# ifndef DGL_FILE_BROWSER_DISABLED
#if DISTRHO_UI_FILE_BROWSER
void UI::uiFileBrowserSelected(const char*)
{
}
# endif
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
#endif

/* ------------------------------------------------------------------------------------------------------------
* UI Resize Handling, internal */
@@ -327,9 +395,29 @@ void UI::onResize(const ResizeEvent& ev)
{
UIWidget::onResize(ev);

#ifndef DISTRHO_PLUGIN_TARGET_VST3
if (uiData->initializing)
return;

const uint width = ev.size.getWidth();
const uint height = ev.size.getHeight();
uiData->setSizeCallback(width, height);
#endif
}

// NOTE: only used for VST3
void UI::requestSizeChange(const uint width, const uint height)
{
# ifdef DISTRHO_PLUGIN_TARGET_VST3
if (uiData->initializing)
uiData->window->setSizeForVST3(width, height);
else
uiData->setSizeCallback(width, height);
# else
// unused
(void)width;
(void)height;
# endif
}
#endif



+ 120
- 26
distrho/src/DistrhoUIInternal.hpp View File

@@ -24,10 +24,10 @@ 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;
extern const char* g_nextBundlePath;
#endif

// -----------------------------------------------------------------------
@@ -62,6 +62,7 @@ public:
uiData(new UI::PrivateData())
{
uiData->sampleRate = sampleRate;
uiData->bundlePath = bundlePath != nullptr ? strdup(bundlePath) : nullptr;
uiData->dspPtr = dspPtr;

uiData->bgColor = bgColor;
@@ -77,27 +78,28 @@ public:
uiData->setSizeCallbackFunc = setSizeCall;
uiData->fileRequestCallbackFunc = fileRequestCall;

g_nextBundlePath = bundlePath;
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
g_nextWindowId = winId;
g_nextScaleFactor = scaleFactor;
g_nextBundlePath = bundlePath;
#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;
g_nextBundlePath = nullptr;
#else
// Leave context called in the PluginWindow constructor, see DistrhoUIPrivateData.hpp
// 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
@@ -108,6 +110,9 @@ public:
~UIExporter()
{
quit();
#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
uiData->window->enterContextForDeletion();
#endif
delete ui;
delete uiData;
}
@@ -129,6 +134,23 @@ public:
return uiData->window->getScaleFactor();
}

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

bool isResizable() const noexcept
{
return uiData->window->isResizable();
}

bool isVisible() const noexcept
{
return uiData->window->isVisible();
@@ -210,7 +232,14 @@ public:

ui->uiIdle();
}
#else

void showAndFocus()
{
uiData->window->show();
uiData->window->focus();
}
#endif

bool plugin_idle()
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false);
@@ -219,7 +248,6 @@ public:
ui->uiIdle();
return ! uiData->app.isQuitting();
}
#endif

void focus()
{
@@ -234,19 +262,62 @@ public:

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

#if defined(DISTRHO_PLUGIN_TARGET_VST3) && (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS))
void idleForVST3()
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);

uiData->app.triggerIdleCallbacks();
ui->uiIdle();
}

# if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
void addIdleCallbackForVST3(IdleCallback* const cb, const uint timerFrequencyInMs)
{
uiData->window->addIdleCallback(cb, timerFrequencyInMs);
}

void removeIdleCallbackForVST3(IdleCallback* const cb)
{
uiData->window->removeIdleCallback(cb);
}
# 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_PLUGIN_HAS_EXTERNAL_UI
ui->setSize(width, height);
# else
uiData->window->setSizeForVST3(width, height);
# endif
}
#endif

void setWindowTitle(const char* const uiTitle)
{
uiData->window->setTitle(uiTitle);
}

void setWindowTransientWinId(const uintptr_t winId)
void setWindowTransientWinId(const uintptr_t transientParentWindowHandle)
{
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
ui->setTransientWindowId(winId);
#elif 0 /* TODO */
glWindow.setTransientWinId(winId);
ui->setTransientWindowId(transientParentWindowHandle);
#else
(void)winId;
uiData->window->setTransientParent(transientParentWindowHandle);
#endif
}

@@ -258,23 +329,37 @@ public:
}

#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
bool handlePluginKeyboard(const bool press, const uint key, const uint16_t mods)
bool handlePluginKeyboardVST(const bool press, const bool special, const uint keychar, const uint keycode, const uint16_t mods)
{
// TODO also trigger Character input event
DGL_NAMESPACE::Widget::KeyboardEvent ev;
ev.press = press;
ev.key = key;
ev.mod = mods;
return ui->onKeyboard(ev);
}
using namespace DGL_NAMESPACE;

bool handlePluginSpecial(const bool press, const DGL_NAMESPACE::Key key, const uint16_t mods)
{
DGL_NAMESPACE::Widget::SpecialEvent ev;
ev.press = press;
ev.key = key;
ev.mod = mods;
return ui->onSpecial(ev);
Widget::KeyboardEvent ev;
ev.mod = mods;
ev.press = press;
ev.key = keychar;
ev.keycode = keycode;

// keyboard events must always be lowercase
if (ev.key >= 'A' && ev.key <= 'Z')
ev.key += 'a' - 'A'; // A-Z -> a-z

const bool ret = ui->onKeyboard(ev);

if (press && !special && (mods & (kModifierControl|kModifierAlt|kModifierSuper)) == 0)
{
Widget::CharacterInputEvent cev;
cev.mod = mods;
cev.character = keychar;
cev.keycode = keycode;

// if shift modifier is on, convert a-z -> A-Z for character input
if (cev.character >= 'a' && cev.character <= 'z' && (mods & kModifierShift) != 0)
cev.character -= 'a' - 'A';

ui->onCharacterInput(cev);
}

return ret;
}
#endif

@@ -287,6 +372,15 @@ 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)
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);


+ 49
- 12
distrho/src/DistrhoUILV2.cpp View File

@@ -181,6 +181,33 @@ public:

fUI.stateChanged(key, value);
}
else if (atom->type == fURIDs.atomObject)
{
/* TODO
const LV2_Atom_Object* const obj = (const LV2_Atom_Object*)LV2_ATOM_BODY_CONST(atom);

const LV2_Atom* property = nullptr;
const LV2_Atom* avalue = nullptr;
lv2_atom_object_get(obj, fURIDs.patchProperty, &property, fURIDs.patchValue, &avalue, 0);

DISTRHO_SAFE_ASSERT_RETURN(property != nullptr,);
DISTRHO_SAFE_ASSERT_RETURN(avalue != nullptr,);

DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID,);
DISTRHO_SAFE_ASSERT_RETURN(avalue->type == fURIDs.atomPath || avalue->type == fURIDs.atomString,);

if (property != nullptr && property->type == fURIDs.atomURID &&
avalue != nullptr && (avalue->type == fURIDs.atomPath || avalue->type == fURIDs.atomString))
{
const char* const key = (const char*)LV2_ATOM_BODY_CONST(property);
const char* const value = (const char*)LV2_ATOM_BODY_CONST(avalue);

d_stdout("received atom object '%s' '%s'", key, value);

fUI.stateChanged(key, value);
}
*/
}
else
{
d_stdout("received atom not dpfKeyValue");
@@ -285,11 +312,13 @@ protected:
tmpStr[std::strlen(key)] = '\0';

// set msg size (key + separator + value + null terminator)
const size_t msgSize = tmpStr.length() + 1U;
const uint32_t msgSize = static_cast<uint32_t>(tmpStr.length()) + 1U;

// reserve atom space
const size_t atomSize = sizeof(LV2_Atom) + msgSize;
const uint32_t atomSize = sizeof(LV2_Atom) + msgSize;
char* const atomBuf = (char*)malloc(atomSize);
DISTRHO_SAFE_ASSERT_RETURN(atomBuf != nullptr,);

std::memset(atomBuf, 0, atomSize);

// set atom info
@@ -366,15 +395,19 @@ private:
// LV2 URIDs
const struct URIDs {
const LV2_URID_Map* _uridMap;
LV2_URID dpfKeyValue;
LV2_URID atomEventTransfer;
LV2_URID atomFloat;
LV2_URID atomLong;
LV2_URID atomPath;
LV2_URID atomString;
LV2_URID midiEvent;
LV2_URID paramSampleRate;
LV2_URID patchSet;
const LV2_URID dpfKeyValue;
const LV2_URID atomEventTransfer;
const LV2_URID atomFloat;
const LV2_URID atomLong;
const LV2_URID atomObject;
const LV2_URID atomPath;
const LV2_URID atomString;
const LV2_URID atomURID;
const LV2_URID midiEvent;
const LV2_URID paramSampleRate;
const LV2_URID patchProperty;
const LV2_URID patchSet;
const LV2_URID patchValue;

URIDs(const LV2_URID_Map* const uridMap)
: _uridMap(uridMap),
@@ -382,11 +415,15 @@ private:
atomEventTransfer(map(LV2_ATOM__eventTransfer)),
atomFloat(map(LV2_ATOM__Float)),
atomLong(map(LV2_ATOM__Long)),
atomObject(map(LV2_ATOM__Object)),
atomPath(map(LV2_ATOM__Path)),
atomString(map(LV2_ATOM__String)),
atomURID(map(LV2_ATOM__URID)),
midiEvent(map(LV2_MIDI__MidiEvent)),
paramSampleRate(map(LV2_PARAMETERS__sampleRate)),
patchSet(map(LV2_PATCH__Set)) {}
patchProperty(map(LV2_PATCH__property)),
patchSet(map(LV2_PATCH__Set)),
patchValue(map(LV2_PATCH__value)) {}

inline LV2_URID map(const char* const uri) const
{


+ 97
- 45
distrho/src/DistrhoUIPrivateData.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
@@ -19,10 +19,15 @@

#include "../DistrhoUI.hpp"

#ifdef DISTRHO_PLUGIN_TARGET_VST3
# include "DistrhoPluginVST.hpp"
#endif

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
# include "../extra/Sleep.hpp"
// TODO import and use file browser here
#else
# include "../../dgl/Application.hpp"
# include "../../dgl/src/ApplicationPrivateData.hpp"
# include "../../dgl/src/WindowPrivateData.hpp"
# include "../../dgl/src/pugl.hpp"
#endif
@@ -33,18 +38,18 @@
# define DISTRHO_UI_IS_STANDALONE 0
#endif

#if defined(DISTRHO_PLUGIN_TARGET_VST2) || defined(DISTRHO_PLUGIN_TARGET_VST3)
#ifdef DISTRHO_PLUGIN_TARGET_VST3
# define DISTRHO_UI_IS_VST3 1
#else
# define DISTRHO_UI_IS_VST3 0
#endif

#ifdef DISTRHO_PLUGIN_TARGET_VST2
# undef DISTRHO_UI_USER_RESIZABLE
# define DISTRHO_UI_USER_RESIZABLE 0
#endif

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

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
START_NAMESPACE_DISTRHO
#else
START_NAMESPACE_DGL
#endif

// -----------------------------------------------------------------------
// Plugin Application, will set class name based on plugin details
@@ -92,16 +97,18 @@ struct PluginApplication
// these are not needed
void idle() {}
void quit() {}
void triggerIdleCallbacks() {}

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication)
};
#else
class PluginApplication : public Application
class PluginApplication : public DGL_NAMESPACE::Application
{
public:
explicit PluginApplication()
: Application(DISTRHO_UI_IS_STANDALONE)
: DGL_NAMESPACE::Application(DISTRHO_UI_IS_STANDALONE)
{
#ifndef DISTRHO_OS_WASM
const char* const className = (
#ifdef DISTRHO_PLUGIN_BRAND
DISTRHO_PLUGIN_BRAND
@@ -111,6 +118,12 @@ public:
"-" DISTRHO_PLUGIN_NAME
);
setClassName(className);
#endif
}

void triggerIdleCallbacks()
{
pData->triggerIdleCallbacks();
}

DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication)
@@ -146,24 +159,31 @@ public:
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 Window
class PluginWindow : public DGL_NAMESPACE::Window
{
DISTRHO_NAMESPACE::UI* const ui;
UI* const ui;
bool initializing;
bool receivedReshapeDuringInit;

public:
explicit PluginWindow(DISTRHO_NAMESPACE::UI* const uiPtr,
explicit PluginWindow(UI* const uiPtr,
PluginApplication& app,
const uintptr_t parentWindowHandle,
const uint width,
const uint height,
const double scaleFactor)
: Window(app, parentWindowHandle, width, height, scaleFactor, DISTRHO_UI_USER_RESIZABLE, false),
: Window(app, parentWindowHandle, width, height, scaleFactor,
DISTRHO_UI_USER_RESIZABLE, DISTRHO_UI_IS_VST3, false),
ui(uiPtr),
initializing(true),
receivedReshapeDuringInit(false)
@@ -171,10 +191,18 @@ public:
if (pData->view == nullptr)
return;

// this is called just before creating UI, ensuring proper context to it
if (pData->initPost())
puglBackendEnter(pData->view);
}

~PluginWindow() override
{
if (pData->view != nullptr)
puglBackendLeave(pData->view);
}

// called after creating UI, restoring proper context
void leaveContext()
{
if (pData->view == nullptr)
@@ -187,12 +215,42 @@ public:
puglBackendLeave(pData->view);
}

// used for temporary windows (VST2/3 get size without active/visible view)
void setIgnoreIdleCallbacks(const bool ignore = true)
{
pData->ignoreIdleCallbacks = ignore;
}

// called right before deleting UI, ensuring correct context
void enterContextForDeletion()
{
if (pData->view != nullptr)
puglBackendEnter(pData->view);
}

#ifdef DISTRHO_PLUGIN_TARGET_VST3
void setSizeForVST3(const uint width, const uint height)
{
puglSetSizeAndDefault(pData->view, width, height);
}
#endif

std::vector<DGL_NAMESPACE::ClipboardDataOffer> getClipboardDataOfferTypes()
{
return Window::getClipboardDataOfferTypes();
}

protected:
uint32_t onClipboardDataOffer() override
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, 0);

if (initializing)
return 0;

return ui->uiClipboardDataOffer();
}

void onFocus(const bool focus, const DGL_NAMESPACE::CrossingMode mode) override
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
@@ -226,7 +284,7 @@ protected:
ui->uiScaleFactorChanged(scaleFactor);
}

# ifndef DGL_FILE_BROWSER_DISABLED
# if DISTRHO_UI_FILE_BROWSER
void onFileSelected(const char* filename) override;
# endif

@@ -234,21 +292,6 @@ protected:
};
#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI

#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI
END_NAMESPACE_DISTRHO
#else
END_NAMESPACE_DGL
#endif

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

START_NAMESPACE_DISTRHO

#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
using DGL_NAMESPACE::PluginApplication;
using DGL_NAMESPACE::PluginWindow;
#endif

// -----------------------------------------------------------------------
// UI callbacks

@@ -277,9 +320,13 @@ struct UI::PrivateData {
uint fgColor;
double scaleFactor;
uintptr_t winId;
#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED)
#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
char* uiStateFileKeyRequest;
#endif
char* bundlePath;

// Ignore initial resize events while initializing
bool initializing;

// Callbacks
void* callbacksPtr;
@@ -300,9 +347,11 @@ struct UI::PrivateData {
fgColor(0xffffffff),
scaleFactor(1.0),
winId(0),
#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED)
#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
uiStateFileKeyRequest(nullptr),
#endif
bundlePath(nullptr),
initializing(true),
callbacksPtr(nullptr),
editParamCallbackFunc(nullptr),
setParamCallbackFunc(nullptr),
@@ -325,14 +374,19 @@ struct UI::PrivateData {
# if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE)
parameterOffset += 1;
# endif
#endif

#ifdef DISTRHO_PLUGIN_TARGET_VST3
parameterOffset += kVst3InternalParameterCount;
#endif
}

~PrivateData() noexcept
{
#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED)
#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
std::free(uiStateFileKeyRequest);
#endif
std::free(bundlePath);
}

void editParamCallback(const uint32_t rindex, const bool started)
@@ -384,7 +438,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key)
if (fileRequestCallbackFunc != nullptr)
return fileRequestCallbackFunc(callbacksPtr, key);

#if DISTRHO_PLUGIN_WANT_STATEFILES && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED)
#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
std::free(uiStateFileKeyRequest);
uiStateFileKeyRequest = strdup(key);
DISTRHO_SAFE_ASSERT_RETURN(uiStateFileKeyRequest != nullptr, false);
@@ -393,7 +447,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key)
snprintf(title, sizeof(title)-1u, DISTRHO_PLUGIN_NAME ": %s", key);
title[sizeof(title)-1u] = '\0';

DGL_NAMESPACE::Window::FileBrowserOptions opts;
DGL_NAMESPACE::FileBrowserOptions opts;
opts.title = title;
return window->openFileBrowser(opts);
#endif
@@ -401,14 +455,10 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key)
return false;
}

END_NAMESPACE_DISTRHO

// -----------------------------------------------------------------------
// PluginWindow onFileSelected that require UI::PrivateData definitions

#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED)
START_NAMESPACE_DGL

#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI
inline void PluginWindow::onFileSelected(const char* const filename)
{
DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
@@ -416,7 +466,7 @@ inline void PluginWindow::onFileSelected(const char* const filename)
if (initializing)
return;

# if DISTRHO_PLUGIN_WANT_STATEFILES
#if DISTRHO_PLUGIN_WANT_STATE
if (char* const key = ui->uiData->uiStateFileKeyRequest)
{
ui->uiData->uiStateFileKeyRequest = nullptr;
@@ -430,14 +480,16 @@ inline void PluginWindow::onFileSelected(const char* const filename)
std::free(key);
return;
}
# endif
#endif

puglBackendEnter(pData->view);
ui->uiFileBrowserSelected(filename);
puglBackendLeave(pData->view);
}

END_NAMESPACE_DGL
#endif

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

END_NAMESPACE_DISTRHO

#endif // DISTRHO_UI_PRIVATE_DATA_HPP_INCLUDED

+ 1571
- 0
distrho/src/DistrhoUIVST3.cpp
File diff suppressed because it is too large
View File


+ 161
- 0
distrho/src/DistrhoUtils.cpp View File

@@ -0,0 +1,161 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef DISTRHO_IS_STANDALONE
# error Wrong build configuration
#endif

#include "../extra/String.hpp"
#include "../DistrhoStandaloneUtils.hpp"

#ifdef DISTRHO_OS_WINDOWS
# include <windows.h>
#else
# ifndef STATIC_BUILD
# include <dlfcn.h>
# endif
# include <limits.h>
# include <stdlib.h>
#endif

#if defined(DISTRHO_OS_WINDOWS) && !defined(STATIC_BUILD) && !DISTRHO_IS_STANDALONE
static HINSTANCE hInstance = nullptr;

DISTRHO_PLUGIN_EXPORT
BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
hInstance = hInst;
return 1;
}
#endif

START_NAMESPACE_DISTRHO

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

const char* getBinaryFilename()
{
static String filename;

#ifndef STATIC_BUILD
if (filename.isNotEmpty())
return filename;

# ifdef DISTRHO_OS_WINDOWS
# if DISTRHO_IS_STANDALONE
constexpr const HINSTANCE hInstance = nullptr;
# endif
CHAR filenameBuf[MAX_PATH];
filenameBuf[0] = '\0';
GetModuleFileNameA(hInstance, filenameBuf, sizeof(filenameBuf));
filename = filenameBuf;
# else
Dl_info info;
dladdr((void*)getBinaryFilename, &info);
char filenameBuf[PATH_MAX];
filename = realpath(info.dli_fname, filenameBuf);
# endif
#endif

return filename;
}

const char* getPluginFormatName() noexcept
{
#if defined(DISTRHO_PLUGIN_TARGET_CARLA)
return "Carla";
#elif defined(DISTRHO_PLUGIN_TARGET_JACK)
# ifdef DISTRHO_OS_WASM
return "Wasm/Standalone";
# else
return "JACK/Standalone";
# endif
#elif defined(DISTRHO_PLUGIN_TARGET_LADSPA)
return "LADSPA";
#elif defined(DISTRHO_PLUGIN_TARGET_DSSI)
return "DSSI";
#elif defined(DISTRHO_PLUGIN_TARGET_LV2)
return "LV2";
#elif defined(DISTRHO_PLUGIN_TARGET_VST2)
return "VST2";
#elif defined(DISTRHO_PLUGIN_TARGET_VST3)
return "VST3";
#else
return "Unknown";
#endif
}

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)
static String resourcePath;

if (resourcePath.isEmpty())
{
resourcePath = bundlePath;
# ifdef DISTRHO_OS_MAC
resourcePath += "/Contents/Resources";
# else
resourcePath += DISTRHO_OS_SEP_STR "resources";
# endif
}

return resourcePath.buffer();
#elif defined(DISTRHO_PLUGIN_TARGET_LV2)
static String resourcePath;

if (resourcePath.isEmpty())
{
resourcePath = bundlePath;
resourcePath += DISTRHO_OS_SEP_STR "resources";
}

return resourcePath.buffer();
#elif defined(DISTRHO_PLUGIN_TARGET_VST3)
static String resourcePath;

if (resourcePath.isEmpty())
{
resourcePath = bundlePath;
resourcePath += "/Contents/Resources";
}

return resourcePath.buffer();
#endif

return nullptr;
}

#ifndef DISTRHO_PLUGIN_TARGET_JACK
// all these are null for non-standalone targets
bool isUsingNativeAudio() noexcept { return false; }
bool supportsAudioInput() { return false; }
bool supportsBufferSizeChanges() { return false; }
bool supportsMIDI() { return false; }
bool isAudioInputEnabled() { return false; }
bool isMIDIEnabled() { return false; }
uint getBufferSize() { return 0; }
bool requestAudioInput() { return false; }
bool requestBufferSizeChange(uint) { return false; }
bool requestMIDI() { return false; }
#endif

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

END_NAMESPACE_DISTRHO

+ 10
- 0
distrho/src/dssi/seq_event-compat.h View File

@@ -1,3 +1,13 @@
/**
* \file include/seq_event.h
* \brief Application interface library for the ALSA driver
* \author Jaroslav Kysela <perex@perex.cz>
* \author Abramo Bagnara <abramo@alsa-project.org>
* \author Takashi Iwai <tiwai@suse.de>
* \date 1998-2001
*
* Application interface library for the ALSA driver
*/
/*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as


+ 324
- 177
distrho/src/jackbridge/JackBridge.cpp
File diff suppressed because it is too large
View File


+ 2
- 17
distrho/src/jackbridge/JackBridge.hpp View File

@@ -300,8 +300,8 @@ JACKBRIDGE_API const char* jackbridge_get_version_string();
JACKBRIDGE_API jack_client_t* jackbridge_client_open(const char* client_name, uint32_t options, jack_status_t* status);
JACKBRIDGE_API bool jackbridge_client_close(jack_client_t* client);

JACKBRIDGE_API int jackbridge_client_name_size();
JACKBRIDGE_API char* jackbridge_get_client_name(jack_client_t* client);
JACKBRIDGE_API int jackbridge_client_name_size();
JACKBRIDGE_API const char* jackbridge_get_client_name(jack_client_t* client);

JACKBRIDGE_API char* jackbridge_client_get_uuid(jack_client_t* client);
JACKBRIDGE_API char* jackbridge_get_uuid_for_client_name(jack_client_t* client, const char* name);
@@ -408,19 +408,4 @@ JACKBRIDGE_API int jackbridge_remove_properties(jack_client_t* client, jack_uui
JACKBRIDGE_API bool jackbridge_remove_all_properties(jack_client_t* client);
JACKBRIDGE_API bool jackbridge_set_property_change_callback(jack_client_t* client, JackPropertyChangeCallback callback, void* arg);

JACKBRIDGE_API bool jackbridge_sem_init(void* sem) noexcept;
JACKBRIDGE_API void jackbridge_sem_destroy(void* sem) noexcept;
JACKBRIDGE_API bool jackbridge_sem_connect(void* sem) noexcept;
JACKBRIDGE_API void jackbridge_sem_post(void* sem, bool server) noexcept;
JACKBRIDGE_API bool jackbridge_sem_timedwait(void* sem, uint msecs, bool server) noexcept;

JACKBRIDGE_API bool jackbridge_shm_is_valid(const void* shm) noexcept;
JACKBRIDGE_API void jackbridge_shm_init(void* shm) noexcept;
JACKBRIDGE_API void jackbridge_shm_attach(void* shm, const char* name) noexcept;
JACKBRIDGE_API void jackbridge_shm_close(void* shm) noexcept;
JACKBRIDGE_API void* jackbridge_shm_map(void* shm, uint64_t size) noexcept;
JACKBRIDGE_API void jackbridge_shm_unmap(void* shm, void* ptr) noexcept;

JACKBRIDGE_API void jackbridge_parent_deathsig(bool kill) noexcept;

#endif // JACKBRIDGE_HPP_INCLUDED

+ 0
- 257
distrho/src/jackbridge/Makefile View File

@@ -1,257 +0,0 @@
#!/usr/bin/make -f
# Makefile for jackbridge #
# ----------------------- #
# Created by falkTX
#

CWD=..
MODULENAME=jackbridge
include ../modules/Makefile.mk

# ---------------------------------------------------------------------------------------------------------------------

BUILD_CXX_FLAGS += $(JACKBRIDGE_FLAGS)
LINK_FLAGS += $(JACKBRIDGE_LIBS)

WINE_32BIT_FLAGS = $(32BIT_FLAGS) -fpermissive
WINE_64BIT_FLAGS = $(64BIT_FLAGS) -fpermissive
WINE_LINK_FLAGS = $(LINK_FLAGS) $(LIBDL_LIBS) -lpthread -lstdc++

ifeq ($(JACKBRIDGE_DIRECT),true)
BUILD_CXX_FLAGS += $(JACK_FLAGS) -DJACKBRIDGE_DIRECT
LINK_FLAGS += $(JACK_LIBS)
endif

ifneq ($(MACOS),true)
WINE_32BIT_FLAGS += -I/usr/include/wine/wine/windows
WINE_32BIT_FLAGS += -I/usr/include/wine-development/windows
WINE_32BIT_FLAGS += -I/opt/wine-devel/include/wine/windows
WINE_32BIT_FLAGS += -L/usr/lib32/wine
WINE_32BIT_FLAGS += -L/usr/lib/wine
WINE_32BIT_FLAGS += -L/usr/lib/i386-linux-gnu/wine
WINE_32BIT_FLAGS += -L/usr/lib/i386-linux-gnu/wine-development
WINE_32BIT_FLAGS += -L/opt/wine-stable/lib
WINE_32BIT_FLAGS += -L/opt/wine-stable/lib/wine
WINE_32BIT_FLAGS += -L/opt/wine-staging/lib
WINE_32BIT_FLAGS += -L/opt/wine-staging/lib/wine

WINE_64BIT_FLAGS += -I/usr/include/wine/wine/windows
WINE_64BIT_FLAGS += -I/usr/include/wine-development/windows
WINE_64BIT_FLAGS += -I/opt/wine-devel/include/wine/windows
WINE_64BIT_FLAGS += -L/usr/lib64/wine
WINE_64BIT_FLAGS += -L/usr/lib/x86_64-linux-gnu/wine
WINE_64BIT_FLAGS += -L/usr/lib/x86_64-linux-gnu/wine-development
WINE_64BIT_FLAGS += -L/opt/wine-stable/lib64
WINE_64BIT_FLAGS += -L/opt/wine-stable/lib64/wine
WINE_64BIT_FLAGS += -L/opt/wine-staging/lib64
WINE_64BIT_FLAGS += -L/opt/wine-staging/lib64/wine

WINE_LINK_FLAGS += -lrt
endif

# ---------------------------------------------------------------------------------------------------------------------

OBJS = $(OBJDIR)/JackBridge1.cpp.o $(OBJDIR)/JackBridge2.cpp.o
OBJS_arm32 = $(OBJDIR)/JackBridge1.cpp.arm32.o $(OBJDIR)/JackBridge2.cpp.arm32.o
OBJS_posix32 = $(OBJDIR)/JackBridge1.cpp.posix32.o $(OBJDIR)/JackBridge2.cpp.posix32.o
OBJS_posix64 = $(OBJDIR)/JackBridge1.cpp.posix64.o $(OBJDIR)/JackBridge2.cpp.posix64.o
OBJS_win32 = $(OBJDIR)/JackBridge1.cpp.win32.o $(OBJDIR)/JackBridge2.cpp.win32.o
OBJS_win64 = $(OBJDIR)/JackBridge1.cpp.win64.o $(OBJDIR)/JackBridge2.cpp.win64.o
OBJS_wine32 = $(OBJDIR)/JackBridge1.cpp.wine32.o $(OBJDIR)/JackBridge2.cpp.wine32.o $(OBJDIR)/JackBridge3.cpp.wine32.o
OBJS_wine64 = $(OBJDIR)/JackBridge1.cpp.wine64.o $(OBJDIR)/JackBridge2.cpp.wine64.o $(OBJDIR)/JackBridge3.cpp.wine64.o

OBJS_posix32e = $(OBJDIR)/JackBridgeExport.cpp.posix32e.o
OBJS_posix64e = $(OBJDIR)/JackBridgeExport.cpp.posix64e.o
OBJS_win64e = $(OBJDIR)/JackBridgeExport.cpp.win64e.o
OBJS_win32e = $(OBJDIR)/JackBridgeExport.cpp.win32e.o

# ---------------------------------------------------------------------------------------------------------------------

all: $(MODULEDIR)/$(MODULENAME).a

ifeq ($(WIN32),true)
posix32:
posix64:
posix32e:
posix64e:
win32: $(MODULEDIR)/$(MODULENAME).win32.a
win64: $(MODULEDIR)/$(MODULENAME).win64.a
win32e: $(MODULEDIR)/$(MODULENAME).win32e.a
win64e: $(MODULEDIR)/$(MODULENAME).win64e.a
wine32:
wine64:
else
arm32: $(MODULEDIR)/$(MODULENAME).arm32.a
posix32: $(MODULEDIR)/$(MODULENAME).posix32.a
posix64: $(MODULEDIR)/$(MODULENAME).posix64.a
posix32e: $(MODULEDIR)/$(MODULENAME).posix32e.a
posix64e: $(MODULEDIR)/$(MODULENAME).posix64e.a
win32:
win64:
win32e:
win64e:
wine32: $(MODULEDIR)/$(MODULENAME)-wine32.dll$(LIB_EXT)
wine64: $(MODULEDIR)/$(MODULENAME)-wine64.dll$(LIB_EXT)
endif

# ---------------------------------------------------------------------------------------------------------------------

clean:
rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.*

debug:
$(MAKE) DEBUG=true

# ---------------------------------------------------------------------------------------------------------------------

$(MODULEDIR)/$(MODULENAME).a: $(OBJS)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).a"
@rm -f $@
@$(AR) crs $@ $^

$(MODULEDIR)/$(MODULENAME).arm32.a: $(OBJS_arm32)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).arm32.a"
@rm -f $@
@$(AR) crs $@ $^

$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).posix32.a"
@rm -f $@
@$(AR) crs $@ $^

$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).posix64.a"
@rm -f $@
@$(AR) crs $@ $^

$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).win32.a"
@rm -f $@
@$(AR) crs $@ $^

$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).win64.a"
@rm -f $@
@$(AR) crs $@ $^

# ---------------------------------------------------------------------------------------------------------------------

$(MODULEDIR)/$(MODULENAME).posix32e.a: $(OBJS_posix32e)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).posix32e.a"
@rm -f $@
@$(AR) crs $@ $^

$(MODULEDIR)/$(MODULENAME).posix64e.a: $(OBJS_posix64e)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).posix64e.a"
@rm -f $@
@$(AR) crs $@ $^

$(MODULEDIR)/$(MODULENAME).win32e.a: $(OBJS_win32e)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).win32e.a"
@rm -f $@
@$(AR) crs $@ $^

$(MODULEDIR)/$(MODULENAME).win64e.a: $(OBJS_win64e)
-@mkdir -p $(MODULEDIR)
@echo "Creating $(MODULENAME).win64e.a"
@rm -f $@
@$(AR) crs $@ $^

# ---------------------------------------------------------------------------------------------------------------------

$(MODULEDIR)/$(MODULENAME)-wine32.dll$(LIB_EXT): $(OBJS_wine32) JackBridgeExport.def
-@mkdir -p $(MODULEDIR)
@echo "Linking $(MODULENAME)-wine32.dll$(LIB_EXT)"
@$(WINECC) $^ $(WINE_32BIT_FLAGS) $(WINE_LINK_FLAGS) $(SHARED) -o $@

$(MODULEDIR)/$(MODULENAME)-wine64.dll$(LIB_EXT): $(OBJS_wine64) JackBridgeExport.def
-@mkdir -p $(MODULEDIR)
@echo "Linking $(MODULENAME)-wine64.dll$(LIB_EXT)"
@$(WINECC) $^ $(WINE_64BIT_FLAGS) $(WINE_LINK_FLAGS) $(SHARED) -o $@

# ---------------------------------------------------------------------------------------------------------------------

$(OBJDIR)/JackBridge1.cpp.o: JackBridge1.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling JackBridge1.cpp"
@$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@

$(OBJDIR)/JackBridge2.cpp.o: JackBridge2.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling JackBridge2.cpp"
@$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@

# ---------------------------------------------------------------------------------------------------------------------

$(OBJDIR)/JackBridgeExport.cpp.%32e.o: JackBridgeExport.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling $<"
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -fpermissive -c -o $@

$(OBJDIR)/JackBridgeExport.cpp.%64e.o: JackBridgeExport.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling $<"
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -fpermissive -c -o $@

# ---------------------------------------------------------------------------------------------------------------------

$(OBJDIR)/%.cpp.arm32.o: %.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling $< (arm32)"
@$(CXX) $< $(BUILD_CXX_FLAGS) $(ARM32_FLAGS) -c -o $@

$(OBJDIR)/%.cpp.posix32.o: %.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling $< (posix32)"
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@

$(OBJDIR)/%.cpp.posix64.o: %.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling $< (posix64)"
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@

$(OBJDIR)/%.cpp.win32.o: %.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling $< (win32)"
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@

$(OBJDIR)/%.cpp.win64.o: %.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling $< (win64)"
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@

$(OBJDIR)/%.cpp.wine32.o: %.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling $< (wine32)"
@$(WINECC) $< $(BUILD_CXX_FLAGS) $(WINE_32BIT_FLAGS) -c -o $@

$(OBJDIR)/%.cpp.wine64.o: %.cpp
-@mkdir -p $(OBJDIR)
@echo "Compiling $< (wine64)"
@$(WINECC) $< $(BUILD_CXX_FLAGS) $(WINE_64BIT_FLAGS) -c -o $@

# ---------------------------------------------------------------------------------------------------------------------

-include $(OBJS:%.o=%.d)
-include $(OBJS_arm32:%.o=%.d)
-include $(OBJS_posix32:%.o=%.d)
-include $(OBJS_posix32e:%.o=%.d)
-include $(OBJS_posix64:%.o=%.d)
-include $(OBJS_posix64e:%.o=%.d)
-include $(OBJS_win32:%.o=%.d)
-include $(OBJS_win32e:%.o=%.d)
-include $(OBJS_win64:%.o=%.d)
-include $(OBJS_win64e:%.o=%.d)
-include $(OBJS_wine32:%.o=%.d)
-include $(OBJS_wine64:%.o=%.d)

# ---------------------------------------------------------------------------------------------------------------------

+ 300
- 0
distrho/src/jackbridge/NativeBridge.hpp View File

@@ -0,0 +1,300 @@
/*
* Native Bridge for DPF
* Copyright (C) 2021-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
* permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef NATIVE_BRIDGE_HPP_INCLUDED
#define NATIVE_BRIDGE_HPP_INCLUDED

#include "JackBridge.hpp"

#include "../../extra/RingBuffer.hpp"

using DISTRHO_NAMESPACE::HeapRingBuffer;

struct NativeBridge {
// Current status information
uint bufferSize;
uint sampleRate;

// Port caching information
uint numAudioIns;
uint numAudioOuts;
uint numMidiIns;
uint numMidiOuts;

// JACK callbacks
JackProcessCallback jackProcessCallback = nullptr;
JackBufferSizeCallback bufferSizeCallback = nullptr;
void* jackProcessArg = nullptr;
void* jackBufferSizeArg = nullptr;

// Runtime buffers
enum PortMask {
kPortMaskAudio = 0x1000,
kPortMaskMIDI = 0x2000,
kPortMaskInput = 0x4000,
kPortMaskOutput = 0x8000,
kPortMaskInputMIDI = kPortMaskInput|kPortMaskMIDI,
kPortMaskOutputMIDI = kPortMaskOutput|kPortMaskMIDI,
};
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
float* audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS];
float* audioBufferStorage;
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
bool midiAvailable;
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
static constexpr const uint32_t kMaxMIDIInputMessageSize = 3;
uint8_t midiDataStorage[kMaxMIDIInputMessageSize];
HeapRingBuffer midiInBufferCurrent;
HeapRingBuffer midiInBufferPending;
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
HeapRingBuffer midiOutBuffer;
#endif

NativeBridge()
: bufferSize(0),
sampleRate(0),
numAudioIns(0),
numAudioOuts(0),
numMidiIns(0),
numMidiOuts(0),
jackProcessCallback(nullptr),
bufferSizeCallback(nullptr),
jackProcessArg(nullptr),
jackBufferSizeArg(nullptr)
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
, audioBuffers()
, audioBufferStorage(nullptr)
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
, midiAvailable(false)
#endif
{
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
std::memset(audioBuffers, 0, sizeof(audioBuffers));
#endif
}

virtual ~NativeBridge() {}
virtual bool open(const char* const clientName) = 0;
virtual bool close() = 0;
virtual bool activate() = 0;
virtual bool deactivate() = 0;

virtual bool supportsAudioInput() const
{
#if DISTRHO_PLUGIN_NUM_INPUTS > 0
return true;
#else
return false;
#endif
}

virtual bool isAudioInputEnabled() const
{
#if DISTRHO_PLUGIN_NUM_INPUTS > 0
return true;
#else
return false;
#endif
}

virtual bool supportsBufferSizeChanges() const { return false; }
virtual bool isMIDIEnabled() const { return false; }
virtual bool requestAudioInput() { return false; }
virtual bool requestBufferSizeChange(uint32_t) { return false; }
virtual bool requestMIDI() { return false; }

uint32_t getBufferSize() const noexcept
{
return bufferSize;
}

bool supportsMIDI() const noexcept
{
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
return midiAvailable;
#else
return false;
#endif
}

uint32_t getEventCount()
{
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
if (midiAvailable)
{
// NOTE: this function is only called once per run
midiInBufferCurrent.copyFromAndClearOther(midiInBufferPending);
return midiInBufferCurrent.getReadableDataSize() / (kMaxMIDIInputMessageSize + 1u);
}
#endif

return 0;
}

bool getEvent(jack_midi_event_t* const event)
{
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
// NOTE: this function is called for all events in index succession
if (midiAvailable && midiInBufferCurrent.getReadableDataSize() >= (kMaxMIDIInputMessageSize + 1u))
{
event->time = 0; // TODO
event->size = midiInBufferCurrent.readByte();
event->buffer = midiDataStorage;
return midiInBufferCurrent.readCustomData(midiDataStorage, kMaxMIDIInputMessageSize);
}
#endif
return false;
// maybe unused
(void)event;
}

void clearEventBuffer()
{
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
if (midiAvailable)
midiOutBuffer.clearData();
#endif
}
bool writeEvent(const jack_nframes_t time, const jack_midi_data_t* const data, const uint32_t size)
{
if (size > 3)
return false;

#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
if (midiAvailable)
{
if (midiOutBuffer.writeByte(size) && midiOutBuffer.writeCustomData(data, size))
{
bool fail = false;
// align
switch (size)
{
case 1: fail |= !midiOutBuffer.writeByte(0);
// fall-through
case 2: fail |= !midiOutBuffer.writeByte(0);
}
fail |= !midiOutBuffer.writeUInt(time);
midiOutBuffer.commitWrite();
return !fail;
}
midiOutBuffer.commitWrite();
}
#endif

return false;
// maybe unused
(void)data;
(void)time;
}

void allocBuffers(const bool audio, const bool midi)
{
DISTRHO_SAFE_ASSERT_RETURN(bufferSize != 0,);

if (audio)
{
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
audioBufferStorage = new float[bufferSize*(DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS)];

for (uint i=0; i<DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
audioBuffers[i] = audioBufferStorage + (bufferSize * i);
#endif

#if DISTRHO_PLUGIN_NUM_INPUTS > 0
std::memset(audioBufferStorage, 0, sizeof(float)*bufferSize*DISTRHO_PLUGIN_NUM_INPUTS);
#endif
}

if (midi)
{
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
midiInBufferCurrent.createBuffer(kMaxMIDIInputMessageSize * 512);
midiInBufferPending.createBuffer(kMaxMIDIInputMessageSize * 512);
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
midiOutBuffer.createBuffer(2048);
#endif
}
}

void freeBuffers()
{
#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
delete[] audioBufferStorage;
audioBufferStorage = nullptr;
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
midiInBufferCurrent.deleteBuffer();
midiInBufferPending.deleteBuffer();
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
midiOutBuffer.deleteBuffer();
#endif
}

jack_port_t* registerPort(const char* const type, const ulong flags)
{
bool isAudio, isInput;

/**/ if (std::strcmp(type, JACK_DEFAULT_AUDIO_TYPE) == 0)
isAudio = true;
else if (std::strcmp(type, JACK_DEFAULT_MIDI_TYPE) == 0)
isAudio = false;
else
return nullptr;

/**/ if (flags & JackPortIsInput)
isInput = true;
else if (flags & JackPortIsOutput)
isInput = false;
else
return nullptr;

const uintptr_t ret = (isAudio ? kPortMaskAudio : kPortMaskMIDI)
| (isInput ? kPortMaskInput : kPortMaskOutput);

return (jack_port_t*)(ret + (isAudio ? (isInput ? numAudioIns++ : numAudioOuts++)
: (isInput ? numMidiIns++ : numMidiOuts++)));
}

void* getPortBuffer(jack_port_t* const port)
{
const uintptr_t portMask = (uintptr_t)port;
DISTRHO_SAFE_ASSERT_RETURN(portMask != 0x0, nullptr);

#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0
if (portMask & kPortMaskAudio)
return audioBuffers[(portMask & kPortMaskInput ? 0 : DISTRHO_PLUGIN_NUM_INPUTS) + (portMask & 0x0fff)];
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
if ((portMask & kPortMaskInputMIDI) == kPortMaskInputMIDI)
return (void*)0x1;
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
if ((portMask & kPortMaskOutputMIDI) == kPortMaskOutputMIDI)
return (void*)0x2;
#endif

return nullptr;
}
};

#endif // NATIVE_BRIDGE_HPP_INCLUDED

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save