comparison DPF-Prymula-audioplugins/dpf/distrho/extra/ExternalWindow.hpp @ 3:84e66ea83026

DPF-Prymula-audioplugins-0.231015-2
author prymula <prymula76@outlook.com>
date Mon, 16 Oct 2023 21:53:34 +0200
parents
children
comparison
equal deleted inserted replaced
2:cf2cb71d31dd 3:84e66ea83026
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 */
72 class ExternalWindow
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;
372 transientParentWindowChanged(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
576 END_NAMESPACE_DISTRHO
577
578 #endif // DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED