Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -0,0 +1,159 @@
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)
@@ -0,0 +1,18 @@
# 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)**
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>editorhost</string>
<key>CFBundleIdentifier</key>
<string>com.steinberg.vstsdk.editorhost</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>editorhost</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Steinberg. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>vst3</string>
</array>
<key>CFBundleTypeName</key>
<string>VST3 Plug-In</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
</array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.content</string>
</array>
<key>UTTypeDescription</key>
<string>VST3 Plug-In</string>
<key>UTTypeIdentifier</key>
<string>com.steinberg.vst3.plugin</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>vst3</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>
@@ -0,0 +1,426 @@
//-----------------------------------------------------------------------------
// 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 <cstdio>
#include <iostream>
//------------------------------------------------------------------------
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<App> ());
//------------------------------------------------------------------------
class WindowController : public IWindowController, public IPlugFrame
{
public:
WindowController (const IPtr<IPlugView>& 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<IPlugView> 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<VST3::UID> 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<WindowController> (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<std::string>& cmdArgs)
{
VST3::Optional<VST3::UID> 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<IPlugView>& 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<IPlugViewContentScaleSupport> (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
@@ -0,0 +1,61 @@
//-----------------------------------------------------------------------------
// 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<std::string>& cmdArgs) override;
void terminate () override;
private:
enum OpenFlags
{
kSetComponentHandler = 1 << 0,
kSecondWindow = 1 << 1,
};
void openEditor (const std::string& path, VST3::Optional<VST3::UID> effectID, uint32 flags);
void createViewAndShow (IEditController* controller);
VST3::Hosting::Module::Ptr module {nullptr};
IPtr<PlugProvider> plugProvider {nullptr};
Vst::HostApplication pluginContext;
WindowPtr window;
std::shared_ptr<WindowController> windowController;
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,40 @@
//------------------------------------------------------------------------
// 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
@@ -0,0 +1,44 @@
//-----------------------------------------------------------------------------
// 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 <memory>
#include <string>
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class IApplication
{
public:
virtual ~IApplication () noexcept = default;
virtual void init (const std::vector<std::string>& cmdArgs) = 0;
virtual void terminate () = 0;
};
using ApplicationPtr = std::unique_ptr<IApplication>;
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,52 @@
//-----------------------------------------------------------------------------
// 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 <functional>
//------------------------------------------------------------------------
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[]);
@@ -0,0 +1,120 @@
//-----------------------------------------------------------------------------
// 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 <memory>
#include <string>
//------------------------------------------------------------------------
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<IWindowController>;
//------------------------------------------------------------------------
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<IWindow>;
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,135 @@
//-----------------------------------------------------------------------------
// 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 <algorithm>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Linux {
//------------------------------------------------------------------------
class RunLoopImpl : U::ImplementsNonDestroyable<U::Directly<IRunLoop>>
{
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<Linux::IEventHandler>;
using TimerHandler = IPtr<Linux::ITimerHandler>;
using EventHandlers = std::unordered_map<Linux::FileDescriptor, EventHandler>;
using TimerHandlers = std::unordered_map<TimerID, TimerHandler>;
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
@@ -0,0 +1,205 @@
//-----------------------------------------------------------------------------
// 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 <chrono>
#include <iostream>
#include <string>
#include <vector>
//------------------------------------------------------------------------
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<std::string>& cmdArgs);
static const int kMinEventLoopRate = 16; // 60Hz
private:
void onWindowClosed (X11Window* window);
void closeAllWindows ();
void eventLoop ();
ApplicationPtr application;
Display* xDisplay {nullptr};
std::vector<std::shared_ptr<X11Window>> 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<X11Window> (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<std::string>& 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<std::string> cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
Steinberg::Vst::EditorHost::Platform::instance ().run (cmdArgs);
return 0;
}
#endif
@@ -0,0 +1,280 @@
//-----------------------------------------------------------------------------
// 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 <algorithm>
#include <iostream>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
using LockGuard = std::lock_guard<std::recursive_mutex>;
//------------------------------------------------------------------------
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<Millisecond> (Clock::now ());
std::vector<TimerID> 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<Millisecond> (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
@@ -0,0 +1,110 @@
//-----------------------------------------------------------------------------
// 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 <X11/Xlib.h>
#include <chrono>
#include <functional>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
using TimerID = uint64_t;
using TimerInterval = uint64_t;
using TimerCallback = std::function<void (TimerID)>;
//------------------------------------------------------------------------
class TimerProcessor
{
public:
TimerID registerTimer (TimerInterval interval, const TimerCallback& callback);
void unregisterTimer (TimerID id);
static constexpr uint64_t noTimers = std::numeric_limits<uint64_t>::max ();
uint64_t handleTimersAndReturnNextFireTimeInMs ();
private:
using Clock = std::chrono::steady_clock;
using Millisecond = std::chrono::milliseconds;
using TimePoint = std::chrono::time_point<Clock, Millisecond>;
struct Timer
{
TimerID id;
TimerInterval interval;
TimerCallback callback;
TimePoint nextFireTime;
};
using Timers = std::vector<Timer>;
Timers timers;
TimerID timerIdCounter {0};
void updateTimerNextFireTime (Timer& timer, TimePoint current);
void sortTimers ();
TimePoint now ();
};
//------------------------------------------------------------------------
class RunLoop
{
public:
using EventCallback = std::function<bool (const XEvent& event)>;
using FileDescriptorCallback = std::function<void (int fd)>;
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<XID, EventCallback>;
using FileDescriptorCallbacks = std::unordered_map<int, FileDescriptorCallback>;
WindowMap map;
FileDescriptorCallbacks fileDescriptors;
TimerProcessor timerProcessor;
Display* display {nullptr};
bool running {false};
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,493 @@
//-----------------------------------------------------------------------------
// 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 <algorithm>
#include <string>
#include <unordered_map>
#include <vector>
// 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<IContextListener*>;
using NamedWaylandOutput = std::pair<uint32_t, WaylandOutput>;
using WaylandOutputs = std::vector<NamedWaylandOutput>;
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<uint32_t, WlInterfaceName>;
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<WaylandGlobals*> (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<WaylandGlobals*> (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<wl_compositor*> (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<wl_subcompositor*> (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<wl_shm*> (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<xdg_wm_base*> (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<wl_seat*> (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<wl_output*> (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<zwp_linux_dmabuf_v1*> (object);
globals.dmabuf = dmabuf;
return;
}
}
//------------------------------------------------------------------------
static void handleGlobalRemove (void* data, struct wl_registry* registry, uint32_t name)
{
auto globals = reinterpret_cast<WaylandGlobals*> (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, &registry_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<WaylandClientContext::Impl*> (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<wl_output*> (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<WaylandClientContext::Impl*> (data);
self->removeWaylandOutput (name);
},
};
auto registry = wl_display_get_registry (display);
wl_registry_add_listener (registry, &registry_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<WaylandClientContext::Impl*> (data);
self->setSeatCapabilities (capabilities);
},
.name =
[] (void* data, struct wl_seat* wl_seat, const char* name) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (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<WaylandClientContext::Impl*> (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<WaylandClientContext::Impl*> (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<WaylandClientContext::Impl*> (data);
self->notifyListeners (IContextListener::kOutputsChanged);
},
.scale =
[] (void* data, wl_output* wl_output, int32_t factor) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (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<WaylandClientContext::Impl*> (data);
},
.description =
[] (void* data, wl_output* wl_output, const char* description) {
// Hmm, was never called.
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
},
};
for (auto& output : outputs)
wl_output_add_listener (output.second.handle, &listener, this);
}
//------------------------------------------------------------------------
// WaylandClientContext
//------------------------------------------------------------------------
WaylandClientContext::WaylandClientContext ()
{
impl = std::make_unique<Impl> ();
}
//------------------------------------------------------------------------
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
@@ -0,0 +1,61 @@
//-----------------------------------------------------------------------------
// 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 <memory>
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> impl;
};
//------------------------------------------------------------------------
} // namespace Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,375 @@
//-----------------------------------------------------------------------------
// 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 <array>
#include <cassert>
#include <fcntl.h>
#include <iostream>
#include <mutex>
#include <optional>
#include <poll.h>
#include <string_view>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
#include <wayland-server.h>
namespace Steinberg::Vst::EditorHost {
namespace Wayland {
//------------------------------------------------------------------------
struct ReleaseWaylandDisplay
{
void operator () (wl_display* d) const { wl_display_disconnect (d); }
};
using WaylandDisplayPtr = std::unique_ptr<wl_display, ReleaseWaylandDisplay>;
//------------------------------------------------------------------------
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<std::mutex>;
using Thread = std::thread;
} // namespace Threading
//------------------------------------------------------------------------
// Platform
//------------------------------------------------------------------------
class Platform
: public IPlatform,
public U::ImplementsNonDestroyable<U::Directly<IWaylandHost, Linux::IEventHandler>>,
public IWaylandProxyCreator
{
public:
//------------------------------------------------------------------------
static Platform& instance ()
{
static Platform gInstance;
return gInstance;
}
void run (const std::vector<std::string>& cmdArgs);
//------------------------------------------------------------------------
private:
using WaylandWindows = std::vector<std::shared_ptr<WaylandWindow>>;
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> 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<std::string>& 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<std::string> cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
Steinberg::Vst::EditorHost::Wayland::Platform::instance ().run (cmdArgs);
return 0;
}
#endif
@@ -0,0 +1,285 @@
//-----------------------------------------------------------------------------
// 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 <algorithm>
#include <cstdio>
#include <iostream>
#include <memory>
#include <poll.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <vector>
#include <wayland-client.h>
#include <wayland-server.h>
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<IntervalTimer>
{
using ITimerHandler = Linux::ITimerHandler;
IntervalTimer (ITimerHandler* handler) : handler (handler) {}
auto onFDIsSet () -> void
{
if (handler)
handler->onTimer ();
}
ITimerHandler* getHandler () const { return handler; }
private:
IPtr<ITimerHandler> handler;
};
//------------------------------------------------------------------------
// RunLoop::Impl
//------------------------------------------------------------------------
struct RunLoop::Impl
{
using FileDescriptors = std::vector<int>;
using Timers = std::vector<std::pair<std::shared_ptr<IntervalTimer>, FileDescriptor>>;
using EventHandlers = std::vector<std::pair<IPtr<IEventHandler>, FileDescriptor>>;
using PollRequests = std::vector<pollfd>;
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> ();
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<IntervalTimer> (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
@@ -0,0 +1,56 @@
//-----------------------------------------------------------------------------
// 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 <memory>
struct wl_display;
struct wl_event_loop;
namespace Steinberg::Vst::EditorHost::Wayland {
//------------------------------------------------------------------------
// RunLoop
//------------------------------------------------------------------------
struct RunLoop : U::Implements<U::Directly<Linux::IRunLoop>>
{
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> impl;
};
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,256 @@
//-----------------------------------------------------------------------------
// 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<wl_surface_interface*> (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<xdg_surface_interface*> (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<xdg_toplevel_interface*> (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
@@ -0,0 +1,105 @@
//-----------------------------------------------------------------------------
// 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
@@ -0,0 +1,594 @@
//-----------------------------------------------------------------------------
// 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 <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
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<int32_t*> (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> runLoop;
IPtr<IWaylandHost> host;
using FuncDoClose = std::function<void ()>;
FuncDoClose doCloseFunc;
using FuncOnScaleFactorChanged = std::function<void (float scaleFactor)>;
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<Impl> (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> runLoop,
IPtr<IWaylandHost> host,
const IWaylandClientContext& waylandClientContext) -> Ptr
{
auto window = std::make_shared<WaylandWindow> (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<zxdg_decoration_manager_v1*> (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<WaylandWindow::Impl*> (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<WaylandWindow::Impl*> (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<WaylandWindow::Impl*> (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<WaylandWindow::Impl*> (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleXdgToplevelWmCapabilities (void* data,
struct xdg_toplevel* xdg_toplevel,
struct wl_array* capabilities)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handleTopLevelDecorationConfigure (
void* data, struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, uint32_t mode)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (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<WaylandWindow::Impl*> (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<WaylandWindow::Impl*> (data);
}
//------------------------------------------------------------------------
void WaylandWindow::Impl::handlePreferredBufferScale (void* data, wl_surface* wl_surface,
int32_t factor)
{
auto self = reinterpret_cast<WaylandWindow::Impl*> (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<WaylandWindow::Impl*> (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<wl_surface*> (impl->wlSurfaceProxy);
impl->wlSurfaceProxy = impl->wlProxyCreator->createProxy (
display, reinterpret_cast<wl_proxy*> (impl->wlSurface), new SurfaceDelegate);
return reinterpret_cast<wl_surface*> (impl->wlSurfaceProxy);
}
//------------------------------------------------------------------------
xdg_surface* PLUGIN_API WaylandWindow::getParentSurface (ViewRect& parentSize, wl_display* display)
{
if (!impl->wlProxyCreator)
return nullptr;
if (impl->xdgSurfaceProxy)
return reinterpret_cast<xdg_surface*> (impl->xdgSurfaceProxy);
impl->xdgSurfaceProxy = impl->wlProxyCreator->createProxy (
display, reinterpret_cast<wl_proxy*> (impl->xdgSurface), new XdgSurfaceDelegate);
parentSize = ViewRect (0, 0, impl->size.width, impl->size.height);
return reinterpret_cast<xdg_surface*> (impl->xdgSurfaceProxy);
}
//------------------------------------------------------------------------
xdg_toplevel* PLUGIN_API WaylandWindow::getParentToplevel (wl_display* display)
{
if (!impl->wlProxyCreator)
return nullptr;
if (impl->xdgToplevelProxy)
return reinterpret_cast<xdg_toplevel*> (impl->xdgToplevelProxy);
impl->xdgToplevelProxy = impl->wlProxyCreator->createProxy (
display, reinterpret_cast<wl_proxy*> (impl->xdgToplevelProxy), new XdgToplevelDelegate);
return reinterpret_cast<xdg_toplevel*> (impl->xdgToplevelProxy);
}
//------------------------------------------------------------------------
tresult WaylandWindow::queryInterface (const TUID iid, void** obj)
{
if (FUnknownPrivate::iidEqual (iid, IWaylandFrame::iid))
{
*obj = static_cast<IWaylandFrame*> (this);
return kResultTrue;
}
if (FUnknownPrivate::iidEqual (iid, IWaylandHost::iid))
{
*obj = static_cast<IWaylandHost*> (impl->host);
impl->host->addRef ();
return kResultTrue;
}
if (FUnknownPrivate::iidEqual (iid, Linux::IRunLoop::iid))
{
*obj = static_cast<Linux::IRunLoop*> (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
@@ -0,0 +1,93 @@
//-----------------------------------------------------------------------------
// 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 <functional>
#include <unordered_map>
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<U::Directly<IWaylandFrame>>
{
public:
//------------------------------------------------------------------------
using Ptr = std::shared_ptr<WaylandWindow>;
using WindowClosedFunc = std::function<void (WaylandWindow*)>;
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> runLoop, IPtr<IWaylandHost> 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> impl;
};
//------------------------------------------------------------------------
} // Steinberg::Vst::EditorHost::Wayland
@@ -0,0 +1,626 @@
//-----------------------------------------------------------------------------
// 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 <cassert>
#include <iostream>
#include <unordered_map>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
//------------------------------------------------------------------------
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<Linux::IEventHandler>;
using TimerHandler = IPtr<Linux::ITimerHandler>;
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<X11Window> ();
if (window->init (name, size, resizeable, controller, display, windowClosedFunc))
{
return window;
}
return nullptr;
}
//------------------------------------------------------------------------
X11Window::X11Window ()
{
impl = std::unique_ptr<Impl> (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<void*> (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<char*> (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<int> (width), static_cast<int> (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<unsigned char**> (&xembedInfo));
if (err != Success)
return nullptr;
return xembedInfo;
}
//------------------------------------------------------------------------
bool X11Window::Impl::handlePlugEvent (const XEvent& event)
{
bool res = false;
switch (event.type)
{
// XEMBED specific
case ClientMessage:
{
auto name = XGetAtomName (xDisplay, event.xclient.message_type);
std::cout << name << std::endl;
if (event.xclient.message_type == xEmbedAtom)
{
switch (event.xclient.data.l[1])
{
case XEMBED_REQUEST_FOCUS:
{
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_FOCUS_IN, 0,
plugParentWindow, xembedInfo->version);
break;
}
}
}
break;
}
case PropertyNotify:
{
auto name = XGetAtomName (xDisplay, event.xproperty.atom);
std::cout << name << std::endl;
if (event.xany.window == plugWindow)
{
if (event.xproperty.atom == xEmbedInfoAtom)
{
if (auto embedInfo = getXEmbedInfo ())
{
}
}
else
{
}
}
break;
}
case CreateNotify:
{
if (event.xcreatewindow.parent != plugParentWindow)
{
res = true;
break;
}
plugWindow = event.xcreatewindow.window;
xembedInfo = getXEmbedInfo ();
if (!xembedInfo)
{
std::cerr << "XGetWindowProperty for _XEMBED_INFO failed" << std::endl;
exit (-1);
}
if (xembedInfo->flags & XEMBED_MAPPED)
{
std::cerr << "Window already mapped error" << std::endl;
exit (-1);
}
RunLoop::instance ().registerWindow (
plugWindow, [this] (const XEvent& e) { return handlePlugEvent (e); });
// XSelectInput (xDisplay, plugWindow, PropertyChangeMask);
if (xEmbedAtom == None)
xEmbedAtom = XInternAtom (xDisplay, "_XEMBED", true);
assert (xEmbedAtom != None);
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_EMBEDDED_NOTIFY, 0,
plugParentWindow, xembedInfo->version);
XMapWindow (xDisplay, plugWindow);
XResizeWindow (xDisplay, plugWindow, mCurrentSize.width, mCurrentSize.height);
// XSetInputFocus (xDisplay, plugWindow, RevertToParent, CurrentTime);
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_ACTIVATE, 0,
plugParentWindow, xembedInfo->version);
send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_FOCUS_IN, 0,
plugParentWindow, xembedInfo->version);
XSync (xDisplay, False);
res = true;
break;
}
}
return res;
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,69 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/window.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/iwindow.h"
#include <functional>
struct _XDisplay;
typedef struct _XDisplay Display;
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class X11Window : public IWindow
{
public:
using Ptr = std::shared_ptr<X11Window>;
using WindowClosedFunc = std::function<void (X11Window*)>;
static Ptr make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc);
X11Window ();
~X11Window () override;
bool init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, Display* display,
const WindowClosedFunc& windowClosedFunc);
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;
void onIdle ();
private:
Size getSize () const;
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,222 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/mac/platform.mm
// 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.
//-----------------------------------------------------------------------------
#import "public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h"
#import "public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.h"
#import <Cocoa/Cocoa.h>
#import <iostream>
#if !__has_feature(objc_arc)
#error this file needs to be compiled with automatic reference counting enabled
#endif
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class Platform : public IPlatform
{
public:
static Platform& instance ()
{
static Platform gInstance;
return gInstance;
}
Platform ();
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;
ApplicationPtr application;
bool quitRequested {false};
};
//------------------------------------------------------------------------
Platform::Platform ()
{
NSApplicationLoad ();
}
//------------------------------------------------------------------------
void Platform::setApplication (ApplicationPtr&& app)
{
application = std::move (app);
}
//------------------------------------------------------------------------
WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
return Window::make (title, size, resizeable, controller);
}
//------------------------------------------------------------------------
void Platform::quit ()
{
if (quitRequested)
return;
quitRequested = true;
dispatch_after (dispatch_time (DISPATCH_TIME_NOW, (int64_t) (0.1 * NSEC_PER_SEC)),
dispatch_get_main_queue (), ^{
@autoreleasepool
{
for (NSWindow* window in NSApp.windows)
[window close];
}
application = nullptr;
[NSApp terminate:nil];
});
}
//------------------------------------------------------------------------
void Platform::kill (int resultCode, const std::string& reason)
{
std::cout << reason << "\n";
std::cout.flush ();
auto alert = [NSAlert new];
alert.messageText = [NSString stringWithUTF8String:reason.data ()];
alert.alertStyle = NSCriticalAlertStyle;
[alert addButtonWithTitle:@"Quit"];
[alert runModal];
exit (resultCode);
}
//------------------------------------------------------------------------
FUnknown* Platform::getPluginFactoryContext ()
{
return nullptr;
}
//------------------------------------------------------------------------
IPlatform& IPlatform::instance ()
{
return Platform::instance ();
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
//------------------------------------------------------------------------
@interface VSTSDK_AppDelegate : NSObject <NSApplicationDelegate>
{
std::vector<std::string> cmdArgs;
}
@end
//------------------------------------------------------------------------
@implementation VSTSDK_AppDelegate
//------------------------------------------------------------------------
- (void)setCmdArgs:(std::vector<std::string>&&)args
{
cmdArgs = std::move (args);
}
//------------------------------------------------------------------------
- (NSMenu*)createAppMenu
{
auto appName =
static_cast<NSString*> ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]);
NSMenu* menu = [[NSMenu alloc] initWithTitle:appName];
[menu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName]
action:@selector (hide:)
keyEquivalent:@"h"];
[menu addItemWithTitle:@"Hide Others"
action:@selector (hideOtherApplications:)
keyEquivalent:@""];
[menu addItemWithTitle:@"Show All" action:@selector (unhideAllApplications:) keyEquivalent:@""];
[menu addItem:[NSMenuItem separatorItem]];
[menu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName]
action:@selector (terminate:)
keyEquivalent:@"q"];
return menu;
}
//------------------------------------------------------------------------
- (NSMenu*)createFileMenu
{
NSMenu* menu = [[NSMenu alloc] initWithTitle:@"File"];
[menu addItemWithTitle:@"Close Window" action:@selector (performClose:) keyEquivalent:@"w"];
return menu;
}
//------------------------------------------------------------------------
- (void)setupMenubar
{
auto mainMenu = [NSMenu new];
[NSApp setMainMenu:mainMenu];
auto appMenuItem = [[NSMenuItem alloc] initWithTitle:@"App" action:nil keyEquivalent:@""];
[mainMenu addItem:appMenuItem];
appMenuItem.submenu = [self createAppMenu];
auto fileMenuItem = [[NSMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""];
[mainMenu addItem:fileMenuItem];
fileMenuItem.submenu = [self createFileMenu];
}
//------------------------------------------------------------------------
- (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename
{
if (cmdArgs.empty ())
cmdArgs.push_back ([filename UTF8String]);
return YES;
}
//------------------------------------------------------------------------
- (void)applicationDidFinishLaunching:(NSNotification*)notification
{
[self setupMenubar];
Steinberg::Vst::EditorHost::Platform::instance ().application->init (cmdArgs);
cmdArgs.clear ();
}
//------------------------------------------------------------------------
- (void)applicationWillTerminate:(NSNotification*)notification
{
if (auto& app = Steinberg::Vst::EditorHost::Platform::instance ().application)
app->terminate ();
}
@end
//------------------------------------------------------------------------
int main (int argc, const char* argv[])
{
auto delegate = [VSTSDK_AppDelegate new];
std::vector<std::string> cmdArgs;
for (int i = 1; i < argc; ++i)
cmdArgs.push_back (argv[i]);
[delegate setCmdArgs:std::move (cmdArgs)];
[NSApplication sharedApplication].delegate = delegate;
return NSApplicationMain (argc, argv);
}
@@ -0,0 +1,60 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.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/iwindow.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class Window : public IWindow, public std::enable_shared_from_this<Window>
{
public:
static WindowPtr make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller);
Window ();
~Window () noexcept override;
bool init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller);
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 { return kNoInterface; }
WindowControllerPtr getController () const;
void windowClosed ();
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,209 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.mm
// 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.
//-----------------------------------------------------------------------------
#import "public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.h"
#import <Cocoa/Cocoa.h>
#if !__has_feature(objc_arc)
#error this file needs to be compiled with automatic reference counting enabled
#endif
using namespace Steinberg::Vst;
//------------------------------------------------------------------------
@interface VSTSDK_WindowDelegate : NSObject <NSWindowDelegate>
#if __i386__
{
std::shared_ptr<EditorHost::Window> _window;
NSWindow* _nsWindow;
}
#endif
@property (readonly) std::shared_ptr<EditorHost::Window> window;
@property (strong, readwrite) NSWindow* nsWindow;
- (id)initWithWindow:(std::shared_ptr<EditorHost::Window>)window;
@end
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
struct Window::Impl
{
WindowControllerPtr controller;
VSTSDK_WindowDelegate* nsWindowDelegate {nullptr};
};
//------------------------------------------------------------------------
WindowPtr Window::make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
auto window = std::make_shared<Window> ();
if (window->init (name, size, resizeable, controller))
return window;
return nullptr;
}
//------------------------------------------------------------------------
Window::Window ()
{
impl = std::unique_ptr<Impl> (new Impl);
}
//------------------------------------------------------------------------
bool Window::init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
impl->controller = controller;
NSUInteger styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
if (resizeable)
styleMask |= NSWindowStyleMaskResizable;
auto contentRect =
NSMakeRect (0., 0., static_cast<CGFloat> (size.width), static_cast<CGFloat> (size.height));
impl->nsWindowDelegate = [[VSTSDK_WindowDelegate alloc] initWithWindow:shared_from_this ()];
auto nsWindow = [[NSWindow alloc] initWithContentRect:contentRect
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:YES];
[nsWindow setDelegate:impl->nsWindowDelegate];
nsWindow.releasedWhenClosed = NO;
impl->nsWindowDelegate.nsWindow = nsWindow;
[nsWindow center];
return true;
}
//------------------------------------------------------------------------
Window::~Window () noexcept
{
}
//------------------------------------------------------------------------
void Window::show ()
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
impl->controller->onShow (*this);
[nsWindow makeKeyAndOrderFront:nil];
}
//------------------------------------------------------------------------
void Window::close ()
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
[nsWindow close];
}
//------------------------------------------------------------------------
void Window::resize (Size newSize)
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
auto r = [nsWindow contentRectForFrameRect:nsWindow.frame];
auto diff = newSize.height - r.size.height;
r.size.width = newSize.width;
r.size.height = newSize.height;
r.origin.y -= diff;
[nsWindow setFrame:[nsWindow frameRectForContentRect:r]
display:[nsWindow isVisible]
animate:NO];
}
//------------------------------------------------------------------------
Size Window::getContentSize ()
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
auto r = [nsWindow contentRectForFrameRect:nsWindow.frame];
return {static_cast<Coord> (r.size.width), static_cast<Coord> (r.size.height)};
}
//------------------------------------------------------------------------
NativePlatformWindow Window::getNativePlatformWindow () const
{
auto nsWindow = impl->nsWindowDelegate.nsWindow;
return {kPlatformTypeNSView, (__bridge void*)([nsWindow contentView])};
}
//------------------------------------------------------------------------
WindowControllerPtr Window::getController () const
{
return impl->controller;
}
//------------------------------------------------------------------------
void Window::windowClosed ()
{
impl->controller->onClose (*this);
impl->nsWindowDelegate = nullptr;
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
//------------------------------------------------------------------------
@implementation VSTSDK_WindowDelegate
#if __i386__
@synthesize window = _window;
#endif
//------------------------------------------------------------------------
- (id)initWithWindow:(std::shared_ptr<EditorHost::Window>)window
{
if (self = [super init])
{
self->_window = window;
}
return self;
}
//------------------------------------------------------------------------
- (NSSize)windowWillResize:(nonnull NSWindow*)sender toSize:(NSSize)frameSize
{
NSRect r {};
r.size = frameSize;
r = [sender contentRectForFrameRect:r];
EditorHost::Size size {static_cast<EditorHost::Coord> (r.size.width),
static_cast<EditorHost::Coord> (r.size.height)};
size = self.window->getController ()->constrainSize (*self.window, size);
r.size.width = size.width;
r.size.height = size.height;
r = [sender frameRectForContentRect:r];
return r.size;
}
//------------------------------------------------------------------------
- (void)windowDidResize:(nonnull NSNotification*)notification
{
NSWindow* window = [notification object];
NSRect r = window.frame;
r = [window contentRectForFrameRect:r];
EditorHost::Size size {static_cast<EditorHost::Coord> (r.size.width),
static_cast<EditorHost::Coord> (r.size.height)};
self.window->getController ()->onResize (*self.window, size);
}
//------------------------------------------------------------------------
- (void)windowWillClose:(nonnull NSNotification*)notification
{
self.window->windowClosed ();
self->_window = nullptr;
self->_nsWindow = nullptr;
}
@end
@@ -0,0 +1,163 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/win32/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/win32/window.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "pluginterfaces/base/ftypes.h"
#include <algorithm>
#include <objbase.h>
#include <shellapi.h>
#include <string>
#include <vector>
#include <windows.h>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
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 (LPWSTR lpCmdLine, HINSTANCE instance);
private:
ApplicationPtr application;
HINSTANCE hInstance {nullptr};
bool quitRequested {false};
};
//------------------------------------------------------------------------
IPlatform& IPlatform::instance ()
{
return Platform::instance ();
}
//------------------------------------------------------------------------
void Platform::setApplication (ApplicationPtr&& app)
{
application = std::move (app);
}
//------------------------------------------------------------------------
WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable,
const WindowControllerPtr& controller)
{
return Window::make (title, size, resizeable, controller, hInstance);
}
//------------------------------------------------------------------------
void Platform::quit ()
{
if (quitRequested)
return;
quitRequested = true;
for (auto& window : Window::getWindows ())
window->closeImmediately ();
if (application)
application->terminate ();
PostQuitMessage (0);
}
//------------------------------------------------------------------------
void Platform::kill (int resultCode, const std::string& reason)
{
auto str = StringConvert::convert (reason);
MessageBox (nullptr, reinterpret_cast<LPCWSTR> (str.data ()), nullptr, MB_OK);
exit (resultCode);
}
//------------------------------------------------------------------------
FUnknown* Platform::getPluginFactoryContext ()
{
return nullptr;
}
//------------------------------------------------------------------------
void Platform::run (LPWSTR lpCmdLine, HINSTANCE _hInstance)
{
hInstance = _hInstance;
std::vector<std::string> cmdArgStrings;
int numArgs = 0;
auto cmdArgsArray = CommandLineToArgvW (lpCmdLine, &numArgs);
cmdArgStrings.reserve (numArgs);
for (int i = 0; i < numArgs; ++i)
{
cmdArgStrings.push_back (StringConvert::convert (Steinberg::wscast (cmdArgsArray[i])));
}
LocalFree (cmdArgsArray);
auto noHIDPI = std::find (cmdArgStrings.begin (), cmdArgStrings.end (), "-noHIDPI");
if (noHIDPI == cmdArgStrings.end ())
ShcoreLibrary::instance ().setProcessDpiAwareness (true);
application->init (cmdArgStrings);
MSG msg;
while (GetMessage (&msg, nullptr, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
#ifndef _In_
#define _In_
#endif
#ifndef _In_opt_
#define _In_opt_
#endif
//------------------------------------------------------------------------
int APIENTRY wWinMain (_In_ HINSTANCE instance, _In_opt_ HINSTANCE /*prevInstance*/,
_In_ LPWSTR lpCmdLine, _In_ int /*nCmdShow*/)
{
HRESULT hr = CoInitialize (nullptr);
if (FAILED (hr))
return FALSE;
Steinberg::Vst::EditorHost::Platform::instance ().run (lpCmdLine, instance);
CoUninitialize ();
return 0;
}
@@ -0,0 +1,298 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/win32/window.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 "window.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include <algorithm>
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0
#endif
#ifndef WM_GETDPISCALEDSIZE
#define WM_GETDPISCALEDSIZE 0x02E4
#endif
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
namespace {
static Window::WindowList gAllWindows;
//------------------------------------------------------------------------
static void addWindow (Window* window)
{
gAllWindows.push_back (window);
}
//------------------------------------------------------------------------
static void removeWindow (Window* window)
{
auto it = std::find (gAllWindows.begin (), gAllWindows.end (), window);
if (it != gAllWindows.end ())
gAllWindows.erase (it);
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
static const WCHAR* gWindowClassName = L"VSTSDK WindowClass";
//------------------------------------------------------------------------
WindowPtr Window::make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, HINSTANCE instance)
{
auto window = std::make_shared<Window> ();
if (window->init (name, size, resizeable, controller, instance))
return window;
return nullptr;
}
//------------------------------------------------------------------------
LRESULT CALLBACK Window::WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
auto* window = reinterpret_cast<Window*> ((LONG_PTR)GetWindowLongPtr (hWnd, GWLP_USERDATA));
if (window)
return window->proc (message, wParam, lParam);
return DefWindowProc (hWnd, message, wParam, lParam);
}
//------------------------------------------------------------------------
void Window::registerWindowClass (HINSTANCE instance)
{
static bool once = true;
if (!once)
return;
once = true;
WNDCLASSEX wcex {};
wcex.cbSize = sizeof (WNDCLASSEX);
wcex.style = CS_DBLCLKS;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = instance;
wcex.hCursor = LoadCursor (instance, IDC_ARROW);
wcex.hbrBackground = nullptr;
wcex.lpszClassName = gWindowClassName;
RegisterClassEx (&wcex);
}
//------------------------------------------------------------------------
auto Window::getWindows () -> WindowList
{
return gAllWindows;
}
//------------------------------------------------------------------------
bool Window::init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& _controller, HINSTANCE instance)
{
controller = _controller;
registerWindowClass (instance);
DWORD exStyle = WS_EX_APPWINDOW;
DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
if (resizeable)
dwStyle |= WS_SIZEBOX | WS_MAXIMIZEBOX;
auto windowTitle = Vst::StringConvert::convert (name);
RECT rect {0, 0, size.width, size.height};
AdjustWindowRectEx (&rect, dwStyle, false, exStyle);
hwnd = CreateWindowEx (exStyle, gWindowClassName, (const TCHAR*)windowTitle.data (), dwStyle, 0,
0, rect.right - rect.left, rect.bottom - rect.top, nullptr, nullptr,
instance, nullptr);
if (hwnd)
{
SetWindowLongPtr (hwnd, GWLP_USERDATA, (__int3264) (LONG_PTR)this);
This = shared_from_this ();
addWindow (this);
}
return hwnd != nullptr;
}
//------------------------------------------------------------------------
LRESULT Window::proc (UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND:
{
return TRUE; // do not draw background
}
case WM_PAINT:
{
PAINTSTRUCT ps {};
BeginPaint (hwnd, &ps);
EndPaint (hwnd, &ps);
return FALSE;
}
case WM_SIZE:
{
controller->onResize (*this, getContentSize ());
break;
}
case WM_SIZING:
{
auto* newSize = reinterpret_cast<RECT*> (lParam);
RECT oldSize;
GetWindowRect (hwnd, &oldSize);
RECT clientSize;
GetClientRect (hwnd, &clientSize);
auto diffX = (newSize->right - newSize->left) - (oldSize.right - oldSize.left);
auto diffY = (newSize->bottom - newSize->top) - (oldSize.bottom - oldSize.top);
Size newClientSize = {(clientSize.right - clientSize.left),
(clientSize.bottom - clientSize.top)};
newClientSize.width += diffX;
newClientSize.height += diffY;
auto constraintSize = controller->constrainSize (*this, newClientSize);
if (constraintSize != newClientSize)
{
auto diffX2 = (oldSize.right - oldSize.left) - (clientSize.right - clientSize.left);
auto diffY2 = (oldSize.bottom - oldSize.top) - (clientSize.bottom - clientSize.top);
newSize->right = newSize->left + static_cast<LONG> (constraintSize.width + diffX2);
newSize->bottom = newSize->top + static_cast<LONG> (constraintSize.height + diffY2);
}
return TRUE;
}
case WM_CLOSE:
{
closeImmediately ();
return TRUE;
}
case WM_GETDPISCALEDSIZE:
{
inDpiChangeState = true;
auto* proposedSize = reinterpret_cast<SIZE*> (lParam);
auto newScaleFactor =
static_cast<float> (wParam) / static_cast<float> (USER_DEFAULT_SCREEN_DPI);
controller->onContentScaleFactorChanged (*this, newScaleFactor);
if (dpiChangedSize.width != 0 && dpiChangedSize.height != 0)
{
WINDOWINFO windowInfo {0};
GetWindowInfo (hwnd, &windowInfo);
RECT clientRect {};
clientRect.right = dpiChangedSize.width;
clientRect.bottom = dpiChangedSize.height;
User32Library::instance ().adjustWindowRectExForDpi (
&clientRect, windowInfo.dwStyle, false, windowInfo.dwExStyle,
static_cast<UINT> (wParam));
proposedSize->cx = clientRect.right - clientRect.left;
proposedSize->cy = clientRect.bottom - clientRect.top;
return TRUE;
}
return FALSE;
}
case WM_DPICHANGED:
{
if (inDpiChangeState)
{
auto* rect = reinterpret_cast<RECT*> (lParam);
inDpiChangeState = false;
dpiChangedSize = {};
SetWindowPos (hwnd, nullptr, rect->left, rect->top, rect->right - rect->left,
rect->bottom - rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
controller->onContentScaleFactorChanged (*this, getContentScaleFactor ());
return 0;
}
}
return DefWindowProc (hwnd, message, wParam, lParam);
}
//------------------------------------------------------------------------
void Window::closeImmediately ()
{
close ();
removeWindow (this);
controller->onClose (*this);
SetWindowLongPtr (hwnd, GWLP_USERDATA, (__int3264) (LONG_PTR) nullptr);
This = nullptr;
}
//------------------------------------------------------------------------
Size Window::getContentSize ()
{
RECT r;
GetClientRect (hwnd, &r);
return {r.right - r.left, r.bottom - r.top};
}
//------------------------------------------------------------------------
float Window::getContentScaleFactor () const
{
if (auto dpiForWindow = ShcoreLibrary::instance ().getDpiForWindow (hwnd))
{
return static_cast<float> (dpiForWindow->x) / static_cast<float> (USER_DEFAULT_SCREEN_DPI);
}
return 1.f;
}
//------------------------------------------------------------------------
void Window::show ()
{
controller->onContentScaleFactorChanged (*this, getContentScaleFactor ());
controller->onShow (*this);
SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOCOPYBITS | SWP_SHOWWINDOW);
}
//------------------------------------------------------------------------
void Window::close ()
{
ShowWindow (hwnd, false);
}
//------------------------------------------------------------------------
void Window::resize (Size newSize)
{
if (inDpiChangeState)
{
dpiChangedSize = newSize;
return;
}
if (getContentSize () == newSize)
return;
WINDOWINFO windowInfo {0};
GetWindowInfo (hwnd, &windowInfo);
RECT clientRect {};
clientRect.right = newSize.width;
clientRect.bottom = newSize.height;
AdjustWindowRectEx (&clientRect, windowInfo.dwStyle, false, windowInfo.dwExStyle);
SetWindowPos (hwnd, HWND_TOP, 0, 0, clientRect.right - clientRect.left,
clientRect.bottom - clientRect.top, SWP_NOMOVE | SWP_NOCOPYBITS | SWP_NOACTIVATE);
}
//------------------------------------------------------------------------
NativePlatformWindow Window::getNativePlatformWindow () const
{
return {kPlatformTypeHWND, hwnd};
}
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,220 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/win32/window.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/iwindow.h"
#include "public.sdk/source/vst/utility/optional.h"
#include <windows.h>
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace EditorHost {
//------------------------------------------------------------------------
class Window : public IWindow, public std::enable_shared_from_this<Window>
{
public:
static WindowPtr make (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, HINSTANCE instance);
bool init (const std::string& name, Size size, bool resizeable,
const WindowControllerPtr& controller, HINSTANCE instance);
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 { return kNoInterface; }
void closeImmediately ();
using WindowList = std::vector<Window*>;
static WindowList getWindows ();
private:
LRESULT CALLBACK proc (UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
float getContentScaleFactor () const;
void registerWindowClass (HINSTANCE instance);
WindowPtr This;
WindowControllerPtr controller {nullptr};
HWND hwnd {nullptr};
bool inDpiChangeState {false};
Size dpiChangedSize {};
};
//------------------------------------------------------------------------
struct DynamicLibrary
{
DynamicLibrary (const char* name) { module = LoadLibraryA (name); }
~DynamicLibrary () { FreeLibrary (module); }
template <typename T>
T getProcAddress (const char* name)
{
return module ? reinterpret_cast<T> (GetProcAddress (module, name)) : nullptr;
}
private:
HMODULE module {nullptr};
};
#ifndef _DPI_AWARENESS_CONTEXTS_
DECLARE_HANDLE (DPI_AWARENESS_CONTEXT);
typedef enum DPI_AWARENESS {
DPI_AWARENESS_INVALID = -1,
DPI_AWARENESS_UNAWARE = 0,
DPI_AWARENESS_SYSTEM_AWARE = 1,
DPI_AWARENESS_PER_MONITOR_AWARE = 2
} DPI_AWARENESS;
#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
#endif
struct User32Library : DynamicLibrary
{
static User32Library& instance ()
{
static User32Library gInstance;
return gInstance;
}
bool setProcessDpiAwarenessContext (DPI_AWARENESS_CONTEXT context) const
{
if (!setProcessDpiAwarenessContextProc)
return false;
return setProcessDpiAwarenessContextProc (context);
}
bool adjustWindowRectExForDpi (LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle,
UINT dpi) const
{
if (!adjustWindowRectExForDpiProc)
return false;
return adjustWindowRectExForDpiProc (lpRect, dwStyle, bMenu, dwExStyle, dpi);
}
private:
using SetProcessDpiAwarenessContextProc = BOOL (WINAPI*) (DPI_AWARENESS_CONTEXT);
using AdjustWindowRectExForDpiProc = BOOL (WINAPI*) (LPRECT, DWORD, BOOL, DWORD, UINT);
User32Library () : DynamicLibrary ("User32.dll")
{
setProcessDpiAwarenessContextProc =
getProcAddress<SetProcessDpiAwarenessContextProc> ("SetProcessDpiAwarenessContext");
adjustWindowRectExForDpiProc =
getProcAddress<AdjustWindowRectExForDpiProc> ("AdjustWindowRectExForDpi");
}
SetProcessDpiAwarenessContextProc setProcessDpiAwarenessContextProc {nullptr};
AdjustWindowRectExForDpiProc adjustWindowRectExForDpiProc {nullptr};
};
struct ShcoreLibrary : DynamicLibrary
{
static ShcoreLibrary& instance ()
{
static ShcoreLibrary gInstance;
return gInstance;
}
struct DPI
{
UINT x;
UINT y;
};
VST3::Optional<DPI> getDpiForWindow (HWND window) const
{
if (!getDpiForMonitorProc)
return {};
auto monitor = MonitorFromWindow (window, MONITOR_DEFAULTTONEAREST);
UINT x, y;
getDpiForMonitorProc (monitor, MDT_EFFECTIVE_DPI, &x, &y);
return DPI {x, y};
}
HRESULT setProcessDpiAwareness (bool perMonitor = true)
{
if (perMonitor)
{
if (User32Library::instance ().setProcessDpiAwarenessContext (
perMonitor ? DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 :
DPI_AWARENESS_CONTEXT_UNAWARE))
return true;
}
if (!setProcessDpiAwarenessProc)
return S_FALSE;
return setProcessDpiAwarenessProc (perMonitor ? PROCESS_PER_MONITOR_DPI_AWARE :
PROCESS_SYSTEM_DPI_AWARE);
}
private:
enum PROCESS_DPI_AWARENESS
{
PROCESS_DPI_UNAWARE = 0,
PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2
};
enum MONITOR_DPI_TYPE
{
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
};
using GetDpiForMonitorProc = HRESULT (WINAPI*) (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
using SetProcessDpiAwarenessProc = HRESULT (WINAPI*) (PROCESS_DPI_AWARENESS);
ShcoreLibrary () : DynamicLibrary ("Shcore.dll")
{
getDpiForMonitorProc = getProcAddress<GetDpiForMonitorProc> ("GetDpiForMonitor");
setProcessDpiAwarenessProc =
getProcAddress<SetProcessDpiAwarenessProc> ("SetProcessDpiAwareness");
}
GetDpiForMonitorProc getDpiForMonitorProc {nullptr};
SetProcessDpiAwarenessProc setProcessDpiAwarenessProc {nullptr};
};
#ifndef DPI_ENUMS_DECLARED
#define DPI_ENUMS_DECLARED
#endif // (DPI_ENUMS_DECLARED)
//------------------------------------------------------------------------
} // EditorHost
} // Vst
} // Steinberg
@@ -0,0 +1,24 @@
//-----------------------------------------------------------------------------
// Flags : clang-format auto
// Project : VST SDK
//
// Category : EditorHost
// Filename : public.sdk/samples/vst-hosting/editorhost/source/usediids.cpp
// Created by : Steinberg 09.2008
// Description : Interface symbols file
//
//-----------------------------------------------------------------------------
// 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