| @@ -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: | |||
| @@ -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 | |||
| @@ -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 }} | |||
| @@ -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 | |||
| @@ -17,3 +17,4 @@ bin/ | |||
| build/ | |||
| docs/ | |||
| utils/lv2_ttl_generator | |||
| utils/lv2_ttl_generator.dSYM/ | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -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,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). | |||
| @@ -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 | |||
| # ------------------------------------------------------------------------------ | |||
| # | |||
| @@ -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) | |||
| }; | |||
| @@ -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; | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -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: | |||
| /** | |||
| @@ -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) | |||
| }; | |||
| @@ -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 | |||
| @@ -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 | |||
| { | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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, | |||
| @@ -47,7 +47,7 @@ public: | |||
| /** | |||
| Destructor. | |||
| */ | |||
| virtual ~SubWidget(); | |||
| ~SubWidget() override; | |||
| /** | |||
| Check if this widget contains the point defined by @a x and @a y. | |||
| @@ -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) | |||
| }; | |||
| @@ -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; | |||
| @@ -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) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| @@ -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,) | |||
| @@ -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,); | |||
| @@ -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); | |||
| @@ -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); | |||
| @@ -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; | |||
| } | |||
| @@ -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" | |||
| @@ -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); | |||
| @@ -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()); | |||
| @@ -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; | |||
| @@ -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 | |||
| @@ -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; | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -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); | |||
| } | |||
| @@ -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); | |||
| @@ -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; | |||
| @@ -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); | |||
| @@ -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()); | |||
| } | |||
| } | |||
| @@ -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); | |||
| @@ -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 | |||
| @@ -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; | |||
| @@ -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 | |||
| @@ -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) | |||
| @@ -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) | |||
| @@ -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); | |||
| @@ -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(¶ms); | |||
| ctx = nvgCreateInternal(¶ms, other); | |||
| if (ctx == NULL) goto error; | |||
| return ctx; | |||
| @@ -1 +1 @@ | |||
| Subproject commit 0fdc19059de5214973ae6ec0d775470f94ceb1c9 | |||
| Subproject commit 09afe84fcaa67adba7a168b8490046dab6ecf67d | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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(); | |||
| @@ -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" | |||
| @@ -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 | |||
| /** @} */ | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| @@ -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 | |||
| @@ -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) | |||
| }; | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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) {} | |||
| @@ -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 | |||
| @@ -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, ¤t_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, | |||
| ¤t_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 | |||
| @@ -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); | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -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, | |||
| @@ -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; | |||
| @@ -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 | |||
| @@ -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. | |||
| */ | |||
| //============================================================================== | |||
| /** | |||
| @@ -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: | |||
| @@ -25,6 +25,10 @@ | |||
| # include <sys/prctl.h> | |||
| #endif | |||
| #ifdef DISTRHO_OS_WASM | |||
| # error Threads do not work under wasm! | |||
| #endif | |||
| START_NAMESPACE_DISTRHO | |||
| // ----------------------------------------------------------------------- | |||
| @@ -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")) { | |||
| @@ -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) | |||
| @@ -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) {} | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| @@ -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); | |||
| } | |||
| @@ -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 | |||
| @@ -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 | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| @@ -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 | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| @@ -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; | |||
| @@ -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); | |||
| } | |||
| @@ -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"; | |||
| @@ -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 | |||
| @@ -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; | |||
| } | |||
| @@ -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 | |||
| @@ -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,); | |||
| @@ -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 | |||
| { | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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) | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| @@ -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 | |||