Mercurial > hg > pub > prymula > com
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DPF-Prymula-audioplugins/dpf/distrho/extra/ExternalWindow.hpp Mon Oct 16 21:53:34 2023 +0200 @@ -0,0 +1,578 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED +#define DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED + +#include "String.hpp" + +#ifndef DISTRHO_OS_WINDOWS +# include <cerrno> +# include <signal.h> +# include <sys/wait.h> +# include <unistd.h> +#endif + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------- +// ExternalWindow class + +/** + External Window class. + + This is a standalone TopLevelWidget/Window-compatible class, but without any real event handling. + Being compatible with TopLevelWidget/Window, it allows to be used as DPF UI target. + + It can be used to embed non-DPF things or to run a tool in a new process as the "UI". + The uiIdle() function will be called at regular intervals to keep UI running. + There are helper methods in place to launch external tools and keep track of its running state. + + External windows can be setup to run in 3 different modes: + * Embed: + Embed into the host UI, even-loop driven by the host. + This is basically working as a regular plugin UI, as you typically expect them to. + The plugin side does not get control over showing, hiding or closing the window (as usual for plugins). + No restrictions on supported plugin format, everything should work. + Requires DISTRHO_PLUGIN_HAS_EMBED_UI to be set to 1. + + * Semi-external: + The UI is not embed into the host, but the even-loop is still driven by it. + In this mode the host does not have control over the UI except for showing, hiding and setting transient parent. + It is possible to close the window from the plugin, the host will be notified of such case. + Host regularly calls isQuitting() to check if the UI got closed by the user or plugin side. + This mode is only possible in LV2 plugin formats, using lv2ui:showInterface extension. + + * Standalone: + The UI is not embed into the host or uses its event-loop, basically running as standalone. + The host only has control over showing and hiding the window, nothing else. + The UI is still free to close itself at any point. + DPF will keep calling isRunning() to check if it should keep the event-loop running. + Only possible in JACK and DSSI targets, as the UIs are literally standalone applications there. + + Please note that for non-embed windows, you cannot show the window yourself. + The plugin window is only allowed to hide or close itself, a "show" action needs to come from the host. + + A few callbacks are provided so that implementations do not need to care about checking for state changes. + They are not called on construction, but will be everytime something changes either by the host or the window itself. + */ +class ExternalWindow +{ + struct PrivateData; + +public: + /** + Constructor. + */ + explicit ExternalWindow() + : pData() {} + + /** + Constructor for DPF internal use. + */ + explicit ExternalWindow(const PrivateData& data) + : pData(data) {} + + /** + Destructor. + */ + virtual ~ExternalWindow() + { + DISTRHO_SAFE_ASSERT(!pData.visible); + } + + /* -------------------------------------------------------------------------------------------------------- + * ExternalWindow specific calls - Host side calls that you can reimplement for fine-grained funtionality */ + + /** + Check if main-loop is running. + This is used under standalone mode to check whether to keep things running. + Returning false from this function will stop the event-loop and close the window. + */ + virtual bool isRunning() const + { +#ifndef DISTRHO_OS_WINDOWS + if (ext.inUse) + return ext.isRunning(); +#endif + return isVisible(); + } + + /** + Check if we are about to close. + This is used when the event-loop is provided by the host to check if it should close the window. + It is also used in standalone mode right after isRunning() returns false to verify if window needs to be closed. + */ + virtual bool isQuitting() const + { +#ifndef DISTRHO_OS_WINDOWS + return ext.inUse ? ext.isQuitting : pData.isQuitting; +#else + return pData.isQuitting; +#endif + } + + /** + Get the "native" window handle. + This can be reimplemented in order to pass the native window to hosts that can use such informaton. + + Returned value type depends on the platform: + - HaikuOS: This is a pointer to a `BView`. + - MacOS: This is a pointer to an `NSView*`. + - Windows: This is a `HWND`. + - Everything else: This is an [X11] `Window`. + + @note Only available to override if DISTRHO_PLUGIN_HAS_EMBED_UI is set to 1. + */ + virtual uintptr_t getNativeWindowHandle() const noexcept + { + return 0; + } + + /** + Grab the keyboard input focus. + Typically you would setup OS-native methods to bring the window to front and give it focus. + Default implementation does nothing. + */ + virtual void focus() {} + + /* -------------------------------------------------------------------------------------------------------- + * TopLevelWidget-like calls - Information, can be called by either host or plugin */ + +#if DISTRHO_PLUGIN_HAS_EMBED_UI + /** + Whether this Window is embed into another (usually not DGL-controlled) Window. + */ + bool isEmbed() const noexcept + { + return pData.parentWindowHandle != 0; + } +#endif + + /** + Check if this window is visible. + @see setVisible(bool) + */ + bool isVisible() const noexcept + { + return pData.visible; + } + + /** + Whether this Window is running as standalone, that is, without being coupled to a host event-loop. + When in standalone mode, isRunning() is called to check if the event-loop should keep running. + */ + bool isStandalone() const noexcept + { + return pData.isStandalone; + } + + /** + Get width of this window. + Only relevant to hosts when the UI is embedded. + */ + uint getWidth() const noexcept + { + return pData.width; + } + + /** + Get height of this window. + Only relevant to hosts when the UI is embedded. + */ + uint getHeight() const noexcept + { + return pData.height; + } + + /** + Get the scale factor requested for this window. + This is purely informational, and up to developers to choose what to do with it. + */ + double getScaleFactor() const noexcept + { + return pData.scaleFactor; + } + + /** + Get the title of the window previously set with setTitle(). + This is typically displayed in the title bar or in window switchers. + */ + const char* getTitle() const noexcept + { + return pData.title; + } + +#if DISTRHO_PLUGIN_HAS_EMBED_UI + /** + Get the "native" window handle that this window should embed itself into. + Returned value type depends on the platform: + - HaikuOS: This is a pointer to a `BView`. + - MacOS: This is a pointer to an `NSView*`. + - Windows: This is a `HWND`. + - Everything else: This is an [X11] `Window`. + */ + uintptr_t getParentWindowHandle() const noexcept + { + return pData.parentWindowHandle; + } +#endif + + /** + Get the transient window that we should attach ourselves to. + TODO what id? also NSView* on macOS, or NSWindow? + */ + uintptr_t getTransientWindowId() const noexcept + { + return pData.transientWinId; + } + + /* -------------------------------------------------------------------------------------------------------- + * TopLevelWidget-like calls - actions called by either host or plugin */ + + /** + Hide window. + This is the same as calling setVisible(false). + Embed windows should never call this! + @see isVisible(), setVisible(bool) + */ + void hide() + { + setVisible(false); + } + + /** + Hide the UI and gracefully terminate. + Embed windows should never call this! + */ + virtual void close() + { + pData.isQuitting = true; + hide(); +#ifndef DISTRHO_OS_WINDOWS + if (ext.inUse) + terminateAndWaitForExternalProcess(); +#endif + } + + /** + Set width of this window. + Can trigger a sizeChanged callback. + Only relevant to hosts when the UI is embedded. + */ + void setWidth(uint width) + { + setSize(width, getHeight()); + } + + /** + Set height of this window. + Can trigger a sizeChanged callback. + Only relevant to hosts when the UI is embedded. + */ + void setHeight(uint height) + { + setSize(getWidth(), height); + } + + /** + Set size of this window using @a width and @a height values. + Can trigger a sizeChanged callback. + Only relevant to hosts when the UI is embedded. + */ + void setSize(uint width, uint height) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(width > 1, width,); + DISTRHO_SAFE_ASSERT_UINT_RETURN(height > 1, height,); + + if (pData.width == width && pData.height == height) + return; + + pData.width = width; + pData.height = height; + sizeChanged(width, height); + } + + /** + Set the title of the window, typically displayed in the title bar or in window switchers. + Can trigger a titleChanged callback. + Only relevant to hosts when the UI is not embedded. + */ + void setTitle(const char* title) + { + if (pData.title == title) + return; + + pData.title = title; + titleChanged(title); + } + + /** + Set geometry constraints for the Window when resized by the user. + */ + void setGeometryConstraints(uint minimumWidth, uint minimumHeight, bool keepAspectRatio = false) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumWidth > 0, minimumWidth,); + DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumHeight > 0, minimumHeight,); + + pData.minWidth = minimumWidth; + pData.minHeight = minimumHeight; + pData.keepAspectRatio = keepAspectRatio; + } + + /* -------------------------------------------------------------------------------------------------------- + * TopLevelWidget-like calls - actions called by the host */ + + /** + Show window. + This is the same as calling setVisible(true). + @see isVisible(), setVisible(bool) + */ + void show() + { + setVisible(true); + } + + /** + Set window visible (or not) according to @a visible. + @see isVisible(), hide(), show() + */ + void setVisible(bool visible) + { + if (pData.visible == visible) + return; + + pData.visible = visible; + visibilityChanged(visible); + } + + /** + Called by the host to set the transient parent window that we should attach ourselves to. + TODO what id? also NSView* on macOS, or NSWindow? + */ + void setTransientWindowId(uintptr_t winId) + { + if (pData.transientWinId == winId) + return; + + pData.transientWinId = winId; + transientParentWindowChanged(winId); + } + +protected: + /* -------------------------------------------------------------------------------------------------------- + * ExternalWindow special calls for running externals tools */ + + bool startExternalProcess(const char* args[]) + { +#ifndef DISTRHO_OS_WINDOWS + ext.inUse = true; + + return ext.start(args); +#else + (void)args; + return false; // TODO +#endif + } + + void terminateAndWaitForExternalProcess() + { +#ifndef DISTRHO_OS_WINDOWS + ext.isQuitting = true; + ext.terminateAndWait(); +#else + // TODO +#endif + } + + /* -------------------------------------------------------------------------------------------------------- + * ExternalWindow specific callbacks */ + + /** + A callback for when the window size changes. + @note WIP this might need to get fed back into the host somehow. + */ + virtual void sizeChanged(uint /* width */, uint /* height */) + { + // unused, meant for custom implementations + } + + /** + A callback for when the window title changes. + @note WIP this might need to get fed back into the host somehow. + */ + virtual void titleChanged(const char* /* title */) + { + // unused, meant for custom implementations + } + + /** + A callback for when the window visibility changes. + @note WIP this might need to get fed back into the host somehow. + */ + virtual void visibilityChanged(bool /* visible */) + { + // unused, meant for custom implementations + } + + /** + A callback for when the transient parent window changes. + */ + virtual void transientParentWindowChanged(uintptr_t /* winId */) + { + // unused, meant for custom implementations + } + +private: + friend class PluginWindow; + friend class UI; + +#ifndef DISTRHO_OS_WINDOWS + struct ExternalProcess { + bool inUse; + bool isQuitting; + mutable pid_t pid; + + ExternalProcess() + : inUse(false), + isQuitting(false), + pid(0) {} + + bool isRunning() const noexcept + { + if (pid <= 0) + return false; + + const pid_t p = ::waitpid(pid, nullptr, WNOHANG); + + if (p == pid || (p == -1 && errno == ECHILD)) + { + d_stdout("NOTICE: Child process exited while idle"); + pid = 0; + return false; + } + + return true; + } + + bool start(const char* args[]) + { + terminateAndWait(); + + pid = vfork(); + + switch (pid) + { + case 0: + execvp(args[0], (char**)args); + _exit(1); + return false; + + case -1: + d_stderr("Could not start external ui"); + return false; + + default: + return true; + } + } + + void terminateAndWait() + { + if (pid <= 0) + return; + + d_stdout("Waiting for external process to stop,,,"); + + bool sendTerm = true; + + for (pid_t p;;) + { + p = ::waitpid(pid, nullptr, WNOHANG); + + switch (p) + { + case 0: + if (sendTerm) + { + sendTerm = false; + ::kill(pid, SIGTERM); + } + break; + + case -1: + if (errno == ECHILD) + { + d_stdout("Done! (no such process)"); + pid = 0; + return; + } + break; + + default: + if (p == pid) + { + d_stdout("Done! (clean wait)"); + pid = 0; + return; + } + break; + } + + // 5 msec + usleep(5*1000); + } + } + } ext; +#endif + + struct PrivateData { + uintptr_t parentWindowHandle; + uintptr_t transientWinId; + uint width; + uint height; + double scaleFactor; + String title; + uint minWidth; + uint minHeight; + bool keepAspectRatio; + bool isQuitting; + bool isStandalone; + bool visible; + + PrivateData() + : parentWindowHandle(0), + transientWinId(0), + width(1), + height(1), + scaleFactor(1.0), + title(), + minWidth(0), + minHeight(0), + keepAspectRatio(false), + isQuitting(false), + isStandalone(false), + visible(false) {} + } pData; + + DISTRHO_DECLARE_NON_COPYABLE(ExternalWindow) +}; + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_EXTERNAL_WINDOW_HPP_INCLUDED