@soundor/vst3-host 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +194 -0
- package/CMakePresets.json +115 -0
- package/LICENSE +21 -0
- package/NOTICE.md +17 -0
- package/README.md +40 -2
- package/cmake/Vst3HostDependencies.cmake +179 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +88 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +586 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/prepare-ayjTVhfk.mjs +1149 -0
- package/dist/prepare-ayjTVhfk.mjs.map +1 -0
- package/native/src/Bundle.cpp +253 -0
- package/native/src/Bundle.h +32 -0
- package/native/src/EditorArea.h +92 -0
- package/native/src/EditorAreaMac.mm +52 -0
- package/native/src/EditorAreaWindows.cpp +66 -0
- package/native/src/EditorAreaX11.cpp +512 -0
- package/native/src/EditorWindow.h +72 -0
- package/native/src/EditorWindowMac.mm +134 -0
- package/native/src/EditorWindowWindows.cpp +148 -0
- package/native/src/EventLoop.h +50 -0
- package/native/src/EventLoopLinux.cpp +204 -0
- package/native/src/EventLoopMac.mm +146 -0
- package/native/src/EventLoopWindows.cpp +132 -0
- package/native/src/HostContext.cpp +217 -0
- package/native/src/HostContext.h +116 -0
- package/native/src/HostInterface.cpp +1026 -0
- package/native/src/HostInterface.h +138 -0
- package/native/src/HostWindow.cpp +901 -0
- package/native/src/HostWindow.h +187 -0
- package/native/src/Ipc.cpp +158 -0
- package/native/src/Ipc.h +91 -0
- package/native/src/Json.cpp +426 -0
- package/native/src/Json.h +85 -0
- package/native/src/LiveAudio.cpp +416 -0
- package/native/src/LiveAudio.h +121 -0
- package/native/src/Main.cpp +178 -0
- package/native/src/Paths.h +20 -0
- package/native/src/Plugin.cpp +1865 -0
- package/native/src/Plugin.h +321 -0
- package/native/src/Session.cpp +656 -0
- package/native/src/Session.h +82 -0
- package/native/src/Settings.cpp +93 -0
- package/native/src/Settings.h +23 -0
- package/native/src/Version.h.in +11 -0
- package/native/src/X11Library.cpp +127 -0
- package/native/src/X11Library.h +83 -0
- package/native/tools/Embed.cpp +49 -0
- package/package.json +58 -3
- package/protocol.json +7 -0
- package/targets.json +33 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// The native window an editor (IPlugView) is attached to: an X11 window on
|
|
2
|
+
// Linux, an NSWindow on macOS and an HWND on Windows. Sizes are in the
|
|
3
|
+
// view's coordinates: physical pixels on Linux and Windows, points on macOS
|
|
4
|
+
// (IPlugView's convention).
|
|
5
|
+
#pragma once
|
|
6
|
+
|
|
7
|
+
#include "EventLoop.h"
|
|
8
|
+
|
|
9
|
+
#include <cstdint>
|
|
10
|
+
#include <functional>
|
|
11
|
+
#include <memory>
|
|
12
|
+
#include <optional>
|
|
13
|
+
#include <string>
|
|
14
|
+
#include <utility>
|
|
15
|
+
|
|
16
|
+
namespace soundor::vst3host
|
|
17
|
+
{
|
|
18
|
+
class EditorWindow
|
|
19
|
+
{
|
|
20
|
+
public:
|
|
21
|
+
struct Callbacks
|
|
22
|
+
{
|
|
23
|
+
/** The user resized the window; the editor gets the new size. */
|
|
24
|
+
std::function<void(int width, int height)> resized;
|
|
25
|
+
/** The user closed the window. */
|
|
26
|
+
std::function<void()> closed;
|
|
27
|
+
std::function<void(bool focused)> focusChanged;
|
|
28
|
+
/** Windows and Linux: the monitor's scale changed. */
|
|
29
|
+
std::function<void(double scale)> scaleChanged;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
virtual ~EditorWindow() = default;
|
|
33
|
+
|
|
34
|
+
/** A window that is the editor alone (the `editor` presentation) on
|
|
35
|
+
macOS and Windows; nullptr and `error` when none can be made. The
|
|
36
|
+
host interface's window, and Linux's in either presentation, is a
|
|
37
|
+
HostWindow. */
|
|
38
|
+
static std::unique_ptr<EditorWindow> create(EventLoop& loop, const std::string& title, int width, int height,
|
|
39
|
+
Callbacks callbacks, std::string& error);
|
|
40
|
+
|
|
41
|
+
/** The IPlugView platform type and the parent handle to attach to. */
|
|
42
|
+
[[nodiscard]] virtual const char* platformType() const = 0;
|
|
43
|
+
[[nodiscard]] virtual void* parentHandle() const = 0;
|
|
44
|
+
|
|
45
|
+
virtual void setResizable(bool resizable) = 0;
|
|
46
|
+
/** Resizes the editor's area to the editor's size, without calling
|
|
47
|
+
`resized`. */
|
|
48
|
+
virtual void setContentSize(int width, int height) = 0;
|
|
49
|
+
/** The smallest size the editor keeps to: the window need not be
|
|
50
|
+
smaller than what that takes. */
|
|
51
|
+
virtual void setEditorMinimum(int width, int height) { (void)width, (void)height; }
|
|
52
|
+
/** The largest editor there is: a window that is the editor need not
|
|
53
|
+
be larger. */
|
|
54
|
+
virtual void setEditorMaximum(int width, int height) { (void)width, (void)height; }
|
|
55
|
+
/** The largest editor the window has room for, where it is not to
|
|
56
|
+
grow for one (the host presentation, as in the web host): at least
|
|
57
|
+
the editor's size. */
|
|
58
|
+
[[nodiscard]] virtual std::optional<std::pair<int, int>> editorRoom() const { return std::nullopt; }
|
|
59
|
+
/** The editor's limits corrected a size from `resized`. */
|
|
60
|
+
virtual void constrainContentSize(int width, int height) { setContentSize(width, height); }
|
|
61
|
+
virtual void show() = 0;
|
|
62
|
+
/** The platform's scale for this window (1 on macOS, whose views are
|
|
63
|
+
in points). */
|
|
64
|
+
[[nodiscard]] virtual double scale() const = 0;
|
|
65
|
+
/** "editor" (the window is the editor) or "host" (with the host interface). */
|
|
66
|
+
[[nodiscard]] virtual const char* presentation() const { return "editor"; }
|
|
67
|
+
/** The window's content size, in the editor's coordinates. */
|
|
68
|
+
[[nodiscard]] virtual std::pair<int, int> windowSize() const = 0;
|
|
69
|
+
/** The host interface's frames presented so far; none without it. */
|
|
70
|
+
[[nodiscard]] virtual std::optional<uint64_t> interfaceFrames() const { return std::nullopt; }
|
|
71
|
+
};
|
|
72
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// An NSWindow whose content view hosts the editor. Notifications arrive
|
|
2
|
+
// through block observers: the host registers no Objective-C classes.
|
|
3
|
+
#include "EditorWindow.h"
|
|
4
|
+
|
|
5
|
+
#import <Cocoa/Cocoa.h>
|
|
6
|
+
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
namespace soundor::vst3host
|
|
10
|
+
{
|
|
11
|
+
namespace
|
|
12
|
+
{
|
|
13
|
+
class MacEditorWindow final : public EditorWindow
|
|
14
|
+
{
|
|
15
|
+
public:
|
|
16
|
+
MacEditorWindow(EventLoop& loop, const std::string& title, int width, int height, Callbacks callbacks)
|
|
17
|
+
: loop_(loop), callbacks_(std::move(callbacks))
|
|
18
|
+
{
|
|
19
|
+
const NSRect frame = NSMakeRect(0, 0, width, height);
|
|
20
|
+
window_ = [[NSWindow alloc]
|
|
21
|
+
initWithContentRect:frame
|
|
22
|
+
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable
|
|
23
|
+
backing:NSBackingStoreBuffered
|
|
24
|
+
defer:NO];
|
|
25
|
+
[window_ setReleasedWhenClosed:NO];
|
|
26
|
+
// It shows the editor at the editor's size: not maximized or
|
|
27
|
+
// minimized.
|
|
28
|
+
[window_ setCollectionBehavior:NSWindowCollectionBehaviorFullScreenNone];
|
|
29
|
+
[[window_ standardWindowButton:NSWindowZoomButton] setEnabled:NO];
|
|
30
|
+
[window_ setTitle:[NSString stringWithUTF8String:title.c_str()]];
|
|
31
|
+
content_ = [[NSView alloc] initWithFrame:frame];
|
|
32
|
+
[window_ setContentView:content_];
|
|
33
|
+
[window_ center];
|
|
34
|
+
|
|
35
|
+
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
|
36
|
+
auto observe = [&](NSNotificationName name, void (^block)(NSNotification*)) {
|
|
37
|
+
observers_.push_back([[center addObserverForName:name object:window_ queue:nil
|
|
38
|
+
usingBlock:block] retain]);
|
|
39
|
+
};
|
|
40
|
+
observe(NSWindowDidResizeNotification, ^(NSNotification*) {
|
|
41
|
+
if (ignoreResize_ || ! callbacks_.resized)
|
|
42
|
+
return;
|
|
43
|
+
const NSSize size = [content_ frame].size;
|
|
44
|
+
callbacks_.resized(static_cast<int>(size.width), static_cast<int>(size.height));
|
|
45
|
+
});
|
|
46
|
+
observe(NSWindowDidBecomeKeyNotification, ^(NSNotification*) {
|
|
47
|
+
if (callbacks_.focusChanged)
|
|
48
|
+
callbacks_.focusChanged(true);
|
|
49
|
+
});
|
|
50
|
+
observe(NSWindowDidResignKeyNotification, ^(NSNotification*) {
|
|
51
|
+
if (callbacks_.focusChanged)
|
|
52
|
+
callbacks_.focusChanged(false);
|
|
53
|
+
});
|
|
54
|
+
observe(NSWindowWillCloseNotification, ^(NSNotification*) {
|
|
55
|
+
if (closing_ || ! callbacks_.closed)
|
|
56
|
+
return;
|
|
57
|
+
// The callback destroys this window.
|
|
58
|
+
loop_.post(callbacks_.closed);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
~MacEditorWindow() override
|
|
63
|
+
{
|
|
64
|
+
closing_ = true;
|
|
65
|
+
for (id observer : observers_)
|
|
66
|
+
{
|
|
67
|
+
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
|
68
|
+
[observer release];
|
|
69
|
+
}
|
|
70
|
+
[window_ orderOut:nil];
|
|
71
|
+
[window_ close];
|
|
72
|
+
[content_ release];
|
|
73
|
+
[window_ release];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
[[nodiscard]] const char* platformType() const override { return "NSView"; }
|
|
77
|
+
[[nodiscard]] void* parentHandle() const override { return static_cast<void*>(content_); }
|
|
78
|
+
|
|
79
|
+
void setResizable(bool resizable) override
|
|
80
|
+
{
|
|
81
|
+
NSWindowStyleMask mask = [window_ styleMask];
|
|
82
|
+
mask = resizable ? (mask | NSWindowStyleMaskResizable) : (mask & ~NSWindowStyleMaskResizable);
|
|
83
|
+
[window_ setStyleMask:mask];
|
|
84
|
+
[[window_ standardWindowButton:NSWindowZoomButton] setEnabled:NO];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
void setContentSize(int width, int height) override
|
|
88
|
+
{
|
|
89
|
+
if (windowSize() == std::pair(width, height))
|
|
90
|
+
return;
|
|
91
|
+
ignoreResize_ = true;
|
|
92
|
+
[window_ setContentSize:NSMakeSize(width, height)];
|
|
93
|
+
ignoreResize_ = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void show() override
|
|
97
|
+
{
|
|
98
|
+
[window_ makeKeyAndOrderFront:nil];
|
|
99
|
+
#pragma clang diagnostic push
|
|
100
|
+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
101
|
+
// Deprecated in macOS 14 for -activate, which 12 and 13 lack.
|
|
102
|
+
[NSApp activateIgnoringOtherApps:YES];
|
|
103
|
+
#pragma clang diagnostic pop
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
[[nodiscard]] double scale() const override { return 1.0; }
|
|
107
|
+
[[nodiscard]] std::pair<int, int> windowSize() const override
|
|
108
|
+
{
|
|
109
|
+
const NSSize size = [content_ frame].size;
|
|
110
|
+
return { static_cast<int>(size.width), static_cast<int>(size.height) };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private:
|
|
114
|
+
EventLoop& loop_;
|
|
115
|
+
Callbacks callbacks_;
|
|
116
|
+
NSWindow* window_ = nil;
|
|
117
|
+
NSView* content_ = nil;
|
|
118
|
+
std::vector<id> observers_;
|
|
119
|
+
bool ignoreResize_ = false;
|
|
120
|
+
bool closing_ = false;
|
|
121
|
+
};
|
|
122
|
+
} // namespace
|
|
123
|
+
|
|
124
|
+
std::unique_ptr<EditorWindow> EditorWindow::create(EventLoop& loop, const std::string& title, int width, int height,
|
|
125
|
+
Callbacks callbacks, std::string& error)
|
|
126
|
+
{
|
|
127
|
+
if (NSApp == nil)
|
|
128
|
+
{
|
|
129
|
+
error = "the editor needs UI mode";
|
|
130
|
+
return nullptr;
|
|
131
|
+
}
|
|
132
|
+
return std::make_unique<MacEditorWindow>(loop, title, width, height, std::move(callbacks));
|
|
133
|
+
}
|
|
134
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#include "EditorWindow.h"
|
|
2
|
+
|
|
3
|
+
#ifndef NOMINMAX
|
|
4
|
+
#define NOMINMAX
|
|
5
|
+
#endif
|
|
6
|
+
#include <windows.h>
|
|
7
|
+
|
|
8
|
+
#include <algorithm>
|
|
9
|
+
#include <string>
|
|
10
|
+
|
|
11
|
+
namespace soundor::vst3host
|
|
12
|
+
{
|
|
13
|
+
namespace
|
|
14
|
+
{
|
|
15
|
+
std::wstring widen(const std::string& text)
|
|
16
|
+
{
|
|
17
|
+
const int size = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, nullptr, 0);
|
|
18
|
+
std::wstring result(static_cast<size_t>(std::max(size, 1)), L'\0');
|
|
19
|
+
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, result.data(), size);
|
|
20
|
+
result.resize(static_cast<size_t>(std::max(size - 1, 0)));
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// It shows the editor at the editor's size: not maximized or minimized.
|
|
25
|
+
constexpr DWORD resizableStyle = (WS_OVERLAPPEDWINDOW & ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX)) | WS_CLIPCHILDREN;
|
|
26
|
+
constexpr DWORD fixedStyle = resizableStyle & ~WS_THICKFRAME;
|
|
27
|
+
|
|
28
|
+
class WindowsEditorWindow final : public EditorWindow
|
|
29
|
+
{
|
|
30
|
+
public:
|
|
31
|
+
WindowsEditorWindow(EventLoop& loop, const std::string& title, int width, int height, Callbacks callbacks)
|
|
32
|
+
: loop_(loop), callbacks_(std::move(callbacks))
|
|
33
|
+
{
|
|
34
|
+
static const ATOM windowClass = []
|
|
35
|
+
{
|
|
36
|
+
WNDCLASSEXW description {};
|
|
37
|
+
description.cbSize = sizeof description;
|
|
38
|
+
description.style = CS_HREDRAW | CS_VREDRAW;
|
|
39
|
+
description.lpfnWndProc = &WindowsEditorWindow::windowProc;
|
|
40
|
+
description.hInstance = GetModuleHandleW(nullptr);
|
|
41
|
+
description.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
|
42
|
+
description.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
|
|
43
|
+
description.lpszClassName = L"SoundorVst3HostEditor";
|
|
44
|
+
return RegisterClassExW(&description);
|
|
45
|
+
}();
|
|
46
|
+
window_ =
|
|
47
|
+
CreateWindowExW(0, MAKEINTATOM(windowClass), widen(title).c_str(), resizableStyle, CW_USEDEFAULT,
|
|
48
|
+
CW_USEDEFAULT, width, height, nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
|
|
49
|
+
SetWindowLongPtrW(window_, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
|
|
50
|
+
setContentSize(width, height);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
~WindowsEditorWindow() override
|
|
54
|
+
{
|
|
55
|
+
SetWindowLongPtrW(window_, GWLP_USERDATA, 0);
|
|
56
|
+
DestroyWindow(window_);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
[[nodiscard]] const char* platformType() const override { return "HWND"; }
|
|
60
|
+
[[nodiscard]] void* parentHandle() const override { return window_; }
|
|
61
|
+
|
|
62
|
+
void setResizable(bool resizable) override
|
|
63
|
+
{
|
|
64
|
+
resizable_ = resizable;
|
|
65
|
+
SetWindowLongPtrW(window_, GWL_STYLE,
|
|
66
|
+
static_cast<LONG_PTR>(resizable ? resizableStyle : fixedStyle)
|
|
67
|
+
| (IsWindowVisible(window_) ? WS_VISIBLE : 0));
|
|
68
|
+
RECT client {};
|
|
69
|
+
GetClientRect(window_, &client);
|
|
70
|
+
setContentSize(client.right - client.left, client.bottom - client.top);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
void setContentSize(int width, int height) override
|
|
74
|
+
{
|
|
75
|
+
RECT rect { 0, 0, width, height };
|
|
76
|
+
const DWORD style = resizable_ ? resizableStyle : fixedStyle;
|
|
77
|
+
AdjustWindowRectExForDpi(&rect, style, FALSE, 0, GetDpiForWindow(window_));
|
|
78
|
+
ignoreResize_ = true;
|
|
79
|
+
SetWindowPos(window_, nullptr, 0, 0, rect.right - rect.left, rect.bottom - rect.top,
|
|
80
|
+
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
|
|
81
|
+
ignoreResize_ = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
void show() override
|
|
85
|
+
{
|
|
86
|
+
ShowWindow(window_, SW_SHOWNORMAL);
|
|
87
|
+
SetForegroundWindow(window_);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
[[nodiscard]] double scale() const override { return static_cast<double>(GetDpiForWindow(window_)) / 96.0; }
|
|
91
|
+
[[nodiscard]] std::pair<int, int> windowSize() const override
|
|
92
|
+
{
|
|
93
|
+
RECT client {};
|
|
94
|
+
GetClientRect(window_, &client);
|
|
95
|
+
return { static_cast<int>(client.right - client.left), static_cast<int>(client.bottom - client.top) };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private:
|
|
99
|
+
static LRESULT CALLBACK windowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
|
|
100
|
+
{
|
|
101
|
+
auto* self = reinterpret_cast<WindowsEditorWindow*>(GetWindowLongPtrW(window, GWLP_USERDATA));
|
|
102
|
+
if (self == nullptr)
|
|
103
|
+
return DefWindowProcW(window, message, wParam, lParam);
|
|
104
|
+
switch (message)
|
|
105
|
+
{
|
|
106
|
+
case WM_SIZE:
|
|
107
|
+
if (! self->ignoreResize_ && wParam != SIZE_MINIMIZED && self->callbacks_.resized)
|
|
108
|
+
self->callbacks_.resized(LOWORD(lParam), HIWORD(lParam));
|
|
109
|
+
return 0;
|
|
110
|
+
case WM_ACTIVATE:
|
|
111
|
+
if (self->callbacks_.focusChanged)
|
|
112
|
+
self->callbacks_.focusChanged(LOWORD(wParam) != WA_INACTIVE);
|
|
113
|
+
break;
|
|
114
|
+
case WM_DPICHANGED:
|
|
115
|
+
{
|
|
116
|
+
const auto* suggested = reinterpret_cast<const RECT*>(lParam);
|
|
117
|
+
SetWindowPos(window, nullptr, suggested->left, suggested->top,
|
|
118
|
+
suggested->right - suggested->left, suggested->bottom - suggested->top,
|
|
119
|
+
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
120
|
+
if (self->callbacks_.scaleChanged)
|
|
121
|
+
self->callbacks_.scaleChanged(static_cast<double>(HIWORD(wParam)) / 96.0);
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
case WM_CLOSE:
|
|
125
|
+
// The callback destroys this window.
|
|
126
|
+
if (self->callbacks_.closed)
|
|
127
|
+
self->loop_.post(self->callbacks_.closed);
|
|
128
|
+
return 0;
|
|
129
|
+
default:
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
return DefWindowProcW(window, message, wParam, lParam);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
EventLoop& loop_;
|
|
136
|
+
Callbacks callbacks_;
|
|
137
|
+
HWND window_ = nullptr;
|
|
138
|
+
bool resizable_ = true;
|
|
139
|
+
bool ignoreResize_ = false;
|
|
140
|
+
};
|
|
141
|
+
} // namespace
|
|
142
|
+
|
|
143
|
+
std::unique_ptr<EditorWindow> EditorWindow::create(EventLoop& loop, const std::string& title, int width, int height,
|
|
144
|
+
Callbacks callbacks, std::string& /*error*/)
|
|
145
|
+
{
|
|
146
|
+
return std::make_unique<WindowsEditorWindow>(loop, title, width, height, std::move(callbacks));
|
|
147
|
+
}
|
|
148
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// The main thread's loop. Every SDK call except process() runs on it, as
|
|
2
|
+
// VST3 requires of the "UI thread"; other threads hand work to it with post().
|
|
3
|
+
//
|
|
4
|
+
// Headless mode creates no window and initializes no video backend, but still
|
|
5
|
+
// runs this loop: plugins post work to their main thread (dispatch's main
|
|
6
|
+
// queue on macOS, window messages on Windows, IRunLoop handlers on Linux).
|
|
7
|
+
#pragma once
|
|
8
|
+
|
|
9
|
+
#include <functional>
|
|
10
|
+
#include <memory>
|
|
11
|
+
|
|
12
|
+
namespace soundor::vst3host
|
|
13
|
+
{
|
|
14
|
+
class EventLoop
|
|
15
|
+
{
|
|
16
|
+
public:
|
|
17
|
+
virtual ~EventLoop() = default;
|
|
18
|
+
|
|
19
|
+
/** `ui`: the platform's application object is set up for windows
|
|
20
|
+
(NSApplication on macOS); otherwise nothing visual is touched. */
|
|
21
|
+
static std::unique_ptr<EventLoop> create(bool ui);
|
|
22
|
+
|
|
23
|
+
/** Runs until quit(). */
|
|
24
|
+
virtual void run() = 0;
|
|
25
|
+
/** Main thread only. */
|
|
26
|
+
virtual void quit() = 0;
|
|
27
|
+
/** Any thread. Tasks run on the main thread, in order. */
|
|
28
|
+
virtual void post(std::function<void()> task) = 0;
|
|
29
|
+
|
|
30
|
+
using Id = unsigned long;
|
|
31
|
+
/** A repeating timer on the main thread. Main thread only. */
|
|
32
|
+
virtual Id addTimer(unsigned milliseconds, std::function<void()> fire) = 0;
|
|
33
|
+
virtual void removeTimer(Id id) = 0;
|
|
34
|
+
|
|
35
|
+
#if defined(__APPLE__)
|
|
36
|
+
/** In UI mode, waits for and dispatches Cocoa's events instead of the
|
|
37
|
+
loop's own wait (the host interface's SDL window reads them
|
|
38
|
+
itself); nullptr goes back to the loop's. */
|
|
39
|
+
virtual void setWaiter(std::function<void(double seconds)> wait) = 0;
|
|
40
|
+
#endif
|
|
41
|
+
|
|
42
|
+
#if defined(__linux__)
|
|
43
|
+
// With the timers, Linux::IRunLoop's primitives (HostContext.cpp),
|
|
44
|
+
// also used by the X11 windows for their display connections. Main
|
|
45
|
+
// thread only.
|
|
46
|
+
virtual Id addFd(int fd, std::function<void()> readable) = 0;
|
|
47
|
+
virtual void removeFd(Id id) = 0;
|
|
48
|
+
#endif
|
|
49
|
+
};
|
|
50
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#include "EventLoop.h"
|
|
2
|
+
|
|
3
|
+
#include <fcntl.h>
|
|
4
|
+
#include <poll.h>
|
|
5
|
+
#include <unistd.h>
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <cerrno>
|
|
9
|
+
#include <chrono>
|
|
10
|
+
#include <deque>
|
|
11
|
+
#include <mutex>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
namespace soundor::vst3host
|
|
15
|
+
{
|
|
16
|
+
namespace
|
|
17
|
+
{
|
|
18
|
+
class LinuxEventLoop final : public EventLoop
|
|
19
|
+
{
|
|
20
|
+
public:
|
|
21
|
+
LinuxEventLoop()
|
|
22
|
+
{
|
|
23
|
+
if (pipe2(wake_, O_CLOEXEC | O_NONBLOCK) != 0)
|
|
24
|
+
wake_[0] = wake_[1] = -1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
~LinuxEventLoop() override
|
|
28
|
+
{
|
|
29
|
+
for (const int fd : wake_)
|
|
30
|
+
if (fd >= 0)
|
|
31
|
+
close(fd);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void run() override
|
|
35
|
+
{
|
|
36
|
+
running_ = true;
|
|
37
|
+
while (running_)
|
|
38
|
+
{
|
|
39
|
+
runTasks();
|
|
40
|
+
if (! running_)
|
|
41
|
+
break;
|
|
42
|
+
|
|
43
|
+
std::vector<pollfd> fds;
|
|
44
|
+
std::vector<Id> ids;
|
|
45
|
+
fds.push_back({ wake_[0], POLLIN, 0 });
|
|
46
|
+
ids.push_back(0);
|
|
47
|
+
for (const auto& handler : fds_)
|
|
48
|
+
{
|
|
49
|
+
fds.push_back({ handler.fd, POLLIN, 0 });
|
|
50
|
+
ids.push_back(handler.id);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const int ready = poll(fds.data(), fds.size(), timeoutMs());
|
|
54
|
+
if (ready < 0 && errno != EINTR)
|
|
55
|
+
break;
|
|
56
|
+
if (ready > 0)
|
|
57
|
+
{
|
|
58
|
+
if ((fds[0].revents & POLLIN) != 0)
|
|
59
|
+
{
|
|
60
|
+
char buffer[64];
|
|
61
|
+
while (read(wake_[0], buffer, sizeof buffer) > 0)
|
|
62
|
+
{
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (size_t i = 1; i < fds.size(); ++i)
|
|
66
|
+
if ((fds[i].revents & (POLLIN | POLLHUP | POLLERR)) != 0)
|
|
67
|
+
fireFd(ids[i]);
|
|
68
|
+
}
|
|
69
|
+
fireTimers();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
void quit() override
|
|
74
|
+
{
|
|
75
|
+
running_ = false;
|
|
76
|
+
wake();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
void post(std::function<void()> task) override
|
|
80
|
+
{
|
|
81
|
+
{
|
|
82
|
+
const std::scoped_lock lock(mutex_);
|
|
83
|
+
tasks_.push_back(std::move(task));
|
|
84
|
+
}
|
|
85
|
+
wake();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Id addFd(int fd, std::function<void()> readable) override
|
|
89
|
+
{
|
|
90
|
+
const Id id = ++nextId_;
|
|
91
|
+
fds_.push_back({ id, fd, std::move(readable) });
|
|
92
|
+
return id;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
void removeFd(Id id) override
|
|
96
|
+
{
|
|
97
|
+
std::erase_if(fds_, [id](const FdHandler& handler) { return handler.id == id; });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
Id addTimer(unsigned milliseconds, std::function<void()> fire) override
|
|
101
|
+
{
|
|
102
|
+
const Id id = ++nextId_;
|
|
103
|
+
const auto interval = std::chrono::milliseconds(std::max(1U, milliseconds));
|
|
104
|
+
timers_.push_back({ id, interval, Clock::now() + interval, std::move(fire) });
|
|
105
|
+
return id;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void removeTimer(Id id) override
|
|
109
|
+
{
|
|
110
|
+
std::erase_if(timers_, [id](const Timer& timer) { return timer.id == id; });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private:
|
|
114
|
+
using Clock = std::chrono::steady_clock;
|
|
115
|
+
struct FdHandler
|
|
116
|
+
{
|
|
117
|
+
Id id;
|
|
118
|
+
int fd;
|
|
119
|
+
std::function<void()> readable;
|
|
120
|
+
};
|
|
121
|
+
struct Timer
|
|
122
|
+
{
|
|
123
|
+
Id id;
|
|
124
|
+
Clock::duration interval;
|
|
125
|
+
Clock::time_point due;
|
|
126
|
+
std::function<void()> fire;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
void wake()
|
|
130
|
+
{
|
|
131
|
+
if (wake_[1] >= 0)
|
|
132
|
+
{
|
|
133
|
+
const char byte = 1;
|
|
134
|
+
[[maybe_unused]] const auto written = write(wake_[1], &byte, 1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
void runTasks()
|
|
139
|
+
{
|
|
140
|
+
std::deque<std::function<void()>> tasks;
|
|
141
|
+
{
|
|
142
|
+
const std::scoped_lock lock(mutex_);
|
|
143
|
+
tasks.swap(tasks_);
|
|
144
|
+
}
|
|
145
|
+
for (auto& task : tasks)
|
|
146
|
+
task();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
[[nodiscard]] int timeoutMs() const
|
|
150
|
+
{
|
|
151
|
+
if (timers_.empty())
|
|
152
|
+
return -1;
|
|
153
|
+
const auto now = Clock::now();
|
|
154
|
+
auto next = timers_.front().due;
|
|
155
|
+
for (const auto& timer : timers_)
|
|
156
|
+
next = std::min(next, timer.due);
|
|
157
|
+
if (next <= now)
|
|
158
|
+
return 0;
|
|
159
|
+
return static_cast<int>(std::chrono::ceil<std::chrono::milliseconds>(next - now).count());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Handlers may add or remove handlers; each is looked up again.
|
|
163
|
+
void fireFd(Id id)
|
|
164
|
+
{
|
|
165
|
+
const auto it = std::ranges::find(fds_, id, &FdHandler::id);
|
|
166
|
+
if (it == fds_.end())
|
|
167
|
+
return;
|
|
168
|
+
const auto callback = it->readable;
|
|
169
|
+
callback();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
void fireTimers()
|
|
173
|
+
{
|
|
174
|
+
const auto now = Clock::now();
|
|
175
|
+
std::vector<Id> due;
|
|
176
|
+
for (const auto& timer : timers_)
|
|
177
|
+
if (timer.due <= now)
|
|
178
|
+
due.push_back(timer.id);
|
|
179
|
+
for (const Id id : due)
|
|
180
|
+
{
|
|
181
|
+
const auto it = std::ranges::find(timers_, id, &Timer::id);
|
|
182
|
+
if (it == timers_.end())
|
|
183
|
+
continue;
|
|
184
|
+
it->due = now + it->interval;
|
|
185
|
+
const auto callback = it->fire;
|
|
186
|
+
callback();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
int wake_[2] = { -1, -1 };
|
|
191
|
+
bool running_ = false;
|
|
192
|
+
std::mutex mutex_;
|
|
193
|
+
std::deque<std::function<void()>> tasks_;
|
|
194
|
+
std::vector<FdHandler> fds_;
|
|
195
|
+
std::vector<Timer> timers_;
|
|
196
|
+
Id nextId_ = 0;
|
|
197
|
+
};
|
|
198
|
+
} // namespace
|
|
199
|
+
|
|
200
|
+
std::unique_ptr<EventLoop> EventLoop::create(bool /*ui*/)
|
|
201
|
+
{
|
|
202
|
+
return std::make_unique<LinuxEventLoop>();
|
|
203
|
+
}
|
|
204
|
+
} // namespace soundor::vst3host
|