Mercurial > hg > pub > prymula > com
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 |