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 
24 namespace vst3
25 {
26 namespace Linux = Steinberg::Linux;
27 
28 struct 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 
52 struct 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 
101 struct SocketHandler
102 {
103  Linux::IEventHandler* handler{};
104  QSocketNotifier* r_notifier{};
105  QSocketNotifier* w_notifier{};
106  QSocketNotifier* e_notifier{};
107  int fd{};
108 };
109 
110 class GlobalSocketHandlers : public QObject
111 {
112  XcbConnection xcb;
113 
114 public:
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 };
150 class PlugFrame final
151  : virtual public Steinberg::IPlugFrame
152  , virtual public Steinberg::Linux::IRunLoop
153 {
154 public:
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  auto on_fd = [=] { handler->onFDIsSet(fd); };
205  QObject::connect(
206  readnotifier, &QSocketNotifier::activated, &internalContextObject, on_fd);
207 
208  auto writenotifier = new QSocketNotifier{fd, QSocketNotifier::Write};
209  writenotifier->setEnabled(true);
210  QObject::connect(
211  writenotifier, &QSocketNotifier::activated, &internalContextObject, on_fd);
212 
213  auto errnotifier = new QSocketNotifier{fd, QSocketNotifier::Exception};
214  errnotifier->setEnabled(true);
215  QObject::connect(
216  errnotifier, &QSocketNotifier::activated, &internalContextObject, on_fd);
217 
218  handlers.push_back(
219  SocketHandler{handler, readnotifier, writenotifier, errnotifier, fd});
220  GlobalSocketHandlers::instance().registerHandler(handler, fd);
221 
222  return kResultTrue;
223  }
224 
225  tresult unregisterEventHandler(Linux::IEventHandler* handler) SMTG_OVERRIDE
226  {
227  using namespace Steinberg;
228  if(!handler)
229  {
230  return kInvalidArgument;
231  }
232 
233  tresult res{kResultFalse};
234  for(auto it = handlers.begin(); it != handlers.end();)
235  {
236  if(it->handler == handler)
237  {
238  GlobalSocketHandlers::instance().unregisterHandler(handler, it->fd);
239  delete it->r_notifier;
240  delete it->w_notifier;
241  delete it->e_notifier;
242  it = handlers.erase(it);
243  res = kResultTrue;
244  }
245  else
246  {
247  ++it;
248  }
249  }
250 
251  return res;
252  }
253 
254  tresult registerTimer(
255  Linux::ITimerHandler* handler, Linux::TimerInterval milliseconds) override
256  {
257  auto t = new QTimer;
258  QObject::connect(t, &QTimer::timeout, [=] { handler->onTimer(); });
259  t->start(milliseconds);
260  timers.push_back({handler, t});
261  return Steinberg::kResultOk;
262  }
263 
264  tresult unregisterTimer(Linux::ITimerHandler* handler) override
265  {
266  auto t = ossia::find_if(timers, [=](auto& p1) { return p1.first == handler; });
267  if(t != timers.end())
268  {
269  delete t->second;
270  timers.erase(t);
271  }
272  return Steinberg::kResultOk;
273  }
274 
275  QDialog& w;
276  WindowContainer wc;
277  explicit PlugFrame(QDialog& w, WindowContainer wc)
278  : w{w}
279  , wc{wc}
280  {
281  }
282 
283  ~PlugFrame()
284  {
285  for(auto timer : timers)
286  delete timer.second;
287  for(auto handler : handlers)
288  {
289  GlobalSocketHandlers::instance().unregisterHandler(handler.handler, handler.fd);
290  delete handler.r_notifier;
291  delete handler.w_notifier;
292  delete handler.e_notifier;
293  }
294  }
295 
296  tresult resizeView(Steinberg::IPlugView* view, Steinberg::ViewRect* newSize) override
297  {
298  wc.setSizeFromVst(*view, *newSize, w);
299  return Steinberg::kResultOk;
300  }
301 };
302 
303 }
304 #endif