|
- /*
- * 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
|