@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,52 @@
|
|
|
1
|
+
#include "EditorArea.h"
|
|
2
|
+
|
|
3
|
+
#import <Cocoa/Cocoa.h>
|
|
4
|
+
#include <SDL3/SDL.h>
|
|
5
|
+
|
|
6
|
+
namespace soundor::vst3host
|
|
7
|
+
{
|
|
8
|
+
namespace
|
|
9
|
+
{
|
|
10
|
+
class MacEditorArea final : public EditorArea
|
|
11
|
+
{
|
|
12
|
+
public:
|
|
13
|
+
explicit MacEditorArea(NSView* content) : content_([content retain])
|
|
14
|
+
{
|
|
15
|
+
view_ = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
|
|
16
|
+
[content_ addSubview:view_ positioned:NSWindowAbove relativeTo:nil];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
~MacEditorArea() override
|
|
20
|
+
{
|
|
21
|
+
[view_ removeFromSuperview];
|
|
22
|
+
[view_ release];
|
|
23
|
+
[content_ release];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
[[nodiscard]] const char* platformType() const override { return "NSView"; }
|
|
27
|
+
[[nodiscard]] void* handle() const override { return static_cast<void*>(view_); }
|
|
28
|
+
|
|
29
|
+
void setFrame(int x, int y, int width, int height) override
|
|
30
|
+
{
|
|
31
|
+
const CGFloat top = [content_ isFlipped] ? y : [content_ bounds].size.height - y - height;
|
|
32
|
+
[view_ setFrame:NSMakeRect(x, top, width, height)];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private:
|
|
36
|
+
NSView* content_;
|
|
37
|
+
NSView* view_ = nil;
|
|
38
|
+
};
|
|
39
|
+
} // namespace
|
|
40
|
+
|
|
41
|
+
std::unique_ptr<EditorArea> EditorArea::create(SDL_Window* window, std::string& error)
|
|
42
|
+
{
|
|
43
|
+
auto* nsWindow = static_cast<NSWindow*>(
|
|
44
|
+
SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr));
|
|
45
|
+
if (nsWindow == nil || [nsWindow contentView] == nil)
|
|
46
|
+
{
|
|
47
|
+
error = "the host interface's window is not a Cocoa window";
|
|
48
|
+
return nullptr;
|
|
49
|
+
}
|
|
50
|
+
return std::make_unique<MacEditorArea>([nsWindow contentView]);
|
|
51
|
+
}
|
|
52
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#include "EditorArea.h"
|
|
2
|
+
|
|
3
|
+
#include <SDL3/SDL.h>
|
|
4
|
+
|
|
5
|
+
#ifndef NOMINMAX
|
|
6
|
+
#define NOMINMAX
|
|
7
|
+
#endif
|
|
8
|
+
#include <windows.h>
|
|
9
|
+
|
|
10
|
+
#include <algorithm>
|
|
11
|
+
|
|
12
|
+
namespace soundor::vst3host
|
|
13
|
+
{
|
|
14
|
+
namespace
|
|
15
|
+
{
|
|
16
|
+
class WindowsEditorArea final : public EditorArea
|
|
17
|
+
{
|
|
18
|
+
public:
|
|
19
|
+
explicit WindowsEditorArea(HWND parent)
|
|
20
|
+
{
|
|
21
|
+
static const ATOM windowClass = []
|
|
22
|
+
{
|
|
23
|
+
WNDCLASSEXW description {};
|
|
24
|
+
description.cbSize = sizeof description;
|
|
25
|
+
description.lpfnWndProc = DefWindowProcW;
|
|
26
|
+
description.hInstance = GetModuleHandleW(nullptr);
|
|
27
|
+
description.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
|
28
|
+
description.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
|
|
29
|
+
description.lpszClassName = L"SoundorVst3HostEditorArea";
|
|
30
|
+
return RegisterClassExW(&description);
|
|
31
|
+
}();
|
|
32
|
+
// The interface must not draw over the editor.
|
|
33
|
+
SetWindowLongPtrW(parent, GWL_STYLE, GetWindowLongPtrW(parent, GWL_STYLE) | WS_CLIPCHILDREN);
|
|
34
|
+
window_ = CreateWindowExW(0, MAKEINTATOM(windowClass), L"",
|
|
35
|
+
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, 1, 1, parent,
|
|
36
|
+
nullptr, GetModuleHandleW(nullptr), nullptr);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
~WindowsEditorArea() override { DestroyWindow(window_); }
|
|
40
|
+
|
|
41
|
+
[[nodiscard]] const char* platformType() const override { return "HWND"; }
|
|
42
|
+
[[nodiscard]] void* handle() const override { return window_; }
|
|
43
|
+
|
|
44
|
+
void setFrame(int x, int y, int width, int height) override
|
|
45
|
+
{
|
|
46
|
+
SetWindowPos(window_, nullptr, x, y, std::max(width, 1), std::max(height, 1),
|
|
47
|
+
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private:
|
|
51
|
+
HWND window_ = nullptr;
|
|
52
|
+
};
|
|
53
|
+
} // namespace
|
|
54
|
+
|
|
55
|
+
std::unique_ptr<EditorArea> EditorArea::create(SDL_Window* window, std::string& error)
|
|
56
|
+
{
|
|
57
|
+
auto* parent = static_cast<HWND>(
|
|
58
|
+
SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr));
|
|
59
|
+
if (parent == nullptr)
|
|
60
|
+
{
|
|
61
|
+
error = "the host interface's window is not a Win32 window";
|
|
62
|
+
return nullptr;
|
|
63
|
+
}
|
|
64
|
+
return std::make_unique<WindowsEditorArea>(parent);
|
|
65
|
+
}
|
|
66
|
+
} // namespace soundor::vst3host
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
#include "EditorArea.h"
|
|
2
|
+
#include "X11Library.h"
|
|
3
|
+
|
|
4
|
+
#include <SDL3/SDL.h>
|
|
5
|
+
|
|
6
|
+
#include <algorithm>
|
|
7
|
+
#include <array>
|
|
8
|
+
#include <chrono>
|
|
9
|
+
#include <cstddef>
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
#include <utility>
|
|
13
|
+
|
|
14
|
+
namespace soundor::vst3host
|
|
15
|
+
{
|
|
16
|
+
namespace
|
|
17
|
+
{
|
|
18
|
+
// How long a resized editor may take to paint before it is asked for
|
|
19
|
+
// another size anyway.
|
|
20
|
+
constexpr auto paintTimeout = std::chrono::milliseconds(100);
|
|
21
|
+
// How long its last image stands in before the new one is shown as it
|
|
22
|
+
// is: an editor can be black in all its corners.
|
|
23
|
+
constexpr auto unpaintedTimeout = std::chrono::milliseconds(500);
|
|
24
|
+
// A window resized this recently may be being resized still.
|
|
25
|
+
constexpr auto windowSettling = std::chrono::milliseconds(200);
|
|
26
|
+
// The host interface's stage (HostInterface.cpp).
|
|
27
|
+
constexpr unsigned long stageColour = 0x0b0c0f;
|
|
28
|
+
|
|
29
|
+
class X11EditorArea final : public EditorArea
|
|
30
|
+
{
|
|
31
|
+
public:
|
|
32
|
+
X11EditorArea(const Xlib& x, Display* display, Window parent) : x_(x), display_(display), parent_(parent)
|
|
33
|
+
{
|
|
34
|
+
x_.setWindowBackground(display_, parent_, stageColour);
|
|
35
|
+
const int screen = x_.defaultScreen(display_);
|
|
36
|
+
const auto black = x_.blackPixel(display_, screen);
|
|
37
|
+
window_ = x_.createSimpleWindow(display_, parent, 0, 0, 1, 1, 0, black, black);
|
|
38
|
+
// Never painted by the server: what is under an editor that
|
|
39
|
+
// has not drawn yet is the host's last frame, not a black box.
|
|
40
|
+
x_.setWindowBackgroundPixmap(display_, window_, None);
|
|
41
|
+
keepTopLeft(false);
|
|
42
|
+
x_.mapWindow(display_, window_);
|
|
43
|
+
x_.flush(display_);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
~X11EditorArea() override
|
|
47
|
+
{
|
|
48
|
+
if (live_)
|
|
49
|
+
endLive();
|
|
50
|
+
if (connection_ != nullptr)
|
|
51
|
+
{
|
|
52
|
+
setQuietDisplay(nullptr);
|
|
53
|
+
x_.closeDisplay(connection_);
|
|
54
|
+
}
|
|
55
|
+
x_.destroyWindow(display_, window_);
|
|
56
|
+
x_.flush(display_);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
[[nodiscard]] const char* platformType() const override { return "X11EmbedWindowID"; }
|
|
60
|
+
[[nodiscard]] void* handle() const override
|
|
61
|
+
{
|
|
62
|
+
return reinterpret_cast<void*>(window_); // NOLINT(performance-no-int-to-ptr): an X11 id
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
void setFrame(int x, int y, int width, int height) override
|
|
66
|
+
{
|
|
67
|
+
// Still showing until a frame has drawn its image, its pixels
|
|
68
|
+
// stay where they are: where it moves to has none.
|
|
69
|
+
if (live_ && ! hidden_)
|
|
70
|
+
{
|
|
71
|
+
heldFrame_ = { x, y, width, height };
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
applyFrame(x, y, width, height);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
[[nodiscard]] bool isAt(int x, int y) const override
|
|
78
|
+
{
|
|
79
|
+
return heldFrame_.has_value() || place() == std::pair { x, y };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
void keepTopLeft(bool keep) override
|
|
83
|
+
{
|
|
84
|
+
// A resized window keeps its pixels, which are the last frame
|
|
85
|
+
// and the editor's, and what the server fills the new part with
|
|
86
|
+
// until the next frame is the stage's colour, not black. By
|
|
87
|
+
// default it would discard them all, and fill the whole window.
|
|
88
|
+
XSetWindowAttributes gravity {};
|
|
89
|
+
gravity.bit_gravity = keep ? NorthWestGravity : CenterGravity;
|
|
90
|
+
gravity.win_gravity = gravity.bit_gravity;
|
|
91
|
+
x_.changeWindowAttributes(display_, parent_, CWBitGravity, &gravity);
|
|
92
|
+
x_.changeWindowAttributes(display_, window_, CWWinGravity, &gravity);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
bool beginLive() override
|
|
96
|
+
{
|
|
97
|
+
if (live_)
|
|
98
|
+
return true;
|
|
99
|
+
if (! connect())
|
|
100
|
+
return false;
|
|
101
|
+
Window root = 0;
|
|
102
|
+
Window parent = 0;
|
|
103
|
+
Window* children = nullptr;
|
|
104
|
+
unsigned count = 0;
|
|
105
|
+
if (x_.queryTree(connection_, window_, &root, &parent, &children, &count) == 0)
|
|
106
|
+
return false;
|
|
107
|
+
editor_ = count > 0 ? children[count - 1] : 0;
|
|
108
|
+
if (children != nullptr)
|
|
109
|
+
x_.free(children);
|
|
110
|
+
if (editor_ == 0)
|
|
111
|
+
return false;
|
|
112
|
+
// The editor keeps its size and paints into a pixmap of its
|
|
113
|
+
// own, whatever the area does. Shaped to nothing, the area
|
|
114
|
+
// stops clipping the window's own drawing, and stays where
|
|
115
|
+
// it is: an editor tracking the pointer through a drag of its
|
|
116
|
+
// own keeps its coordinates.
|
|
117
|
+
live->redirectSubwindows(connection_, window_, CompositeRedirectManual);
|
|
118
|
+
damage_ = live->damageCreate(connection_, editor_, XDamageReportRawRectangles);
|
|
119
|
+
x_.sync(connection_, False);
|
|
120
|
+
// Its pixels now are what the server could copy of what showed,
|
|
121
|
+
// which may be nothing. It stays showing until it has painted
|
|
122
|
+
// into its own pixmap, which it is asked to. Not if the window
|
|
123
|
+
// is being resized, or has shrunk around it, as a window
|
|
124
|
+
// manager's restore does: the server has let go of what was on
|
|
125
|
+
// the screen, so there is nothing to keep there. Either way the
|
|
126
|
+
// view goes only once a frame has drawn its image.
|
|
127
|
+
askEditorToPaint();
|
|
128
|
+
live_ = true;
|
|
129
|
+
hidden_ = false;
|
|
130
|
+
showing_ = ! clippedByWindow() && std::chrono::steady_clock::now() - windowChangedAt_ >= windowSettling;
|
|
131
|
+
repainted_ = false;
|
|
132
|
+
beganAt_ = std::chrono::steady_clock::now();
|
|
133
|
+
firstImage_ = true;
|
|
134
|
+
dirty_ = ! showing_;
|
|
135
|
+
unpainted_ = false;
|
|
136
|
+
width_ = 0;
|
|
137
|
+
height_ = 0;
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
void endLive() override
|
|
142
|
+
{
|
|
143
|
+
if (! live_)
|
|
144
|
+
return;
|
|
145
|
+
live_ = false;
|
|
146
|
+
dropImage();
|
|
147
|
+
if (pixmap_ != 0)
|
|
148
|
+
x_.freePixmap(connection_, pixmap_);
|
|
149
|
+
pixmap_ = 0;
|
|
150
|
+
live->damageDestroy(connection_, damage_);
|
|
151
|
+
// Back first: the server hands a redirected window's pixels to
|
|
152
|
+
// its parent as it unredirects it.
|
|
153
|
+
if (hidden_)
|
|
154
|
+
{
|
|
155
|
+
keepingParentPixels(
|
|
156
|
+
[this] { live->shapeCombineMask(display_, window_, ShapeBounding, 0, 0, None, ShapeSet); });
|
|
157
|
+
}
|
|
158
|
+
hidden_ = false;
|
|
159
|
+
showing_ = false;
|
|
160
|
+
applyHeldFrame();
|
|
161
|
+
x_.sync(display_, False);
|
|
162
|
+
live->unredirectSubwindows(connection_, window_, CompositeRedirectManual);
|
|
163
|
+
x_.sync(connection_, False);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
std::optional<Image> takeImage() override
|
|
167
|
+
{
|
|
168
|
+
if (! live_)
|
|
169
|
+
return std::nullopt;
|
|
170
|
+
x_.sync(connection_, False);
|
|
171
|
+
XWindowAttributes attributes {};
|
|
172
|
+
if (x_.getWindowAttributes(connection_, editor_, &attributes) == 0
|
|
173
|
+
|| attributes.map_state != IsViewable)
|
|
174
|
+
return std::nullopt;
|
|
175
|
+
if (given_)
|
|
176
|
+
{
|
|
177
|
+
// Not there yet: its pixmap is the old size, part painted.
|
|
178
|
+
if ((attributes.width != given_->first || attributes.height != given_->second)
|
|
179
|
+
&& std::chrono::steady_clock::now() - givenAt_ < paintTimeout)
|
|
180
|
+
return std::nullopt;
|
|
181
|
+
given_.reset();
|
|
182
|
+
}
|
|
183
|
+
if (attributes.width != width_ || attributes.height != height_)
|
|
184
|
+
{
|
|
185
|
+
// Each size has its own pixmap.
|
|
186
|
+
if (pixmap_ != 0)
|
|
187
|
+
x_.freePixmap(connection_, pixmap_);
|
|
188
|
+
pixmap_ = live->nameWindowPixmap(connection_, editor_);
|
|
189
|
+
unpainted_ = width_ != 0;
|
|
190
|
+
resizedAt_ = std::chrono::steady_clock::now();
|
|
191
|
+
width_ = attributes.width;
|
|
192
|
+
height_ = attributes.height;
|
|
193
|
+
}
|
|
194
|
+
while (x_.pending(connection_) > 0)
|
|
195
|
+
{
|
|
196
|
+
XEvent event {};
|
|
197
|
+
x_.nextEvent(connection_, &event);
|
|
198
|
+
if (event.type != damageEvent_ + XDamageNotify)
|
|
199
|
+
continue;
|
|
200
|
+
dirty_ = true;
|
|
201
|
+
const auto& notify = reinterpret_cast<const XDamageNotifyEvent&>(event);
|
|
202
|
+
if (notify.area.x == 0 && notify.area.y == 0 && notify.area.width >= width_
|
|
203
|
+
&& notify.area.height >= height_)
|
|
204
|
+
repainted_ = true;
|
|
205
|
+
}
|
|
206
|
+
if (showing_)
|
|
207
|
+
{
|
|
208
|
+
// Whole, or as whole as it will get.
|
|
209
|
+
if (! repainted_ && windowChangedAt_ < beganAt_
|
|
210
|
+
&& std::chrono::steady_clock::now() - beganAt_ < paintTimeout)
|
|
211
|
+
return std::nullopt;
|
|
212
|
+
dirty_ = true;
|
|
213
|
+
firstImage_ = ! repainted_;
|
|
214
|
+
}
|
|
215
|
+
if (! dirty_)
|
|
216
|
+
return std::nullopt;
|
|
217
|
+
dirty_ = false;
|
|
218
|
+
live->damageSubtract(connection_, damage_, None, None);
|
|
219
|
+
dropImage();
|
|
220
|
+
image_ = x_.getImage(connection_, pixmap_, 0, 0, static_cast<unsigned>(width_),
|
|
221
|
+
static_cast<unsigned>(height_), AllPlanes, ZPixmap);
|
|
222
|
+
if (image_ == nullptr)
|
|
223
|
+
return std::nullopt;
|
|
224
|
+
// A pixmap's image has no masks: the window's visual has them.
|
|
225
|
+
if (image_->bits_per_pixel != 32 || attributes.visual->red_mask != 0xff0000
|
|
226
|
+
|| attributes.visual->blue_mask != 0xff)
|
|
227
|
+
return std::nullopt;
|
|
228
|
+
// A resized window's new pixmap is zeros, and the server's
|
|
229
|
+
// clearing it counts as damage: not painted while a corner or
|
|
230
|
+
// the middle is still zero. An editor changing its scale can
|
|
231
|
+
// leave zeros again in a later frame, so this goes on for a while.
|
|
232
|
+
if (! painted() && std::chrono::steady_clock::now() - resizedAt_ < unpaintedTimeout)
|
|
233
|
+
{
|
|
234
|
+
dirty_ = true;
|
|
235
|
+
return std::nullopt;
|
|
236
|
+
}
|
|
237
|
+
unpainted_ = false;
|
|
238
|
+
Image result { image_->data, width_, height_, image_->bytes_per_line };
|
|
239
|
+
showing_ = false;
|
|
240
|
+
if (std::exchange(firstImage_, false))
|
|
241
|
+
{
|
|
242
|
+
// Redirecting keeps what showed, and the window may already
|
|
243
|
+
// have shrunk: a window manager's restore starts that way.
|
|
244
|
+
result.first = true;
|
|
245
|
+
XWindowAttributes outer {};
|
|
246
|
+
if (x_.getWindowAttributes(display_, parent_, &outer) != 0)
|
|
247
|
+
{
|
|
248
|
+
const auto [x, y] = place();
|
|
249
|
+
result.validX = std::clamp(-x, 0, width_ - 1);
|
|
250
|
+
result.validY = std::clamp(-y, 0, height_ - 1);
|
|
251
|
+
result.validWidth = std::clamp(outer.width - x - result.validX, 1, width_ - result.validX);
|
|
252
|
+
result.validHeight = std::clamp(outer.height - y - result.validY, 1, height_ - result.validY);
|
|
253
|
+
result.shownX = x + result.validX;
|
|
254
|
+
result.shownY = y + result.validY;
|
|
255
|
+
result.partial = result.validWidth < width_ || result.validHeight < height_;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
void allowOnlyMoveResizeClose() override
|
|
262
|
+
{
|
|
263
|
+
// As Motif defined it: flags (functions), functions (resize,
|
|
264
|
+
// move, close), decorations, input mode, status.
|
|
265
|
+
const std::array<unsigned long, 5> hints { 1, 2 | 4 | 32, 0, 0, 0 };
|
|
266
|
+
const Atom property = x_.internAtom(display_, "_MOTIF_WM_HINTS", False);
|
|
267
|
+
x_.changeProperty(display_, parent_, property, property, 32, PropModeReplace,
|
|
268
|
+
reinterpret_cast<const unsigned char*>(hints.data()), hints.size());
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
bool hideView() override
|
|
272
|
+
{
|
|
273
|
+
if (! live_ || showing_)
|
|
274
|
+
return false;
|
|
275
|
+
if (hidden_)
|
|
276
|
+
return true;
|
|
277
|
+
// The editor keeps its coordinates: shaped to nothing, the area
|
|
278
|
+
// stays where it is.
|
|
279
|
+
hidden_ = true;
|
|
280
|
+
keepingParentPixels(
|
|
281
|
+
[this] {
|
|
282
|
+
live->shapeCombineRectangles(display_, window_, ShapeBounding, 0, 0, nullptr, 0, ShapeSet,
|
|
283
|
+
Unsorted);
|
|
284
|
+
});
|
|
285
|
+
applyHeldFrame();
|
|
286
|
+
x_.sync(display_, False);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
[[nodiscard]] bool awaitingEditor() const { return live_ && showing_; }
|
|
291
|
+
|
|
292
|
+
void windowChanged() override { windowChangedAt_ = std::chrono::steady_clock::now(); }
|
|
293
|
+
|
|
294
|
+
void editorGiven(int width, int height) override
|
|
295
|
+
{
|
|
296
|
+
if (! live_ || (width == width_ && height == height_))
|
|
297
|
+
return;
|
|
298
|
+
given_ = { width, height };
|
|
299
|
+
givenAt_ = std::chrono::steady_clock::now();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
[[nodiscard]] bool whole() const override
|
|
303
|
+
{
|
|
304
|
+
const auto now = std::chrono::steady_clock::now();
|
|
305
|
+
if (awaitingEditor() || (given_ && now - givenAt_ < unpaintedTimeout))
|
|
306
|
+
return false;
|
|
307
|
+
return ! unpainted_ || now - resizedAt_ >= unpaintedTimeout;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
[[nodiscard]] bool settled() const override
|
|
311
|
+
{
|
|
312
|
+
const auto now = std::chrono::steady_clock::now();
|
|
313
|
+
if (awaitingEditor() || (given_ && now - givenAt_ < paintTimeout))
|
|
314
|
+
return false;
|
|
315
|
+
return ! unpainted_ || now - resizedAt_ >= paintTimeout;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private:
|
|
319
|
+
/** A connection of its own: redirection ends with it, and SDL
|
|
320
|
+
does not see its events. */
|
|
321
|
+
bool connect()
|
|
322
|
+
{
|
|
323
|
+
if (connection_ != nullptr)
|
|
324
|
+
return true;
|
|
325
|
+
if (unavailable_)
|
|
326
|
+
return false;
|
|
327
|
+
unavailable_ = true;
|
|
328
|
+
live = loadXLive();
|
|
329
|
+
if (live == nullptr)
|
|
330
|
+
return false;
|
|
331
|
+
connection_ = x_.openDisplay(x_.displayString(display_));
|
|
332
|
+
if (connection_ == nullptr)
|
|
333
|
+
return false;
|
|
334
|
+
int event = 0;
|
|
335
|
+
int error = 0;
|
|
336
|
+
if (live->compositeQueryExtension(connection_, &event, &error) == 0
|
|
337
|
+
|| live->damageQueryExtension(connection_, &damageEvent_, &error) == 0
|
|
338
|
+
|| live->shapeQueryExtension(connection_, &event, &error) == 0)
|
|
339
|
+
{
|
|
340
|
+
x_.closeDisplay(connection_);
|
|
341
|
+
connection_ = nullptr;
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
setQuietDisplay(connection_);
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
void applyFrame(int x, int y, int width, int height)
|
|
349
|
+
{
|
|
350
|
+
const bool moved = ! isAt(x, y);
|
|
351
|
+
x_.moveResizeWindow(display_, window_, x, y, static_cast<unsigned>(std::max(width, 1)),
|
|
352
|
+
static_cast<unsigned>(std::max(height, 1)));
|
|
353
|
+
if (moved)
|
|
354
|
+
tellEditorItMoved();
|
|
355
|
+
x_.flush(display_);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
void applyHeldFrame()
|
|
359
|
+
{
|
|
360
|
+
if (const auto frame = std::exchange(heldFrame_, std::nullopt))
|
|
361
|
+
applyFrame((*frame)[0], (*frame)[1], (*frame)[2], (*frame)[3]);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Showing or hiding the view exposes the window under it, which
|
|
365
|
+
must not fill with its background: what is there stays until
|
|
366
|
+
the next frame. */
|
|
367
|
+
template <typename Change>
|
|
368
|
+
void keepingParentPixels(Change change)
|
|
369
|
+
{
|
|
370
|
+
x_.setWindowBackgroundPixmap(display_, parent_, None);
|
|
371
|
+
change();
|
|
372
|
+
x_.setWindowBackground(display_, parent_, stageColour);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Where the view is now, which the window's gravity decides too. */
|
|
376
|
+
[[nodiscard]] std::pair<int, int> place() const
|
|
377
|
+
{
|
|
378
|
+
XWindowAttributes attributes {};
|
|
379
|
+
x_.getWindowAttributes(display_, window_, &attributes);
|
|
380
|
+
return { attributes.x, attributes.y };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
[[nodiscard]] bool clippedByWindow() const
|
|
384
|
+
{
|
|
385
|
+
XWindowAttributes outer {};
|
|
386
|
+
XWindowAttributes editor {};
|
|
387
|
+
if (x_.getWindowAttributes(display_, parent_, &outer) == 0
|
|
388
|
+
|| x_.getWindowAttributes(display_, editor_, &editor) == 0)
|
|
389
|
+
return true;
|
|
390
|
+
const auto [x, y] = place();
|
|
391
|
+
return x < 0 || y < 0 || x + editor.width > outer.width || y + editor.height > outer.height;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
void askEditorToPaint()
|
|
395
|
+
{
|
|
396
|
+
XWindowAttributes attributes {};
|
|
397
|
+
if (x_.getWindowAttributes(display_, editor_, &attributes) == 0)
|
|
398
|
+
return;
|
|
399
|
+
XEvent event {};
|
|
400
|
+
event.xexpose.type = Expose;
|
|
401
|
+
event.xexpose.window = editor_;
|
|
402
|
+
event.xexpose.width = attributes.width;
|
|
403
|
+
event.xexpose.height = attributes.height;
|
|
404
|
+
x_.sendEvent(display_, editor_, False, ExposureMask, &event);
|
|
405
|
+
x_.flush(display_);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** The server tells a window of its own moves only, and an editor
|
|
409
|
+
keeps its place on the screen from that (a drag of its own reads
|
|
410
|
+
the pointer's screen position): as a window manager does for a
|
|
411
|
+
window it moves, a synthetic ConfigureNotify. */
|
|
412
|
+
void tellEditorItMoved()
|
|
413
|
+
{
|
|
414
|
+
Window root = 0;
|
|
415
|
+
Window parent = 0;
|
|
416
|
+
Window* children = nullptr;
|
|
417
|
+
unsigned count = 0;
|
|
418
|
+
if (x_.queryTree(display_, window_, &root, &parent, &children, &count) == 0)
|
|
419
|
+
return;
|
|
420
|
+
for (unsigned index = 0; index < count; ++index)
|
|
421
|
+
{
|
|
422
|
+
XWindowAttributes attributes {};
|
|
423
|
+
Window child = 0;
|
|
424
|
+
int rootX = 0;
|
|
425
|
+
int rootY = 0;
|
|
426
|
+
if (x_.getWindowAttributes(display_, children[index], &attributes) == 0
|
|
427
|
+
|| x_.translateCoordinates(display_, children[index], root, 0, 0, &rootX, &rootY, &child) == 0)
|
|
428
|
+
continue;
|
|
429
|
+
XEvent event {};
|
|
430
|
+
event.xconfigure.type = ConfigureNotify;
|
|
431
|
+
event.xconfigure.event = children[index];
|
|
432
|
+
event.xconfigure.window = children[index];
|
|
433
|
+
event.xconfigure.x = rootX;
|
|
434
|
+
event.xconfigure.y = rootY;
|
|
435
|
+
event.xconfigure.width = attributes.width;
|
|
436
|
+
event.xconfigure.height = attributes.height;
|
|
437
|
+
x_.sendEvent(display_, children[index], False, StructureNotifyMask, &event);
|
|
438
|
+
}
|
|
439
|
+
if (children != nullptr)
|
|
440
|
+
x_.free(children);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
[[nodiscard]] bool painted() const
|
|
444
|
+
{
|
|
445
|
+
auto zero = [this](int x, int y)
|
|
446
|
+
{
|
|
447
|
+
const char* row = image_->data + static_cast<std::ptrdiff_t>(y) * image_->bytes_per_line;
|
|
448
|
+
uint32_t pixel = 0;
|
|
449
|
+
std::memcpy(&pixel, row + static_cast<std::ptrdiff_t>(x) * 4, sizeof pixel);
|
|
450
|
+
return (pixel & 0xffffffU) == 0;
|
|
451
|
+
};
|
|
452
|
+
return ! (zero(0, 0) || zero(width_ - 1, 0) || zero(0, height_ - 1) || zero(width_ - 1, height_ - 1)
|
|
453
|
+
|| zero(width_ / 2, height_ / 2));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
void dropImage()
|
|
457
|
+
{
|
|
458
|
+
if (image_ != nullptr)
|
|
459
|
+
image_->f.destroy_image(image_);
|
|
460
|
+
image_ = nullptr;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const Xlib& x_;
|
|
464
|
+
const XLive* live = nullptr;
|
|
465
|
+
Display* display_;
|
|
466
|
+
Display* connection_ = nullptr;
|
|
467
|
+
Window parent_ = 0;
|
|
468
|
+
Window window_ = 0;
|
|
469
|
+
Window editor_ = 0;
|
|
470
|
+
Pixmap pixmap_ = 0;
|
|
471
|
+
Damage damage_ = 0;
|
|
472
|
+
XImage* image_ = nullptr;
|
|
473
|
+
int damageEvent_ = 0;
|
|
474
|
+
int width_ = 0;
|
|
475
|
+
int height_ = 0;
|
|
476
|
+
std::chrono::steady_clock::time_point resizedAt_;
|
|
477
|
+
std::optional<std::pair<int, int>> given_;
|
|
478
|
+
std::chrono::steady_clock::time_point givenAt_;
|
|
479
|
+
bool unavailable_ = false;
|
|
480
|
+
bool live_ = false;
|
|
481
|
+
bool firstImage_ = false;
|
|
482
|
+
/** Live, with the view still showing until the editor has painted. */
|
|
483
|
+
bool showing_ = false;
|
|
484
|
+
/** Shaped to nothing: the window draws its image. */
|
|
485
|
+
bool hidden_ = false;
|
|
486
|
+
std::optional<std::array<int, 4>> heldFrame_;
|
|
487
|
+
bool repainted_ = false;
|
|
488
|
+
std::chrono::steady_clock::time_point beganAt_;
|
|
489
|
+
std::chrono::steady_clock::time_point windowChangedAt_;
|
|
490
|
+
bool dirty_ = false;
|
|
491
|
+
bool unpainted_ = false;
|
|
492
|
+
};
|
|
493
|
+
} // namespace
|
|
494
|
+
|
|
495
|
+
std::unique_ptr<EditorArea> EditorArea::create(SDL_Window* window, std::string& error)
|
|
496
|
+
{
|
|
497
|
+
const Xlib* x = loadXlib(error);
|
|
498
|
+
if (x == nullptr)
|
|
499
|
+
return nullptr;
|
|
500
|
+
const SDL_PropertiesID properties = SDL_GetWindowProperties(window);
|
|
501
|
+
auto* display =
|
|
502
|
+
static_cast<Display*>(SDL_GetPointerProperty(properties, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr));
|
|
503
|
+
const auto parent =
|
|
504
|
+
static_cast<Window>(SDL_GetNumberProperty(properties, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0));
|
|
505
|
+
if (display == nullptr || parent == 0)
|
|
506
|
+
{
|
|
507
|
+
error = "the host interface's window is not an X11 window";
|
|
508
|
+
return nullptr;
|
|
509
|
+
}
|
|
510
|
+
return std::make_unique<X11EditorArea>(*x, display, parent);
|
|
511
|
+
}
|
|
512
|
+
} // namespace soundor::vst3host
|