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 
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_UINT_RETURN(width > 1, width,);
299  DISTRHO_SAFE_ASSERT_UINT_RETURN(height > 1, height,);
300 
301  if (pData.width == width && pData.height == height)
302  return;
303 
304  pData.width = width;
305  pData.height = height;
306  sizeChanged(width, height);
307  }
308 
309  /**
310  Set the title of the window, typically displayed in the title bar or in window switchers.
311  Can trigger a titleChanged callback.
312  Only relevant to hosts when the UI is not embedded.
313  */
314  void setTitle(const char* title)
315  {
316  if (pData.title == title)
317  return;
318 
319  pData.title = title;
320  titleChanged(title);
321  }
322 
323  /**
324  Set geometry constraints for the Window when resized by the user.
325  */
326  void setGeometryConstraints(uint minimumWidth, uint minimumHeight, bool keepAspectRatio = false)
327  {
328  DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumWidth > 0, minimumWidth,);
329  DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumHeight > 0, minimumHeight,);
330 
331  pData.minWidth = minimumWidth;
332  pData.minHeight = minimumHeight;
333  pData.keepAspectRatio = keepAspectRatio;
334  }
335 
336  /* --------------------------------------------------------------------------------------------------------
337  * TopLevelWidget-like calls - actions called by the host */
338 
339  /**
340  Show window.
341  This is the same as calling setVisible(true).
342  @see isVisible(), setVisible(bool)
343  */
344  void show()
345  {
346  setVisible(true);
347  }
348 
349  /**
350  Set window visible (or not) according to @a visible.
351  @see isVisible(), hide(), show()
352  */
353  void setVisible(bool visible)
354  {
355  if (pData.visible == visible)
356  return;
357 
358  pData.visible = visible;
359  visibilityChanged(visible);
360  }
361 
362  /**
363  Called by the host to set the transient parent window that we should attach ourselves to.
364  TODO what id? also NSView* on macOS, or NSWindow?
365  */
366  void setTransientWindowId(uintptr_t winId)
367  {
368  if (pData.transientWinId == winId)
369  return;
370 
371  pData.transientWinId = winId;
373  }
374 
375 protected:
376  /* --------------------------------------------------------------------------------------------------------
377  * ExternalWindow special calls for running externals tools */
378 
379  bool startExternalProcess(const char* args[])
380  {
381 #ifndef DISTRHO_OS_WINDOWS
382  ext.inUse = true;
383 
384  return ext.start(args);
385 #else
386  (void)args;
387  return false; // TODO
388 #endif
389  }
390 
391  void terminateAndWaitForExternalProcess()
392  {
393 #ifndef DISTRHO_OS_WINDOWS
394  ext.isQuitting = true;
395  ext.terminateAndWait();
396 #else
397  // TODO
398 #endif
399  }
400 
401  /* --------------------------------------------------------------------------------------------------------
402  * ExternalWindow specific callbacks */
403 
404  /**
405  A callback for when the window size changes.
406  @note WIP this might need to get fed back into the host somehow.
407  */
408  virtual void sizeChanged(uint /* width */, uint /* height */)
409  {
410  // unused, meant for custom implementations
411  }
412 
413  /**
414  A callback for when the window title changes.
415  @note WIP this might need to get fed back into the host somehow.
416  */
417  virtual void titleChanged(const char* /* title */)
418  {
419  // unused, meant for custom implementations
420  }
421 
422  /**
423  A callback for when the window visibility changes.
424  @note WIP this might need to get fed back into the host somehow.
425  */
426  virtual void visibilityChanged(bool /* visible */)
427  {
428  // unused, meant for custom implementations
429  }
430 
431  /**
432  A callback for when the transient parent window changes.
433  */
434  virtual void transientParentWindowChanged(uintptr_t /* winId */)
435  {
436  // unused, meant for custom implementations
437  }
438 
439 private:
440  friend class PluginWindow;
441  friend class UI;
442 
443 #ifndef DISTRHO_OS_WINDOWS
444  struct ExternalProcess {
445  bool inUse;
446  bool isQuitting;
447  mutable pid_t pid;
448 
449  ExternalProcess()
450  : inUse(false),
451  isQuitting(false),
452  pid(0) {}
453 
454  bool isRunning() const noexcept
455  {
456  if (pid <= 0)
457  return false;
458 
459  const pid_t p = ::waitpid(pid, nullptr, WNOHANG);
460 
461  if (p == pid || (p == -1 && errno == ECHILD))
462  {
463  d_stdout("NOTICE: Child process exited while idle");
464  pid = 0;
465  return false;
466  }
467 
468  return true;
469  }
470 
471  bool start(const char* args[])
472  {
473  terminateAndWait();
474 
475  pid = vfork();
476 
477  switch (pid)
478  {
479  case 0:
480  execvp(args[0], (char**)args);
481  _exit(1);
482  return false;
483 
484  case -1:
485  d_stderr("Could not start external ui");
486  return false;
487 
488  default:
489  return true;
490  }
491  }
492 
493  void terminateAndWait()
494  {
495  if (pid <= 0)
496  return;
497 
498  d_stdout("Waiting for external process to stop,,,");
499 
500  bool sendTerm = true;
501 
502  for (pid_t p;;)
503  {
504  p = ::waitpid(pid, nullptr, WNOHANG);
505 
506  switch (p)
507  {
508  case 0:
509  if (sendTerm)
510  {
511  sendTerm = false;
512  ::kill(pid, SIGTERM);
513  }
514  break;
515 
516  case -1:
517  if (errno == ECHILD)
518  {
519  d_stdout("Done! (no such process)");
520  pid = 0;
521  return;
522  }
523  break;
524 
525  default:
526  if (p == pid)
527  {
528  d_stdout("Done! (clean wait)");
529  pid = 0;
530  return;
531  }
532  break;
533  }
534 
535  // 5 msec
536  usleep(5*1000);
537  }
538  }
539  } ext;
540 #endif
541 
542  struct PrivateData {
543  uintptr_t parentWindowHandle;
544  uintptr_t transientWinId;
545  uint width;
546  uint height;
547  double scaleFactor;
548  String title;
549  uint minWidth;
550  uint minHeight;
551  bool keepAspectRatio;
552  bool isQuitting;
553  bool isStandalone;
554  bool visible;
555 
556  PrivateData()
557  : parentWindowHandle(0),
558  transientWinId(0),
559  width(1),
560  height(1),
561  scaleFactor(1.0),
562  title(),
563  minWidth(0),
564  minHeight(0),
565  keepAspectRatio(false),
566  isQuitting(false),
567  isStandalone(false),
568  visible(false) {}
569  } pData;
570 
571  DISTRHO_DECLARE_NON_COPYABLE(ExternalWindow)
572 };
573 
574 // -----------------------------------------------------------------------
575 
577 
578 #endif // DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED
Definition: ExternalWindow.hpp:73
double getScaleFactor() const noexcept
Definition: ExternalWindow.hpp:205
uint getWidth() const noexcept
Definition: ExternalWindow.hpp:187
void setGeometryConstraints(uint minimumWidth, uint minimumHeight, bool keepAspectRatio=false)
Definition: ExternalWindow.hpp:326
bool isEmbed() const noexcept
Definition: ExternalWindow.hpp:159
void setVisible(bool visible)
Definition: ExternalWindow.hpp:353
bool isVisible() const noexcept
Definition: ExternalWindow.hpp:169
void show()
Definition: ExternalWindow.hpp:344
void setWidth(uint width)
Definition: ExternalWindow.hpp:276
ExternalWindow(const PrivateData &data)
Definition: ExternalWindow.hpp:86
virtual void transientParentWindowChanged(uintptr_t)
Definition: ExternalWindow.hpp:434
void setSize(uint width, uint height)
Definition: ExternalWindow.hpp:296
uintptr_t getParentWindowHandle() const noexcept
Definition: ExternalWindow.hpp:228
bool isStandalone() const noexcept
Definition: ExternalWindow.hpp:178
virtual void visibilityChanged(bool)
Definition: ExternalWindow.hpp:426
uint getHeight() const noexcept
Definition: ExternalWindow.hpp:196
virtual void focus()
Definition: ExternalWindow.hpp:150
virtual bool isQuitting() const
Definition: ExternalWindow.hpp:119
void setTitle(const char *title)
Definition: ExternalWindow.hpp:314
virtual uintptr_t getNativeWindowHandle() const noexcept
Definition: ExternalWindow.hpp:140
virtual ~ExternalWindow()
Definition: ExternalWindow.hpp:92
void hide()
Definition: ExternalWindow.hpp:252
virtual void sizeChanged(uint, uint)
Definition: ExternalWindow.hpp:408
virtual void close()
Definition: ExternalWindow.hpp:261
const char * getTitle() const noexcept
Definition: ExternalWindow.hpp:214
uintptr_t getTransientWindowId() const noexcept
Definition: ExternalWindow.hpp:238
void setHeight(uint height)
Definition: ExternalWindow.hpp:286
virtual bool isRunning() const
Definition: ExternalWindow.hpp:105
ExternalWindow()
Definition: ExternalWindow.hpp:80
virtual void titleChanged(const char *)
Definition: ExternalWindow.hpp:417
void setTransientWindowId(uintptr_t winId)
Definition: ExternalWindow.hpp:366
Definition: String.hpp:35
Definition: DistrhoUI.hpp:78
#define END_NAMESPACE_DISTRHO
Definition: DistrhoInfo.hpp:949
#define START_NAMESPACE_DISTRHO
Definition: DistrhoInfo.hpp:943
static void d_stderr(const char *const fmt,...) noexcept
Definition: DistrhoUtils.hpp:156
static void d_stdout(const char *const fmt,...) noexcept
Definition: DistrhoUtils.hpp:141