From a23952199481e42c85fc45397f58bba39fc05789 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 4 May 2024 20:25:08 +0200 Subject: [PATCH] Add initial bits for web ui, starting with linux Signed-off-by: falkTX --- dgl/Web.hpp | 63 ++ dgl/src/Web.cpp | 81 +++ distrho/extra/ChildProcess.hpp | 276 +++++++++ distrho/extra/FileBrowserDialogImpl.hpp | 6 +- distrho/extra/Time.hpp | 127 ++++ distrho/extra/WebView.hpp | 28 + distrho/extra/WebViewImpl.cpp | 585 ++++++++++++++++++ distrho/extra/WebViewImpl.hpp | 34 + distrho/src/DistrhoUI.cpp | 10 + examples/WebMeters/CMakeLists.txt | 12 + examples/WebMeters/DistrhoPluginInfo.h | 44 ++ examples/WebMeters/ExamplePluginWebMeters.cpp | 288 +++++++++ examples/WebMeters/ExampleUIWebMeters.cpp | 72 +++ examples/WebMeters/Makefile | 50 ++ examples/WebMeters/README.md | 8 + 15 files changed, 1681 insertions(+), 3 deletions(-) create mode 100644 dgl/Web.hpp create mode 100644 dgl/src/Web.cpp create mode 100644 distrho/extra/ChildProcess.hpp create mode 100644 distrho/extra/Time.hpp create mode 100644 distrho/extra/WebView.hpp create mode 100644 distrho/extra/WebViewImpl.cpp create mode 100644 distrho/extra/WebViewImpl.hpp create mode 100644 examples/WebMeters/CMakeLists.txt create mode 100644 examples/WebMeters/DistrhoPluginInfo.h create mode 100644 examples/WebMeters/ExamplePluginWebMeters.cpp create mode 100644 examples/WebMeters/ExampleUIWebMeters.cpp create mode 100644 examples/WebMeters/Makefile create mode 100644 examples/WebMeters/README.md diff --git a/dgl/Web.hpp b/dgl/Web.hpp new file mode 100644 index 00000000..41c6f730 --- /dev/null +++ b/dgl/Web.hpp @@ -0,0 +1,63 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 Filipe Coelho + * + * 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 DGL_WEBVIEW_HPP_INCLUDED +#define DGL_WEBVIEW_HPP_INCLUDED + +#include "TopLevelWidget.hpp" + +// -------------------------------------------------------------------------------------------------------------------- + +START_NAMESPACE_DISTRHO + +struct WebViewData; +typedef WebViewData* WebViewHandle; + +END_NAMESPACE_DISTRHO + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- +// WebViewWidget + +class WebViewWidget : public TopLevelWidget +{ +public: + /** + Constructor for a WebViewWidget. + */ + explicit WebViewWidget(Window& windowToMapTo); + + /** + Destructor. + */ + ~WebViewWidget() override; + +protected: + void onResize(const ResizeEvent& ev) override; + +private: + const DISTRHO_NAMESPACE::WebViewHandle webview; + void onDisplay() {} + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WebViewWidget) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif // DGL_WEBVIEW_HPP_INCLUDED diff --git a/dgl/src/Web.cpp b/dgl/src/Web.cpp new file mode 100644 index 00000000..4e81f60f --- /dev/null +++ b/dgl/src/Web.cpp @@ -0,0 +1,81 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 Filipe Coelho + * + * 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. + */ + +#include "../Web.hpp" + +#include "../distrho/extra/WebView.hpp" + +#include "TopLevelWidgetPrivateData.hpp" +#include "WindowPrivateData.hpp" + +START_NAMESPACE_DGL + +// ----------------------------------------------------------------------- + +WebViewWidget::WebViewWidget(Window& windowToMapTo) + : TopLevelWidget(windowToMapTo), + webview(addWebView(windowToMapTo.getNativeWindowHandle(), + 0, 0, + windowToMapTo.getWidth(), + windowToMapTo.getHeight(), + windowToMapTo.getScaleFactor())) {} + +WebViewWidget::~WebViewWidget() +{ + if (webview != nullptr) + destroyWebView(webview); +} + +void WebViewWidget::onResize(const ResizeEvent& ev) +{ + TopLevelWidget::onResize(ev); + + if (webview != nullptr) + resizeWebView(webview, 0, 0, ev.size.getWidth(), ev.size.getHeight()); +} + +// ----------------------------------------------------------------------- + +static void notImplemented(const char* const name) +{ + d_stderr2("web function not implemented: %s", name); +} + +// ----------------------------------------------------------------------- + +void TopLevelWidget::PrivateData::display() +{ +} + +// ----------------------------------------------------------------------- + +void Window::PrivateData::renderToPicture(const char*, const GraphicsContext&, uint, uint) +{ + notImplemented("Window::PrivateData::renderToPicture"); +} + +// ----------------------------------------------------------------------- + +const GraphicsContext& Window::PrivateData::getGraphicsContext() const noexcept +{ + return (const GraphicsContext&)graphicsContext; +} + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DGL + +// ----------------------------------------------------------------------- diff --git a/distrho/extra/ChildProcess.hpp b/distrho/extra/ChildProcess.hpp new file mode 100644 index 00000000..16f76ab2 --- /dev/null +++ b/distrho/extra/ChildProcess.hpp @@ -0,0 +1,276 @@ +// SPDX-FileCopyrightText: 2023-2024 MOD Audio UG +// SPDX-License-Identifier: AGPL-3.0-or-later + +#pragma once + +#include "extra/Sleep.hpp" +#include "Time.hpp" + +#ifdef DISTRHO_OS_WINDOWS +# include +# include +# include +#else +# include +# include +# include +# include +#endif + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +class ChildProcess +{ + #ifdef _WIN32 + PROCESS_INFORMATION pinfo = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 }; + #else + pid_t pid = -1; + #endif + +public: + ChildProcess() + { + } + + ~ChildProcess() + { + stop(); + } + + #ifdef _WIN32 + bool start(const char* const args[], const WCHAR* const envp) + #else + bool start(const char* const args[], char* const* const envp = nullptr) + #endif + { + #ifdef _WIN32 + std::string cmd; + + for (uint i = 0; args[i] != nullptr; ++i) + { + if (i != 0) + cmd += " "; + + if (args[i][0] != '"' && std::strchr(args[i], ' ') != nullptr) + { + cmd += "\""; + cmd += args[i]; + cmd += "\""; + } + else + { + cmd += args[i]; + } + } + + wchar_t wcmd[PATH_MAX]; + if (MultiByteToWideChar(CP_UTF8, 0, cmd.data(), -1, wcmd, PATH_MAX) <= 0) + return false; + + STARTUPINFOW si = {}; + si.cb = sizeof(si); + + d_stdout("will start process with args '%s'", cmd.data()); + + return CreateProcessW(nullptr, // lpApplicationName + wcmd, // lpCommandLine + nullptr, // lpProcessAttributes + nullptr, // lpThreadAttributes + TRUE, // bInheritHandles + CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags + const_cast(envp), // lpEnvironment + nullptr, // lpCurrentDirectory + &si, // lpStartupInfo + &pinfo) != FALSE; + #else + const pid_t ret = pid = vfork(); + + switch (ret) + { + // child process + case 0: + if (envp != nullptr) + execve(args[0], const_cast(args), envp); + else + execvp(args[0], const_cast(args)); + + d_stderr2("exec failed: %d:%s", errno, std::strerror(errno)); + _exit(1); + break; + + // error + case -1: + d_stderr2("vfork() failed: %d:%s", errno, std::strerror(errno)); + break; + } + + return ret > 0; + #endif + } + + void stop(const uint32_t timeoutInMilliseconds = 2000) + { + const uint32_t timeout = d_gettime_ms() + timeoutInMilliseconds; + bool sendTerminate = true; + + #ifdef _WIN32 + if (pinfo.hProcess == INVALID_HANDLE_VALUE) + return; + + const PROCESS_INFORMATION opinfo = pinfo; + pinfo = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 }; + + for (DWORD exitCode;;) + { + if (GetExitCodeProcess(opinfo.hProcess, &exitCode) == FALSE || + exitCode != STILL_ACTIVE || + WaitForSingleObject(opinfo.hProcess, 0) != WAIT_TIMEOUT) + { + CloseHandle(opinfo.hThread); + CloseHandle(opinfo.hProcess); + return; + } + + if (sendTerminate) + { + sendTerminate = false; + TerminateProcess(opinfo.hProcess, ERROR_BROKEN_PIPE); + } + + if (d_gettime_ms() < timeout) + { + d_msleep(5); + continue; + } + d_stderr("ChildProcess::stop() - timed out"); + TerminateProcess(opinfo.hProcess, 9); + d_msleep(5); + CloseHandle(opinfo.hThread); + CloseHandle(opinfo.hProcess); + break; + } + #else + if (pid <= 0) + return; + + const pid_t opid = pid; + pid = -1; + + for (pid_t ret;;) + { + try { + ret = ::waitpid(opid, nullptr, WNOHANG); + } DISTRHO_SAFE_EXCEPTION_BREAK("waitpid"); + + switch (ret) + { + case -1: + if (errno == ECHILD) + { + // success, child doesn't exist + return; + } + else + { + d_stderr("ChildProcess::stop() - waitpid failed: %d:%s", errno, std::strerror(errno)); + return; + } + break; + + case 0: + if (sendTerminate) + { + sendTerminate = false; + kill(opid, SIGTERM); + } + if (d_gettime_ms() < timeout) + { + d_msleep(5); + continue; + } + + d_stderr("ChildProcess::stop() - timed out"); + kill(opid, SIGKILL); + waitpid(opid, nullptr, WNOHANG); + break; + + default: + if (ret == opid) + { + // success + return; + } + else + { + d_stderr("ChildProcess::stop() - got wrong pid %i (requested was %i)", int(ret), int(opid)); + return; + } + } + + break; + } + #endif + } + + bool isRunning() + { + #ifdef _WIN32 + if (pinfo.hProcess == INVALID_HANDLE_VALUE) + return false; + + DWORD exitCode; + if (GetExitCodeProcess(pinfo.hProcess, &exitCode) == FALSE || + exitCode != STILL_ACTIVE || + WaitForSingleObject(pinfo.hProcess, 0) != WAIT_TIMEOUT) + { + const PROCESS_INFORMATION opinfo = pinfo; + pinfo = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, 0, 0 }; + CloseHandle(opinfo.hThread); + CloseHandle(opinfo.hProcess); + return false; + } + + return true; + #else + if (pid <= 0) + return false; + + const pid_t ret = ::waitpid(pid, nullptr, WNOHANG); + + if (ret == pid || (ret == -1 && errno == ECHILD)) + { + pid = 0; + return false; + } + + return true; + #endif + } + + #ifndef _WIN32 + void signal(const int sig) + { + if (pid > 0) + kill(pid, sig); + } + #endif + + void terminate() + { + #ifdef _WIN32 + if (pinfo.hProcess != INVALID_HANDLE_VALUE) + TerminateProcess(pinfo.hProcess, 15); + #else + if (pid > 0) + kill(pid, SIGTERM); + #endif + } + + DISTRHO_DECLARE_NON_COPYABLE(ChildProcess) +}; + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/distrho/extra/FileBrowserDialogImpl.hpp b/distrho/extra/FileBrowserDialogImpl.hpp index 225e34f9..db01719b 100644 --- a/distrho/extra/FileBrowserDialogImpl.hpp +++ b/distrho/extra/FileBrowserDialogImpl.hpp @@ -109,17 +109,17 @@ FileBrowserHandle fileBrowserCreate(bool isEmbed, in which case this idle function must not be called anymore for this handle. You can then call fileBrowserGetPath to know the selected file (or null if cancelled). */ -bool fileBrowserIdle(const FileBrowserHandle handle); +bool fileBrowserIdle(FileBrowserHandle handle); /** Close and free the file browser dialog, handle must not be used afterwards. */ -void fileBrowserClose(const FileBrowserHandle handle); +void fileBrowserClose(FileBrowserHandle handle); /** Get the path chosen by the user or null.@n Should only be called after fileBrowserIdle returns true. */ -const char* fileBrowserGetPath(const FileBrowserHandle handle); +const char* fileBrowserGetPath(FileBrowserHandle handle); // -------------------------------------------------------------------------------------------------------------------- diff --git a/distrho/extra/Time.hpp b/distrho/extra/Time.hpp new file mode 100644 index 00000000..91a1987a --- /dev/null +++ b/distrho/extra/Time.hpp @@ -0,0 +1,127 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 Filipe Coelho + * + * 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_TIME_HPP_INCLUDED +#define DISTRHO_TIME_HPP_INCLUDED + +#include "DistrhoUtils.hpp" + +#ifdef DISTRHO_OS_WINDOWS +# include +# include +# include +#else +# include +#endif + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- +// d_gettime_* + +/* + * Get a monotonically-increasing time in milliseconds. + */ +static inline +uint32_t d_gettime_ms() noexcept +{ + #if defined(DISTRHO_OS_MAC) + static const time_t s = clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1000000; + return (clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1000000) - s; + #elif defined(DISTRHO_OS_WINDOWS) + return static_cast(timeGetTime()); + #else + static struct { + timespec ts; + int r; + uint32_t ms; + } s = { {}, clock_gettime(CLOCK_MONOTONIC, &s.ts), static_cast(s.ts.tv_sec * 1000 + + s.ts.tv_nsec / 1000000) }; + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000) - s.ms; + #endif +} + +/* + * Get a monotonically-increasing time in microseconds. + */ +static inline +uint64_t d_gettime_us() noexcept +{ + #if defined(DISTRHO_OS_MAC) + static const uint64_t s = clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1000; + return (clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1000) - s; + #elif defined(DISTRHO_OS_WINDOWS) + static struct { + LARGE_INTEGER freq; + LARGE_INTEGER counter; + BOOL r1, r2; + } s = { {}, {}, QueryPerformanceFrequency(&s.freq), QueryPerformanceCounter(&s.counter) }; + + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return (counter.QuadPart - s.counter.QuadPart) * 1000000 / s.freq.QuadPart; + #else + static struct { + timespec ts; + int r; + uint64_t us; + } s = { {}, clock_gettime(CLOCK_MONOTONIC, &s.ts), static_cast(s.ts.tv_sec * 1000000 + + s.ts.tv_nsec / 1000) }; + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (ts.tv_sec * 1000000 + ts.tv_nsec / 1000) - s.us; + #endif +} + +/* + * Get a monotonically-increasing time in nanoseconds. + */ +static inline +uint64_t d_gettime_ns() noexcept +{ + #if defined(DISTRHO_OS_MAC) + static const uint64_t s = clock_gettime_nsec_np(CLOCK_UPTIME_RAW); + return clock_gettime_nsec_np(CLOCK_UPTIME_RAW) - s; + #elif defined(DISTRHO_OS_WINDOWS) + static struct { + LARGE_INTEGER freq; + LARGE_INTEGER counter; + BOOL r1, r2; + } s = { {}, {}, QueryPerformanceFrequency(&s.freq), QueryPerformanceCounter(&s.counter) }; + + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return (counter.QuadPart - s.counter.QuadPart) * 1000000000ULL / s.freq.QuadPart; + #else + static struct { + timespec ts; + int r; + uint64_t ns; + } s = { {}, clock_gettime(CLOCK_MONOTONIC, &s.ts), static_cast(s.ts.tv_sec * 1000000000ULL + + s.ts.tv_nsec) }; + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (ts.tv_sec * 1000000000ULL + ts.tv_nsec) - s.ns; + #endif +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_TIME_HPP_INCLUDED diff --git a/distrho/extra/WebView.hpp b/distrho/extra/WebView.hpp new file mode 100644 index 00000000..61a80a30 --- /dev/null +++ b/distrho/extra/WebView.hpp @@ -0,0 +1,28 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 Filipe Coelho + * + * 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_WEBVIEW_HPP_INCLUDED +#define DISTRHO_WEBVIEW_HPP_INCLUDED + +#include "../DistrhoUtils.hpp" + +START_NAMESPACE_DISTRHO + +#include "WebViewImpl.hpp" + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_WEBVIEW_HPP_INCLUDED diff --git a/distrho/extra/WebViewImpl.cpp b/distrho/extra/WebViewImpl.cpp new file mode 100644 index 00000000..81b31cb0 --- /dev/null +++ b/distrho/extra/WebViewImpl.cpp @@ -0,0 +1,585 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 Filipe Coelho + * + * 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. + */ + +#if !defined(DISTRHO_WEBVIEW_HPP_INCLUDED) && !defined(DGL_WEBVIEW_HPP_INCLUDED) +# error bad include +#endif +#if !defined(WEBVIEW_DISTRHO_NAMESPACE) && !defined(WEBVIEW_DGL_NAMESPACE) +# error bad usage +#endif + +// #define QT_NO_VERSION_TAGGING +// #include +// #include +// #include +// #undef signals + +#include "ChildProcess.hpp" +#include "String.hpp" + +#include +#include +#include +#include +#include +#include + +#ifdef WEBVIEW_DGL_NAMESPACE +START_NAMESPACE_DGL +using DISTRHO_NAMESPACE::String; +#else +START_NAMESPACE_DISTRHO +#endif + +// ----------------------------------------------------------------------------------------------------------- + +struct WebViewData { + ChildProcess p; + ::Display* display; + ::Window childWindow; + ::Window ourWindow; +}; + +// ----------------------------------------------------------------------------------------------------------- + +static void getFilenameFromFunctionPtr(char filename[PATH_MAX], const void* const ptr) +{ + Dl_info info = {}; + dladdr(ptr, &info); + + if (info.dli_fname[0] == '.') + { + getcwd(filename, PATH_MAX - 1); + std::strncat(filename, info.dli_fname + 1, PATH_MAX - 1); + } + else if (info.dli_fname[0] != '/') + { + getcwd(filename, PATH_MAX - 1); + std::strncat(filename, "/", PATH_MAX - 1); + std::strncat(filename, info.dli_fname, PATH_MAX - 1); + } + else + { + std::strncpy(filename, info.dli_fname, PATH_MAX - 1); + } +} + +WebViewHandle addWebView(const uintptr_t parentWindowId, + const int x, + const int y, + const uint width, + const uint height, + const double scaleFactor) +{ + char ldlinux[PATH_MAX] = {}; + getFilenameFromFunctionPtr(ldlinux, dlsym(nullptr, "_rtld_global")); + + char filename[PATH_MAX] = {}; + getFilenameFromFunctionPtr(filename, reinterpret_cast(addWebView)); + + d_stdout("ld-linux is '%s'", ldlinux); + d_stdout("filename is '%s'", filename); + + ::Display* const display = XOpenDisplay(nullptr); + DISTRHO_SAFE_ASSERT_RETURN(display != nullptr, nullptr); + + // set up custom child environment + uint envsize = 0; + while (environ[envsize] != nullptr) + ++envsize; + + char** const envp = new char*[envsize + 5]; + { + uint e = 0; + for (uint i = 0; i < envsize; ++i) + { + if (std::strncmp(environ[i], "LD_PRELOAD=", 11) == 0) + continue; + if (std::strncmp(environ[i], "LD_LIBRARY_PATH=", 16) == 0) + continue; + envp[e++] = strdup(environ[i]); + } + + envp[e++] = strdup("LANG=en_US.UTF-8"); + envp[e++] = ("DPF_WEBVIEW_SCALE_FACTOR=" + String(scaleFactor)).getAndReleaseBuffer(); + envp[e++] = ("DPF_WEBVIEW_WIN_ID=" +String(parentWindowId)).getAndReleaseBuffer(); + + for (uint i = e; i < envsize + 5; ++i) + envp[e++] = nullptr; + } + + WebViewData* const handle = new WebViewData(); + handle->display = display; + handle->childWindow = 0; + handle->ourWindow = parentWindowId; + + const char* const args[] = { ldlinux, filename, "dpf-ld-linux-webview", nullptr }; + handle->p.start(args, envp); + + for (uint i = 0; envp[i] != nullptr; ++i) + std::free(envp[i]); + delete[] envp; + + return handle; +} + +void destroyWebView(const WebViewHandle handle) +{ + XCloseDisplay(handle->display); + delete handle; +} + +void reloadWebView(const WebViewHandle handle, uint) +{ + handle->p.signal(SIGUSR1); +} + +void resizeWebView(const WebViewHandle handle, int x, int y, uint width, uint height) +{ + if (handle->childWindow == 0) + { + ::Window rootWindow, parentWindow; + ::Window* childWindows = nullptr; + uint numChildren = 0; + + XFlush(handle->display); + XQueryTree(handle->display, handle->ourWindow, &rootWindow, &parentWindow, &childWindows, &numChildren); + + if (numChildren == 0 || childWindows == nullptr) + return; + + handle->childWindow = childWindows[0]; + XFree(childWindows); + } + + XMoveResizeWindow(handle->display, handle->childWindow, x, y, width, height); + XFlush(handle->display); +} + +// ----------------------------------------------------------------------------------------------------------- + +static std::function reloadFn; + +// ----------------------------------------------------------------------------------------------------------- + +struct GtkContainer; +struct GtkPlug; +struct GtkWidget; +struct GtkWindow; +struct WebKitSettings; +struct WebKitWebView; + +#define GTK_CONTAINER(p) reinterpret_cast(p) +#define GTK_PLUG(p) reinterpret_cast(p) +#define GTK_WINDOW(p) reinterpret_cast(p) +#define WEBKIT_WEB_VIEW(p) reinterpret_cast(p) + +// struct QApplication; +// struct QUrl; +// struct QWebEngineView; +// struct QWindow; + +// ----------------------------------------------------------------------------------------------------------- + +#define JOIN(A, B) A ## B + +#define AUTOSYM(S) \ + using JOIN(gtk3_, S) = decltype(&S); \ + JOIN(gtk3_, S) S = reinterpret_cast(dlsym(nullptr, #S)); \ + DISTRHO_SAFE_ASSERT_RETURN(S != nullptr, false); + +#define CSYM(S, NAME) \ + S NAME = reinterpret_cast(dlsym(nullptr, #NAME)); \ + DISTRHO_SAFE_ASSERT_RETURN(NAME != nullptr, false); + +#define CPPSYM(S, NAME, SN) \ + S NAME = reinterpret_cast(dlsym(nullptr, #SN)); \ + DISTRHO_SAFE_ASSERT_RETURN(NAME != nullptr, false); + +// ----------------------------------------------------------------------------------------------------------- +// gtk3 variant + +static bool gtk3(Display* const display, + const Window winId, + const uint x, + const uint y, + const uint width, + const uint height, + double scaleFactor, + const char* const url) +{ + void* lib; + if ((lib = dlopen("libwebkit2gtk-4.0.so.37", RTLD_NOW|RTLD_GLOBAL)) == nullptr || + (lib = dlopen("libwebkit2gtk-4.0.so", RTLD_NOW|RTLD_GLOBAL)) == nullptr) + return false; + + using gdk_set_allowed_backends_t = void (*)(const char*); + using gtk_container_add_t = void (*)(GtkContainer*, GtkWidget*); + using gtk_init_check_t = bool (*)(int*, char***); + using gtk_main_t = void (*)(); + using gtk_plug_get_id_t = Window (*)(GtkPlug*); + using gtk_plug_new_t = GtkWidget* (*)(Window); + using gtk_widget_show_all_t = void (*)(GtkWidget*); + using gtk_window_move_t = void (*)(GtkWindow*, int, int); + using gtk_window_set_default_size_t = void (*)(GtkWindow*, int, int); + using webkit_settings_new_t = WebKitSettings* (*)(); + using webkit_settings_set_hardware_acceleration_policy_t = void (*)(WebKitSettings*, int); + using webkit_settings_set_javascript_can_access_clipboard_t = void (*)(WebKitSettings*, bool); + using webkit_web_view_load_uri_t = void (*)(WebKitWebView*, const char*); + using webkit_web_view_new_with_settings_t = GtkWidget* (*)(WebKitSettings*); + + CSYM(gdk_set_allowed_backends_t, gdk_set_allowed_backends) + CSYM(gtk_container_add_t, gtk_container_add) + CSYM(gtk_init_check_t, gtk_init_check) + CSYM(gtk_main_t, gtk_main) + CSYM(gtk_plug_get_id_t, gtk_plug_get_id) + CSYM(gtk_plug_new_t, gtk_plug_new) + CSYM(gtk_widget_show_all_t, gtk_widget_show_all) + CSYM(gtk_window_move_t, gtk_window_move) + CSYM(gtk_window_set_default_size_t, gtk_window_set_default_size) + CSYM(webkit_settings_new_t, webkit_settings_new) + CSYM(webkit_settings_set_hardware_acceleration_policy_t, webkit_settings_set_hardware_acceleration_policy) + CSYM(webkit_settings_set_javascript_can_access_clipboard_t, webkit_settings_set_javascript_can_access_clipboard) + CSYM(webkit_web_view_load_uri_t, webkit_web_view_load_uri) + CSYM(webkit_web_view_new_with_settings_t, webkit_web_view_new_with_settings) + + const int gdkScale = std::fmod(scaleFactor, 1.0) >= 0.75 + ? static_cast(scaleFactor + 0.5) + : static_cast(scaleFactor); + + if (gdkScale != 1) + { + char scale[8] = {}; + std::snprintf(scale, 7, "%d", gdkScale); + setenv("GDK_SCALE", scale, 1); + + std::snprintf(scale, 7, "%.2f", (1.0 / scaleFactor) * 1.2); + setenv("GDK_DPI_SCALE", scale, 1); + } + else if (scaleFactor > 1.0) + { + char scale[8] = {}; + std::snprintf(scale, 7, "%.2f", (1.0 / scaleFactor) * 1.4); + setenv("GDK_DPI_SCALE", scale, 1); + } + + scaleFactor /= gdkScale; + + gdk_set_allowed_backends("x11"); + + if (! gtk_init_check (nullptr, nullptr)) + return false; + + GtkWidget* const window = gtk_plug_new(winId); + DISTRHO_SAFE_ASSERT_RETURN(window != nullptr, false); + + gtk_window_set_default_size(GTK_WINDOW(window), + (width - x) * scaleFactor, + (height - y) * scaleFactor); + gtk_window_move(GTK_WINDOW(window), x * scaleFactor, y * scaleFactor); + + WebKitSettings* const settings = webkit_settings_new(); + DISTRHO_SAFE_ASSERT_RETURN(settings != nullptr, false); + + webkit_settings_set_javascript_can_access_clipboard(settings, true); + webkit_settings_set_hardware_acceleration_policy(settings, 2 /* WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER */); + + GtkWidget* const webview = webkit_web_view_new_with_settings(settings); + DISTRHO_SAFE_ASSERT_RETURN(webview != nullptr, false); + + webkit_web_view_load_uri(WEBKIT_WEB_VIEW (webview), url); + + gtk_container_add(GTK_CONTAINER(window), webview); + + gtk_widget_show_all(window); + + Window wid = gtk_plug_get_id(GTK_PLUG(window)); + XMapWindow(display, wid); + XFlush(display); + + reloadFn = [=](){ + webkit_web_view_load_uri(WEBKIT_WEB_VIEW (webview), url); + }; + + gtk_main(); + + dlclose(lib); + return true; +} + +#if 0 +// ----------------------------------------------------------------------------------------------------------- +// qt5webengine variant + +static bool qt5webengine(const Window winId, const double scaleFactor, const char* const url) +{ + void* lib; + if ((lib = dlopen("libQt5WebEngineWidgets.so.5", RTLD_NOW|RTLD_GLOBAL)) == nullptr || + (lib = dlopen("libQt5WebEngineWidgets.so", RTLD_NOW|RTLD_GLOBAL)) == nullptr) + return false; + + using QApplication__init_t = void (*)(QApplication*, int&, char**, int); + using QApplication_exec_t = void (*)(); + using QApplication_setAttribute_t = void (*)(Qt::ApplicationAttribute, bool); + using QString__init_t = void (*)(void*, const QChar*, ptrdiff_t); + using QUrl__init_t = void (*)(void*, const QString&, int /* QUrl::ParsingMode */); + using QWebEngineView__init_t = void (*)(QWebEngineView*, void*); + using QWebEngineView_move_t = void (*)(QWebEngineView*, const QPoint&); + using QWebEngineView_resize_t = void (*)(QWebEngineView*, const QSize&); + using QWebEngineView_setUrl_t = void (*)(QWebEngineView*, const QUrl&); + using QWebEngineView_show_t = void (*)(QWebEngineView*); + using QWebEngineView_winId_t = ulonglong (*)(QWebEngineView*); + using QWebEngineView_windowHandle_t = QWindow* (*)(QWebEngineView*); + using QWindow_fromWinId_t = QWindow* (*)(ulonglong); + using QWindow_setParent_t = void (*)(QWindow*, void*); + + CPPSYM(QApplication__init_t, QApplication__init, _ZN12QApplicationC1ERiPPci) + CPPSYM(QApplication_exec_t, QApplication_exec, _ZN15QGuiApplication4execEv) + CPPSYM(QApplication_setAttribute_t, QApplication_setAttribute, _ZN16QCoreApplication12setAttributeEN2Qt20ApplicationAttributeEb) + CPPSYM(QString__init_t, QString__init, _ZN7QStringC2EPK5QChari) + CPPSYM(QUrl__init_t, QUrl__init, _ZN4QUrlC1ERK7QStringNS_11ParsingModeE) + CPPSYM(QWebEngineView__init_t, QWebEngineView__init, _ZN14QWebEngineViewC1EP7QWidget) + CPPSYM(QWebEngineView_move_t, QWebEngineView_move, _ZN7QWidget4moveERK6QPoint) + CPPSYM(QWebEngineView_resize_t, QWebEngineView_resize, _ZN7QWidget6resizeERK5QSize) + CPPSYM(QWebEngineView_setUrl_t, QWebEngineView_setUrl, _ZN14QWebEngineView6setUrlERK4QUrl) + CPPSYM(QWebEngineView_show_t, QWebEngineView_show, _ZN7QWidget4showEv) + CPPSYM(QWebEngineView_winId_t, QWebEngineView_winId, _ZNK7QWidget5winIdEv) + CPPSYM(QWebEngineView_windowHandle_t, QWebEngineView_windowHandle, _ZNK7QWidget12windowHandleEv) + CPPSYM(QWindow_fromWinId_t, QWindow_fromWinId, _ZN7QWindow9fromWinIdEy) + CPPSYM(QWindow_setParent_t, QWindow_setParent, _ZN7QWindow9setParentEPS_) + + unsetenv("QT_FONT_DPI"); + unsetenv("QT_SCREEN_SCALE_FACTORS"); + unsetenv("QT_USE_PHYSICAL_DPI"); + setenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0", 1); + + char scale[8] = {}; + std::snprintf(scale, 7, "%.2f", scaleFactor); + setenv("QT_SCALE_FACTOR", scale, 1); + + QApplication_setAttribute(Qt::AA_X11InitThreads, true); + QApplication_setAttribute(Qt::AA_EnableHighDpiScaling, true); + QApplication_setAttribute(Qt::AA_UseHighDpiPixmaps, true); + + static int argc = 0; + static char* argv[] = { nullptr }; + + uint8_t _app[64]; // sizeof(QApplication) == 16 + QApplication* const app = reinterpret_cast(_app); + QApplication__init(app, argc, argv, 0); + + uint8_t _qstrurl[32]; // sizeof(QString) == 8 + QString* const qstrurl(reinterpret_cast(_qstrurl)); + + { + const size_t url_len = std::strlen(url); + QChar* const url_qchar = new QChar[url_len + 1]; + + for (size_t i = 0; i < url_len; ++i) + url_qchar[i] = QChar(url[i]); + + url_qchar[url_len] = 0; + + QString__init(qstrurl, url_qchar, url_len); + } + + uint8_t _qurl[32]; // sizeof(QUrl) == 8 + QUrl* const qurl(reinterpret_cast(_qurl)); + QUrl__init(qurl, *qstrurl, 1 /* QUrl::StrictMode */); + + uint8_t _webview[128]; // sizeof(QWebEngineView) == 56 + QWebEngineView* const webview = reinterpret_cast(_webview); + QWebEngineView__init(webview, nullptr); + + QWebEngineView_move(webview, QPoint(0, kVerticalOffset)); + QWebEngineView_resize(webview, QSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT - kVerticalOffset)); + QWebEngineView_winId(webview); + QWindow_setParent(QWebEngineView_windowHandle(webview), QWindow_fromWinId(winId)); + QWebEngineView_setUrl(webview, *qurl); + QWebEngineView_show(webview); + + reloadFn = [=](){ + QWebEngineView_setUrl(webview, *qurl); + }; + + QApplication_exec(); + + dlclose(lib); + return true; +} + +// ----------------------------------------------------------------------------------------------------------- +// qt6webengine variant (same as qt5 but `QString__init_t` has different arguments) + +static bool qt6webengine(const Window winId, const double scaleFactor, const char* const url) +{ + void* lib; + if ((lib = dlopen("libQt6WebEngineWidgets.so.6", RTLD_NOW|RTLD_GLOBAL)) == nullptr || + (lib = dlopen("libQt6WebEngineWidgets.so", RTLD_NOW|RTLD_GLOBAL)) == nullptr) + return false; + + using QApplication__init_t = void (*)(QApplication*, int&, char**, int); + using QApplication_exec_t = void (*)(); + using QApplication_setAttribute_t = void (*)(Qt::ApplicationAttribute, bool); + using QString__init_t = void (*)(void*, const QChar*, long long); + using QUrl__init_t = void (*)(void*, const QString&, int /* QUrl::ParsingMode */); + using QWebEngineView__init_t = void (*)(QWebEngineView*, void*); + using QWebEngineView_move_t = void (*)(QWebEngineView*, const QPoint&); + using QWebEngineView_resize_t = void (*)(QWebEngineView*, const QSize&); + using QWebEngineView_setUrl_t = void (*)(QWebEngineView*, const QUrl&); + using QWebEngineView_show_t = void (*)(QWebEngineView*); + using QWebEngineView_winId_t = ulonglong (*)(QWebEngineView*); + using QWebEngineView_windowHandle_t = QWindow* (*)(QWebEngineView*); + using QWindow_fromWinId_t = QWindow* (*)(ulonglong); + using QWindow_setParent_t = void (*)(QWindow*, void*); + + CPPSYM(QApplication__init_t, QApplication__init, _ZN12QApplicationC1ERiPPci) + CPPSYM(QApplication_exec_t, QApplication_exec, _ZN15QGuiApplication4execEv) + CPPSYM(QApplication_setAttribute_t, QApplication_setAttribute, _ZN16QCoreApplication12setAttributeEN2Qt20ApplicationAttributeEb) + CPPSYM(QString__init_t, QString__init, _ZN7QStringC2EPK5QCharx) + CPPSYM(QUrl__init_t, QUrl__init, _ZN4QUrlC1ERK7QStringNS_11ParsingModeE) + CPPSYM(QWebEngineView__init_t, QWebEngineView__init, _ZN14QWebEngineViewC1EP7QWidget) + CPPSYM(QWebEngineView_move_t, QWebEngineView_move, _ZN7QWidget4moveERK6QPoint) + CPPSYM(QWebEngineView_resize_t, QWebEngineView_resize, _ZN7QWidget6resizeERK5QSize) + CPPSYM(QWebEngineView_setUrl_t, QWebEngineView_setUrl, _ZN14QWebEngineView6setUrlERK4QUrl) + CPPSYM(QWebEngineView_show_t, QWebEngineView_show, _ZN7QWidget4showEv) + CPPSYM(QWebEngineView_winId_t, QWebEngineView_winId, _ZNK7QWidget5winIdEv) + CPPSYM(QWebEngineView_windowHandle_t, QWebEngineView_windowHandle, _ZNK7QWidget12windowHandleEv) + CPPSYM(QWindow_fromWinId_t, QWindow_fromWinId, _ZN7QWindow9fromWinIdEy) + CPPSYM(QWindow_setParent_t, QWindow_setParent, _ZN7QWindow9setParentEPS_) + + unsetenv("QT_FONT_DPI"); + unsetenv("QT_SCREEN_SCALE_FACTORS"); + unsetenv("QT_USE_PHYSICAL_DPI"); + setenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0", 1); + + char scale[8] = {}; + std::snprintf(scale, 7, "%.2f", scaleFactor); + setenv("QT_SCALE_FACTOR", scale, 1); + + QApplication_setAttribute(Qt::AA_X11InitThreads, true); + QApplication_setAttribute(Qt::AA_EnableHighDpiScaling, true); + QApplication_setAttribute(Qt::AA_UseHighDpiPixmaps, true); + + static int argc = 0; + static char* argv[] = { nullptr }; + + uint8_t _app[64]; // sizeof(QApplication) == 16 + QApplication* const app = reinterpret_cast(_app); + QApplication__init(app, argc, argv, 0); + + uint8_t _qstrurl[32]; // sizeof(QString) == 8 + QString* const qstrurl(reinterpret_cast(_qstrurl)); + + { + const size_t url_len = std::strlen(url); + QChar* const url_qchar = new QChar[url_len + 1]; + + for (size_t i = 0; i < url_len; ++i) + url_qchar[i] = QChar(url[i]); + + url_qchar[url_len] = 0; + + QString__init(qstrurl, url_qchar, url_len); + } + + uint8_t _qurl[32]; // sizeof(QUrl) == 8 + QUrl* const qurl(reinterpret_cast(_qurl)); + QUrl__init(qurl, *qstrurl, 1 /* QUrl::StrictMode */); + + uint8_t _webview[128]; // sizeof(QWebEngineView) == 56 + QWebEngineView* const webview = reinterpret_cast(_webview); + QWebEngineView__init(webview, nullptr); + + QWebEngineView_move(webview, QPoint(0, kVerticalOffset)); + QWebEngineView_resize(webview, QSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT - kVerticalOffset)); + QWebEngineView_winId(webview); + QWindow_setParent(QWebEngineView_windowHandle(webview), QWindow_fromWinId(winId)); + QWebEngineView_setUrl(webview, *qurl); + QWebEngineView_show(webview); + + reloadFn = [=](){ + QWebEngineView_setUrl(webview, *qurl); + }; + + QApplication_exec(); + + dlclose(lib); + return true; +} +#endif + +// ----------------------------------------------------------------------------------------------------------- +// startup via ld-linux + +static void signalHandler(const int sig) +{ + DISTRHO_SAFE_ASSERT_RETURN(sig == SIGUSR1,); + + reloadFn(); +} + +int dpf_webview_start(int /* argc */, char** /* argv[] */) +{ + uselocale(newlocale(LC_NUMERIC_MASK, "C", nullptr)); + + const char* const envScaleFactor = std::getenv("DPF_WEBVIEW_SCALE_FACTOR"); + DISTRHO_SAFE_ASSERT_RETURN(envScaleFactor != nullptr, 1); + + const char* const envWinId = std::getenv("DPF_WEBVIEW_WIN_ID"); + DISTRHO_SAFE_ASSERT_RETURN(envWinId != nullptr, 1); + + const Window winId = std::strtoul(envWinId, nullptr, 10); + DISTRHO_SAFE_ASSERT_RETURN(winId != 0, 1); + + const double scaleFactor = std::atof(envScaleFactor); + DISTRHO_SAFE_ASSERT_RETURN(scaleFactor > 0.0, 1); + + Display* const display = XOpenDisplay(nullptr); + DISTRHO_SAFE_ASSERT_RETURN(display != nullptr, 1); + +// const char* url = "file:///home/falktx/"; + const char* url = "https://mastodon.falktx.com/"; + + struct sigaction sig = {}; + sig.sa_handler = signalHandler; + sig.sa_flags = SA_RESTART; + sigemptyset(&sig.sa_mask); + sigaction(SIGUSR1, &sig, nullptr); + +// qt5webengine(winId, scaleFactor, url) || +// qt6webengine(winId, scaleFactor, url) || + gtk3(display, winId, 0, 0, 600, 400, scaleFactor, url); + + XCloseDisplay(display); + + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------- + +#ifdef WEBVIEW_DGL_NAMESPACE +END_NAMESPACE_DGL +#else +END_NAMESPACE_DISTRHO +#endif + +#undef WEBVIEW_DISTRHO_NAMESPACE +#undef WEBVIEW_DGL_NAMESPACE +#undef WEBVIEW_NAMESPACE + +#undef fileBrowserSetPathNamespaced +#undef fileBrowserSetPathFuncName diff --git a/distrho/extra/WebViewImpl.hpp b/distrho/extra/WebViewImpl.hpp new file mode 100644 index 00000000..c2550204 --- /dev/null +++ b/distrho/extra/WebViewImpl.hpp @@ -0,0 +1,34 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 Filipe Coelho + * + * 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. + */ + +#if !defined(DISTRHO_WEBVIEW_HPP_INCLUDED) && !defined(DGL_WEBVIEW_HPP_INCLUDED) +# error bad include +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// Web View stuff + +struct WebViewData; +typedef WebViewData* WebViewHandle; + +// ----------------------------------------------------------------------------------------------------------- + +WebViewHandle addWebView(uintptr_t parentWinId, int x, int y, uint width, uint height,double scaleFactor); +void destroyWebView(WebViewHandle webview); +void reloadWebView(WebViewHandle webview, uint port); +void resizeWebView(WebViewHandle webview, int x, int y, uint width, uint height); + +// ----------------------------------------------------------------------------------------------------------- diff --git a/distrho/src/DistrhoUI.cpp b/distrho/src/DistrhoUI.cpp index 12cffb10..8050e248 100644 --- a/distrho/src/DistrhoUI.cpp +++ b/distrho/src/DistrhoUI.cpp @@ -53,6 +53,16 @@ END_NAMESPACE_DISTRHO # include "../extra/FileBrowserDialogImpl.cpp" #endif +#if DISTRHO_UI_USE_WEBVIEW && !defined(DISTRHO_OS_MAC) +# define DISTRHO_WEBVIEW_HPP_INCLUDED +# define WEBVIEW_NAMESPACE DISTRHO_NAMESPACE +# define WEBVIEW_DISTRHO_NAMESPACE +START_NAMESPACE_DISTRHO +# include "../extra/WebViewImpl.hpp" +END_NAMESPACE_DISTRHO +# include "../extra/WebViewImpl.cpp" +#endif + #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI # if defined(DISTRHO_OS_WINDOWS) # include diff --git a/examples/WebMeters/CMakeLists.txt b/examples/WebMeters/CMakeLists.txt new file mode 100644 index 00000000..fd136d36 --- /dev/null +++ b/examples/WebMeters/CMakeLists.txt @@ -0,0 +1,12 @@ +# CMake file for DISTRHO Plugins # +# ------------------------------ # + +dpf_add_plugin(d_meters + TARGETS jack dssi lv2 vst2 vst3 clap + FILES_DSP + ExamplePluginMeters.cpp + FILES_UI + ExampleUIMeters.cpp) + +target_include_directories( + d_meters PUBLIC ".") diff --git a/examples/WebMeters/DistrhoPluginInfo.h b/examples/WebMeters/DistrhoPluginInfo.h new file mode 100644 index 00000000..89a5cfcf --- /dev/null +++ b/examples/WebMeters/DistrhoPluginInfo.h @@ -0,0 +1,44 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 Filipe Coelho + * + * 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_PLUGIN_INFO_H_INCLUDED +#define DISTRHO_PLUGIN_INFO_H_INCLUDED + +#define DISTRHO_PLUGIN_BRAND "DISTRHO" +#define DISTRHO_PLUGIN_NAME "Web Meters" +#define DISTRHO_PLUGIN_URI "http://distrho.sf.net/examples/WebMeters" +#define DISTRHO_PLUGIN_CLAP_ID "studio.kx.distrho.examples.webmeters" + +#define DISTRHO_PLUGIN_BRAND_ID Dstr +#define DISTRHO_PLUGIN_UNIQUE_ID wMtr + +#define DISTRHO_PLUGIN_HAS_UI 1 +#define DISTRHO_PLUGIN_IS_RT_SAFE 1 +#define DISTRHO_PLUGIN_NUM_INPUTS 2 +#define DISTRHO_PLUGIN_NUM_OUTPUTS 2 +#define DISTRHO_PLUGIN_WANT_STATE 1 +#define DISTRHO_UI_FILE_BROWSER 0 +#define DISTRHO_UI_USER_RESIZABLE 1 +#define DISTRHO_UI_USE_WEBVIEW 1 + +#define METER_COLOR_GREEN 0 +#define METER_COLOR_BLUE 1 + +#define DISTRHO_UI_DEFAULT_WIDTH 600 +#define DISTRHO_UI_DEFAULT_HEIGHT 400 +#define kVerticalOffset 0 + +#endif // DISTRHO_PLUGIN_INFO_H_INCLUDED diff --git a/examples/WebMeters/ExamplePluginWebMeters.cpp b/examples/WebMeters/ExamplePluginWebMeters.cpp new file mode 100644 index 00000000..0b188017 --- /dev/null +++ b/examples/WebMeters/ExamplePluginWebMeters.cpp @@ -0,0 +1,288 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2024 Filipe Coelho + * + * 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. + */ + +#include "DistrhoPlugin.hpp" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +/** + Plugin to demonstrate parameter outputs using meters. + */ +class ExamplePluginMeters : public Plugin +{ +public: + ExamplePluginMeters() + : Plugin(3, 0, 0), // 3 parameters, 0 programs, 0 states + fColor(0.0f), + fOutLeft(0.0f), + fOutRight(0.0f), + fNeedsReset(true) + { + } + +protected: + /* -------------------------------------------------------------------------------------------------------- + * Information */ + + /** + Get the plugin label. + A plugin label follows the same rules as Parameter::symbol, with the exception that it can start with numbers. + */ + const char* getLabel() const override + { + return "meters"; + } + + /** + Get an extensive comment/description about the plugin. + */ + const char* getDescription() const override + { + return "Plugin to demonstrate parameter outputs using meters."; + } + + /** + Get the plugin author/maker. + */ + const char* getMaker() const override + { + return "DISTRHO"; + } + + /** + Get the plugin homepage. + */ + const char* getHomePage() const override + { + return "https://github.com/DISTRHO/DPF"; + } + + /** + Get the plugin license name (a single line of text). + For commercial plugins this should return some short copyright information. + */ + const char* getLicense() const override + { + return "ISC"; + } + + /** + Get the plugin version, in hexadecimal. + */ + uint32_t getVersion() const override + { + return d_version(1, 0, 0); + } + + /* -------------------------------------------------------------------------------------------------------- + * Init */ + + /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupStereo; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + + /** + Initialize the parameter @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initParameter(uint32_t index, Parameter& parameter) override + { + /** + All parameters in this plugin have the same ranges. + */ + parameter.ranges.min = 0.0f; + parameter.ranges.max = 1.0f; + parameter.ranges.def = 0.0f; + + /** + Set parameter data. + */ + switch (index) + { + case 0: + parameter.hints = kParameterIsAutomatable|kParameterIsInteger; + parameter.name = "color"; + parameter.symbol = "color"; + parameter.enumValues.count = 2; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const values = new ParameterEnumerationValue[2]; + parameter.enumValues.values = values; + + values[0].label = "Green"; + values[0].value = METER_COLOR_GREEN; + values[1].label = "Blue"; + values[1].value = METER_COLOR_BLUE; + } + break; + case 1: + parameter.hints = kParameterIsAutomatable|kParameterIsOutput; + parameter.name = "out-left"; + parameter.symbol = "out_left"; + break; + case 2: + parameter.hints = kParameterIsAutomatable|kParameterIsOutput; + parameter.name = "out-right"; + parameter.symbol = "out_right"; + break; + } + } + + /** + Set a state key and default value. + This function will be called once, shortly after the plugin is created. + */ + void initState(uint32_t, String&, String&) override + { + // we are using states but don't want them saved in the host + } + + /* -------------------------------------------------------------------------------------------------------- + * Internal data */ + + /** + Get the current value of a parameter. + */ + float getParameterValue(uint32_t index) const override + { + switch (index) + { + case 0: return fColor; + case 1: return fOutLeft; + case 2: return fOutRight; + } + + return 0.0f; + } + + /** + Change a parameter value. + */ + void setParameterValue(uint32_t index, float value) override + { + // this is only called for input paramters, and we only have one of those. + if (index != 0) return; + + fColor = value; + } + + /** + Change an internal state. + */ + void setState(const char* key, const char*) override + { + if (std::strcmp(key, "reset") != 0) + return; + + fNeedsReset = true; + } + + /* -------------------------------------------------------------------------------------------------------- + * Process */ + + /** + Run/process function for plugins without MIDI input. + */ + void run(const float** inputs, float** outputs, uint32_t frames) override + { + float tmp; + float tmpLeft = 0.0f; + float tmpRight = 0.0f; + + for (uint32_t i=0; i tmpLeft) + tmpLeft = tmp; + + // right + tmp = std::abs(inputs[1][i]); + + if (tmp > tmpRight) + tmpRight = tmp; + } + + if (tmpLeft > 1.0f) + tmpLeft = 1.0f; + if (tmpRight > 1.0f) + tmpRight = 1.0f; + + if (fNeedsReset) + { + fOutLeft = tmpLeft; + fOutRight = tmpRight; + fNeedsReset = false; + } + else + { + if (tmpLeft > fOutLeft) + fOutLeft = tmpLeft; + if (tmpRight > fOutRight) + fOutRight = tmpRight; + } + + // copy inputs over outputs if needed + if (outputs[0] != inputs[0]) + std::memcpy(outputs[0], inputs[0], sizeof(float)*frames); + + if (outputs[1] != inputs[1]) + std::memcpy(outputs[1], inputs[1], sizeof(float)*frames); + } + + // ------------------------------------------------------------------------------------------------------- + +private: + /** + Parameters. + */ + float fColor, fOutLeft, fOutRight; + + /** + Boolean used to reset meter values. + The UI will send a "reset" message which sets this as true. + */ + volatile bool fNeedsReset; + + /** + Set our plugin class as non-copyable and add a leak detector just in case. + */ + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExamplePluginMeters) +}; + +/* ------------------------------------------------------------------------------------------------------------ + * Plugin entry point, called by DPF to create a new plugin instance. */ + +Plugin* createPlugin() +{ + return new ExamplePluginMeters(); +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/examples/WebMeters/ExampleUIWebMeters.cpp b/examples/WebMeters/ExampleUIWebMeters.cpp new file mode 100644 index 00000000..1cce4bdf --- /dev/null +++ b/examples/WebMeters/ExampleUIWebMeters.cpp @@ -0,0 +1,72 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2019 Filipe Coelho + * + * 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. + */ + +#include "DistrhoUI.hpp" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +class ExampleUIMeters : public UI +{ +public: + ExampleUIMeters() + : UI(600, 400) + { + setGeometryConstraints(600, 400, false); + } + +protected: + /* -------------------------------------------------------------------------------------------------------- + * DSP/Plugin Callbacks */ + + /** + A parameter has changed on the plugin side. + This is called by the host to inform the UI about parameter changes. + */ + void parameterChanged(uint32_t index, float value) override + { + } + + /** + A state has changed on the plugin side. + This is called by the host to inform the UI about state changes. + */ + void stateChanged(const char*, const char*) override + { + // nothing here + } + + // ------------------------------------------------------------------------------------------------------- + +private: + /** + Set our UI class as non-copyable and add a leak detector just in case. + */ + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExampleUIMeters) +}; + +/* ------------------------------------------------------------------------------------------------------------ + * UI entry point, called by DPF to create a new UI instance. */ + +UI* createUI() +{ + return new ExampleUIMeters(); +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/examples/WebMeters/Makefile b/examples/WebMeters/Makefile new file mode 100644 index 00000000..2fa36f57 --- /dev/null +++ b/examples/WebMeters/Makefile @@ -0,0 +1,50 @@ +#!/usr/bin/make -f +# Makefile for DISTRHO Plugins # +# ---------------------------- # +# Created by falkTX +# + +# -------------------------------------------------------------- +# Project name, used for binaries + +NAME = d_web + +# -------------------------------------------------------------- +# Files to build + +FILES_DSP = \ + ExamplePluginWebMeters.cpp + +FILES_UI = \ + ExampleUIWebMeters.cpp + +# -------------------------------------------------------------- +# Do some magic + +UI_TYPE = web +include ../../Makefile.plugins.mk + +# -------------------------------------------------------------- +# Enable all possible plugin types + +ifeq ($(HAVE_OPENGL),true) + +TARGETS += jack + +ifneq ($(MACOS_OR_WINDOWS),true) +ifeq ($(HAVE_LIBLO),true) +TARGETS += dssi +endif # HAVE_LIBLO +endif # MACOS_OR_WINDOWS + +TARGETS += lv2_sep +TARGETS += vst2 +TARGETS += vst3 +TARGETS += clap +TARGETS += au + +endif # HAVE_OPENGL + +all: $(TARGETS) + +# -------------------------------------------------------------- diff --git a/examples/WebMeters/README.md b/examples/WebMeters/README.md new file mode 100644 index 00000000..a2386d7b --- /dev/null +++ b/examples/WebMeters/README.md @@ -0,0 +1,8 @@ +# Meters example + +This example will show how parameter outputs can be used for UI meters in DPF.
+The plugin will inspect the host audio buffer but it won't change it in any way.
+ +In this example the UI will display a simple meter based on the plugin's parameter outputs.
+In order to make drawing easier the UI uses NanoVG instead of raw OpenGL.
+Please see the Parameters and States examples before studying this one.