pax_global_header 0000666 0000000 0000000 00000000064 15075464575 0014534 g ustar 00root root 0000000 0000000 52 comment=a3911a4615dabbfdfd9d181ee26b05c70c289a95
vst3_public_sdk-3.8.0_build_66/ 0000775 0000000 0000000 00000000000 15075464575 0016334 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/LICENSE.txt 0000664 0000000 0000000 00000002351 15075464575 0020160 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
MIT License
Copyright (c) 2025, Steinberg Media Technologies GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
//--------------------------------------------------------------------------------- vst3_public_sdk-3.8.0_build_66/README.md 0000664 0000000 0000000 00000000711 15075464575 0017612 0 ustar 00root root 0000000 0000000 # Welcome to VST 3 SDK public_sdk
Here are located:
- helper classes implementing **VST 3** Interfaces
- samples of **VST 3** Hosting and **VST 3** plug-ins
- **AAX** Wrapper
- **AU** Wrapper
- **AUv3** Wrapper
- InterAppAudio
## License & Usage guidelines
More details are found at [VST 3 SDK public_sdk License](https://forums.steinberg.net/t/vst-3-sdk-public-sdk-license/695592)
----
Return to [VST 3 SDK](https://github.com/steinbergmedia/vst3sdk)
vst3_public_sdk-3.8.0_build_66/samples/ 0000775 0000000 0000000 00000000000 15075464575 0020000 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/ 0000775 0000000 0000000 00000000000 15075464575 0022265 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/CMakeLists.txt 0000664 0000000 0000000 00000000145 15075464575 0025025 0 ustar 00root root 0000000 0000000
if(ANDROID)
return()
endif(ANDROID)
include(SMTG_AddSubDirectories)
smtg_add_subdirectories()
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/ 0000775 0000000 0000000 00000000000 15075464575 0024264 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/CMakeLists.txt 0000664 0000000 0000000 00000005714 15075464575 0027033 0 ustar 00root root 0000000 0000000
if(SMTG_ENABLE_VST3_HOSTING_EXAMPLES)
include(SMTG_FindJack)
if(${LIBJACK_FOUND})
set(audiohost_sources
"${SDK_ROOT}/public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
"${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.cpp"
"${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.h"
source/audiohost.cpp
source/audiohost.h
source/media/audioclient.cpp
source/media/audioclient.h
source/media/imediaserver.h
source/media/iparameterclient.h
source/media/jack/jackclient.cpp
source/media/miditovst.h
source/platform/appinit.h
source/usediids.cpp
)
if(SMTG_MAC)
set(audiohost_sources
${audiohost_sources}
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm"
)
set_source_files_properties(
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm" PROPERTIES
COMPILE_FLAGS "-fobjc-arc"
)
set(audiohost_PLATFORM_LIBS "-framework CoreFoundation")
get_filename_component(InfoPlistFile "resource/Info.plist" ABSOLUTE)
set(APP_PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${InfoPlistFile}
)
elseif(SMTG_LINUX)
set(audiohost_sources
${audiohost_sources}
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_linux.cpp"
)
set(audiohost_PLATFORM_LIBS)
set(APP_PROPERTIES)
elseif(SMTG_WIN)
set(audiohost_sources
${audiohost_sources}
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_win32.cpp"
)
set(audiohost_PLATFORM_LIBS)
if(MSVC)
set(APP_PROPERTIES
LINK_FLAGS
"/SUBSYSTEM:console"
)
endif(MSVC)
endif(SMTG_MAC)
set(target audiohost)
add_executable(${target} ${audiohost_sources})
set_target_properties(${target}
PROPERTIES
${SDK_IDE_HOSTING_EXAMPLES_FOLDER}
)
target_compile_features(${target}
PUBLIC
cxx_std_17
)
target_link_libraries(${target}
PRIVATE
sdk_hosting
${audiohost_PLATFORM_LIBS}
${LIBJACK_LIBRARY}
)
target_include_directories(${target}
PRIVATE
${LIBJACK_INCLUDE_DIR}
)
if (APP_PROPERTIES)
set_target_properties(${target}
PROPERTIES
${APP_PROPERTIES}
)
endif(APP_PROPERTIES)
else()
message(STATUS "[SMTG] LIBJACK_FOUND is not set.")
endif(${LIBJACK_FOUND})
endif(SMTG_ENABLE_VST3_HOSTING_EXAMPLES)
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/README.md 0000664 0000000 0000000 00000001500 15075464575 0025537 0 ustar 00root root 0000000 0000000 # AudioHost Application
## Introduction
As Cross-platform source code: Simple cross-platform (only tested on Linux) host application allowing you to register a **VST 3** plug-in with Jack Server.
> See also: [Online Documentation](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/AudioHost.html#audiohost-application).
## Getting Started
This application is part of the VST 3 SDK package. It is created with the VST 3 SDK root project.
> See the top-level README of the VST 3 SDK: https://github.com/steinbergmedia/vst3sdk.git
## Getting Help
* Read through the SDK documentation on the **[VST 3 Developer Portal](https://steinbergmedia.github.io/vst3_dev_portal/pages/index.html)**
* Ask some real people in the official **[VST 3 Developer Forum](https://forums.steinberg.net/c/developer/103)**
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/resource/ 0000775 0000000 0000000 00000000000 15075464575 0026113 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/resource/Info.plist 0000664 0000000 0000000 00000003107 15075464575 0030064 0 ustar 00root root 0000000 0000000
CFBundleDevelopmentRegion
en
CFBundleExecutable
audiohost
CFBundleIdentifier
com.steinberg.vstsdk.audiohost
CFBundleInfoDictionaryVersion
6.0
CFBundleName
audiohost
CFBundlePackageType
BNDL
CFBundleShortVersionString
1.0.0
CFBundleVersion
1
NSHumanReadableCopyright
Copyright © 2024 Steinberg. All rights reserved.
NSPrincipalClass
NSApplication
CFBundleDocumentTypes
CFBundleTypeExtensions
vst3
CFBundleTypeName
VST3 Plug-In
CFBundleTypeRole
Audio
UTImportedTypeDeclarations
UTTypeConformsTo
public.data
public.content
UTTypeDescription
VST3 Plug-In
UTTypeIdentifier
com.steinberg.vst3.plugin
UTTypeTagSpecification
public.filename-extension
vst3
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/ 0000775 0000000 0000000 00000000000 15075464575 0025564 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/audiohost.cpp 0000664 0000000 0000000 00000011071 15075464575 0030267 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/audiohost.cpp
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/audiohost/source/audiohost.h"
#include "public.sdk/samples/vst-hosting/audiohost/source/platform/appinit.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "base/source/fcommandline.h"
#include "pluginterfaces/base/funknown.h"
#include "pluginterfaces/base/fstrdefs.h"
#include "pluginterfaces/gui/iplugview.h"
#include "pluginterfaces/gui/iplugviewcontentscalesupport.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/vsttypes.h"
#include
#include
#if WIN32
#include "windows.h"
#include
#endif
//------------------------------------------------------------------------
namespace Steinberg {
FUnknown* gStandardPluginContext = new Vst::HostApplication ();
namespace Vst {
namespace AudioHost {
static AudioHost::AppInit gInit (std::make_unique ());
//------------------------------------------------------------------------
App::~App () noexcept
{
}
//------------------------------------------------------------------------
void App::startAudioClient (const std::string& path, VST3::Optional effectID,
uint32 flags)
{
std::string error;
module = VST3::Hosting::Module::create (path, error);
if (!module)
{
std::string reason = "Could not create Module for file:";
reason += path;
reason += "\nError: ";
reason += error;
// EditorHost::IPlatform::instance ().kill (-1, reason);
return;
}
auto factory = module->getFactory ();
for (auto& classInfo : factory.classInfos ())
{
if (classInfo.category () == kVstAudioEffectClass)
{
if (effectID)
{
if (*effectID != classInfo.ID ())
continue;
}
plugProvider = owned (new PlugProvider (factory, classInfo, true));
break;
}
}
if (!plugProvider)
{
std::string error;
if (effectID)
error =
"No VST3 Audio Module Class with UID " + effectID->toString () + " found in file ";
else
error = "No VST3 Audio Module Class found in file ";
error += path;
// EditorHost::IPlatform::instance ().kill (-1, error);
return;
}
OPtr component = plugProvider->getComponent ();
OPtr controller = plugProvider->getController ();
auto midiMapping = U::cast (controller);
//! TODO: Query the plugProvider for a proper name which gets displayed in JACK.
vst3Processor = AudioClient::create ("VST 3 SDK", component, midiMapping);
}
//------------------------------------------------------------------------
void App::init (const std::vector& cmdArgs)
{
if (cmdArgs.empty ())
{
/*auto helpText = R"(
usage: AudioHost pluginPath
)";
*/
return;
}
VST3::Optional uid;
uint32 flags {};
startAudioClient (cmdArgs.back (), std::move (uid), flags);
}
//------------------------------------------------------------------------
void App::terminate ()
{
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
//------------------------------------------------------------------------
#if WIN32
int wmain (int argc, wchar_t* argv[])
{
std::vector cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (Steinberg::Vst::StringConvert::convert (Steinberg::wscast (argv[i])));
Steinberg::Vst::AudioHost::gInit.app->init (cmdArgs);
std::cout << "Press to continue . . .";
std::getchar ();
return 0;
}
#else
int main (int argc, char* argv[])
{
std::vector cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
Steinberg::Vst::AudioHost::gInit.app->init (cmdArgs);
std::cout << "Press to continue . . .";
std::getchar ();
return 0;
}
#endif
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/audiohost.h 0000664 0000000 0000000 00000003654 15075464575 0027744 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/audiohost.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
#include "public.sdk/samples/vst-hosting/audiohost/source/media/audioclient.h"
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/hosting/plugprovider.h"
#include "public.sdk/source/vst/utility/optional.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace AudioHost {
//------------------------------------------------------------------------
class App : public EditorHost::IApplication
{
public:
~App () noexcept override;
void init (const std::vector& cmdArgs) override;
void terminate () override;
private:
enum OpenFlags
{
};
void startAudioClient (const std::string& path, VST3::Optional effectID,
uint32 flags);
VST3::Hosting::Module::Ptr module {nullptr};
IPtr plugProvider {nullptr};
AudioClientPtr vst3Processor;
};
//------------------------------------------------------------------------
} // AudioHost
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/media/ 0000775 0000000 0000000 00000000000 15075464575 0026643 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/media/audioclient.cpp 0000664 0000000 0000000 00000030446 15075464575 0031656 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/audioclient.cpp
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "audioclient.h"
#include "miditovst.h"
#include "public.sdk/source/vst/hosting/eventlist.h"
#include "public.sdk/source/vst/hosting/parameterchanges.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivstmidicontrollers.h"
#include
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
// From Vst2Wrapper
static MidiCCMapping initMidiCtrlerAssignment (IComponent* component, IMidiMapping* midiMapping)
{
MidiCCMapping midiCCMapping {};
if (!midiMapping || !component)
return midiCCMapping;
int32 busses = std::min (component->getBusCount (kEvent, kInput), kMaxMidiMappingBusses);
if (midiCCMapping[0][0].empty ())
{
for (int32 b = 0; b < busses; b++)
for (int32 i = 0; i < kMaxMidiChannels; i++)
midiCCMapping[b][i].resize (Vst::kCountCtrlNumber);
}
ParamID paramID;
for (int32 b = 0; b < busses; b++)
{
for (int16 ch = 0; ch < kMaxMidiChannels; ch++)
{
for (int32 i = 0; i < Vst::kCountCtrlNumber; i++)
{
paramID = kNoParamId;
if (midiMapping->getMidiControllerAssignment (b, ch, (CtrlNumber)i, paramID) ==
kResultTrue)
{
// TODO check if tag is associated to a parameter
midiCCMapping[b][ch][i] = paramID;
}
else
midiCCMapping[b][ch][i] = kNoParamId;
}
}
}
return midiCCMapping;
}
//------------------------------------------------------------------------
static void assignBusBuffers (const IAudioClient::Buffers& buffers, HostProcessData& processData,
bool unassign = false)
{
// Set outputs
auto bufferIndex = 0;
for (auto busIndex = 0; busIndex < processData.numOutputs; busIndex++)
{
auto channelCount = processData.outputs[busIndex].numChannels;
for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++)
{
if (bufferIndex < buffers.numOutputs)
{
processData.setChannelBuffer (BusDirections::kOutput, busIndex, chanIndex,
unassign ? nullptr : buffers.outputs[bufferIndex]);
bufferIndex++;
}
}
}
// Set inputs
bufferIndex = 0;
for (auto busIndex = 0; busIndex < processData.numInputs; busIndex++)
{
auto channelCount = processData.inputs[busIndex].numChannels;
for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++)
{
if (bufferIndex < buffers.numInputs)
{
processData.setChannelBuffer (BusDirections::kInput, busIndex, chanIndex,
unassign ? nullptr : buffers.inputs[bufferIndex]);
bufferIndex++;
}
}
}
}
//------------------------------------------------------------------------
static void unassignBusBuffers (const IAudioClient::Buffers& buffers, HostProcessData& processData)
{
assignBusBuffers (buffers, processData, true);
}
//------------------------------------------------------------------------
// Vst3Processor
//------------------------------------------------------------------------
AudioClient::AudioClient ()
{
}
//------------------------------------------------------------------------
AudioClient::~AudioClient ()
{
terminate ();
}
//------------------------------------------------------------------------
AudioClientPtr AudioClient::create (const Name& name, IComponent* component,
IMidiMapping* midiMapping)
{
auto newProcessor = std::make_shared ();
newProcessor->initialize (name, component, midiMapping);
return newProcessor;
}
//------------------------------------------------------------------------
void AudioClient::initProcessContext ()
{
processContext = {};
processContext.tempo = 120;
}
//------------------------------------------------------------------------
void AudioClient::createLocalMediaServer (const Name& name)
{
mediaServer = createMediaServer (name);
mediaServer->registerAudioClient (this);
mediaServer->registerMidiClient (this);
}
//------------------------------------------------------------------------
bool AudioClient::initialize (const Name& name, IComponent* _component, IMidiMapping* midiMapping)
{
component = _component;
if (!component)
return false;
initProcessData ();
paramTransferrer.setMaxParameters (1000);
if (midiMapping)
midiCCMapping = initMidiCtrlerAssignment (component, midiMapping);
createLocalMediaServer (name);
return true;
}
//------------------------------------------------------------------------
void AudioClient::terminate ()
{
mediaServer = nullptr;
FUnknownPtr processor = component;
if (!processor)
return;
processor->setProcessing (false);
component->setActive (false);
}
//------------------------------------------------------------------------
void AudioClient::initProcessData ()
{
// processData.prepare will be done in setBlockSize
processData.inputEvents = &eventList;
processData.inputParameterChanges = &inputParameterChanges;
processData.processContext = &processContext;
initProcessContext ();
}
//------------------------------------------------------------------------
IMidiClient::IOSetup AudioClient::getMidiIOSetup () const
{
IMidiClient::IOSetup iosetup;
auto count = component->getBusCount (MediaTypes::kEvent, BusDirections::kInput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo (MediaTypes::kEvent, BusDirections::kInput, i, info) != kResultOk)
continue;
auto busName = StringConvert::convert (info.name, 128);
iosetup.inputs.push_back (busName);
}
count = component->getBusCount (MediaTypes::kEvent, BusDirections::kOutput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo (MediaTypes::kEvent, BusDirections::kOutput, i, info) !=
kResultOk)
continue;
auto busName = StringConvert::convert (info.name, 128);
iosetup.outputs.push_back (busName);
}
return iosetup;
}
//------------------------------------------------------------------------
IAudioClient::IOSetup AudioClient::getIOSetup () const
{
IAudioClient::IOSetup iosetup;
auto count = component->getBusCount (MediaTypes::kAudio, BusDirections::kOutput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo (MediaTypes::kAudio, BusDirections::kOutput, i, info) !=
kResultOk)
continue;
for (int32_t j = 0; j < info.channelCount; j++)
{
auto channelName = StringConvert::convert (info.name, 128);
iosetup.outputs.push_back (channelName + " " + std::to_string (j));
}
}
count = component->getBusCount (MediaTypes::kAudio, BusDirections::kInput);
for (int32_t i = 0; i < count; i++)
{
BusInfo info;
if (component->getBusInfo (MediaTypes::kAudio, BusDirections::kInput, i, info) != kResultOk)
continue;
for (int32_t j = 0; j < info.channelCount; j++)
{
auto channelName = StringConvert::convert (info.name, 128);
iosetup.inputs.push_back (channelName + " " + std::to_string (j));
}
}
return iosetup;
}
//------------------------------------------------------------------------
void AudioClient::preprocess (Buffers& buffers, int64_t continousFrames)
{
processData.numSamples = buffers.numSamples;
processContext.continousTimeSamples = continousFrames;
assignBusBuffers (buffers, processData);
paramTransferrer.transferChangesTo (inputParameterChanges);
}
//------------------------------------------------------------------------
bool AudioClient::process (Buffers& buffers, int64_t continousFrames)
{
FUnknownPtr processor = component;
if (!processor || !isProcessing)
return false;
preprocess (buffers, continousFrames);
if (processor->process (processData) != kResultOk)
return false;
postprocess (buffers);
return true;
}
//------------------------------------------------------------------------
void AudioClient::postprocess (Buffers& buffers)
{
eventList.clear ();
inputParameterChanges.clearQueue ();
unassignBusBuffers (buffers, processData);
}
//------------------------------------------------------------------------
bool AudioClient::setSamplerate (SampleRate value)
{
if (sampleRate == value)
return true;
sampleRate = value;
processContext.sampleRate = sampleRate;
if (blockSize == 0)
return true;
return updateProcessSetup ();
}
//------------------------------------------------------------------------
bool AudioClient::setBlockSize (int32 value)
{
if (blockSize == value)
return true;
blockSize = value;
if (sampleRate == 0)
return true;
processData.prepare (*component, blockSize, kSample32);
return updateProcessSetup ();
}
//------------------------------------------------------------------------
bool AudioClient::updateProcessSetup ()
{
FUnknownPtr processor = component;
if (!processor)
return false;
if (isProcessing)
{
if (processor->setProcessing (false) != kResultOk)
return false;
if (component->setActive (false) != kResultOk)
return false;
}
ProcessSetup setup {kRealtime, kSample32, blockSize, sampleRate};
if (processor->setupProcessing (setup) != kResultOk)
return false;
if (component->setActive (true) != kResultOk)
return false;
processor->setProcessing (true); // != kResultOk
/*
if (processor->setProcessing(true) != kResultOk)
return false;*/
isProcessing = true;
return isProcessing;
}
//------------------------------------------------------------------------
bool AudioClient::isPortInRange (int32 port, int32 channel) const
{
return port < kMaxMidiMappingBusses && !midiCCMapping[port][channel].empty ();
}
//------------------------------------------------------------------------
bool AudioClient::processVstEvent (const IMidiClient::Event& event, int32 port)
{
auto vstEvent = midiToEvent (event.type, event.channel, event.data0, event.data1);
if (vstEvent)
{
vstEvent->busIndex = port;
if (eventList.addEvent (*vstEvent) != kResultOk)
{
assert (false && "Event was not added to EventList!");
}
return true;
}
return false;
}
//------------------------------------------------------------------------
bool AudioClient::processParamChange (const IMidiClient::Event& event, int32 port)
{
auto paramMapping = [port, this] (int32 channel, MidiData data1) -> ParamID {
if (!isPortInRange (port, channel))
return kNoParamId;
return midiCCMapping[port][channel][data1];
};
auto paramChange =
midiToParameter (event.type, event.channel, event.data0, event.data1, paramMapping);
if (paramChange)
{
int32 index = 0;
IParamValueQueue* queue =
inputParameterChanges.addParameterData ((*paramChange).first, index);
if (queue)
{
if (queue->addPoint (static_cast (event.timestamp), (*paramChange).second,
index) != kResultOk)
{
assert (false && "Parameter point was not added to ParamValueQueue!");
}
}
return true;
}
return false;
}
//------------------------------------------------------------------------
bool AudioClient::onEvent (const IMidiClient::Event& event, int32_t port)
{
// Try to create Event first.
if (processVstEvent (event, port))
return true;
// In case this is no event it must be a parameter.
if (processParamChange (event, port))
return true;
// TODO: Something else???
return true;
}
//------------------------------------------------------------------------
void AudioClient::setParameter (ParamID id, ParamValue value, int32 sampleOffset)
{
paramTransferrer.addChange (id, value, sampleOffset);
}
//------------------------------------------------------------------------
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/media/audioclient.h 0000664 0000000 0000000 00000007540 15075464575 0031322 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/audioclient.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h"
#include "public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h"
#include "public.sdk/source/vst/hosting/eventlist.h"
#include "public.sdk/source/vst/hosting/parameterchanges.h"
#include "public.sdk/source/vst/hosting/processdata.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
class IMidiMapping;
class IComponent;
enum
{
kMaxMidiMappingBusses = 4,
kMaxMidiChannels = 16
};
using Controllers = std::vector;
using Channels = std::array;
using Busses = std::array;
using MidiCCMapping = Busses;
//------------------------------------------------------------------------
using AudioClientPtr = std::shared_ptr;
//------------------------------------------------------------------------
class AudioClient : public IAudioClient, public IMidiClient, public IParameterClient
{
public:
//--------------------------------------------------------------------
using Name = std::string;
AudioClient ();
~AudioClient () override;
static AudioClientPtr create (const Name& name, IComponent* component,
IMidiMapping* midiMapping);
// IAudioClient
bool process (Buffers& buffers, int64_t continousFrames) override;
bool setSamplerate (SampleRate value) override;
bool setBlockSize (int32 value) override;
IAudioClient::IOSetup getIOSetup () const override;
// IMidiClient
bool onEvent (const Event& event, int32_t port) override;
IMidiClient::IOSetup getMidiIOSetup () const override;
// IParameterClient
void setParameter (ParamID id, ParamValue value, int32 sampleOffset) override;
bool initialize (const Name& name, IComponent* component, IMidiMapping* midiMapping);
//--------------------------------------------------------------------
private:
void createLocalMediaServer (const Name& name);
void terminate ();
void updateBusBuffers (Buffers& buffers, HostProcessData& processData);
void initProcessData ();
void initProcessContext ();
bool updateProcessSetup ();
void preprocess (Buffers& buffers, int64_t continousFrames);
void postprocess (Buffers& buffers);
bool isPortInRange (int32 port, int32 channel) const;
bool processVstEvent (const IMidiClient::Event& event, int32 port);
bool processParamChange (const IMidiClient::Event& event, int32 port);
SampleRate sampleRate = 0;
int32 blockSize = 0;
HostProcessData processData;
ProcessContext processContext;
EventList eventList;
ParameterChanges inputParameterChanges;
IComponent* component = nullptr;
ParameterChangeTransfer paramTransferrer;
MidiCCMapping midiCCMapping;
IMediaServerPtr mediaServer;
bool isProcessing = false;
Name name;
};
//------------------------------------------------------------------------
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/media/imediaserver.h 0000664 0000000 0000000 00000005163 15075464575 0031500 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include
#include
#include
#include
//----------------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
using IOName = std::string;
using IONames = std::vector;
using AudioClientName = std::string;
struct IAudioClient
{
struct Buffers
{
float** inputs;
int32_t numInputs;
float** outputs;
int32_t numOutputs;
int32_t numSamples;
};
struct IOSetup
{
IONames inputs;
IONames outputs;
};
virtual bool process (Buffers& buffers, int64_t continousFrames) = 0;
virtual bool setSamplerate (SampleRate value) = 0;
virtual bool setBlockSize (int32 value) = 0;
virtual IOSetup getIOSetup () const = 0;
virtual ~IAudioClient () {}
};
//----------------------------------------------------------------------------------
struct IMidiClient
{
using MidiData = uint8_t;
struct Event
{
MidiData type;
MidiData channel;
MidiData data0;
MidiData data1;
int64_t timestamp;
};
struct IOSetup
{
IONames inputs;
IONames outputs;
};
virtual bool onEvent (const Event& event, int32_t port) = 0;
virtual IOSetup getMidiIOSetup () const = 0;
virtual ~IMidiClient () {}
};
//----------------------------------------------------------------------------------
struct IMediaServer
{
virtual bool registerAudioClient (IAudioClient* client) = 0;
virtual bool registerMidiClient (IMidiClient* client) = 0;
virtual ~IMediaServer () {}
};
//----------------------------------------------------------------------------------
using IMediaServerPtr = std::shared_ptr;
IMediaServerPtr createMediaServer (const AudioClientName& name);
//----------------------------------------------------------------------------------
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/media/iparameterclient.h 0000664 0000000 0000000 00000003010 15075464575 0032336 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include
#include
//----------------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//----------------------------------------------------------------------------------
struct IParameterClient
{
virtual void setParameter (ParamID id, ParamValue value, int32 sampleOffset) = 0;
virtual ~IParameterClient () {}
};
//----------------------------------------------------------------------------------
using IParameterClientPtr = std::weak_ptr;
//----------------------------------------------------------------------------------
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/media/jack/ 0000775 0000000 0000000 00000000000 15075464575 0027553 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/media/jack/jackclient.cpp 0000664 0000000 0000000 00000027330 15075464575 0032373 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/jack/jackclient.cpp
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3 using Jack
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h"
#include
//! Workaround for Jack on Windows
#if defined(SMTG_OS_WINDOWS) && defined(_STDINT)
#define _STDINT_H
#endif
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
static const int kJackSuccess = 0;
//------------------------------------------------------------------------
// jack Client
//------------------------------------------------------------------------
class JackClient : public IMediaServer
{
public:
//--------------------------------------------------------------------
using JackPorts = std::vector;
using JackName = std::string;
JackClient () = default;
~JackClient () override;
// IMediaServer interface
bool registerAudioClient (IAudioClient* client) override;
bool registerMidiClient (IMidiClient* client) override;
bool initialize (JackName name);
// jack process callback
int process (jack_nframes_t nframes);
//--------------------------------------------------------------------
private:
jack_client_t* registerClient (JackName name);
bool registerAudioPorts (IAudioClient* processor);
bool registerMidiPorts (IMidiClient* processor);
bool addAudioOutputPort (JackName name);
bool addAudioInputPort (JackName name);
bool addMidiInputPort (JackName name);
int processMidi (jack_nframes_t nframes);
bool setupJackProcessCallbacks (jack_client_t* client);
bool autoConnectAudioPorts (jack_client_t* client);
bool autoConnectMidiPorts (jack_client_t* client);
void updateAudioBuffers (jack_nframes_t nframes);
// Jack objects
jack_client_t* jackClient = nullptr;
JackPorts audioOutputPorts;
JackPorts audioInputPorts;
JackPorts midiInputPorts;
IAudioClient* audioClient = nullptr;
IMidiClient* midiClient = nullptr;
using BufferPointers = std::vector;
BufferPointers audioOutputPointers;
BufferPointers audioInputPointers;
IAudioClient::Buffers buffers {nullptr};
};
//------------------------------------------------------------------------
int jack_on_process (jack_nframes_t nframes, void* arg)
{
auto client = reinterpret_cast (arg);
return client->process (nframes);
}
//------------------------------------------------------------------------
int jack_on_set_sample_rate (jack_nframes_t nframes, void* arg)
{
auto client = reinterpret_cast (arg);
client->setSamplerate (static_cast (nframes));
return kJackSuccess;
}
//------------------------------------------------------------------------
int jack_on_set_block_size (jack_nframes_t nframes, void* arg)
{
auto client = reinterpret_cast (arg);
client->setBlockSize (static_cast (nframes));
return kJackSuccess;
}
//------------------------------------------------------------------------
IMediaServerPtr createMediaServer (const AudioClientName& name)
{
auto client = std::make_shared ();
client->initialize (name);
return client;
}
//------------------------------------------------------------------------
JackClient::~JackClient ()
{
//! We do not need to "unregister" ports. It is done automatically with "jack_client_close"
jack_deactivate (jackClient); // Stops calls of process
jack_client_close (jackClient); // Remove client from process graph and remove all ports
}
//------------------------------------------------------------------------
bool JackClient::registerAudioClient (IAudioClient* client)
{
if (audioClient)
return false;
audioClient = client;
//! First thing to do: register the audio ports.
if (!registerAudioPorts (audioClient))
return false;
//! Setup all the callbacks like setSampleRate and process etc.
if (!setupJackProcessCallbacks (jackClient))
return false;
//! Activate after defining the callbacks. It is said in the documentation.
if (jack_activate (jackClient) != kJackSuccess)
return false;
//! AFTER activation, register the ports.
if (!autoConnectAudioPorts (jackClient))
return false;
return true;
}
//------------------------------------------------------------------------
bool JackClient::registerMidiClient (IMidiClient* client)
{
if (midiClient)
return false;
midiClient = client;
//! Register the midi ports.
if (!registerMidiPorts (midiClient))
return false;
//! Afterwards auto-connect them.
if (!autoConnectMidiPorts (jackClient))
return false;
return true;
}
//------------------------------------------------------------------------
bool JackClient::initialize (JackClient::JackName name)
{
jackClient = registerClient (name);
if (!jackClient)
return false;
return true;
}
//------------------------------------------------------------------------
void JackClient::updateAudioBuffers (jack_nframes_t nframes)
{
int outputIndex = 0;
for (auto audioOutputPort : audioOutputPorts)
{
auto* portBuffer = jack_port_get_buffer (audioOutputPort, nframes);
if (!portBuffer)
continue;
buffers.outputs[outputIndex++] = static_cast (portBuffer);
}
int inputIndex = 0;
for (auto audioInputPort : audioInputPorts)
{
auto* portBuffer = jack_port_get_buffer (audioInputPort, nframes);
if (!portBuffer)
continue;
buffers.inputs[inputIndex++] = static_cast (portBuffer);
}
}
//------------------------------------------------------------------------
int JackClient::process (jack_nframes_t nframes)
{
processMidi (nframes);
buffers.numSamples = nframes;
updateAudioBuffers (nframes);
if (!audioClient)
return 0;
if (audioClient->process (buffers, jack_last_frame_time (jackClient)) == false)
{
assert (false);
}
return kJackSuccess;
}
//------------------------------------------------------------------------
bool JackClient::registerAudioPorts (IAudioClient* processor)
{
auto ioSetup = processor->getIOSetup ();
for (const auto& output : ioSetup.outputs)
addAudioOutputPort (output);
for (const auto& input : ioSetup.inputs)
addAudioInputPort (input);
buffers.inputs = audioInputPointers.data ();
buffers.numInputs = (int32_t)audioInputPointers.size ();
buffers.numOutputs = (int32_t)audioOutputPointers.size ();
buffers.outputs = audioOutputPointers.data ();
return true;
}
//------------------------------------------------------------------------
bool JackClient::registerMidiPorts (IMidiClient* processor)
{
const auto ioSetup = processor->getMidiIOSetup ();
for (const auto& input : ioSetup.inputs)
addMidiInputPort (input);
return true;
}
//------------------------------------------------------------------------
bool JackClient::addAudioOutputPort (JackClient::JackName name)
{
auto port =
jack_port_register (jackClient, name.data (), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
if (!port)
return false;
audioOutputPorts.push_back (port);
audioOutputPointers.resize (audioOutputPorts.size ());
return true;
}
//------------------------------------------------------------------------
bool JackClient::addAudioInputPort (JackClient::JackName name)
{
auto port =
jack_port_register (jackClient, name.data (), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
if (!port)
return false;
audioInputPorts.push_back (port);
audioInputPointers.resize (audioOutputPorts.size ());
return true;
}
//------------------------------------------------------------------------
bool JackClient::addMidiInputPort (JackClient::JackName name)
{
auto port =
jack_port_register (jackClient, name.data (), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
if (!port)
return false;
midiInputPorts.push_back (port);
return true;
}
//------------------------------------------------------------------------
int JackClient::processMidi (jack_nframes_t nframes)
{
static const uint8_t kChannelMask = 0x0F;
static const uint8_t kStatusMask = 0xF0;
static const uint32_t kDataMask = 0x7F;
for (int32_t portIndex = 0, count = static_cast (midiInputPorts.size ());
portIndex < count; ++portIndex)
{
auto midiInputPort = midiInputPorts.at (portIndex);
auto* portBuffer = jack_port_get_buffer (midiInputPort, nframes);
if (!portBuffer)
continue;
jack_midi_event_t in_event;
auto event_count = jack_midi_get_event_count (portBuffer);
for (uint32_t i = 0; i < event_count; i++)
{
jack_midi_event_get (&in_event, portBuffer, i);
if (in_event.size == 0)
continue;
auto midiData = in_event.buffer;
Steinberg::Vst::IMidiClient::MidiData channel = midiData[0] & kChannelMask;
Steinberg::Vst::IMidiClient::MidiData status = midiData[0] & kStatusMask;
Steinberg::Vst::IMidiClient::MidiData data0 = midiData[1];
Steinberg::Vst::IMidiClient::MidiData data1 = midiData[2];
midiClient->onEvent ({status, channel, data0, data1, in_event.time}, portIndex);
}
}
return kJackSuccess;
}
//------------------------------------------------------------------------
jack_client_t* JackClient::registerClient (JackClient::JackName name)
{
jack_options_t options = JackNullOption;
jack_status_t status;
jackClient = jack_client_open (name.data (), options, &status, nullptr);
return jackClient;
/* Use the status to check for errors:
if (status & JackServerFailed)
{
fprintf (stderr, "Unable to connect to JACK server\n");
}*/
}
//------------------------------------------------------------------------
bool JackClient::setupJackProcessCallbacks (jack_client_t* client)
{
if (jack_set_process_callback (client, jack_on_process, this) != kJackSuccess)
return false;
if (jack_set_sample_rate_callback (client, jack_on_set_sample_rate, audioClient) !=
kJackSuccess)
return false;
if (jack_set_buffer_size_callback (client, jack_on_set_block_size, audioClient) != kJackSuccess)
return false;
return true;
}
//------------------------------------------------------------------------
bool JackClient::autoConnectAudioPorts (jack_client_t* client)
{
int portIndex = 0;
//! Connect Audio Outputs
auto ports = jack_get_ports (client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput);
for (auto& port : audioOutputPorts)
{
if (!ports[portIndex])
break;
auto output__port_name = ports[portIndex++];
auto res = jack_connect (client, jack_port_name (port), output__port_name);
if (res != 0)
break;
}
jack_free (ports);
return true;
}
//------------------------------------------------------------------------
bool JackClient::autoConnectMidiPorts (jack_client_t* client)
{
int portIndex = 1;
//! Connect MIDI Inputs
auto ports = jack_get_ports (client, nullptr, "midi", JackPortIsPhysical | JackPortIsOutput);
if (!ports)
return false;
for (auto& port : midiInputPorts)
{
if (!ports[portIndex])
break;
auto inputPortName = ports[portIndex++];
auto res = jack_connect (client, inputPortName, jack_port_name (port));
if (res != 0)
continue;
}
jack_free (ports);
return true;
}
//------------------------------------------------------------------------
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/media/miditovst.h 0000664 0000000 0000000 00000010463 15075464575 0031042 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/miditovst.h
// Created by : Steinberg 09.2016
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/source/vst/utility/optional.h"
#include "pluginterfaces/vst/ivstevents.h"
#include "pluginterfaces/vst/ivstmidicontrollers.h"
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
const uint8_t kNoteOff = 0x80; ///< note, off velocity
const uint8_t kNoteOn = 0x90; ///< note, on velocity
const uint8_t kPolyPressure = 0xA0; ///< note, pressure
const uint8_t kController = 0xB0; ///< controller, value
const uint8_t kProgramChangeStatus = 0xC0; ///< program change
const uint8_t kAfterTouchStatus = 0xD0; ///< channel pressure
const uint8_t kPitchBendStatus = 0xE0; ///< lsb, msb
static const uint32 kDataMask = 0x7F;
const float kMidiScaler = 1.f / 127.f;
using MidiData = uint8_t;
float toNormalized (const MidiData& data)
{
return (float)data * kMidiScaler;
}
using OptionalEvent = VST3::Optional;
using ParameterChange = std::pair;
using OptionParamChange = VST3::Optional;
OptionalEvent midiToEvent (MidiData status, MidiData channel, MidiData midiData0,
MidiData midiData1)
{
Event new_event = {};
if (status == kNoteOn || status == kNoteOff)
{
if (status == kNoteOff) // note off
{
new_event.noteOff.noteId = -1;
new_event.type = Event::kNoteOffEvent;
new_event.noteOff.channel = channel;
new_event.noteOff.pitch = midiData0;
new_event.noteOff.velocity = toNormalized (midiData1);
return new_event;
}
if (status == kNoteOn) // note on
{
new_event.noteOn.noteId = -1;
new_event.type = Event::kNoteOnEvent;
new_event.noteOn.channel = channel;
new_event.noteOn.pitch = midiData0;
new_event.noteOn.velocity = toNormalized (midiData1);
return new_event;
}
}
//--- -----------------------------
else if (status == kPolyPressure)
{
new_event.type = Vst::Event::kPolyPressureEvent;
new_event.polyPressure.channel = channel;
new_event.polyPressure.pitch = midiData0;
new_event.polyPressure.pressure = toNormalized (midiData1);
return new_event;
}
return {};
}
//------------------------------------------------------------------------
using ToParameterIdFunc = std::function;
OptionParamChange midiToParameter (MidiData status, MidiData channel, MidiData midiData1,
MidiData midiData2, const ToParameterIdFunc& toParamID)
{
if (!toParamID)
return {};
ParameterChange paramChange;
if (status == kController) // controller
{
paramChange.first = toParamID (channel, midiData1);
if (paramChange.first != kNoParamId)
{
paramChange.second = (double)midiData2 * kMidiScaler;
return paramChange;
}
}
else if (status == kPitchBendStatus)
{
paramChange.first = toParamID (channel, Vst::kPitchBend);
if (paramChange.first != kNoParamId)
{
const double kPitchWheelScaler = 1. / (double)0x3FFF;
const int32 ctrl = (midiData1 & kDataMask) | (midiData2 & kDataMask) << 7;
paramChange.second = kPitchWheelScaler * (double)ctrl;
return paramChange;
};
}
else if (status == kAfterTouchStatus)
{
paramChange.first = toParamID (channel, Vst::kAfterTouch);
if (paramChange.first != kNoParamId)
{
paramChange.second = (ParamValue) (midiData1 & kDataMask) * kMidiScaler;
return paramChange;
};
}
else if (status == kProgramChangeStatus)
{
// TODO
}
return {};
}
//------------------------------------------------------------------------
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/platform/ 0000775 0000000 0000000 00000000000 15075464575 0027410 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/platform/appinit.h 0000664 0000000 0000000 00000002603 15075464575 0031226 0 ustar 00root root 0000000 0000000 //------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/platform/appinit.h
// Created by : Steinberg, 04/2005
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace AudioHost {
//------------------------------------------------------------------------
struct AppInit
{
explicit AppInit (EditorHost::ApplicationPtr&& _app) { app = std::move (_app); }
EditorHost::ApplicationPtr app;
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/audiohost/source/usediids.cpp 0000664 0000000 0000000 00000002274 15075464575 0030106 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : AudioHost
// Filename : public.sdk/samples/vst-hosting/audiohost/source/usediids.cpp
// Created by : Steinberg 09.2008
// Description : Audio Host Example for VST 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
//#define INIT_CLASS_IID
// This macro definition modifies the behavior of DECLARE_CLASS_IID (funknown.h)
// and produces the actual symbols for all interface identifiers.
// It must be defined before including the interface headers and
// in only one source file!
//------------------------------------------------------------------------
//#define INIT_CLASS_IID
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/ 0000775 0000000 0000000 00000000000 15075464575 0024451 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/CMakeLists.txt 0000664 0000000 0000000 00000013512 15075464575 0027213 0 ustar 00root root 0000000 0000000
if(SMTG_ENABLE_VST3_HOSTING_EXAMPLES)
set(editorhost_sources
${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.cpp
${SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.h
source/editorhost.cpp
source/editorhost.h
source/platform/appinit.h
source/platform/iapplication.h
source/platform/iplatform.h
source/platform/iwindow.h
source/usediids.cpp
)
if(SMTG_MAC)
set(editorhost_sources
${editorhost_sources}
source/platform/mac/platform.mm
source/platform/mac/window.h
source/platform/mac/window.mm
${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm
)
set_source_files_properties(
source/platform/mac/window.mm
source/platform/mac/platform.mm
"${SDK_ROOT}/public.sdk/source/vst/hosting/module_mac.mm" PROPERTIES
COMPILE_FLAGS "-fobjc-arc"
)
set(editorhost_PLATFORM_LIBS
"-framework Cocoa"
)
get_filename_component(InfoPlistFile "resource/Info.plist" ABSOLUTE)
set(APP_PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${InfoPlistFile}
)
elseif(SMTG_LINUX)
# X11 files and configuration
find_package(X11 REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
pkg_check_modules(GTKMM3 REQUIRED gtkmm-3.0)
include_directories(${X11_INCLUDE_DIR} ${GTKMM3_INCLUDE_DIRS} ${GTK3_INCLUDE_DIRS})
set(editorhost_sources
${editorhost_sources}
source/platform/linux/platform.cpp
source/platform/linux/runloop.cpp
source/platform/linux/runloop.h
source/platform/linux/window.cpp
source/platform/linux/window.h
${SDK_ROOT}/public.sdk/source/vst/hosting/module_linux.cpp
)
set(editorhost_PLATFORM_LIBS
${X11_LIBRARIES}
${GTK3_LIBRARIES}
${GTKMM3_LIBRARIES}
)
set(APP_PROPERTIES
)
# Wayland files and configuration
if(SMTG_ENABLE_WAYLAND_SUPPORT)
# Fetch wayland-server-delegate to get iwaylandclientcontext.h
include (FetchContent)
FetchContent_Declare(
ccl_wayland_server_delegate
GIT_REPOSITORY https://github.com/cclsoftware/wayland-server-delegate.git
GIT_TAG 86c32c61f0c861448635c041da5c343ce097ccff
)
FetchContent_MakeAvailable(ccl_wayland_server_delegate)
# Modify the wayland-server-delegate source directory, because it is needed to build without errors.
set(WAYLAND_SERVER_DELEGATE_FOLDER_NAME "wayland-server-delegate")
execute_process(COMMAND
${CMAKE_COMMAND} -E create_symlink ${ccl_wayland_server_delegate_SOURCE_DIR} ${FETCHCONTENT_BASE_DIR}/${WAYLAND_SERVER_DELEGATE_FOLDER_NAME}
)
list (APPEND CMAKE_PREFIX_PATH "${FETCHCONTENT_BASE_DIR}/${WAYLAND_SERVER_DELEGATE_FOLDER_NAME}/cmake")
list (APPEND CMAKE_MODULE_PATH "${FETCHCONTENT_BASE_DIR}/${WAYLAND_SERVER_DELEGATE_FOLDER_NAME}/cmake")
list (APPEND WAYLAND_PROTOCOLS
"stable/xdg-shell/xdg-shell.xml"
"stable/linux-dmabuf/linux-dmabuf-v1.xml"
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
)
find_package (wayland-server-delegate)
set(editorhost_sources
${editorhost_sources}
source/platform/linux/wayland/clientcontext.cpp
source/platform/linux/wayland/clientcontext.h
source/platform/linux/wayland/platform.cpp
source/platform/linux/wayland/runloop.cpp
source/platform/linux/wayland/runloop.h
source/platform/linux/wayland/surfacedelegate.cpp
source/platform/linux/wayland/surfacedelegate.h
source/platform/linux/wayland/window.cpp
source/platform/linux/wayland/window.h
)
set(editorhost_PLATFORM_LIBS
${editorhost_PLATFORM_LIBS}
wayland-server-delegate
)
endif(SMTG_ENABLE_WAYLAND_SUPPORT)
elseif(SMTG_WIN)
set(editorhost_sources
${editorhost_sources}
source/platform/win32/platform.cpp
source/platform/win32/window.cpp
source/platform/win32/window.h
${SDK_ROOT}/public.sdk/source/vst/hosting/module_win32.cpp
)
set(editorhost_PLATFORM_LIBS
)
if(MSVC)
set(APP_PROPERTIES
LINK_FLAGS "/SUBSYSTEM:windows"
)
endif(MSVC)
if(MINGW)
# It causes the UNICODE preprocessor macro to be predefined,
# and chooses Unicode-capable runtime startup code.
set(APP_PROPERTIES
LINK_FLAGS -municode
)
endif(MINGW)
endif(SMTG_MAC)
set(target editorhost)
add_executable(${target} ${editorhost_sources})
set_target_properties(${target}
PROPERTIES
${SDK_IDE_HOSTING_EXAMPLES_FOLDER}
)
target_compile_features(${target}
PUBLIC
cxx_std_17
)
target_link_libraries(${target}
PRIVATE
sdk_hosting
${editorhost_PLATFORM_LIBS}
)
smtg_target_setup_universal_binary(${target})
if (APP_PROPERTIES)
set_target_properties(${target}
PROPERTIES
${APP_PROPERTIES}
)
endif(APP_PROPERTIES)
if(SMTG_LINUX AND SMTG_ENABLE_WAYLAND_SUPPORT)
target_compile_definitions(${target}
PRIVATE
SMTG_EDITOR_HOST_USE_WAYLAND=1
)
endif()
endif(SMTG_ENABLE_VST3_HOSTING_EXAMPLES)
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/README.md 0000664 0000000 0000000 00000001506 15075464575 0025732 0 ustar 00root root 0000000 0000000 # EditorHost Application
## Introduction
As Cross-platform source code: Simple cross-platform (Win/macOS/Linux) host application allowing you to open the editor of a **VST 3** plug-in (with HiDPI support on Windows/macOS).
> See also: [Online Documentation](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/EditorHost.html).
## Getting Started
This application is part of the VST 3 SDK package. It is created with the VST 3 SDK root project.
> See the top-level README of the VST 3 SDK: https://github.com/steinbergmedia/vst3sdk.git
## Getting Help
* Read through the SDK documentation on the **[VST 3 Developer Portal](https://steinbergmedia.github.io/vst3_dev_portal/pages/index.html)**
* Ask some real people in the official **[VST 3 Developer Forum](https://forums.steinberg.net/c/developer/103)**
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/resource/ 0000775 0000000 0000000 00000000000 15075464575 0026300 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/resource/Info.plist 0000664 0000000 0000000 00000004125 15075464575 0030252 0 ustar 00root root 0000000 0000000
CFBundleDevelopmentRegion
en
CFBundleExecutable
editorhost
CFBundleIdentifier
com.steinberg.vstsdk.editorhost
CFBundleInfoDictionaryVersion
6.0
CFBundleName
editorhost
CFBundlePackageType
BNDL
CFBundleShortVersionString
1.0.0
CFBundleVersion
1
NSHumanReadableCopyright
Copyright © 2020 Steinberg. All rights reserved.
NSPrincipalClass
NSApplication
CFBundleDocumentTypes
CFBundleTypeExtensions
vst3
CFBundleTypeName
VST3 Plug-In
CFBundleTypeRole
Editor
UTImportedTypeDeclarations
UTTypeConformsTo
public.data
public.content
UTTypeDescription
VST3 Plug-In
UTTypeIdentifier
com.steinberg.vst3.plugin
UTTypeTagSpecification
public.filename-extension
vst3
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/ 0000775 0000000 0000000 00000000000 15075464575 0025751 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/editorhost.cpp 0000664 0000000 0000000 00000030053 15075464575 0030642 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/editorhost.cpp
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/editorhost/source/editorhost.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/appinit.h"
#include "base/source/fcommandline.h"
#include "base/source/fdebug.h"
#include "pluginterfaces/base/funknown.h"
#include "pluginterfaces/gui/iplugview.h"
#include "pluginterfaces/gui/iplugviewcontentscalesupport.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/vsttypes.h"
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
//------------------------------------------------------------------------
inline bool operator== (const ViewRect& r1, const ViewRect& r2)
{
return memcmp (&r1, &r2, sizeof (ViewRect)) == 0;
}
//------------------------------------------------------------------------
inline bool operator!= (const ViewRect& r1, const ViewRect& r2)
{
return !(r1 == r2);
}
namespace Vst {
namespace EditorHost {
static AppInit gInit (std::make_unique ());
//------------------------------------------------------------------------
class WindowController : public IWindowController, public IPlugFrame
{
public:
WindowController (const IPtr& plugView);
~WindowController () noexcept override;
void onShow (IWindow& w) override;
void onClose (IWindow& w) override;
void onResize (IWindow& w, Size newSize) override;
Size constrainSize (IWindow& w, Size requestedSize) override;
void onContentScaleFactorChanged (IWindow& window, float newScaleFactor) override;
// IPlugFrame
tresult PLUGIN_API resizeView (IPlugView* view, ViewRect* newSize) override;
void closePlugView ();
private:
tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override
{
if (FUnknownPrivate::iidEqual (_iid, IPlugFrame::iid) ||
FUnknownPrivate::iidEqual (_iid, FUnknown::iid))
{
*obj = this;
addRef ();
return kResultTrue;
}
if (window)
return window->queryInterface (_iid, obj);
return kNoInterface;
}
// we do not care here of the ref-counting. A plug-in call of release should not destroy this
// class!
uint32 PLUGIN_API addRef () override { return 1000; }
uint32 PLUGIN_API release () override { return 1000; }
IPtr plugView;
IWindow* window {nullptr};
bool resizeViewRecursionGard {false};
};
//------------------------------------------------------------------------
class ComponentHandler : public IComponentHandler
{
public:
tresult PLUGIN_API beginEdit (ParamID id) override
{
SMTG_DBPRT1 ("beginEdit called (%d)\n", id);
return kNotImplemented;
}
tresult PLUGIN_API performEdit (ParamID id, ParamValue valueNormalized) override
{
SMTG_DBPRT2 ("performEdit called (%d, %f)\n", id, valueNormalized);
return kNotImplemented;
}
tresult PLUGIN_API endEdit (ParamID id) override
{
SMTG_DBPRT1 ("endEdit called (%d)\n", id);
return kNotImplemented;
}
tresult PLUGIN_API restartComponent (int32 flags) override
{
SMTG_DBPRT1 ("restartComponent called (%d)\n", flags);
return kNotImplemented;
}
private:
tresult PLUGIN_API queryInterface (const TUID /*_iid*/, void** /*obj*/) override
{
return kNoInterface;
}
// we do not care here of the ref-counting. A plug-in call of release should not destroy this
// class!
uint32 PLUGIN_API addRef () override { return 1000; }
uint32 PLUGIN_API release () override { return 1000; }
};
static ComponentHandler gComponentHandler;
//------------------------------------------------------------------------
App::~App () noexcept
{
terminate ();
}
//------------------------------------------------------------------------
void App::openEditor (const std::string& path, VST3::Optional effectID, uint32 flags)
{
std::string error;
module = VST3::Hosting::Module::create (path, error);
if (!module)
{
std::string reason = "Could not create Module for file:";
reason += path;
reason += "\nError: ";
reason += error;
IPlatform::instance ().kill (-1, reason);
}
auto factory = module->getFactory ();
if (auto factoryHostContext = IPlatform::instance ().getPluginFactoryContext ())
factory.setHostContext (factoryHostContext);
for (auto& classInfo : factory.classInfos ())
{
if (classInfo.category () == kVstAudioEffectClass)
{
if (effectID)
{
if (*effectID != classInfo.ID ())
continue;
}
plugProvider = owned (new PlugProvider (factory, classInfo, true));
if (plugProvider->initialize () == false)
plugProvider = nullptr;
break;
}
}
if (!plugProvider)
{
if (effectID)
error =
"No VST3 Audio Module Class with UID " + effectID->toString () + " found in file ";
else
error = "No VST3 Audio Module Class found in file ";
error += path;
IPlatform::instance ().kill (-1, error);
}
auto editController = plugProvider->getController ();
if (!editController)
{
error = "No EditController found (needed for allowing editor) in file " + path;
IPlatform::instance ().kill (-1, error);
}
editController->release (); // plugProvider does an addRef
if (flags & kSetComponentHandler)
{
SMTG_DBPRT0 ("setComponentHandler is used\n");
editController->setComponentHandler (&gComponentHandler);
}
SMTG_DBPRT1 ("Open Editor for %s...\n", path.c_str ());
createViewAndShow (editController);
if (flags & kSecondWindow)
{
SMTG_DBPRT0 ("Open 2cd Editor...\n");
createViewAndShow (editController);
}
}
//------------------------------------------------------------------------
void App::createViewAndShow (IEditController* controller)
{
auto view = owned (controller->createView (ViewType::kEditor));
if (!view)
{
IPlatform::instance ().kill (-1, "EditController does not provide its own editor");
}
ViewRect plugViewSize {};
auto result = view->getSize (&plugViewSize);
if (result != kResultTrue)
{
IPlatform::instance ().kill (-1, "Could not get editor view size");
}
auto viewRect = ViewRectToRect (plugViewSize);
windowController = std::make_shared (view);
window = IPlatform::instance ().createWindow (
"Editor", viewRect.size, view->canResize () == kResultTrue, windowController);
if (!window)
{
IPlatform::instance ().kill (-1, "Could not create window");
}
window->show ();
}
//------------------------------------------------------------------------
void App::init (const std::vector& cmdArgs)
{
VST3::Optional uid;
uint32 flags {};
for (auto it = cmdArgs.begin (), end = cmdArgs.end (); it != end; ++it)
{
if (*it == "--componentHandler")
flags |= kSetComponentHandler;
else if (*it == "--secondWindow")
flags |= kSecondWindow;
else if (*it == "--uid")
{
if (++it != end)
uid = VST3::UID::fromString (*it);
if (!uid)
IPlatform::instance ().kill (-1, "wrong argument to --uid");
}
}
if (cmdArgs.empty () || cmdArgs.back ().find (".vst3") == std::string::npos)
{
auto helpText = R"(
usage: EditorHost [options] pluginPath
options:
--componentHandler
set optional component handler on edit controller
--secondWindow
create a second window
--uid UID
use effect class with unique class ID==UID
)";
IPlatform::instance ().kill (0, helpText);
}
PluginContextFactory::instance ().setPluginContext (&pluginContext);
openEditor (cmdArgs.back (), std::move (uid), flags);
}
//------------------------------------------------------------------------
void App::terminate ()
{
if (windowController)
windowController->closePlugView ();
windowController.reset ();
plugProvider.reset ();
module.reset ();
PluginContextFactory::instance ().setPluginContext (nullptr);
}
//------------------------------------------------------------------------
WindowController::WindowController (const IPtr& plugView) : plugView (plugView)
{
}
//------------------------------------------------------------------------
WindowController::~WindowController () noexcept
{
}
//------------------------------------------------------------------------
void WindowController::onShow (IWindow& w)
{
SMTG_DBPRT1 ("onShow called (%p)\n", (void*)&w);
window = &w;
if (!plugView)
return;
auto platformWindow = window->getNativePlatformWindow ();
if (plugView->isPlatformTypeSupported (platformWindow.type) != kResultTrue)
{
IPlatform::instance ().kill (-1, std::string ("PlugView does not support platform type:") +
platformWindow.type);
}
plugView->setFrame (this);
if (plugView->attached (platformWindow.ptr, platformWindow.type) != kResultTrue)
{
IPlatform::instance ().kill (-1, "Attaching PlugView failed");
}
}
//------------------------------------------------------------------------
void WindowController::closePlugView ()
{
if (plugView)
{
plugView->setFrame (nullptr);
if (plugView->removed () != kResultTrue)
{
IPlatform::instance ().kill (-1, "Removing PlugView failed");
}
plugView = nullptr;
}
window = nullptr;
}
//------------------------------------------------------------------------
void WindowController::onClose (IWindow& w)
{
SMTG_DBPRT1 ("onClose called (%p)\n", (void*)&w);
closePlugView ();
// TODO maybe quit only when the last window is closed
IPlatform::instance ().quit ();
}
//------------------------------------------------------------------------
void WindowController::onResize (IWindow& w, Size newSize)
{
SMTG_DBPRT1 ("onResize called (%p)\n", (void*)&w);
if (plugView)
{
ViewRect r {};
r.right = newSize.width;
r.bottom = newSize.height;
ViewRect r2 {};
if (plugView->getSize (&r2) == kResultTrue && r != r2)
plugView->onSize (&r);
}
}
//------------------------------------------------------------------------
Size WindowController::constrainSize (IWindow& w, Size requestedSize)
{
SMTG_DBPRT1 ("constrainSize called (%p)\n", (void*)&w);
if (plugView)
{
ViewRect r {};
r.right = requestedSize.width;
r.bottom = requestedSize.height;
if (plugView->checkSizeConstraint (&r) != kResultTrue)
{
requestedSize.width = r.right - r.left;
requestedSize.height = r.bottom - r.top;
}
}
return requestedSize;
}
//------------------------------------------------------------------------
void WindowController::onContentScaleFactorChanged (IWindow& w, float newScaleFactor)
{
SMTG_DBPRT1 ("onContentScaleFactorChanged called (%p)\n", (void*)&w);
if (auto css = U::cast (plugView))
{
css->setContentScaleFactor (newScaleFactor);
}
}
//------------------------------------------------------------------------
tresult PLUGIN_API WindowController::resizeView (IPlugView* view, ViewRect* newSize)
{
SMTG_DBPRT1 ("resizeView called (%p)\n", (void*)view);
if (newSize == nullptr || view == nullptr || view != plugView)
return kInvalidArgument;
if (!window)
return kInternalError;
if (resizeViewRecursionGard)
return kResultFalse;
ViewRect r;
if (plugView->getSize (&r) != kResultTrue)
return kInternalError;
if (r == *newSize)
return kResultTrue;
resizeViewRecursionGard = true;
Size size {newSize->right - newSize->left, newSize->bottom - newSize->top};
window->resize (size);
resizeViewRecursionGard = false;
if (plugView->getSize (&r) != kResultTrue)
return kInternalError;
if (r != *newSize)
plugView->onSize (newSize);
return kResultTrue;
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/editorhost.h 0000664 0000000 0000000 00000004236 15075464575 0030313 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/editorhost.h
// Created by : Steinberg 09.2016
// Description : Example of opening a Plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/hosting/plugprovider.h"
#include "public.sdk/source/vst/utility/optional.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
class WindowController;
//------------------------------------------------------------------------
class App : public IApplication
{
public:
~App () noexcept override;
void init (const std::vector& cmdArgs) override;
void terminate () override;
private:
enum OpenFlags
{
kSetComponentHandler = 1 << 0,
kSecondWindow = 1 << 1,
};
void openEditor (const std::string& path, VST3::Optional effectID, uint32 flags);
void createViewAndShow (IEditController* controller);
VST3::Hosting::Module::Ptr module {nullptr};
IPtr plugProvider {nullptr};
Vst::HostApplication pluginContext;
WindowPtr window;
std::shared_ptr windowController;
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/ 0000775 0000000 0000000 00000000000 15075464575 0027575 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/appinit.h 0000664 0000000 0000000 00000002722 15075464575 0031415 0 ustar 00root root 0000000 0000000 //------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/appinit.h
// Created by : Steinberg, 04/2005
// Description : Editor Host Example for VST SDK 3
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
struct AppInit
{
explicit AppInit (ApplicationPtr&& app)
{
IPlatform::instance ().setApplication (std::move (app));
}
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/iapplication.h 0000664 0000000 0000000 00000002730 15075464575 0032424 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class IApplication
{
public:
virtual ~IApplication () noexcept = default;
virtual void init (const std::vector& cmdArgs) = 0;
virtual void terminate () = 0;
};
using ApplicationPtr = std::unique_ptr;
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/iplatform.h 0000664 0000000 0000000 00000003610 15075464575 0031743 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h"
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class IPlatform
{
public:
virtual ~IPlatform () noexcept = default;
virtual void setApplication (ApplicationPtr&& app) = 0;
virtual WindowPtr createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller) = 0;
virtual void quit () = 0;
virtual void kill (int resultCode, const std::string& reason) = 0;
virtual FUnknown* getPluginFactoryContext () = 0;
static IPlatform& instance ();
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
extern void ApplicationInit (int argc, const char* argv[]);
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/iwindow.h 0000664 0000000 0000000 00000006532 15075464575 0031434 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/gui/iplugview.h"
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
using Coord = int32_t;
//------------------------------------------------------------------------
struct Point
{
Coord x;
Coord y;
};
//------------------------------------------------------------------------
struct Size
{
Coord width;
Coord height;
};
//------------------------------------------------------------------------
inline bool operator!= (const Size& lhs, const Size& rhs)
{
return lhs.width != rhs.width || lhs.height != rhs.height;
}
//------------------------------------------------------------------------
inline bool operator== (const Size& lhs, const Size& rhs)
{
return lhs.width == rhs.width && lhs.height == rhs.height;
}
//------------------------------------------------------------------------
struct Rect
{
Point origin;
Size size;
};
//------------------------------------------------------------------------
inline Rect ViewRectToRect (ViewRect r)
{
Rect result {};
result.origin.x = r.left;
result.origin.y = r.top;
result.size.width = r.right - r.left;
result.size.height = r.bottom - r.top;
return result;
}
//------------------------------------------------------------------------
struct NativePlatformWindow
{
FIDString type;
void* ptr;
};
class IWindow;
//------------------------------------------------------------------------
class IWindowController
{
public:
virtual ~IWindowController () noexcept = default;
virtual void onShow (IWindow& window) = 0;
virtual void onClose (IWindow& window) = 0;
virtual void onResize (IWindow& window, Size newSize) = 0;
virtual void onContentScaleFactorChanged (IWindow& window, float newScaleFactor) = 0;
virtual Size constrainSize (IWindow& window, Size requestedSize) = 0;
};
using WindowControllerPtr = std::shared_ptr;
//------------------------------------------------------------------------
class IWindow
{
public:
virtual ~IWindow () noexcept = default;
virtual void show () = 0;
virtual void close () = 0;
virtual void resize (Size newSize) = 0;
virtual Size getContentSize () = 0;
virtual NativePlatformWindow getNativePlatformWindow () const = 0;
virtual tresult queryInterface (const TUID iid, void** obj) = 0;
};
using WindowPtr = std::shared_ptr;
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/ 0000775 0000000 0000000 00000000000 15075464575 0030734 5 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h 0000664 0000000 0000000 00000012753 15075464575 0033646 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h
// Created by : Steinberg 09.2023
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// LICENSE
// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved
//-----------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Steinberg Media Technologies nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.h"
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/gui/iplugview.h"
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Linux {
//------------------------------------------------------------------------
class RunLoopImpl : U::ImplementsNonDestroyable>
{
public:
static IRunLoop& instance ()
{
static RunLoopImpl impl;
return impl;
}
tresult PLUGIN_API registerEventHandler (IEventHandler* handler, FileDescriptor fd) override;
tresult PLUGIN_API unregisterEventHandler (IEventHandler* handler) override;
tresult PLUGIN_API registerTimer (ITimerHandler* handler, TimerInterval milliseconds) override;
tresult PLUGIN_API unregisterTimer (ITimerHandler* handler) override;
private:
using TimerID = uint64_t;
using EventHandler = IPtr;
using TimerHandler = IPtr;
using EventHandlers = std::unordered_map;
using TimerHandlers = std::unordered_map;
EventHandlers eventHandlers;
TimerHandlers timerHandlers;
};
//------------------------------------------------------------------------
inline tresult PLUGIN_API RunLoopImpl::registerEventHandler (IEventHandler* handler,
FileDescriptor fd)
{
if (!handler || eventHandlers.find (fd) != eventHandlers.end ())
return kInvalidArgument;
Vst::EditorHost::RunLoop::instance ().registerFileDescriptor (
fd, [handler] (int fd) { handler->onFDIsSet (fd); });
eventHandlers.emplace (fd, handler);
return kResultTrue;
}
//------------------------------------------------------------------------
inline tresult PLUGIN_API RunLoopImpl::unregisterEventHandler (IEventHandler* handler)
{
if (!handler)
return kInvalidArgument;
auto it = std::find_if (eventHandlers.begin (), eventHandlers.end (),
[&] (const auto& elem) { return elem.second == handler; });
if (it == eventHandlers.end ())
return kResultFalse;
Vst::EditorHost::RunLoop::instance ().unregisterFileDescriptor (it->first);
eventHandlers.erase (it);
return kResultTrue;
}
//------------------------------------------------------------------------
inline tresult PLUGIN_API RunLoopImpl::registerTimer (ITimerHandler* handler,
TimerInterval milliseconds)
{
if (!handler || milliseconds == 0)
return kInvalidArgument;
auto id = Vst::EditorHost::RunLoop::instance ().registerTimer (
milliseconds, [handler] (auto) { handler->onTimer (); });
timerHandlers.emplace (id, handler);
return kResultTrue;
}
//------------------------------------------------------------------------
inline tresult PLUGIN_API RunLoopImpl::unregisterTimer (ITimerHandler* handler)
{
if (!handler)
return kInvalidArgument;
auto it = std::find_if (timerHandlers.begin (), timerHandlers.end (),
[&] (const auto& elem) { return elem.second == handler; });
if (it == timerHandlers.end ())
return kResultFalse;
Vst::EditorHost::RunLoop::instance ().unregisterTimer (it->first);
timerHandlers.erase (it);
return kResultTrue;
}
//------------------------------------------------------------------------
} // Linux
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/platform.cpp 0000664 0000000 0000000 00000013264 15075464575 0033272 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/platform.cpp
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/window.h"
#include
#include
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
using namespace std::chrono;
using clock = high_resolution_clock;
//------------------------------------------------------------------------
static int pause (int milliSeconds)
{
struct timeval timeOut;
if (milliSeconds > 0)
{
timeOut.tv_usec = (milliSeconds % (unsigned long)1000) * 1000;
timeOut.tv_sec = milliSeconds / (unsigned long)1000;
select (0, nullptr, nullptr, nullptr, &timeOut);
}
return (milliSeconds > 0 ? milliSeconds : 0);
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
class Platform : public IPlatform
{
public:
static Platform& instance ()
{
static Platform gInstance;
return gInstance;
}
void setApplication (ApplicationPtr&& app) override;
WindowPtr createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller) override;
void quit () override;
void kill (int resultCode, const std::string& reason) override;
FUnknown* getPluginFactoryContext () override;
void run (const std::vector& cmdArgs);
static const int kMinEventLoopRate = 16; // 60Hz
private:
void onWindowClosed (X11Window* window);
void closeAllWindows ();
void eventLoop ();
ApplicationPtr application;
Display* xDisplay {nullptr};
std::vector> windows;
};
//------------------------------------------------------------------------
#if !SMTG_EDITOR_HOST_USE_WAYLAND
IPlatform& IPlatform::instance ()
{
return Platform::instance ();
}
#endif
//------------------------------------------------------------------------
void Platform::setApplication (ApplicationPtr&& app)
{
application = std::move (app);
}
//------------------------------------------------------------------------
WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
auto window = X11Window::make (title, size, resizeable, controller, xDisplay,
[this] (X11Window* window) { onWindowClosed (window); });
if (window)
windows.push_back (std::static_pointer_cast (window));
return window;
}
//------------------------------------------------------------------------
void Platform::onWindowClosed (X11Window* window)
{
for (auto it = windows.begin (); it != windows.end (); ++it)
{
if (it->get () == window)
{
windows.erase (it);
break;
}
}
}
//------------------------------------------------------------------------
void Platform::closeAllWindows ()
{
for (auto& w : windows)
{
w->close ();
}
}
//------------------------------------------------------------------------
void Platform::quit ()
{
static bool recursiveGuard = false;
if (recursiveGuard)
return;
recursiveGuard = true;
closeAllWindows ();
if (application)
application->terminate ();
RunLoop::instance ().stop ();
recursiveGuard = false;
}
//------------------------------------------------------------------------
void Platform::kill (int resultCode, const std::string& reason)
{
std::cout << reason << "\n";
exit (resultCode);
}
//------------------------------------------------------------------------
FUnknown* Platform::getPluginFactoryContext ()
{
return &Steinberg::Linux::RunLoopImpl::instance ();
}
//------------------------------------------------------------------------
void Platform::run (const std::vector& cmdArgs)
{
// Connect to X server
std::string displayName (getenv ("DISPLAY"));
if (displayName.empty ())
displayName = ":0.0";
if ((xDisplay = XOpenDisplay (displayName.data ())) == nullptr)
{
return;
}
RunLoop::instance ().setDisplay (xDisplay);
application->init (cmdArgs);
eventLoop ();
XCloseDisplay (xDisplay);
}
//------------------------------------------------------------------------
void Platform::eventLoop ()
{
RunLoop::instance ().start ();
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
//------------------------------------------------------------------------
//------------------------------------------------------------------------
#if !SMTG_EDITOR_HOST_USE_WAYLAND
int main (int argc, char* argv[])
{
std::vector cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
Steinberg::Vst::EditorHost::Platform::instance ().run (cmdArgs);
return 0;
}
#endif
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/runloop.cpp 0000664 0000000 0000000 00000016371 15075464575 0033146 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.cpp
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.h"
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
using LockGuard = std::lock_guard;
//------------------------------------------------------------------------
RunLoop& RunLoop::instance ()
{
static RunLoop gInstance;
return gInstance;
}
//------------------------------------------------------------------------
void RunLoop::setDisplay (Display* display)
{
this->display = display;
}
//------------------------------------------------------------------------
void RunLoop::registerWindow (XID window, const EventCallback& callback)
{
map.emplace (window, callback);
}
//------------------------------------------------------------------------
void RunLoop::unregisterWindow (XID window)
{
auto it = map.find (window);
if (it == map.end ())
return;
map.erase (it);
}
//------------------------------------------------------------------------
void RunLoop::registerFileDescriptor (int fd, const FileDescriptorCallback& callback)
{
fileDescriptors.emplace (fd, callback);
}
//------------------------------------------------------------------------
void RunLoop::unregisterFileDescriptor (int fd)
{
auto it = fileDescriptors.find (fd);
if (it == fileDescriptors.end ())
return;
fileDescriptors.erase (it);
}
//------------------------------------------------------------------------
void RunLoop::select (timeval* timeout)
{
int nfds = 0;
fd_set readFDs = {}, writeFDs = {}, exceptFDs = {};
for (auto& e : fileDescriptors)
{
int fd = e.first;
FD_SET (fd, &readFDs);
FD_SET (fd, &writeFDs);
FD_SET (fd, &exceptFDs);
nfds = std::max (nfds, fd);
}
int result = ::select (nfds, &readFDs, &writeFDs, nullptr, timeout);
if (result > 0)
{
for (auto& e : fileDescriptors)
{
if (FD_ISSET (e.first, &readFDs) || FD_ISSET (e.first, &writeFDs) ||
FD_ISSET (e.first, &exceptFDs))
e.second (e.first);
}
}
}
//------------------------------------------------------------------------
bool RunLoop::handleEvents ()
{
auto count = XPending (display);
if (count == 0)
return false;
for (auto i = 0; i < count; ++i)
{
XEvent event {};
XNextEvent (display, &event);
auto it = map.find (event.xany.window);
if (it != map.end ())
{
it->second (event);
if (event.type == DestroyNotify)
{
map.erase (it);
}
}
else
{
XPutBackEvent (display, &event);
break;
}
}
return true;
}
//------------------------------------------------------------------------
TimerID RunLoop::registerTimer (TimerInterval interval, const TimerCallback& callback)
{
return timerProcessor.registerTimer (interval, callback);
}
//------------------------------------------------------------------------
void RunLoop::unregisterTimer (TimerID id)
{
timerProcessor.unregisterTimer (id);
}
//------------------------------------------------------------------------
bool timeValEmpty (timeval& val)
{
return val.tv_sec == 0 && val.tv_usec == 0;
}
//------------------------------------------------------------------------
void RunLoop::start ()
{
using namespace std::chrono;
using clock = high_resolution_clock;
running = true;
auto fd = XConnectionNumber (display);
registerFileDescriptor (fd, [this] (int) { handleEvents (); });
XSync (display, false);
handleEvents ();
timeval selectTimeout {};
while (running && !map.empty ())
{
select (timeValEmpty (selectTimeout) ? nullptr : &selectTimeout);
auto nextFireTime = timerProcessor.handleTimersAndReturnNextFireTimeInMs ();
if (nextFireTime == TimerProcessor::noTimers)
{
selectTimeout = {};
}
else
{
selectTimeout.tv_sec = nextFireTime / 1000;
selectTimeout.tv_usec = (nextFireTime - (selectTimeout.tv_sec * 1000)) * 1000;
}
}
}
//------------------------------------------------------------------------
void RunLoop::stop ()
{
running = false;
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
uint64_t TimerProcessor::handleTimersAndReturnNextFireTimeInMs ()
{
using std::chrono::time_point_cast;
if (timers.empty ())
return noTimers;
auto current = time_point_cast (Clock::now ());
std::vector timersToFire;
for (auto& timer : timers)
{
if (timer.nextFireTime > current)
break;
timersToFire.push_back (timer.id);
updateTimerNextFireTime (timer, current);
}
for (auto id : timersToFire)
{
for (auto& timer : timers)
{
if (timer.id == id)
{
timer.callback (timer.id);
break;
}
}
}
if (timersToFire.empty ())
return noTimers;
sortTimers ();
auto nextFireTime = timers.front ().nextFireTime;
current = now ();
if (nextFireTime < current)
return 0;
return (nextFireTime - current).count ();
}
//------------------------------------------------------------------------
void TimerProcessor::updateTimerNextFireTime (Timer& timer, TimePoint current)
{
timer.nextFireTime = current + Millisecond (timer.interval);
}
//------------------------------------------------------------------------
void TimerProcessor::sortTimers ()
{
std::sort (timers.begin (), timers.end (),
[] (const Timer& t1, const Timer& t2) { return t1.nextFireTime < t2.nextFireTime; });
}
//------------------------------------------------------------------------
auto TimerProcessor::now () -> TimePoint
{
using std::chrono::time_point_cast;
return time_point_cast (Clock::now ());
}
//------------------------------------------------------------------------
auto TimerProcessor::registerTimer (TimerInterval interval, const TimerCallback& callback)
-> TimerID
{
auto timerId = ++timerIdCounter;
Timer timer;
timer.id = timerId;
timer.callback = callback;
timer.interval = interval;
updateTimerNextFireTime (timer, now ());
timers.emplace_back (std::move (timer));
sortTimers ();
return timerId;
}
//------------------------------------------------------------------------
void TimerProcessor::unregisterTimer (TimerID id)
{
for (auto it = timers.begin (), end = timers.end (); it != end; ++it)
{
if (it->id == id)
{
timers.erase (it);
break;
}
}
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/runloop.h 0000664 0000000 0000000 00000006367 15075464575 0032617 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.h
// Created by : Steinberg 09.2016
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include
#include
#include
#include
#include
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
using TimerID = uint64_t;
using TimerInterval = uint64_t;
using TimerCallback = std::function;
//------------------------------------------------------------------------
class TimerProcessor
{
public:
TimerID registerTimer (TimerInterval interval, const TimerCallback& callback);
void unregisterTimer (TimerID id);
static constexpr uint64_t noTimers = std::numeric_limits::max ();
uint64_t handleTimersAndReturnNextFireTimeInMs ();
private:
using Clock = std::chrono::steady_clock;
using Millisecond = std::chrono::milliseconds;
using TimePoint = std::chrono::time_point;
struct Timer
{
TimerID id;
TimerInterval interval;
TimerCallback callback;
TimePoint nextFireTime;
};
using Timers = std::vector;
Timers timers;
TimerID timerIdCounter {0};
void updateTimerNextFireTime (Timer& timer, TimePoint current);
void sortTimers ();
TimePoint now ();
};
//------------------------------------------------------------------------
class RunLoop
{
public:
using EventCallback = std::function;
using FileDescriptorCallback = std::function;
static RunLoop& instance ();
void setDisplay (Display* display);
void registerWindow (XID window, const EventCallback& callback);
void unregisterWindow (XID window);
void registerFileDescriptor (int fd, const FileDescriptorCallback& callback);
void unregisterFileDescriptor (int fd);
TimerID registerTimer (TimerInterval interval, const TimerCallback& callback);
void unregisterTimer (TimerID id);
void start ();
void stop ();
private:
void select (timeval* timeout = nullptr);
bool handleEvents ();
using WindowMap = std::unordered_map;
using FileDescriptorCallbacks = std::unordered_map;
WindowMap map;
FileDescriptorCallbacks fileDescriptors;
TimerProcessor timerProcessor;
Display* display {nullptr};
bool running {false};
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland/ 0000775 0000000 0000000 00000000000 15075464575 0032373 5 ustar 00root root 0000000 0000000 clientcontext.cpp 0000664 0000000 0000000 00000041361 15075464575 0035710 0 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/clientcontext.cpp
// Created by : Steinberg 10.2025
// Description : Implementation of WaylandServerDelegate::IWaylandClientContext
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "clientcontext.h"
#include
#include
#include
#include
// Wayland globals
#include "linux-dmabuf-v1-client-protocol.h"
#include "wayland-client-protocol.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
using namespace WaylandServerDelegate;
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
using ContextListeners = std::vector;
using NamedWaylandOutput = std::pair;
using WaylandOutputs = std::vector;
using StringType = std::string;
//------------------------------------------------------------------------
// WaylandGlobals
// https://wayland.freedesktop.org/docs/html/
// https://wayland-book.com/introduction.html
//------------------------------------------------------------------------
struct WaylandGlobals
{
using WlInterfaceName = std::string;
using Globals = std::unordered_map;
wl_compositor* compositor {nullptr};
wl_seat* seat {nullptr};
wl_shm* shm {nullptr};
wl_subcompositor* subcompositor {nullptr};
xdg_wm_base* wm_base {nullptr};
zwp_linux_dmabuf_v1* dmabuf {nullptr};
Globals objects; // Hold a map of all globals' interface names for better tracking!
};
//------------------------------------------------------------------------
static void handlePing (void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial)
{
auto globals = reinterpret_cast (data);
xdg_wm_base_pong (globals->wm_base, serial);
}
//------------------------------------------------------------------------
static void addXdgWmBaseListener (WaylandGlobals& globals)
{
static const struct xdg_wm_base_listener xdgWmBaseListener = {
.ping = handlePing,
};
xdg_wm_base_add_listener (globals.wm_base, &xdgWmBaseListener, &globals);
}
//------------------------------------------------------------------------
static void handleGlobal (void* data, struct wl_registry* registry, uint32_t name,
const char* interface, uint32_t version)
{
printf ("interface: '%s', version: %d, name: %d\n", interface, version, name);
auto& globals = *reinterpret_cast (data);
globals.objects.insert ({name, {interface}});
if (std::string_view (interface) == wl_compositor_interface.name)
{
static constexpr uint32_t kVersion = 6;
void* object = wl_registry_bind (registry, name, &wl_compositor_interface,
std::min (kVersion, version));
auto compositor = reinterpret_cast (object);
globals.compositor = compositor;
return;
}
if (std::string_view (interface) == wl_subcompositor_interface.name)
{
static constexpr uint32_t kVersion = 1;
void* object = wl_registry_bind (registry, name, &wl_subcompositor_interface,
std::min (kVersion, version));
auto subcompositor = reinterpret_cast (object);
globals.subcompositor = subcompositor;
return;
}
if (std::string_view (interface) == wl_shm_interface.name)
{
static constexpr uint32_t kVersion = 1;
void* object =
wl_registry_bind (registry, name, &wl_shm_interface, std::min (kVersion, version));
auto shm = reinterpret_cast (object);
globals.shm = shm;
return;
}
if (std::string_view (interface) == xdg_wm_base_interface.name)
{
static constexpr uint32_t kVersion = 6;
void* object =
wl_registry_bind (registry, name, &xdg_wm_base_interface, std::min (kVersion, version));
auto wm_base = reinterpret_cast (object);
globals.wm_base = wm_base;
return;
}
if (std::string_view (interface) == wl_seat_interface.name)
{
static constexpr uint32_t kVersion = 8;
void* object =
wl_registry_bind (registry, name, &wl_seat_interface, std::min (kVersion, version));
auto seat = reinterpret_cast (object);
globals.seat = seat;
return;
}
if (std::string_view (interface) == wl_output_interface.name)
{
// We bind outputs in the WaylandClientContext
/*
static constexpr uint32_t kVersion = 3;
void* object =
wl_registry_bind (registry, name, &wl_output_interface, std::min (kVersion, version));
auto output = reinterpret_cast (object);
return;
*/
}
if (std::string_view (interface) == zwp_linux_dmabuf_v1_interface.name)
{
static constexpr uint32_t kVersion = 4;
void* object = wl_registry_bind (registry, name, &zwp_linux_dmabuf_v1_interface,
std::min (kVersion, version));
auto dmabuf = reinterpret_cast (object);
globals.dmabuf = dmabuf;
return;
}
}
//------------------------------------------------------------------------
static void handleGlobalRemove (void* data, struct wl_registry* registry, uint32_t name)
{
auto globals = reinterpret_cast (data);
const auto iter = globals->objects.find (name);
if (iter == globals->objects.end ())
return;
globals->objects.erase (iter);
}
//------------------------------------------------------------------------
static bool bindGlobals (wl_display* display, WaylandGlobals& globals)
{
if (!display)
return false;
static const struct wl_registry_listener registry_listener = {
.global = handleGlobal, .global_remove = handleGlobalRemove,
};
auto registry = wl_display_get_registry (display);
wl_registry_add_listener (registry, ®istry_listener, &globals);
// Roundtrip to call the listener's callbacks.
wl_display_roundtrip (display);
addXdgWmBaseListener (globals);
return true;
}
//------------------------------------------------------------------------
// WaylandClientContext::Impl
//------------------------------------------------------------------------
struct WaylandClientContext::Impl
{
wl_display* display = nullptr;
WaylandGlobals globals;
WaylandOutputs outputs;
ContextListeners contextListeners;
uint32_t seatCapabilities = 0;
StringType seatName;
void setSeatCapabilities (int capabilities);
void notifyListeners (IContextListener::ChangeType changeType);
void addWaylandOutput (uint32_t name, wl_output* out);
void removeWaylandOutput (uint32_t name);
void addRegistryListener ();
void addSeatListener ();
void addOutputsListener ();
};
//------------------------------------------------------------------------
void WaylandClientContext::Impl::setSeatCapabilities (int capabilities)
{
seatCapabilities = capabilities;
// seatCapabilities &= ~(WL_SEAT_CAPABILITY_KEYBOARD);
notifyListeners (IContextListener::kSeatCapabilitiesChanged);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::notifyListeners (IContextListener::ChangeType changeType)
{
for (auto& el : contextListeners)
el->contextChanged (changeType);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addWaylandOutput (uint32_t name, wl_output* output)
{
outputs.push_back ({name, {output}});
notifyListeners (IContextListener::kOutputsChanged);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::removeWaylandOutput (uint32_t name)
{
auto iter = std::find_if (outputs.begin (), outputs.end (),
[name] (const auto& el) { return el.first == name; });
if (iter == outputs.end ())
return;
outputs.erase (iter);
notifyListeners (IContextListener::kOutputsChanged);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addRegistryListener ()
{
static const struct wl_registry_listener registry_listener = {
.global =
[] (void* data, struct wl_registry* registry, uint32_t name, const char* interface,
uint32_t version) {
// We only track outputs here for now
auto self = reinterpret_cast (data);
if (std::string_view (interface) == wl_output_interface.name)
{
static constexpr uint32_t kVersion = 3;
void* object = wl_registry_bind (registry, name, &wl_output_interface,
std::min (kVersion, version));
auto output = reinterpret_cast (object);
self->addWaylandOutput (name, output);
return;
}
},
.global_remove =
[] (void* data, struct wl_registry* registry, uint32_t name) {
// We only track outputs here for now
auto self = reinterpret_cast (data);
self->removeWaylandOutput (name);
},
};
auto registry = wl_display_get_registry (display);
wl_registry_add_listener (registry, ®istry_listener, this);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addSeatListener ()
{
static const struct wl_seat_listener listener = {
.capabilities =
[] (void* data, struct wl_seat* wl_seat, uint32_t capabilities) {
auto self = reinterpret_cast (data);
self->setSeatCapabilities (capabilities);
},
.name =
[] (void* data, struct wl_seat* wl_seat, const char* name) {
auto self = reinterpret_cast (data);
if (name)
self->seatName = name;
}};
wl_seat_add_listener (globals.seat, &listener, this);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addOutputsListener ()
{
static const wl_output_listener listener = {
.geometry =
[] (void* data, wl_output* wl_output, int32_t x, int32_t y, int32_t physical_width,
int32_t physical_height, int32_t subpixel, const char* make, const char* model,
int32_t transform) {
auto self = reinterpret_cast (data);
auto iter = std::find_if (
self->outputs.begin (), self->outputs.end (),
[wl_output] (const auto& el) { return el.second.handle == wl_output; });
if (iter != self->outputs.end ())
{
WaylandOutput& output = iter->second;
output.handle = wl_output;
output.x = x;
output.y = y;
output.physicalWidth = physical_width;
output.physicalHeight = physical_height;
output.subPixelOrientation = subpixel;
output.transformType = transform;
const std::string_view makeStr {make, sizeof (output.manufacturer)};
std::copy (makeStr.begin (), makeStr.end (), output.manufacturer);
const std::string_view modelStr {model, sizeof (output.model)};
std::copy (modelStr.begin (), modelStr.end (), output.model);
}
},
.mode =
[] (void* data, wl_output* wl_output, uint32_t flags, int32_t width, int32_t height,
int32_t refresh) {
auto self = reinterpret_cast (data);
auto iter = std::find_if (
self->outputs.begin (), self->outputs.end (),
[wl_output] (const auto& el) { return el.second.handle == wl_output; });
if (iter != self->outputs.end ())
{
auto& output = iter->second;
output.width = width;
output.height = height;
output.refreshRate = refresh;
}
},
.done =
[] (void* data, wl_output* wl_output) {
auto self = reinterpret_cast (data);
self->notifyListeners (IContextListener::kOutputsChanged);
},
.scale =
[] (void* data, wl_output* wl_output, int32_t factor) {
auto self = reinterpret_cast (data);
auto iter = std::find_if (
self->outputs.begin (), self->outputs.end (),
[wl_output] (const auto& el) { return el.second.handle == wl_output; });
if (iter != self->outputs.end ())
{
auto& output = iter->second;
output.scaleFactor = factor;
}
},
.name =
[] (void* data, wl_output* wl_output, const char* name) {
// Hmm, was never called.
auto self = reinterpret_cast (data);
},
.description =
[] (void* data, wl_output* wl_output, const char* description) {
// Hmm, was never called.
auto self = reinterpret_cast (data);
},
};
for (auto& output : outputs)
wl_output_add_listener (output.second.handle, &listener, this);
}
//------------------------------------------------------------------------
// WaylandClientContext
//------------------------------------------------------------------------
WaylandClientContext::WaylandClientContext ()
{
impl = std::make_unique ();
}
//------------------------------------------------------------------------
WaylandClientContext::~WaylandClientContext ()
{
}
//------------------------------------------------------------------------
bool WaylandClientContext::initWayland (wl_display* display)
{
impl->display = display;
bool done = bindGlobals (display, impl->globals);
impl->addRegistryListener ();
impl->addSeatListener ();
// Initializes seat capabilities, outputs etc.
wl_display_roundtrip (impl->display);
impl->addOutputsListener ();
return done;
}
//------------------------------------------------------------------------
bool WaylandClientContext::addListener (IContextListener* listener)
{
if (!listener)
return false;
impl->contextListeners.push_back (listener);
return true;
}
//------------------------------------------------------------------------
bool WaylandClientContext::removeListener (IContextListener* listener)
{
const auto found = [listener] (const auto el) { return el == listener; };
auto iter =
std::find_if (impl->contextListeners.begin (), impl->contextListeners.end (), found);
if (iter == impl->contextListeners.end ())
return false;
impl->contextListeners.erase (iter);
return true;
}
//------------------------------------------------------------------------
wl_compositor* WaylandClientContext::getCompositor () const
{
return impl->globals.compositor;
}
//------------------------------------------------------------------------
wl_subcompositor* WaylandClientContext::getSubCompositor () const
{
return impl->globals.subcompositor;
}
//------------------------------------------------------------------------
wl_shm* WaylandClientContext::getSharedMemory () const
{
return impl->globals.shm;
}
//------------------------------------------------------------------------
wl_seat* WaylandClientContext::getSeat () const
{
return impl->globals.seat;
}
//------------------------------------------------------------------------
xdg_wm_base* WaylandClientContext::getWindowManager () const
{
return impl->globals.wm_base;
}
//------------------------------------------------------------------------
uint32_t WaylandClientContext::getSeatCapabilities () const
{
return impl->seatCapabilities;
}
//------------------------------------------------------------------------
const char* WaylandClientContext::getSeatName () const
{
return impl->seatName.data ();
}
//------------------------------------------------------------------------
int WaylandClientContext::countOutputs () const
{
return impl->outputs.size ();
}
//------------------------------------------------------------------------
zwp_linux_dmabuf_v1* WaylandClientContext::getDmaBuffer () const
{
return impl->globals.dmabuf;
}
//------------------------------------------------------------------------
const WaylandOutput& WaylandClientContext::getOutput (int index) const
{
if (index < impl->outputs.size ())
return impl->outputs[index].second;
static WaylandOutput output {};
return output;
}
//------------------------------------------------------------------------
} // namespace Steinberg::Vst::EditorHost::Wayland
clientcontext.h 0000664 0000000 0000000 00000004674 15075464575 0035363 0 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/clientcontext.h
// Created by : Steinberg 10.2025
// Description : Implementation of WaylandServerDelegate::IWaylandClientContext
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "wayland-server-delegate/iwaylandclientcontext.h"
#include
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// WaylandClientContext
//------------------------------------------------------------------------
class WaylandClientContext final : public WaylandServerDelegate::IWaylandClientContext
{
public:
//------------------------------------------------------------------------
// WaylandServerDelegate
using WaylandOutput = WaylandServerDelegate::WaylandOutput;
using IContextListener = WaylandServerDelegate::IContextListener;
WaylandClientContext ();
~WaylandClientContext ();
bool initWayland (wl_display* display);
// IWaylandClientContext
bool addListener (IContextListener* listener) override;
bool removeListener (IContextListener* listener) override;
wl_compositor* getCompositor () const override;
wl_subcompositor* getSubCompositor () const override;
wl_shm* getSharedMemory () const override;
wl_seat* getSeat () const override;
xdg_wm_base* getWindowManager () const override;
uint32_t getSeatCapabilities () const override;
const char* getSeatName () const override;
int countOutputs () const override;
zwp_linux_dmabuf_v1* getDmaBuffer () const override;
const WaylandOutput& getOutput (int index) const override;
//------------------------------------------------------------------------
private:
struct Impl;
std::unique_ptr impl;
};
//------------------------------------------------------------------------
} // namespace Steinberg::Vst::EditorHost::Wayland
platform.cpp 0000664 0000000 0000000 00000026653 15075464575 0034660 0 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/platform.cpp
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h"
#include "../../../editorhost.h"
#include "../../iplatform.h"
#include "clientcontext.h"
#include "wayland-server-delegate/iwaylandclientcontext.h"
#include "wayland-server-delegate/iwaylandserver.h"
#include "wayland-server-delegate/waylandresource.h"
#include "window.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/gui/iwaylandframe.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace Steinberg::Vst::EditorHost {
namespace Wayland {
//------------------------------------------------------------------------
struct ReleaseWaylandDisplay
{
void operator () (wl_display* d) const { wl_display_disconnect (d); }
};
using WaylandDisplayPtr = std::unique_ptr;
//------------------------------------------------------------------------
static int createCancelSockets (int (&fds)[2])
{
if (::socketpair (AF_UNIX, SOCK_STREAM, 0, fds) == -1)
return -1;
::fcntl (fds[0], F_SETFD, FD_CLOEXEC);
return 0;
}
//------------------------------------------------------------------------
static void closeCancelSockets (int (&fds)[2])
{
int msg = 0;
ssize_t bytesWritten = write (fds[0], &msg, sizeof (msg));
::close (fds[0]);
::close (fds[1]);
}
//------------------------------------------------------------------------
// Threading
//------------------------------------------------------------------------
namespace Threading {
using Mutex = std::mutex;
using ScopedLock = const std::lock_guard;
using Thread = std::thread;
} // namespace Threading
//------------------------------------------------------------------------
// Platform
//------------------------------------------------------------------------
class Platform
: public IPlatform,
public U::ImplementsNonDestroyable>,
public IWaylandProxyCreator
{
public:
//------------------------------------------------------------------------
static Platform& instance ()
{
static Platform gInstance;
return gInstance;
}
void run (const std::vector& cmdArgs);
//------------------------------------------------------------------------
private:
using WaylandWindows = std::vector>;
void setApplication (ApplicationPtr&& app) override;
WindowPtr createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller) override;
void quit () override;
void kill (int resultCode, const std::string& reason) override;
FUnknown* getPluginFactoryContext () override;
void initWayland ();
wl_display* getDisplay () const { return displayPtr.get (); }
// IWaylandHost
wl_display* PLUGIN_API openWaylandConnection () override;
tresult PLUGIN_API closeWaylandConnection (wl_display* display) override;
// IWaylandProxyCreator
wl_proxy* createProxy (wl_display* display, wl_proxy* object,
WaylandServerDelegate::WaylandResource* implementation) override;
void destroyProxy (wl_proxy* proxy) override;
// Linux::IEventHandler
void PLUGIN_API onFDIsSet (Linux::FileDescriptor fd) override;
WaylandClientContext client;
ApplicationPtr application;
WaylandDisplayPtr displayPtr;
wl_event_queue* eventQueue {nullptr};
WaylandWindows windows;
IPtr runLoop;
bool isQuitting = false;
bool shouldTerminate () { return isQuitting; }
Threading::Mutex lock;
int threadEntry ();
int serverFd {};
int cancelFd[2];
};
//------------------------------------------------------------------------
void Platform::initWayland ()
{
displayPtr = WaylandDisplayPtr (wl_display_connect (nullptr));
if (!getDisplay ())
kill (-1, "wl_display_connect failed");
bool initWaylandDone = client.initWayland (getDisplay ());
if (!initWaylandDone)
kill (-1, "init wayland failed");
eventQueue = wl_display_create_queue (getDisplay ());
serverFd = WaylandServerDelegate::IWaylandServer::instance ().startup (&client, eventQueue);
if (serverFd == -1)
kill (-1, "IWaylandServer startup failed");
wl_display_roundtrip (getDisplay ());
wl_display_flush (getDisplay ());
}
//------------------------------------------------------------------------
FUnknown* Platform::getPluginFactoryContext ()
{
return &Steinberg::Linux::RunLoopImpl::instance ();
}
//------------------------------------------------------------------------
void Platform::run (const std::vector& cmdArgs)
{
initWayland ();
runLoop = owned (new RunLoop (getDisplay ()));
// start server dispatch before initializing the application, otherwise plug-ins might block the
// main thread
createCancelSockets (cancelFd);
Threading::Thread serverThread (&Platform::threadEntry, this);
application->init (cmdArgs);
runLoop->run ();
closeCancelSockets (cancelFd);
serverThread.join ();
}
//------------------------------------------------------------------------
void PLUGIN_API Platform::onFDIsSet (Linux::FileDescriptor fd)
{
{
Threading::ScopedLock guard (lock);
WaylandServerDelegate::IWaylandServer::instance ().dispatch ();
}
}
//------------------------------------------------------------------------
#define ARRAY_COUNT(arr) (std::end (arr) - std::begin (arr))
int Platform::threadEntry ()
{
wl_display* display = getDisplay ();
wl_event_queue* queue = eventQueue;
int displayFd = wl_display_get_fd (display);
bool receivedClientEvents = false;
while (true)
{
// flush server events (server -> clients)
{
Threading::ScopedLock guard (lock);
WaylandServerDelegate::IWaylandServer::instance ().flush ();
}
// flush display (server -> session compositor)
if (receivedClientEvents)
wl_display_roundtrip_queue (display, queue);
else
wl_display_flush (display);
receivedClientEvents = false;
if (wl_display_prepare_read_queue (display, queue) == 0)
{
pollfd fds[] = {
{serverFd, POLLIN, 0}, {displayFd, POLLIN, 0}, {cancelFd[0], POLLIN, 0}};
::poll (fds, ARRAY_COUNT (fds), -1);
// dispatch incoming server events (clients -> server)
if ((fds[0].revents & POLLIN) > 0)
{
Threading::ScopedLock guard (lock);
WaylandServerDelegate::IWaylandServer::instance ().dispatch ();
receivedClientEvents = true;
}
// dispatch server-side Wayland objects (session compositor -> server)
if ((fds[1].revents & POLLIN) > 0)
{
if (wl_display_read_events (display) < 0)
{
// CCL_WARN ("%s: %s\n", "Failed to read server Wayland events", ::strerror
// (errno))
break;
}
}
else
{
wl_display_cancel_read (display);
}
}
else
{
// CCL_PRINTLN ("WaylandServerRunLoop: Dispatching pending server-side Wayland objects
// (session compositor -> server)")
if (wl_display_dispatch_queue_pending (display, queue) < 0)
{
// CCL_WARN ("%s: %s\n", "Failed to dispatch pending Wayland server events to
// display", ::strerror (errno))
break;
}
}
if (shouldTerminate ())
break;
}
return 0;
}
//------------------------------------------------------------------------
void Platform::setApplication (ApplicationPtr&& obj)
{
application = std::move (obj);
}
//------------------------------------------------------------------------
WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
auto window = WaylandWindow::make (
title, size, resizeable, controller,
[this] (WaylandWindow* window) {
auto it = std::find_if (windows.begin (), windows.end (),
[&] (const auto& el) { return el.get () == window; });
if (it != windows.end ())
windows.erase (it);
},
this, runLoop, this, client);
windows.push_back (window);
return window;
}
//------------------------------------------------------------------------
void Platform::quit ()
{
assert (getDisplay ());
isQuitting = true;
runLoop->stop ();
if (application)
application->terminate ();
WaylandServerDelegate::IWaylandServer::instance ().shutdown ();
wl_event_queue_destroy (eventQueue);
}
//------------------------------------------------------------------------
void Platform::kill (int resultCode, const std::string& reason)
{
std::cout << reason << "\n";
exit (resultCode);
}
//------------------------------------------------------------------------
wl_display* PLUGIN_API Platform::openWaylandConnection ()
{
Threading::ScopedLock guard (lock);
return WaylandServerDelegate::IWaylandServer::instance ().openClientConnection ();
}
//------------------------------------------------------------------------
tresult PLUGIN_API Platform::closeWaylandConnection (wl_display* display)
{
Threading::ScopedLock guard (lock);
return WaylandServerDelegate::IWaylandServer::instance ().closeClientConnection (display) ?
kResultTrue :
kResultFalse;
}
//------------------------------------------------------------------------
wl_proxy* Platform::createProxy (wl_display* display, wl_proxy* object,
WaylandServerDelegate::WaylandResource* implementation)
{
Threading::ScopedLock guard (lock);
return WaylandServerDelegate::IWaylandServer::instance ().createProxy (display, object,
implementation);
}
//------------------------------------------------------------------------
void Platform::destroyProxy (wl_proxy* proxy)
{
Threading::ScopedLock guard (lock);
return WaylandServerDelegate::IWaylandServer::instance ().destroyProxy (proxy);
}
//------------------------------------------------------------------------
} // Wayland
//------------------------------------------------------------------------
#if SMTG_EDITOR_HOST_USE_WAYLAND
IPlatform& IPlatform::instance ()
{
return Wayland::Platform::instance ();
}
#endif
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost
//------------------------------------------------------------------------
//------------------------------------------------------------------------
#if SMTG_EDITOR_HOST_USE_WAYLAND
int main (int argc, char* argv[])
{
#ifdef EDITORHOST_GTK
gtk_init (&argc, &argv);
#endif
std::vector cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
Steinberg::Vst::EditorHost::Wayland::Platform::instance ().run (cmdArgs);
return 0;
}
#endif
runloop.cpp 0000664 0000000 0000000 00000020714 15075464575 0034522 0 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/runloop.cpp
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "runloop.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
static uint64_t toSeconds (uint64_t milliseconds)
{
return milliseconds / 1000;
}
//------------------------------------------------------------------------
static uint64_t toNanoSecs (uint64_t milliseconds)
{
return milliseconds * 1000000;
}
//------------------------------------------------------------------------
static auto interval_timerfd_create (Linux::TimerInterval milliseconds) -> int
{
auto timer_fd = timerfd_create (CLOCK_REALTIME, 0);
if (timer_fd == -1)
{
std::cerr << "Failed to create a new timer." << std::endl;
return timer_fd;
}
const auto secs = toSeconds (milliseconds);
const auto nanoSecs = toNanoSecs (milliseconds % 1000);
struct itimerspec timerValue;
timerValue.it_value.tv_sec = secs;
timerValue.it_value.tv_nsec = nanoSecs;
timerValue.it_interval.tv_sec = secs;
timerValue.it_interval.tv_nsec = nanoSecs;
if (timerfd_settime (timer_fd, 0, &timerValue, NULL) == -1)
{
std::cerr << "Failed to set a timer specification." << std::endl;
return -1;
}
return timer_fd;
}
//------------------------------------------------------------------------
// IntervalTimer
//------------------------------------------------------------------------
struct IntervalTimer : std::enable_shared_from_this
{
using ITimerHandler = Linux::ITimerHandler;
IntervalTimer (ITimerHandler* handler) : handler (handler) {}
auto onFDIsSet () -> void
{
if (handler)
handler->onTimer ();
}
ITimerHandler* getHandler () const { return handler; }
private:
IPtr handler;
};
//------------------------------------------------------------------------
// RunLoop::Impl
//------------------------------------------------------------------------
struct RunLoop::Impl
{
using FileDescriptors = std::vector;
using Timers = std::vector, FileDescriptor>>;
using EventHandlers = std::vector, FileDescriptor>>;
using PollRequests = std::vector;
wl_display* display {nullptr};
Timers timers;
EventHandlers eventHandlers;
PollRequests pfds;
bool running {false};
static auto collectPollRequests (const FileDescriptors& fds, const EventHandlers& handlers,
const Timers& timers, PollRequests& pfds) -> void;
static auto dispatchPolledEvents (const PollRequests& pfds, EventHandlers& handlers,
Timers& timers) -> void;
void run ();
void stop () { running = false; }
};
//------------------------------------------------------------------------
// RunLoop::Impl
//------------------------------------------------------------------------
auto RunLoop::Impl::collectPollRequests (const FileDescriptors& fds, const EventHandlers& handlers,
const Timers& timers, PollRequests& pfds) -> void
{
pfds.clear ();
for (const auto& fd : fds)
{
pfds.push_back ({/* .fd = */ fd,
/* .events = */ POLLIN,
/* .revents = */ 0});
}
for (const auto& handler : handlers)
{
pfds.push_back ({/* .fd = */ handler.second,
/* .events = */ POLLIN,
/* .revents = */ 0});
};
for (const auto& timer : timers)
{
pfds.push_back ({/* .fd = */ timer.second,
/* .events = */ POLLIN,
/* .revents = */ 0});
};
}
//------------------------------------------------------------------------
auto RunLoop::Impl::dispatchPolledEvents (const PollRequests& pfds, EventHandlers& handlers,
Timers& timers) -> void
{
for (const auto& pfd : pfds)
{
auto found = [&] (const auto& el) { return el.second == pfd.fd; };
if (pfd.revents & POLLIN)
{
auto it = std::find_if (handlers.begin (), handlers.end (), found);
if (it != handlers.end ())
{
it->first->onFDIsSet (it->second);
continue;
}
auto iter = std::find_if (timers.begin (), timers.end (), found);
if (iter != timers.end ())
{
uint64_t exp;
const int timer_fd = pfd.fd;
// 'read' must be called to clear the file descriptor's events
ssize_t s = read (timer_fd, &exp, sizeof (uint64_t));
iter->first->onFDIsSet ();
}
}
}
}
//------------------------------------------------------------------------
void RunLoop::Impl::run ()
{
running = true;
const auto displayFd = wl_display_get_fd (display);
const FileDescriptors fds = {displayFd};
while (running)
{
collectPollRequests (fds, eventHandlers, timers, pfds);
while (wl_display_prepare_read (display) != 0)
wl_display_dispatch_pending (display);
wl_display_flush (display);
// -1: 'poll' blocks until one of the provided polling requests has events that occurred
const auto res = poll (pfds.data (), pfds.size (), -1);
// Afterwards read all events for the display, if there are any
if (res == -1 || pfds[0].revents == 0)
wl_display_cancel_read (display);
else if (pfds[0].revents & POLLIN)
wl_display_read_events (display);
dispatchPolledEvents (pfds, eventHandlers, timers);
}
}
//------------------------------------------------------------------------
// RunLoop
//------------------------------------------------------------------------
RunLoop::RunLoop (wl_display* display)
{
impl = std::make_unique ();
impl->display = display;
}
//------------------------------------------------------------------------
RunLoop::~RunLoop () noexcept = default;
//------------------------------------------------------------------------
void RunLoop::run ()
{
impl->run ();
}
//------------------------------------------------------------------------
void RunLoop::stop ()
{
impl->stop ();
}
//------------------------------------------------------------------------
tresult PLUGIN_API RunLoop::registerEventHandler (IEventHandler* handler, FileDescriptor fd)
{
if (!handler)
return kInvalidArgument;
impl->eventHandlers.push_back ({handler, fd});
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API RunLoop::unregisterEventHandler (IEventHandler* handler)
{
auto it = std::find_if (impl->eventHandlers.begin (), impl->eventHandlers.end (),
[&] (const auto& el) { return el.first == handler; });
if (it != impl->eventHandlers.end ())
{
impl->eventHandlers.erase (it);
return kResultTrue;
}
return kInvalidArgument;
}
//------------------------------------------------------------------------
tresult PLUGIN_API RunLoop::registerTimer (ITimerHandler* handler, TimerInterval milliseconds)
{
auto src = std::make_shared (handler);
auto timer_fd = interval_timerfd_create (milliseconds);
if (timer_fd == -1)
return kInternalError;
impl->timers.push_back (std::make_pair (src, timer_fd));
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API RunLoop::unregisterTimer (ITimerHandler* handler)
{
auto it = std::find_if (impl->timers.begin (), impl->timers.end (),
[&] (const auto& el) { return el.first->getHandler () == handler; });
if (it != impl->timers.end ())
{
const auto timer_fd = it->second;
::close (timer_fd);
impl->timers.erase (it);
return kResultTrue;
}
return kResultFalse;
}
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
runloop.h 0000664 0000000 0000000 00000004042 15075464575 0034163 0 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/runloop.h
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/gui/iplugview.h"
#include
struct wl_display;
struct wl_event_loop;
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// RunLoop
//------------------------------------------------------------------------
struct RunLoop : U::Implements>
{
using IEventHandler = Linux::IEventHandler;
using FileDescriptor = Linux::FileDescriptor;
using TimerInterval = Linux::TimerInterval;
using ITimerHandler = Linux::ITimerHandler;
// IRunLoop
tresult PLUGIN_API registerEventHandler (IEventHandler* handler, FileDescriptor fd) override;
tresult PLUGIN_API unregisterEventHandler (IEventHandler* handler) override;
tresult PLUGIN_API registerTimer (ITimerHandler* handler, TimerInterval milliseconds) override;
tresult PLUGIN_API unregisterTimer (ITimerHandler* handler) override;
RunLoop (wl_display* display);
~RunLoop () noexcept override;
void run ();
void stop ();
private:
struct Impl;
std::unique_ptr impl;
};
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
surfacedelegate.cpp 0000664 0000000 0000000 00000025132 15075464575 0036146 0 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/surfacedelegate.cpp
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "surfacedelegate.h"
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// SurfaceDelegate
//------------------------------------------------------------------------
SurfaceDelegate::SurfaceDelegate ()
: WaylandServerDelegate::WaylandResource (&::wl_surface_interface,
static_cast (this))
{
wl_surface_interface::destroy = destroy;
wl_surface_interface::attach = attach;
wl_surface_interface::damage = damage;
wl_surface_interface::frame = frame;
wl_surface_interface::set_opaque_region = set_opaque_region;
wl_surface_interface::set_input_region = set_input_region;
wl_surface_interface::commit = commit;
wl_surface_interface::set_buffer_transform = set_buffer_transform;
wl_surface_interface::set_buffer_scale = set_buffer_scale;
wl_surface_interface::damage_buffer = damage_buffer;
wl_surface_interface::offset = offset;
}
//------------------------------------------------------------------------
void SurfaceDelegate::destroy (struct wl_client* client, struct wl_resource* resource)
{
WaylandResource::onDestroy (resource);
}
//------------------------------------------------------------------------
void SurfaceDelegate::SurfaceDelegate::attach (struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* buffer, int32_t x, int32_t y)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::damage (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y, int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::frame (struct wl_client* client, struct wl_resource* resource,
uint32_t callback)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::set_opaque_region (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* region)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::set_input_region (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* region)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::commit (struct wl_client* client, struct wl_resource* resource)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::set_buffer_transform (struct wl_client* client, struct wl_resource* resource,
int32_t transform)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::set_buffer_scale (struct wl_client* client, struct wl_resource* resource,
int32_t scale)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::damage_buffer (struct wl_client* client, struct wl_resource* resource,
int32_t x, int32_t y, int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void SurfaceDelegate::offset (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y)
{
}
//------------------------------------------------------------------------
// XdgSurfaceDelegate
//------------------------------------------------------------------------
XdgSurfaceDelegate::XdgSurfaceDelegate ()
: WaylandServerDelegate::WaylandResource (&::xdg_surface_interface,
static_cast (this))
{
xdg_surface_interface::destroy = destroy;
xdg_surface_interface::get_toplevel = get_toplevel;
xdg_surface_interface::get_popup = get_popup;
xdg_surface_interface::set_window_geometry = set_window_geometry;
xdg_surface_interface::ack_configure = ack_configure;
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::destroy (struct wl_client* client, struct wl_resource* resource)
{
WaylandResource::onDestroy (resource);
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::get_toplevel (struct wl_client* client, struct wl_resource* resource,
uint32_t id)
{
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::get_popup (struct wl_client* client, struct wl_resource* resource,
uint32_t id, struct wl_resource* parent,
struct wl_resource* positioner)
{
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::set_window_geometry (struct wl_client* client,
struct wl_resource* resource, int32_t x, int32_t y,
int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void XdgSurfaceDelegate::ack_configure (struct wl_client* client, struct wl_resource* resource,
uint32_t serial)
{
}
//------------------------------------------------------------------------
// XdgToplevelDelegate
//------------------------------------------------------------------------
XdgToplevelDelegate::XdgToplevelDelegate ()
: WaylandServerDelegate::WaylandResource (&::xdg_toplevel_interface,
static_cast (this))
{
xdg_toplevel_interface::destroy = destroy;
xdg_toplevel_interface::set_parent = set_parent;
xdg_toplevel_interface::set_title = set_title;
xdg_toplevel_interface::set_app_id = set_app_id;
xdg_toplevel_interface::show_window_menu = show_window_menu;
xdg_toplevel_interface::move = move;
xdg_toplevel_interface::resize = resize;
xdg_toplevel_interface::set_max_size = set_max_size;
xdg_toplevel_interface::set_min_size = set_min_size;
xdg_toplevel_interface::set_maximized = set_maximized;
xdg_toplevel_interface::unset_maximized = unset_maximized;
xdg_toplevel_interface::set_fullscreen = set_fullscreen;
xdg_toplevel_interface::unset_fullscreen = unset_fullscreen;
xdg_toplevel_interface::set_minimized = set_minimized;
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::destroy (struct wl_client* client, struct wl_resource* resource)
{
WaylandResource::onDestroy (resource);
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_parent (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* parent)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_title (struct wl_client* client, struct wl_resource* resource,
const char* title)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_app_id (struct wl_client* client, struct wl_resource* resource,
const char* app_id)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::show_window_menu (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial, int32_t x,
int32_t y)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::move (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::resize (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial, uint32_t edges)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_max_size (struct wl_client* client, struct wl_resource* resource,
int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_min_size (struct wl_client* client, struct wl_resource* resource,
int32_t width, int32_t height)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_maximized (struct wl_client* client, struct wl_resource* resource)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::unset_maximized (struct wl_client* client, struct wl_resource* resource)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_fullscreen (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* output)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::unset_fullscreen (struct wl_client* client, struct wl_resource* resource)
{
}
//------------------------------------------------------------------------
void XdgToplevelDelegate::set_minimized (struct wl_client* client, struct wl_resource* resource)
{
}
//-----------------------------------------------------------------------------
} // namespace Steinberg::Vst::EditorHost::Wayland
surfacedelegate.h 0000664 0000000 0000000 00000013411 15075464575 0035610 0 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/surfacedelegate.h
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "wayland-server-delegate/iwaylandserver.h"
#include "wayland-server-delegate/waylandresource.h"
#include "xdg-shell-client-protocol.h"
#include "xdg-shell-server-protocol.h"
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// SurfaceDelegate
//------------------------------------------------------------------------
struct SurfaceDelegate : WaylandServerDelegate::WaylandResource, wl_surface_interface
{
SurfaceDelegate ();
static void destroy (struct wl_client* client, struct wl_resource* resource);
static void attach (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* buffer, int32_t x, int32_t y);
static void damage (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y, int32_t width, int32_t height);
static void frame (struct wl_client* client, struct wl_resource* resource, uint32_t callback);
static void set_opaque_region (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* region);
static void set_input_region (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* region);
static void commit (struct wl_client* client, struct wl_resource* resource);
static void set_buffer_transform (struct wl_client* client, struct wl_resource* resource,
int32_t transform);
static void set_buffer_scale (struct wl_client* client, struct wl_resource* resource,
int32_t scale);
static void damage_buffer (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y, int32_t width, int32_t height);
static void offset (struct wl_client* client, struct wl_resource* resource, int32_t x,
int32_t y);
};
//------------------------------------------------------------------------
// XdgSurfaceDelegate
//------------------------------------------------------------------------
struct XdgSurfaceDelegate : WaylandServerDelegate::WaylandResource, xdg_surface_interface
{
XdgSurfaceDelegate ();
static void destroy (struct wl_client* client, struct wl_resource* resource);
static void get_toplevel (struct wl_client* client, struct wl_resource* resource, uint32_t id);
static void get_popup (struct wl_client* client, struct wl_resource* resource, uint32_t id,
struct wl_resource* parent, struct wl_resource* positioner);
static void set_window_geometry (struct wl_client* client, struct wl_resource* resource,
int32_t x, int32_t y, int32_t width, int32_t height);
static void ack_configure (struct wl_client* client, struct wl_resource* resource,
uint32_t serial);
};
//------------------------------------------------------------------------
// XdgToplevelDelegate
//------------------------------------------------------------------------
struct XdgToplevelDelegate : WaylandServerDelegate::WaylandResource, xdg_toplevel_interface
{
XdgToplevelDelegate ();
static void destroy (struct wl_client* client, struct wl_resource* resource);
static void set_parent (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* parent);
static void set_title (struct wl_client* client, struct wl_resource* resource,
const char* title);
static void set_app_id (struct wl_client* client, struct wl_resource* resource,
const char* app_id);
static void show_window_menu (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial, int32_t x, int32_t y);
static void move (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial);
static void resize (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* seat, uint32_t serial, uint32_t edges);
static void set_max_size (struct wl_client* client, struct wl_resource* resource, int32_t width,
int32_t height);
static void set_min_size (struct wl_client* client, struct wl_resource* resource, int32_t width,
int32_t height);
static void set_maximized (struct wl_client* client, struct wl_resource* resource);
static void unset_maximized (struct wl_client* client, struct wl_resource* resource);
static void set_fullscreen (struct wl_client* client, struct wl_resource* resource,
struct wl_resource* output);
static void unset_fullscreen (struct wl_client* client, struct wl_resource* resource);
static void set_minimized (struct wl_client* client, struct wl_resource* resource);
};
//-----------------------------------------------------------------------------
} // namespace Steinberg::Vst::EditorHost::Wayland
window.cpp 0000664 0000000 0000000 00000046737 15075464575 0034350 0 ustar 00root root 0000000 0000000 vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/window.cpp
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "window.h"
#include "runloop.h"
#include "surfacedelegate.h"
#include "wayland-server-delegate/iwaylandclientcontext.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include
#include
#include
#include
DEF_CLASS_IID (Steinberg::IWaylandFrame)
DEF_CLASS_IID (Steinberg::IWaylandHost);
namespace Steinberg::Vst::EditorHost::Wayland {
namespace {
//------------------------------------------------------------------------
/* Shared memory support code */
void randname (char* buf)
{
struct timespec ts;
clock_gettime (CLOCK_REALTIME, &ts);
long r = ts.tv_nsec;
for (int i = 0; i < 6; ++i)
{
buf[i] = 'A' + (r & 15) + (r & 16) * 2;
r >>= 5;
}
}
//------------------------------------------------------------------------
int create_shm_file (void)
{
int retries = 100;
do
{
char name[] = "/wl_shm-XXXXXX";
randname (name + sizeof (name) - 7);
--retries;
int fd = shm_open (name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0)
{
shm_unlink (name);
return fd;
}
} while (retries > 0 && errno == EEXIST);
return -1;
}
//------------------------------------------------------------------------
int allocate_shm_file (size_t size)
{
int fd = create_shm_file ();
if (fd < 0)
return -1;
int ret;
do
{
ret = ftruncate (fd, size);
} while (ret < 0 && errno == EINTR);
if (ret < 0)
{
close (fd);
return -1;
}
return fd;
}
//------------------------------------------------------------------------
struct wl_buffer* draw_frame (wl_shm* shm, Size inSize)
{
const int width = inSize.width, height = inSize.height;
int stride = width * 4;
int size = stride * height;
int fd = allocate_shm_file (size);
if (fd == -1)
{
return NULL;
}
auto data = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
{
close (fd);
return NULL;
}
struct wl_shm_pool* pool = wl_shm_create_pool (shm, fd, size);
struct wl_buffer* buffer =
wl_shm_pool_create_buffer (pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
wl_shm_pool_destroy (pool);
close (fd);
auto int32data = reinterpret_cast (data);
/* Draw checkerboxed background */
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
if ((x + y / 8 * 8) % 16 < 8)
int32data[y * width + x] = 0xFF666666;
else
int32data[y * width + x] = 0xFFEEEEEE;
}
}
munmap (data, size);
static const struct wl_buffer_listener wlBufferListener = {
.release = [] (void* data, struct wl_buffer* wl_buffer) { wl_buffer_destroy (wl_buffer); },
};
wl_buffer_add_listener (buffer, &wlBufferListener, NULL);
return buffer;
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
// WaylandWindow::Impl
//------------------------------------------------------------------------
struct WaylandWindow::Impl
{
using IWaylandClientContext = WaylandServerDelegate::IWaylandClientContext;
Impl (const IWaylandClientContext& waylandClientContext)
: waylandClientContext (waylandClientContext)
{
}
WindowControllerPtr controller;
WindowClosedFunc windowClosedFunc;
IWaylandProxyCreator* wlProxyCreator = nullptr;
const IWaylandClientContext& waylandClientContext;
wl_surface* wlSurface {nullptr};
xdg_surface* xdgSurface {nullptr};
xdg_toplevel* xdgToplevel {nullptr};
zxdg_toplevel_decoration_v1* topLevelDeco {nullptr};
Size size {};
std::string title;
int32_t currentPreferredBufferScale {1};
wl_proxy* wlSurfaceProxy {nullptr};
wl_proxy* xdgSurfaceProxy {nullptr};
wl_proxy* xdgToplevelProxy {nullptr};
IPtr runLoop;
IPtr host;
using FuncDoClose = std::function;
FuncDoClose doCloseFunc;
using FuncOnScaleFactorChanged = std::function;
FuncOnScaleFactorChanged onScaleFactorChangedFunc;
void initSurfaceGeometry (int32_t width, int32_t height);
void addSurfaceListener ();
void addXdgSurfaceListener ();
void addToplevelListener ();
void addTopLevelDecorationListener ();
static void updateScaleFactor (WaylandWindow::Impl* self, int32_t factor);
static void handleXdgSurfaceConfigure (void* data, struct xdg_surface* xdg_surface,
uint32_t serial);
static void handleXdgToplevelConfigure (void* data, struct xdg_toplevel* xdg_toplevel,
int32_t width, int32_t height, struct wl_array* states);
static void handleXdgToplevelClose (void* data, struct xdg_toplevel* toplevel);
static void handleXdgToplevelConfigureBounds (void* data, struct xdg_toplevel* xdg_toplevel,
int32_t width, int32_t height);
static void handleXdgToplevelWmCapabilities (void* data, struct xdg_toplevel* xdg_toplevel,
struct wl_array* capabilities);
static void handleTopLevelDecorationConfigure (
void* data, struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode);
static void handleEnter (void* data, wl_surface* wl_surface, wl_output* output);
static void handleLeave (void* data, wl_surface* wl_surface, wl_output* output);
static void handlePreferredBufferScale (void* data, wl_surface* wl_surface, int32_t factor);
static void handlePreferredBufferTransform (void* data, wl_surface* wl_surface,
uint32_t transform);
};
//------------------------------------------------------------------------
WaylandWindow::WaylandWindow (const IWaylandClientContext& waylandClientContext)
{
impl = std::make_unique (waylandClientContext);
}
//------------------------------------------------------------------------
WaylandWindow::~WaylandWindow () noexcept
{
}
//------------------------------------------------------------------------
auto WaylandWindow::make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller,
const WindowClosedFunc& windowClosedFunc,
IWaylandProxyCreator* wlProxyCreator, IPtr runLoop,
IPtr host,
const IWaylandClientContext& waylandClientContext) -> Ptr
{
auto window = std::make_shared (waylandClientContext);
if (!window)
return {};
window->impl->runLoop = runLoop;
window->impl->host = host;
window->impl->controller = controller;
window->impl->windowClosedFunc = windowClosedFunc;
window->impl->wlProxyCreator = wlProxyCreator;
window->impl->title = name;
window->impl->size = size;
window->impl->doCloseFunc = [window] () { window->doClose (); };
window->impl->onScaleFactorChangedFunc = [window, controller] (float scaleFactor) {
controller->onContentScaleFactorChanged (*window.get (), scaleFactor);
};
window->impl->wlSurface = wl_compositor_create_surface (waylandClientContext.getCompositor ());
if (!window->impl->wlSurface)
return {};
window->impl->xdgSurface = xdg_wm_base_get_xdg_surface (
waylandClientContext.getWindowManager (), window->impl->wlSurface);
if (!window->impl->xdgSurface)
return {};
window->impl->xdgToplevel = xdg_surface_get_toplevel (window->impl->xdgSurface);
if (!window->impl->xdgToplevel)
return {};
window->impl->addSurfaceListener ();
window->impl->addXdgSurfaceListener ();
window->impl->addToplevelListener ();
window->impl->initSurfaceGeometry (window->impl->size.width, window->impl->size.height);
/* TODO
if (std::string_view (interface) == zxdg_decoration_manager_v1_interface.name)
{
static constexpr uint32_t kVersion = 8;
void* object = wl_registry_bind (
registry, name, &zxdg_decoration_manager_v1_interface, kVersion);
auto deco_manager = reinterpret_cast (object);
globals.deco_manager = deco_manager;
return;
}
if (waylandGlobals.deco_manager != nullptr)
{
window->impl->topLevelDeco = zxdg_decoration_manager_v1_get_toplevel_decoration (
waylandGlobals.deco_manager, window->impl->xdgToplevel);
window->impl->addTopLevelDecorationListener ();
zxdg_toplevel_decoration_v1_set_mode (window->impl->topLevelDeco,
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
*/
return window;
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgSurfaceConfigure (void* data, struct xdg_surface* xdg_surface,
uint32_t serial)
{
auto self = reinterpret_cast (data);
if (!self)
return;
xdg_surface_ack_configure (xdg_surface, serial);
auto buffer = draw_frame (self->waylandClientContext.getSharedMemory (), self->size);
wl_surface_set_buffer_scale (self->wlSurface, self->currentPreferredBufferScale);
wl_surface_attach (self->wlSurface, buffer, 0, 0);
wl_surface_commit (self->wlSurface);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgToplevelConfigure (void* data, xdg_toplevel* xdg_toplevel,
int32_t width, int32_t height,
wl_array* states)
{
auto self = reinterpret_cast (data);
if (!self)
return;
if (width > 0 && height > 0)
{
self->size.width = width;
self->size.height = height;
}
xdg_toplevel_set_title (self->xdgToplevel, self->title.data ());
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgToplevelClose (void* data, struct xdg_toplevel* toplevel)
{
auto self = reinterpret_cast (data);
if (self && self->doCloseFunc)
self->doCloseFunc ();
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgToplevelConfigureBounds (void* data,
struct xdg_toplevel* xdg_toplevel,
int32_t width, int32_t height)
{
auto self = reinterpret_cast (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgToplevelWmCapabilities (void* data,
struct xdg_toplevel* xdg_toplevel,
struct wl_array* capabilities)
{
auto self = reinterpret_cast (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleTopLevelDecorationConfigure (
void* data, struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode)
{
auto self = reinterpret_cast (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::updateScaleFactor (WaylandWindow::Impl* self, int32_t factor)
{
self->currentPreferredBufferScale = factor;
wl_surface_set_buffer_scale (self->wlSurface, factor);
wl_surface_commit (self->wlSurface);
if (self->onScaleFactorChangedFunc)
self->onScaleFactorChangedFunc (factor);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleEnter (void* data, wl_surface* wl_surface, wl_output* output)
{
auto self = reinterpret_cast (data);
if (!self)
return;
if (wl_surface != self->wlSurface)
return;
#if WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION
// 'preferred_buffer_scale' is handling the scale factor
#else
const auto count = self->waylandClientContext.countOutputs ();
for (auto i = 0; i < count; ++i)
{
const auto& wlOutput = self->waylandClientContext.getOutput (i);
if (wlOutput.handle != output)
continue;
updateScaleFactor (self, wlOutput.scaleFactor);
}
#endif
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleLeave (void* data, wl_surface* wl_surface, wl_output* output)
{
auto self = reinterpret_cast (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handlePreferredBufferScale (void* data, wl_surface* wl_surface,
int32_t factor)
{
auto self = reinterpret_cast (data);
if (!self)
return;
if (wl_surface != self->wlSurface)
return;
updateScaleFactor (self, factor);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handlePreferredBufferTransform (void* data, wl_surface* wl_surface,
uint32_t transform)
{
auto self = reinterpret_cast (data);
}
//------------------------------------------------------------------------
void WaylandWindow::doClose ()
{
impl->controller->onClose (*this);
if (impl->windowClosedFunc)
impl->windowClosedFunc (this);
if (impl->wlProxyCreator)
{
if (impl->wlSurfaceProxy)
impl->wlProxyCreator->destroyProxy (impl->wlSurfaceProxy);
if (impl->xdgSurfaceProxy)
impl->wlProxyCreator->destroyProxy (impl->xdgSurfaceProxy);
if (impl->xdgToplevelProxy)
impl->wlProxyCreator->destroyProxy (impl->xdgToplevelProxy);
}
impl->xdgToplevelProxy = {};
impl->xdgSurfaceProxy = {};
impl->wlSurfaceProxy = {};
if (impl->xdgToplevel)
xdg_toplevel_destroy (impl->xdgToplevel);
if (impl->xdgSurface)
xdg_surface_destroy (impl->xdgSurface);
if (impl->wlSurface)
wl_surface_destroy (impl->wlSurface);
impl->xdgToplevel = {};
impl->xdgSurface = {};
impl->wlSurface = {};
}
//------------------------------------------------------------------------
void WaylandWindow::show ()
{
impl->controller->onShow (*this);
}
//------------------------------------------------------------------------
void WaylandWindow::close ()
{
impl->controller->onClose (*this);
}
//------------------------------------------------------------------------
void WaylandWindow::resize (Size newSize)
{
impl->size = newSize;
if (impl->xdgSurface)
xdg_surface_set_window_geometry (impl->xdgSurface, 0, 0, impl->size.width,
impl->size.height);
impl->controller->onResize (*this, impl->size);
}
//------------------------------------------------------------------------
Size WaylandWindow::getContentSize ()
{
return impl->size;
}
//------------------------------------------------------------------------
NativePlatformWindow WaylandWindow::getNativePlatformWindow () const
{
return {kPlatformTypeWaylandSurfaceID, nullptr};
}
//------------------------------------------------------------------------
wl_surface* PLUGIN_API WaylandWindow::getWaylandSurface (wl_display* display)
{
if (!impl->wlProxyCreator)
return nullptr;
if (impl->wlSurfaceProxy)
return reinterpret_cast (impl->wlSurfaceProxy);
impl->wlSurfaceProxy = impl->wlProxyCreator->createProxy (
display, reinterpret_cast (impl->wlSurface), new SurfaceDelegate);
return reinterpret_cast (impl->wlSurfaceProxy);
}
//------------------------------------------------------------------------
xdg_surface* PLUGIN_API WaylandWindow::getParentSurface (ViewRect& parentSize, wl_display* display)
{
if (!impl->wlProxyCreator)
return nullptr;
if (impl->xdgSurfaceProxy)
return reinterpret_cast (impl->xdgSurfaceProxy);
impl->xdgSurfaceProxy = impl->wlProxyCreator->createProxy (
display, reinterpret_cast (impl->xdgSurface), new XdgSurfaceDelegate);
parentSize = ViewRect (0, 0, impl->size.width, impl->size.height);
return reinterpret_cast (impl->xdgSurfaceProxy);
}
//------------------------------------------------------------------------
xdg_toplevel* PLUGIN_API WaylandWindow::getParentToplevel (wl_display* display)
{
if (!impl->wlProxyCreator)
return nullptr;
if (impl->xdgToplevelProxy)
return reinterpret_cast (impl->xdgToplevelProxy);
impl->xdgToplevelProxy = impl->wlProxyCreator->createProxy (
display, reinterpret_cast (impl->xdgToplevelProxy), new XdgToplevelDelegate);
return reinterpret_cast (impl->xdgToplevelProxy);
}
//------------------------------------------------------------------------
tresult WaylandWindow::queryInterface (const TUID iid, void** obj)
{
if (FUnknownPrivate::iidEqual (iid, IWaylandFrame::iid))
{
*obj = static_cast (this);
return kResultTrue;
}
if (FUnknownPrivate::iidEqual (iid, IWaylandHost::iid))
{
*obj = static_cast (impl->host);
impl->host->addRef ();
return kResultTrue;
}
if (FUnknownPrivate::iidEqual (iid, Linux::IRunLoop::iid))
{
*obj = static_cast (impl->runLoop);
impl->runLoop->addRef ();
return kResultTrue;
}
return kNoInterface;
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::initSurfaceGeometry (int32_t width, int32_t height)
{
xdg_surface_set_window_geometry (xdgSurface, 0, 0, size.width, size.height);
wl_surface_commit (wlSurface);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::addSurfaceListener ()
{
static const wl_surface_listener listener = {
.enter = handleEnter,
.leave = handleLeave,
.preferred_buffer_scale = handlePreferredBufferScale,
.preferred_buffer_transform = handlePreferredBufferTransform,
};
wl_surface_add_listener (wlSurface, &listener, this);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::addXdgSurfaceListener ()
{
static const xdg_surface_listener listener = {
.configure = handleXdgSurfaceConfigure,
};
xdg_surface_add_listener (xdgSurface, &listener, this);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::addToplevelListener ()
{
static const struct xdg_toplevel_listener listener = {
.configure = handleXdgToplevelConfigure,
.close = handleXdgToplevelClose,
.configure_bounds = handleXdgToplevelConfigureBounds,
.wm_capabilities = handleXdgToplevelWmCapabilities,
};
xdg_toplevel_add_listener (xdgToplevel, &listener, this);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::addTopLevelDecorationListener ()
{
static struct zxdg_toplevel_decoration_v1_listener zxdgTopLevelDecorationListener = {
.configure = handleTopLevelDecorationConfigure,
};
zxdg_toplevel_decoration_v1_add_listener (topLevelDeco, &zxdgTopLevelDecorationListener, this);
}
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/wayland/window.h0000664 0000000 0000000 00000007110 15075464575 0034052 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/wayland/window.h
// Created by : Steinberg 10.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h"
#include "runloop.h"
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/gui/iwaylandframe.h"
#include
#include
namespace WaylandServerDelegate {
class IWaylandClientContext;
class WaylandResource;
}
struct wl_compositor;
struct xdg_wm_base;
struct wl_shm;
struct zxdg_decoration_manager_v1;
struct wl_proxy;
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// IWaylandProxyCreator, see also: IWaylandServer
//------------------------------------------------------------------------
struct IWaylandProxyCreator
{
virtual wl_proxy* createProxy (wl_display* display, wl_proxy* object,
WaylandServerDelegate::WaylandResource* implementation) = 0;
virtual void destroyProxy (wl_proxy* proxy) = 0;
virtual ~IWaylandProxyCreator () noexcept = default;
};
//------------------------------------------------------------------------
// WaylandWindow
//------------------------------------------------------------------------
class WaylandWindow : public IWindow,
private U::ImplementsNonDestroyable>
{
public:
//------------------------------------------------------------------------
using Ptr = std::shared_ptr;
using WindowClosedFunc = std::function;
using IWaylandClientContext = WaylandServerDelegate::IWaylandClientContext;
static Ptr make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller,
const WindowClosedFunc& windowClosedFunc, IWaylandProxyCreator* proxyCreator,
IPtr runLoop, IPtr host,
const IWaylandClientContext& waylandContext);
WaylandWindow (const IWaylandClientContext& waylandContext);
~WaylandWindow () noexcept override;
//------------------------------------------------------------------------
private:
void doClose ();
// IWindow
void show () override;
void close () override;
void resize (Size newSize) override;
Size getContentSize () override;
NativePlatformWindow getNativePlatformWindow () const override;
tresult queryInterface (const TUID iid, void** obj) override;
// IWaylandFrame
wl_surface* PLUGIN_API getWaylandSurface (wl_display* display) override;
xdg_surface* PLUGIN_API getParentSurface (ViewRect& parentSize, wl_display* display) override;
xdg_toplevel* PLUGIN_API getParentToplevel (wl_display* display) override;
struct Impl;
std::unique_ptr impl;
};
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
vst3_public_sdk-3.8.0_build_66/samples/vst-hosting/editorhost/source/platform/linux/window.cpp 0000664 0000000 0000000 00000043054 15075464575 0032755 0 ustar 00root root 0000000 0000000 //-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/window.cpp
// Created by : Steinberg 05.2025
// Description : Example of opening a plug-in editor
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "window.h"
#include "public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include
#include
#include
#include
#include
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
struct X11Window::Impl
{
Impl (X11Window* x11Window);
bool init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc);
void show ();
void close ();
Size getSize () const;
void resize (Size newSize, bool force);
//------------------------------------------------------------------------
struct XEmbedInfo
{
uint32_t version;
uint32_t flags;
};
~Impl ();
void onClose ();
bool handleMainWindowEvent (const XEvent& event);
bool handlePlugEvent (const XEvent& event);
XEmbedInfo* getXEmbedInfo ();
void checkSize ();
void callPlugEventHandlers ();
WindowControllerPtr controller {nullptr};
WindowClosedFunc windowClosedFunc;
Display* xDisplay {nullptr};
XEmbedInfo* xembedInfo {nullptr};
Window xWindow {};
Window plugParentWindow {};
Window plugWindow {};
GC xGraphicContext {};
Atom xEmbedInfoAtom {None};
Atom xEmbedAtom {None};
bool isMapped {false};
using EventHandler = IPtr;
using TimerHandler = IPtr;
Size mCurrentSize {};
X11Window* x11Window {nullptr};
};
//------------------------------------------------------------------------
auto X11Window::make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc) -> Ptr
{
auto window = std::make_shared ();
if (window->init (name, size, resizeable, controller, display, windowClosedFunc))
{
return window;
}
return nullptr;
}
//------------------------------------------------------------------------
X11Window::X11Window ()
{
impl = std::unique_ptr (new Impl (this));
}
//------------------------------------------------------------------------
X11Window::~X11Window ()
{
}
//------------------------------------------------------------------------
bool X11Window::init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc)
{
return impl->init (name, size, resizeable, controller, display, windowClosedFunc);
}
//------------------------------------------------------------------------
Size X11Window::getSize () const
{
return impl->getSize ();
}
//------------------------------------------------------------------------
void X11Window::show ()
{
impl->show ();
}
//------------------------------------------------------------------------
void X11Window::close ()
{
impl->close ();
}
//------------------------------------------------------------------------
void X11Window::resize (Size newSize)
{
impl->resize (newSize, false);
}
//------------------------------------------------------------------------
Size X11Window::getContentSize ()
{
return {};
}
//------------------------------------------------------------------------
NativePlatformWindow X11Window::getNativePlatformWindow () const
{
return {kPlatformTypeX11EmbedWindowID, reinterpret_cast (impl->plugParentWindow)};
}
//------------------------------------------------------------------------
tresult X11Window::queryInterface (const TUID iid, void** obj)
{
if (FUnknownPrivate::iidEqual (iid, Linux::IRunLoop::iid))
{
*obj = &(Linux::RunLoopImpl::instance ());
// as the run loop is a singleton it is not necessary to call a retain on that object
return kResultTrue;
}
return kNoInterface;
}
//------------------------------------------------------------------------
void X11Window::onIdle ()
{
}
/* XEMBED messages */
#define XEMBED_EMBEDDED_NOTIFY 0
#define XEMBED_WINDOW_ACTIVATE 1
#define XEMBED_WINDOW_DEACTIVATE 2
#define XEMBED_REQUEST_FOCUS 3
#define XEMBED_FOCUS_IN 4
#define XEMBED_FOCUS_OUT 5
#define XEMBED_FOCUS_NEXT 6
#define XEMBED_FOCUS_PREV 7
/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
#define XEMBED_MODALITY_ON 10
#define XEMBED_MODALITY_OFF 11
#define XEMBED_REGISTER_ACCELERATOR 12
#define XEMBED_UNREGISTER_ACCELERATOR 13
#define XEMBED_ACTIVATE_ACCELERATOR 14
void send_xembed_message (Display* dpy, /* display */
Window w, /* receiver */
Atom messageType, long message, /* message opcode */
long detail, /* message detail */
long data1, /* message data 1 */
long data2 /* message data 2 */
)
{
XEvent ev;
memset (&ev, 0, sizeof (ev));
ev.xclient.type = ClientMessage;
ev.xclient.window = w;
ev.xclient.message_type = messageType;
ev.xclient.format = 32;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = message;
ev.xclient.data.l[2] = detail;
ev.xclient.data.l[3] = data1;
ev.xclient.data.l[4] = data2;
XSendEvent (dpy, w, False, NoEventMask, &ev);
XSync (dpy, False);
}
#define XEMBED_MAPPED (1 << 0)
//------------------------------------------------------------------------
X11Window::Impl::Impl (X11Window* x11Window) : x11Window (x11Window)
{
}
//------------------------------------------------------------------------
X11Window::Impl::~Impl ()
{
}
//------------------------------------------------------------------------
bool X11Window::Impl::init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc)
{
this->windowClosedFunc = windowClosedFunc;
this->controller = controller;
xDisplay = display;
xEmbedInfoAtom = XInternAtom (xDisplay, "_XEMBED_INFO", true);
if (xEmbedInfoAtom == None)
{
std::cerr << "_XEMBED_INFO does not exist" << std::endl;
return false;
}
// Get screen size from display
auto screen_num = DefaultScreen (xDisplay);
auto displayWidth = DisplayWidth (xDisplay, screen_num);
auto displayHeight = DisplayHeight (xDisplay, screen_num);
unsigned int border_width = 1;
XVisualInfo vInfo;
if (!XMatchVisualInfo (xDisplay, screen_num, 24, TrueColor, &vInfo))
{
exit (-1);
}
XSetWindowAttributes winAttr {};
winAttr.border_pixel = BlackPixel (xDisplay, screen_num);
winAttr.background_pixel = WhitePixel (xDisplay, screen_num);
winAttr.colormap =
XCreateColormap (xDisplay, XDefaultRootWindow (xDisplay), vInfo.visual, AllocNone);
uint32_t winAttrMask = CWBackPixel | CWColormap | CWBorderPixel;
xWindow = XCreateWindow (xDisplay, RootWindow (xDisplay, screen_num), 0, 0, displayWidth,
displayHeight, border_width, vInfo.depth, InputOutput, vInfo.visual,
winAttrMask, &winAttr);
XFlush (xDisplay);
resize (size, true);
XSelectInput (xDisplay, xWindow, /* KeyPressMask | ButtonPressMask |*/
ExposureMask | /*ResizeRedirectMask |*/ StructureNotifyMask |
SubstructureNotifyMask | FocusChangeMask);
auto sizeHints = XAllocSizeHints ();
sizeHints->flags = PMinSize;
if (!resizeable)
{
sizeHints->flags |= PMaxSize;
sizeHints->min_width = sizeHints->max_width = size.width;
sizeHints->min_height = sizeHints->max_height = size.height;
}
else
{
sizeHints->min_width = sizeHints->min_height = 80;
}
XSetWMNormalHints (xDisplay, xWindow, sizeHints);
XFree (sizeHints);
// set a title
XStoreName (xDisplay, xWindow, name.data ());
XTextProperty iconName;
auto icon_name = const_cast (name.data ());
XStringListToTextProperty (&icon_name, 1, &iconName);
XSetWMIconName (xDisplay, xWindow, &iconName);
Atom wm_delete_window;
wm_delete_window = XInternAtom (xDisplay, "WM_DELETE_WINDOW", False);
XSetWMProtocols (xDisplay, xWindow, &wm_delete_window, 1);
xGraphicContext = XCreateGC (xDisplay, xWindow, 0, 0);
XSetForeground (xDisplay, xGraphicContext, WhitePixel (xDisplay, screen_num));
XSetBackground (xDisplay, xGraphicContext, BlackPixel (xDisplay, screen_num));
winAttr = {};
winAttr.override_redirect = true;
winAttr.event_mask =
ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask;
plugParentWindow =
XCreateWindow (xDisplay, xWindow, 0, 0, size.width, size.height, border_width, vInfo.depth,
InputOutput, CopyFromParent, winAttrMask, &winAttr);
XSelectInput (xDisplay, plugParentWindow, SubstructureNotifyMask | PropertyChangeMask);
XMapWindow (xDisplay, plugParentWindow);
RunLoop::instance ().registerWindow (plugParentWindow,
[this] (const XEvent& e) { return handlePlugEvent (e); });
RunLoop::instance ().registerWindow (
xWindow, [this] (const XEvent& e) { return handleMainWindowEvent (e); });
return true;
}
//------------------------------------------------------------------------
void X11Window::Impl::show ()
{
XMapWindow (xDisplay, xWindow);
}
//------------------------------------------------------------------------
void X11Window::Impl::close ()
{
XUnmapWindow (xDisplay, xWindow);
}
//------------------------------------------------------------------------
void X11Window::Impl::onClose ()
{
XFreeGC (xDisplay, xGraphicContext);
XDestroyWindow (xDisplay, xWindow);
xDisplay = nullptr;
xWindow = 0;
isMapped = false;
if (windowClosedFunc)
windowClosedFunc (x11Window);
}
//------------------------------------------------------------------------
void X11Window::Impl::resize (Size newSize, bool force)
{
if (!force && mCurrentSize == newSize)
return;
if (xWindow)
XResizeWindow (xDisplay, xWindow, newSize.width, newSize.height);
if (plugParentWindow)
XResizeWindow (xDisplay, plugParentWindow, newSize.width, newSize.height);
mCurrentSize = newSize;
}
//------------------------------------------------------------------------
Size X11Window::Impl::getSize () const
{
::Window root;
int x, y;
unsigned int width, height;
unsigned int border_width;
unsigned int depth;
XGetGeometry (xDisplay, xWindow, &root, &x, &y, &width, &height, &border_width, &depth);
return {static_cast (width), static_cast (height)};
}
//------------------------------------------------------------------------
void X11Window::Impl::checkSize ()
{
if (getSize () != mCurrentSize)
{
resize (mCurrentSize, true);
}
}
//------------------------------------------------------------------------
bool X11Window::Impl::handleMainWindowEvent (const XEvent& event)
{
#if LOG_EVENTS
std::cout << "event " << event.type << "\n";
#endif
bool res = false;
switch (event.type)
{
case Expose:
if (event.xexpose.count == 0)
{
XClearWindow (xDisplay, xWindow);
XFillRectangle (xDisplay, xWindow, xGraphicContext, 0, 0, mCurrentSize.width,
mCurrentSize.height);
}
res = true;
break;
//--- StructureNotifyMask ------------------------------
// Window has been resized
case ConfigureNotify:
{
if (event.xconfigure.window != xWindow)
break;
auto width = event.xconfigure.width;
auto height = event.xconfigure.height;
Size size {width, height};
if (mCurrentSize != size)
{
auto constraintSize = controller->constrainSize (*x11Window, size);
if (constraintSize != mCurrentSize)
{
mCurrentSize = size;
controller->onResize (*x11Window, size);
}
if (constraintSize != size)
resize (constraintSize, true);
else
{
if (plugParentWindow)
XResizeWindow (xDisplay, plugParentWindow, size.width, size.height);
}
#if LOG_EVENTS
std::cout << "new size " << width << " x " << height << "\n";
#endif
}
res = true;
}
break;
// Window has been map to the screen
case MapNotify:
{
if (event.xany.window == xWindow && !isMapped)
{
controller->onShow (*x11Window);
isMapped = true;
res = true;
}
}
break;
case UnmapNotify:
{
if (event.xunmap.window == xWindow)
{
controller->onClose (*x11Window);
onClose ();
res = true;
}
break;
}
case DestroyNotify: break;
case ClientMessage:
{
if (event.xany.window == xWindow)
{
controller->onClose (*x11Window);
onClose ();
res = true;
}
break;
}
case FocusIn:
{
if (xembedInfo)
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_ACTIVATE, 0,
plugParentWindow, xembedInfo->version);
break;
}
case FocusOut:
{
if (xembedInfo)
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_DEACTIVATE, 0,
plugParentWindow, xembedInfo->version);
break;
}
//--- ResizeRedirectMask --------------------------------
case ResizeRequest:
{
if (event.xany.window == xWindow)
{
auto width = event.xresizerequest.width;
auto height = event.xresizerequest.height;
Size request {width, height};
if (mCurrentSize != request)
{
#if LOG_EVENTS
std::cout << "requested Size " << width << " x " << height << "\n";
#endif
auto constraintSize = controller->constrainSize (*x11Window, request);
if (constraintSize != request)
{
#if LOG_EVENTS
std::cout << "constraint Size " << constraintSize.width << " x "
<< constraintSize.height << "\n";
#endif
}
resize (constraintSize, true);
}
res = true;
}
}
break;
}
return res;
}
//------------------------------------------------------------------------
auto X11Window::Impl::getXEmbedInfo () -> XEmbedInfo*
{
int actualFormat;
unsigned long itemsReturned;
unsigned long bytesAfterReturn;
Atom actualType;
XEmbedInfo* xembedInfo = NULL;
if (xEmbedInfoAtom == None)
xEmbedInfoAtom = XInternAtom (xDisplay, "_XEMBED_INFO", true);
auto err =
XGetWindowProperty (xDisplay, plugWindow, xEmbedInfoAtom, 0, sizeof (xembedInfo), false,
xEmbedInfoAtom, &actualType, &actualFormat, &itemsReturned,
&bytesAfterReturn, reinterpret_cast