Loading...
Searching...
No Matches
Linux/PlugFrame.hpp
1#pragma once
2#include <cstddef>
3
4#if (!(defined(__APPLE__) || defined(_WIN32))) && __has_include(<xcb/xcb.h>)
5#include <Vst3/UI/WindowContainer.hpp>
6
7#include <ossia/detail/algorithms.hpp>
8#include <ossia/detail/hash_map.hpp>
9
10#include <QDebug>
11#include <QSocketNotifier>
12#include <QTimer>
13#include <QWindow>
14
15#include <pluginterfaces/gui/iplugview.h>
16#include <xcb/xcb.h>
17
18#include <dlfcn.h>
19
20#include <memory>
21#include <utility>
22#include <vector>
23
24namespace vst3
25{
26namespace Linux = Steinberg::Linux;
27
28struct SocketPair
29{
30 SocketPair(int fd)
31 : read{fd, QSocketNotifier::Read}
32 , write{fd, QSocketNotifier::Write}
33 , error{fd, QSocketNotifier::Exception}
34 {
35 }
36 template <typename F>
37 void connect(QObject* sink, F f)
38 {
39 QObject::connect(&read, &QSocketNotifier::activated, sink, f);
40 QObject::connect(&write, &QSocketNotifier::activated, sink, f);
41 QObject::connect(&error, &QSocketNotifier::activated, sink, f);
42
43 read.setEnabled(true);
44 write.setEnabled(true);
45 error.setEnabled(true);
46 }
47 QSocketNotifier read;
48 QSocketNotifier write;
49 QSocketNotifier error;
50};
51
52struct XcbConnection
53{
54 void* xcb{};
55 xcb_connection_t* connection{};
56 int fd{-1};
57
58 std::unique_ptr<SocketPair> notifiers;
59 XcbConnection()
60 : xcb{dlopen("libxcb.so.1", RTLD_LOCAL)}
61 {
62 if(!xcb)
63 return;
64
65 auto xcb_connect
66 = reinterpret_cast<decltype(&::xcb_connect)>(dlsym(xcb, "xcb_connect"));
67 if(!xcb_connect)
68 return;
69 auto xcb_get_file_descriptor
70 = reinterpret_cast<decltype(&::xcb_get_file_descriptor)>(
71 dlsym(xcb, "xcb_get_file_descriptor"));
72 if(!xcb_get_file_descriptor)
73 return;
74
75 connection = xcb_connect(nullptr, nullptr);
76 if(!connection)
77 return;
78
79 fd = xcb_get_file_descriptor(connection);
80 if(fd < 0)
81 return;
82
83 notifiers = std::make_unique<SocketPair>(fd);
84 }
85
86 ~XcbConnection()
87 {
88 if(!xcb)
89 return;
90 if(!connection)
91 return;
92
93 auto xcb_disconnect
94 = reinterpret_cast<decltype(&::xcb_disconnect)>(dlsym(xcb, "xcb_disconnect"));
95 if(!xcb_disconnect)
96 return;
97 xcb_disconnect(connection);
98 }
99};
100
101struct SocketHandler
102{
103 Linux::IEventHandler* handler{};
104 QSocketNotifier* r_notifier{};
105 QSocketNotifier* w_notifier{};
106 QSocketNotifier* e_notifier{};
107 int fd{};
108};
109
110class GlobalSocketHandlers : public QObject
111{
112 XcbConnection xcb;
113
114public:
115 ossia::hash_map<int, std::vector<Linux::IEventHandler*>> handlers;
116 GlobalSocketHandlers()
117 {
118 if(xcb.notifiers)
119 xcb.notifiers->connect(this, [this] { on_xcb(); });
120 }
121
122 void on_xcb()
123 {
124 if(auto it = handlers.find(xcb.fd); it != handlers.end())
125 {
126 for(auto& hdl : it->second)
127 hdl->onFDIsSet(xcb.fd);
128 }
129 }
130
131 void registerHandler(Linux::IEventHandler* handler, int fd)
132 {
133 handlers[fd].push_back(handler);
134 }
135 void unregisterHandler(Linux::IEventHandler* handler, int fd)
136 {
137 if(auto it = handlers.find(fd); it != handlers.end())
138 {
139 ossia::remove_erase(it->second, handler);
140 if(it->second.empty())
141 handlers.erase(it);
142 }
143 }
144 static GlobalSocketHandlers& instance()
145 {
146 static GlobalSocketHandlers handlers;
147 return handlers;
148 }
149};
150class PlugFrame final
151 : virtual public Steinberg::IPlugFrame
152 , virtual public Steinberg::Linux::IRunLoop
153{
154public:
155 using TUID = Steinberg::TUID;
156 using FUID = Steinberg::FUID;
157 using uint32 = Steinberg::uint32;
158 using tresult = Steinberg::tresult;
159
160 QObject internalContextObject;
161
162 std::vector<std::pair<Linux::ITimerHandler*, QTimer*>> timers;
163
164 std::vector<SocketHandler> handlers;
165
166 tresult queryInterface(const TUID _iid, void** obj) override
167 {
168 using namespace Steinberg;
169 if(FUnknownPrivate::iidEqual(_iid, FUnknown::iid))
170 {
171 *obj = this;
172 return kResultOk;
173 }
174 if(FUnknownPrivate::iidEqual(_iid, IPlugFrame::iid))
175 {
176 *obj = this;
177 return kResultOk;
178 }
179 if(FUnknownPrivate::iidEqual(_iid, Linux::IRunLoop::iid))
180 {
181 *obj = static_cast<Linux::IRunLoop*>(this);
182 return kResultOk;
183 }
184 *obj = nullptr;
185 return kNoInterface;
186 }
187
188 uint32 addRef() override { return 1; }
189 uint32 release() override { return 1; }
190
191 tresult registerEventHandler(Linux::IEventHandler* handler, Linux::FileDescriptor fd)
192 SMTG_OVERRIDE
193 {
194 using namespace Steinberg;
195 if(!handler)
196 return kInvalidArgument;
197 auto existing = ossia::find_if(handlers, [=](auto p) { return p.fd == fd; });
198
199 if(existing != handlers.end())
200 return kInvalidArgument;
201
202 auto readnotifier = new QSocketNotifier{fd, QSocketNotifier::Read};
203 readnotifier->setEnabled(true);
204 readnotifier->setParent(&internalContextObject);
205 auto on_fd = [=] { handler->onFDIsSet(fd); };
206 QObject::connect(
207 readnotifier, &QSocketNotifier::activated, &internalContextObject, on_fd);
208
209 auto writenotifier = new QSocketNotifier{fd, QSocketNotifier::Write};
210 writenotifier->setEnabled(true);
211 writenotifier->setParent(&internalContextObject);
212 QObject::connect(
213 writenotifier, &QSocketNotifier::activated, &internalContextObject, on_fd);
214
215 auto errnotifier = new QSocketNotifier{fd, QSocketNotifier::Exception};
216 errnotifier->setEnabled(true);
217 errnotifier->setParent(&internalContextObject);
218 QObject::connect(
219 errnotifier, &QSocketNotifier::activated, &internalContextObject, on_fd);
220
221 handlers.push_back(
222 SocketHandler{handler, readnotifier, writenotifier, errnotifier, fd});
223 GlobalSocketHandlers::instance().registerHandler(handler, fd);
224
225 return kResultTrue;
226 }
227
228 tresult unregisterEventHandler(Linux::IEventHandler* handler) SMTG_OVERRIDE
229 {
230 using namespace Steinberg;
231 if(!handler)
232 {
233 return kInvalidArgument;
234 }
235
236 tresult res{kResultFalse};
237 for(auto it = handlers.begin(); it != handlers.end();)
238 {
239 if(it->handler == handler)
240 {
241 GlobalSocketHandlers::instance().unregisterHandler(handler, it->fd);
242 delete it->r_notifier;
243 delete it->w_notifier;
244 delete it->e_notifier;
245 it = handlers.erase(it);
246 res = kResultTrue;
247 }
248 else
249 {
250 ++it;
251 }
252 }
253
254 return res;
255 }
256
257 tresult registerTimer(
258 Linux::ITimerHandler* handler, Linux::TimerInterval milliseconds) override
259 {
260 auto t = new QTimer;
261 t->setParent(&internalContextObject);
262 QObject::connect(t, &QTimer::timeout, [=] { handler->onTimer(); });
263 t->start(milliseconds);
264 timers.push_back({handler, t});
265 return Steinberg::kResultOk;
266 }
267
268 tresult unregisterTimer(Linux::ITimerHandler* handler) override
269 {
270 auto t = ossia::find_if(timers, [=](auto& p1) { return p1.first == handler; });
271 if(t != timers.end())
272 {
273 delete t->second;
274 timers.erase(t);
275 }
276 return Steinberg::kResultOk;
277 }
278
279 QDialog* w;
280 WindowContainer wc;
281 explicit PlugFrame(QDialog& w, WindowContainer wc)
282 : w{&w}
283 , wc{wc}
284 {
285 }
286
287 void cleanup()
288 {
289 for(auto timer : timers)
290 delete timer.second;
291 timers.clear();
292 for(auto handler : handlers)
293 {
294 GlobalSocketHandlers::instance().unregisterHandler(handler.handler, handler.fd);
295 delete handler.r_notifier;
296 delete handler.w_notifier;
297 delete handler.e_notifier;
298 }
299 handlers.clear();
300 }
301
302 ~PlugFrame() { cleanup(); }
303
304 tresult resizeView(Steinberg::IPlugView* view, Steinberg::ViewRect* newSize) override
305 {
306 wc.setSizeFromVst(*view, *newSize, *w);
307 return Steinberg::kResultOk;
308 }
309};
310
311}
312#endif