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