DISTRHO Plugin Framework
ExternalWindow.hpp
1 /*
2  * DISTRHO Plugin Framework (DPF)
3  * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
4  *
5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
6  * or without fee is hereby granted, provided that the above copyright notice and this
7  * permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #ifndef DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED
18 #define DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED
19 
20 #include "String.hpp"
21 
22 #ifndef DISTRHO_OS_WINDOWS
23 # include <cerrno>
24 # include <signal.h>
25 # include <sys/wait.h>
26 # include <unistd.h>
27 #endif
28 
29 START_NAMESPACE_DISTRHO
30 
31 // -----------------------------------------------------------------------
32 // ExternalWindow class
33 
34 /**
35  External Window class.
36 
37  This is a standalone TopLevelWidget/Window-compatible class, but without any real event handling.
38  Being compatible with TopLevelWidget/Window, it allows to be used as DPF UI target.
39 
40  It can be used to embed non-DPF things or to run a tool in a new process as the "UI".
41  The uiIdle() function will be called at regular intervals to keep UI running.
42  There are helper methods in place to launch external tools and keep track of its running state.
43 
44  External windows can be setup to run in 3 different modes:
45  * Embed:
46  Embed into the host UI, even-loop driven by the host.
47  This is basically working as a regular plugin UI, as you typically expect them to.
48  The plugin side does not get control over showing, hiding or closing the window (as usual for plugins).
49  No restrictions on supported plugin format, everything should work.
50  Requires DISTRHO_PLUGIN_HAS_EMBED_UI to be set to 1.
51 
52  * Semi-external:
53  The UI is not embed into the host, but the even-loop is still driven by it.
54  In this mode the host does not have control over the UI except for showing, hiding and setting transient parent.
55  It is possible to close the window from the plugin, the host will be notified of such case.
56  Host regularly calls isQuitting() to check if the UI got closed by the user or plugin side.
57  This mode is only possible in LV2 plugin formats, using lv2ui:showInterface extension.
58 
59  * Standalone:
60  The UI is not embed into the host or uses its event-loop, basically running as standalone.
61  The host only has control over showing and hiding the window, nothing else.
62  The UI is still free to close itself at any point.
63  DPF will keep calling isRunning() to check if it should keep the event-loop running.
64  Only possible in JACK and DSSI targets, as the UIs are literally standalone applications there.
65 
66  Please note that for non-embed windows, you cannot show the window yourself.
67  The plugin window is only allowed to hide or close itself, a "show" action needs to come from the host.
68 
69  A few callbacks are provided so that implementations do not need to care about checking for state changes.
70  They are not called on construction, but will be everytime something changes either by the host or the window itself.
71  */
73 {
74  struct PrivateData;
75 
76 public:
77  /**
78  Constructor.
79  */
80  explicit ExternalWindow()
81  : pData() {}
82 
83  /**
84  Constructor for DPF internal use.
85  */
86  explicit ExternalWindow(const PrivateData& data)
87  : pData(data) {}
88 
89  /**
90  Destructor.
91  */
92  virtual ~ExternalWindow()
93  {
94  DISTRHO_SAFE_ASSERT(!pData.visible);
95  }
96 
97  /* --------------------------------------------------------------------------------------------------------
98  * ExternalWindow specific calls - Host side calls that you can reimplement for fine-grained funtionality */
99 
100  /**
101  Check if main-loop is running.
102  This is used under standalone mode to check whether to keep things running.
103  Returning false from this function will stop the event-loop and close the window.
104  */
105  virtual bool isRunning() const
106  {
107 #ifndef DISTRHO_OS_WINDOWS
108  if (ext.inUse)
109  return ext.isRunning();
110 #endif
111  return isVisible();
112  }
113 
114  /**
115  Check if we are about to close.
116  This is used when the event-loop is provided by the host to check if it should close the window.
117  It is also used in standalone mode right after isRunning() returns false to verify if window needs to be closed.
118  */
119  virtual bool isQuitting() const
120  {
121 #ifndef DISTRHO_OS_WINDOWS
122  return ext.inUse ? ext.isQuitting : pData.isQuitting;
123 #else
124  return pData.isQuitting;
125 #endif
126  }
127 
128  /**
129  Get the "native" window handle.
130  This can be reimplemented in order to pass the native window to hosts that can use such informaton.
131 
132  Returned value type depends on the platform:
133  - HaikuOS: This is a pointer to a `BView`.
134  - MacOS: This is a pointer to an `NSView*`.
135  - Windows: This is a `HWND`.
136  - Everything else: This is an [X11] `Window`.
137 
138  @note Only available to override if DISTRHO_PLUGIN_HAS_EMBED_UI is set to 1.
139  */
140  virtual uintptr_t getNativeWindowHandle() const noexcept
141  {
142  return 0;
143  }
144 
145  /**
146  Grab the keyboard input focus.
147  Typically you would setup OS-native methods to bring the window to front and give it focus.
148  Default implementation does nothing.
149  */
150  virtual void focus() {}
151 
152  /* --------------------------------------------------------------------------------------------------------
153  * TopLevelWidget-like calls - Information, can be called by either host or plugin */
154 
155 #if DISTRHO_PLUGIN_HAS_EMBED_UI
156  /**
157  Whether this Window is embed into another (usually not DGL-controlled) Window.
158  */
159  bool isEmbed() const noexcept
160  {
161  return pData.parentWindowHandle != 0;
162  }
163 #endif
164 
165  /**
166  Check if this window is visible.
167  @see setVisible(bool)
168  */
169  bool isVisible() const noexcept
170  {
171  return pData.visible;
172  }
173 
174  /**
175  Whether this Window is running as standalone, that is, without being coupled to a host event-loop.
176  When in standalone mode, isRunning() is called to check if the event-loop should keep running.
177  */
178  bool isStandalone() const noexcept
179  {
180  return pData.isStandalone;
181  }
182 
183  /**
184  Get width of this window.
185  Only relevant to hosts when the UI is embedded.
186  */
187  uint getWidth() const noexcept
188  {
189  return pData.width;
190  }
191 
192  /**
193  Get height of this window.
194  Only relevant to hosts when the UI is embedded.
195  */
196  uint getHeight() const noexcept
197  {
198  return pData.height;
199  }
200 
201  /**
202  Get the scale factor requested for this window.
203  This is purely informational, and up to developers to choose what to do with it.
204  */
205  double getScaleFactor() const noexcept
206  {
207  return pData.scaleFactor;
208  }
209 
210  /**
211  Get the title of the window previously set with setTitle().
212  This is typically displayed in the title bar or in window switchers.
213  */
214  const char* getTitle() const noexcept
215  {
216  return pData.title;
217  }
218 
219 #if DISTRHO_PLUGIN_HAS_EMBED_UI
220  /**
221  Get the "native" window handle that this window should embed itself into.
222  Returned value type depends on the platform:
223  - HaikuOS: This is a pointer to a `BView`.
224  - MacOS: This is a pointer to an `NSView*`.
225  - Windows: This is a `HWND`.
226  - Everything else: This is an [X11] `Window`.
227  */
228  uintptr_t getParentWindowHandle() const noexcept
229  {
230  return pData.parentWindowHandle;
231  }
232 #endif
233 
234  /**
235  Get the transient window that we should attach ourselves to.
236  TODO what id? also NSView* on macOS, or NSWindow?
237  */
238  uintptr_t getTransientWindowId() const noexcept
239  {
240  return pData.transientWinId;
241  }
242 
243  /* --------------------------------------------------------------------------------------------------------
244  * TopLevelWidget-like calls - actions called by either host or plugin */
245 
246  /**
247  Hide window.
248  This is the same as calling setVisible(false).
249  Embed windows should never call this!
250  @see isVisible(), setVisible(bool)
251  */
252  void hide()
253  {
254  setVisible(false);
255  }
256 
257  /**
258  Hide the UI and gracefully terminate.
259  Embed windows should never call this!
260  */
261  virtual void close()
262  {
263  pData.isQuitting = true;
264  hide();
265 #ifndef DISTRHO_OS_WINDOWS
266  if (ext.inUse)
267  terminateAndWaitForExternalProcess();
268 #endif
269  }
270 
271  /**
272  Set width of this window.
273  Can trigger a sizeChanged callback.
274  Only relevant to hosts when the UI is embedded.
275  */
276  void setWidth(uint width)
277  {
278  setSize(width, getHeight());
279  }
280 
281  /**
282  Set height of this window.
283  Can trigger a sizeChanged callback.
284  Only relevant to hosts when the UI is embedded.
285  */
286  void setHeight(uint height)
287  {
288  setSize(getWidth(), height);
289  }
290 
291  /**
292  Set size of this window using @a width and @a height values.
293  Can trigger a sizeChanged callback.
294  Only relevant to hosts when the UI is embedded.
295  */
296  void setSize(uint width, uint height)
297  {
298  DISTRHO_SAFE_ASSERT_UINT2_RETURN(width > 1 && height > 1, width, height,);
299 
300  if (pData.width == width && pData.height == height)
301  return;
302 
303  pData.width = width;
304  pData.height = height;
305  sizeChanged(width, height);
306  }
307 
308  /**
309  Set the title of the window, typically displayed in the title bar or in window switchers.
310  Can trigger a titleChanged callback.
311  Only relevant to hosts when the UI is not embedded.
312  */
313  void setTitle(const char* title)
314  {
315  if (pData.title == title)
316  return;
317  pData.title = title;
318  titleChanged(title);
319  }
320 
321  /* --------------------------------------------------------------------------------------------------------
322  * TopLevelWidget-like calls - actions called by the host */
323 
324  /**
325  Show window.
326  This is the same as calling setVisible(true).
327  @see isVisible(), setVisible(bool)
328  */
329  void show()
330  {
331  setVisible(true);
332  }
333 
334  /**
335  Set window visible (or not) according to @a visible.
336  @see isVisible(), hide(), show()
337  */
338  void setVisible(bool visible)
339  {
340  if (pData.visible == visible)
341  return;
342  pData.visible = visible;
343  visibilityChanged(visible);
344  }
345 
346  /**
347  Called by the host to set the transient parent window that we should attach ourselves to.
348  TODO what id? also NSView* on macOS, or NSWindow?
349  */
350  void setTransientWindowId(uintptr_t winId)
351  {
352  if (pData.transientWinId == winId)
353  return;
354  pData.transientWinId = winId;
356  }
357 
358 protected:
359  /* --------------------------------------------------------------------------------------------------------
360  * ExternalWindow special calls for running externals tools */
361 
362  bool startExternalProcess(const char* args[])
363  {
364 #ifndef DISTRHO_OS_WINDOWS
365  ext.inUse = true;
366 
367  return ext.start(args);
368 #else
369  (void)args;
370  return false; // TODO
371 #endif
372  }
373 
374  void terminateAndWaitForExternalProcess()
375  {
376 #ifndef DISTRHO_OS_WINDOWS
377  ext.isQuitting = true;
378  ext.terminateAndWait();
379 #else
380  // TODO
381 #endif
382  }
383 
384  /* --------------------------------------------------------------------------------------------------------
385  * ExternalWindow specific callbacks */
386 
387  /**
388  A callback for when the window size changes.
389  @note WIP this might need to get fed back into the host somehow.
390  */
391  virtual void sizeChanged(uint width, uint height)
392  {
393  // unused, meant for custom implementations
394  return; (void)width; (void)height;
395  }
396 
397  /**
398  A callback for when the window title changes.
399  @note WIP this might need to get fed back into the host somehow.
400  */
401  virtual void titleChanged(const char* title)
402  {
403  // unused, meant for custom implementations
404  return; (void)title;
405  }
406 
407  /**
408  A callback for when the window visibility changes.
409  @note WIP this might need to get fed back into the host somehow.
410  */
411  virtual void visibilityChanged(bool visible)
412  {
413  // unused, meant for custom implementations
414  return; (void)visible;
415  }
416 
417  /**
418  A callback for when the transient parent window changes.
419  */
420  virtual void transientParentWindowChanged(uintptr_t winId)
421  {
422  // unused, meant for custom implementations
423  return; (void)winId;
424  }
425 
426 private:
427  friend class PluginWindow;
428  friend class UI;
429 
430 #ifndef DISTRHO_OS_WINDOWS
431  struct ExternalProcess {
432  bool inUse;
433  bool isQuitting;
434  mutable pid_t pid;
435 
436  ExternalProcess()
437  : inUse(false),
438  isQuitting(false),
439  pid(0) {}
440 
441  bool isRunning() const noexcept
442  {
443  if (pid <= 0)
444  return false;
445 
446  const pid_t p = ::waitpid(pid, nullptr, WNOHANG);
447 
448  if (p == pid || (p == -1 && errno == ECHILD))
449  {
450  d_stdout("NOTICE: Child process exited while idle");
451  pid = 0;
452  return false;
453  }
454 
455  return true;
456  }
457 
458  bool start(const char* args[])
459  {
460  terminateAndWait();
461 
462  pid = vfork();
463 
464  switch (pid)
465  {
466  case 0:
467  execvp(args[0], (char**)args);
468  _exit(1);
469  return false;
470 
471  case -1:
472  d_stderr("Could not start external ui");
473  return false;
474 
475  default:
476  return true;
477  }
478  }
479 
480  void terminateAndWait()
481  {
482  if (pid <= 0)
483  return;
484 
485  d_stdout("Waiting for external process to stop,,,");
486 
487  bool sendTerm = true;
488 
489  for (pid_t p;;)
490  {
491  p = ::waitpid(pid, nullptr, WNOHANG);
492 
493  switch (p)
494  {
495  case 0:
496  if (sendTerm)
497  {
498  sendTerm = false;
499  ::kill(pid, SIGTERM);
500  }
501  break;
502 
503  case -1:
504  if (errno == ECHILD)
505  {
506  d_stdout("Done! (no such process)");
507  pid = 0;
508  return;
509  }
510  break;
511 
512  default:
513  if (p == pid)
514  {
515  d_stdout("Done! (clean wait)");
516  pid = 0;
517  return;
518  }
519  break;
520  }
521 
522  // 5 msec
523  usleep(5*1000);
524  }
525  }
526  } ext;
527 #endif
528 
529  struct PrivateData {
530  uintptr_t parentWindowHandle;
531  uintptr_t transientWinId;
532  uint width;
533  uint height;
534  double scaleFactor;
535  String title;
536  bool isQuitting;
537  bool isStandalone;
538  bool visible;
539 
540  PrivateData()
541  : parentWindowHandle(0),
542  transientWinId(0),
543  width(1),
544  height(1),
545  scaleFactor(1.0),
546  title(),
547  isQuitting(false),
548  isStandalone(false),
549  visible(false) {}
550  } pData;
551 
552  DISTRHO_DECLARE_NON_COPYABLE(ExternalWindow)
553 };
554 
555 // -----------------------------------------------------------------------
556 
557 END_NAMESPACE_DISTRHO
558 
559 #endif // DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED
ExternalWindow::isQuitting
virtual bool isQuitting() const
Definition: ExternalWindow.hpp:119
ExternalWindow::transientParentWindowChanged
virtual void transientParentWindowChanged(uintptr_t winId)
Definition: ExternalWindow.hpp:420
ExternalWindow::isRunning
virtual bool isRunning() const
Definition: ExternalWindow.hpp:105
ExternalWindow::getTitle
const char * getTitle() const noexcept
Definition: ExternalWindow.hpp:214
ExternalWindow::ExternalWindow
ExternalWindow()
Definition: ExternalWindow.hpp:80
ExternalWindow::getNativeWindowHandle
virtual uintptr_t getNativeWindowHandle() const noexcept
Definition: ExternalWindow.hpp:140
String
Definition: String.hpp:30
ExternalWindow::hide
void hide()
Definition: ExternalWindow.hpp:252
ExternalWindow::getParentWindowHandle
uintptr_t getParentWindowHandle() const noexcept
Definition: ExternalWindow.hpp:228
ExternalWindow::show
void show()
Definition: ExternalWindow.hpp:329
ExternalWindow::setHeight
void setHeight(uint height)
Definition: ExternalWindow.hpp:286
ExternalWindow::titleChanged
virtual void titleChanged(const char *title)
Definition: ExternalWindow.hpp:401
UI
Definition: DistrhoUI.hpp:71
ExternalWindow
Definition: ExternalWindow.hpp:72
ExternalWindow::setVisible
void setVisible(bool visible)
Definition: ExternalWindow.hpp:338
ExternalWindow::setTitle
void setTitle(const char *title)
Definition: ExternalWindow.hpp:313
ExternalWindow::~ExternalWindow
virtual ~ExternalWindow()
Definition: ExternalWindow.hpp:92
ExternalWindow::visibilityChanged
virtual void visibilityChanged(bool visible)
Definition: ExternalWindow.hpp:411
ExternalWindow::getHeight
uint getHeight() const noexcept
Definition: ExternalWindow.hpp:196
ExternalWindow::setTransientWindowId
void setTransientWindowId(uintptr_t winId)
Definition: ExternalWindow.hpp:350
ExternalWindow::close
virtual void close()
Definition: ExternalWindow.hpp:261
ExternalWindow::isEmbed
bool isEmbed() const noexcept
Definition: ExternalWindow.hpp:159
ExternalWindow::getWidth
uint getWidth() const noexcept
Definition: ExternalWindow.hpp:187
ExternalWindow::sizeChanged
virtual void sizeChanged(uint width, uint height)
Definition: ExternalWindow.hpp:391
ExternalWindow::setSize
void setSize(uint width, uint height)
Definition: ExternalWindow.hpp:296
ExternalWindow::focus
virtual void focus()
Definition: ExternalWindow.hpp:150
ExternalWindow::getScaleFactor
double getScaleFactor() const noexcept
Definition: ExternalWindow.hpp:205
ExternalWindow::getTransientWindowId
uintptr_t getTransientWindowId() const noexcept
Definition: ExternalWindow.hpp:238
ExternalWindow::isStandalone
bool isStandalone() const noexcept
Definition: ExternalWindow.hpp:178
ExternalWindow::isVisible
bool isVisible() const noexcept
Definition: ExternalWindow.hpp:169
ExternalWindow::setWidth
void setWidth(uint width)
Definition: ExternalWindow.hpp:276
ExternalWindow::ExternalWindow
ExternalWindow(const PrivateData &data)
Definition: ExternalWindow.hpp:86