Initial release
This commit is contained in:
+159
@@ -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)**
|
||||
+60
@@ -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>
|
||||
+426
@@ -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
|
||||
+61
@@ -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
|
||||
+40
@@ -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
|
||||
Vendored
+44
@@ -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
|
||||
Vendored
+52
@@ -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[]);
|
||||
+120
@@ -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
|
||||
engine/vendor/vst3sdk/public.sdk/samples/vst-hosting/editorhost/source/platform/linux/irunloopimpl.h
Vendored
+135
@@ -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
|
||||
Vendored
+205
@@ -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
|
||||
Vendored
+280
@@ -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
|
||||
Vendored
+110
@@ -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
|
||||
+493
@@ -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, ®istry_listener, &globals);
|
||||
|
||||
// Roundtrip to call the listener's callbacks.
|
||||
wl_display_roundtrip (display);
|
||||
|
||||
addXdgWmBaseListener (globals);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// WaylandClientContext::Impl
|
||||
//------------------------------------------------------------------------
|
||||
struct WaylandClientContext::Impl
|
||||
{
|
||||
wl_display* display = nullptr;
|
||||
WaylandGlobals globals;
|
||||
WaylandOutputs outputs;
|
||||
ContextListeners contextListeners;
|
||||
uint32_t seatCapabilities = 0;
|
||||
StringType seatName;
|
||||
|
||||
void setSeatCapabilities (int capabilities);
|
||||
void notifyListeners (IContextListener::ChangeType changeType);
|
||||
|
||||
void addWaylandOutput (uint32_t name, wl_output* out);
|
||||
void removeWaylandOutput (uint32_t name);
|
||||
void addRegistryListener ();
|
||||
void addSeatListener ();
|
||||
void addOutputsListener ();
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void WaylandClientContext::Impl::setSeatCapabilities (int capabilities)
|
||||
{
|
||||
seatCapabilities = capabilities;
|
||||
// seatCapabilities &= ~(WL_SEAT_CAPABILITY_KEYBOARD);
|
||||
notifyListeners (IContextListener::kSeatCapabilitiesChanged);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void WaylandClientContext::Impl::notifyListeners (IContextListener::ChangeType changeType)
|
||||
{
|
||||
for (auto& el : contextListeners)
|
||||
el->contextChanged (changeType);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void WaylandClientContext::Impl::addWaylandOutput (uint32_t name, wl_output* output)
|
||||
{
|
||||
outputs.push_back ({name, {output}});
|
||||
notifyListeners (IContextListener::kOutputsChanged);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void WaylandClientContext::Impl::removeWaylandOutput (uint32_t name)
|
||||
{
|
||||
auto iter = std::find_if (outputs.begin (), outputs.end (),
|
||||
[name] (const auto& el) { return el.first == name; });
|
||||
|
||||
if (iter == outputs.end ())
|
||||
return;
|
||||
|
||||
outputs.erase (iter);
|
||||
notifyListeners (IContextListener::kOutputsChanged);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void WaylandClientContext::Impl::addRegistryListener ()
|
||||
{
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global =
|
||||
[] (void* data, struct wl_registry* registry, uint32_t name, const char* interface,
|
||||
uint32_t version) {
|
||||
// We only track outputs here for now
|
||||
auto self = reinterpret_cast<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, ®istry_listener, this);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void WaylandClientContext::Impl::addSeatListener ()
|
||||
{
|
||||
static const struct wl_seat_listener listener = {
|
||||
.capabilities =
|
||||
[] (void* data, struct wl_seat* wl_seat, uint32_t capabilities) {
|
||||
auto self = reinterpret_cast<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
|
||||
+61
@@ -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
|
||||
+375
@@ -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
|
||||
+285
@@ -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
|
||||
+56
@@ -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
|
||||
+256
@@ -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
|
||||
+105
@@ -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
|
||||
+594
@@ -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
|
||||
+93
@@ -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
|
||||
Vendored
+626
@@ -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
|
||||
Vendored
+69
@@ -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
|
||||
Vendored
+222
@@ -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);
|
||||
}
|
||||
Vendored
+60
@@ -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
|
||||
Vendored
+209
@@ -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
|
||||
Vendored
+163
@@ -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;
|
||||
}
|
||||
Vendored
+298
@@ -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
|
||||
Vendored
+220
@@ -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
|
||||
+24
@@ -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
|
||||
Reference in New Issue
Block a user