Browse Source

Add initial bits for web ui, starting with linux

Signed-off-by: falkTX <falktx@falktx.com>
web-ui
falkTX 1 year ago
parent
commit
a239521994
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
15 changed files with 1681 additions and 3 deletions
  1. +63
    -0
      dgl/Web.hpp
  2. +81
    -0
      dgl/src/Web.cpp
  3. +276
    -0
      distrho/extra/ChildProcess.hpp
  4. +3
    -3
      distrho/extra/FileBrowserDialogImpl.hpp
  5. +127
    -0
      distrho/extra/Time.hpp
  6. +28
    -0
      distrho/extra/WebView.hpp
  7. +585
    -0
      distrho/extra/WebViewImpl.cpp
  8. +34
    -0
      distrho/extra/WebViewImpl.hpp
  9. +10
    -0
      distrho/src/DistrhoUI.cpp
  10. +12
    -0
      examples/WebMeters/CMakeLists.txt
  11. +44
    -0
      examples/WebMeters/DistrhoPluginInfo.h
  12. +288
    -0
      examples/WebMeters/ExamplePluginWebMeters.cpp
  13. +72
    -0
      examples/WebMeters/ExampleUIWebMeters.cpp
  14. +50
    -0
      examples/WebMeters/Makefile
  15. +8
    -0
      examples/WebMeters/README.md

+ 63
- 0
dgl/Web.hpp View File

@@ -0,0 +1,63 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2024 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 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

+ 81
- 0
dgl/src/Web.cpp View File

@@ -0,0 +1,81 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2024 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.
*/

#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

// -----------------------------------------------------------------------

+ 276
- 0
distrho/extra/ChildProcess.hpp View File

@@ -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 <string>
# include <winsock2.h>
# include <windows.h>
#else
# include <cerrno>
# include <ctime>
# include <signal.h>
# include <sys/wait.h>
#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<LPWSTR>(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<char* const*>(args), envp);
else
execvp(args[0], const_cast<char* const*>(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

+ 3
- 3
distrho/extra/FileBrowserDialogImpl.hpp View File

@@ -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);

// --------------------------------------------------------------------------------------------------------------------

+ 127
- 0
distrho/extra/Time.hpp View File

@@ -0,0 +1,127 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2024 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_TIME_HPP_INCLUDED
#define DISTRHO_TIME_HPP_INCLUDED

#include "DistrhoUtils.hpp"

#ifdef DISTRHO_OS_WINDOWS
# include <winsock2.h>
# include <windows.h>
# include <mmsystem.h>
#else
# include <ctime>
#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<uint32_t>(timeGetTime());
#else
static struct {
timespec ts;
int r;
uint32_t ms;
} s = { {}, clock_gettime(CLOCK_MONOTONIC, &s.ts), static_cast<uint32_t>(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<uint64_t>(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<uint64_t>(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

+ 28
- 0
distrho/extra/WebView.hpp View File

@@ -0,0 +1,28 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2024 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_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

+ 585
- 0
distrho/extra/WebViewImpl.cpp View File

@@ -0,0 +1,585 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2024 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.
*/

#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 <QtCore/QChar>
// #include <QtCore/QPoint>
// #include <QtCore/QSize>
// #undef signals

#include "ChildProcess.hpp"
#include "String.hpp"

#include <clocale>
#include <cstdio>
#include <dlfcn.h>
#include <functional>
#include <linux/limits.h>
#include <X11/Xlib.h>

#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<const void*>(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<void()> reloadFn;

// -----------------------------------------------------------------------------------------------------------

struct GtkContainer;
struct GtkPlug;
struct GtkWidget;
struct GtkWindow;
struct WebKitSettings;
struct WebKitWebView;

#define GTK_CONTAINER(p) reinterpret_cast<GtkContainer*>(p)
#define GTK_PLUG(p) reinterpret_cast<GtkPlug*>(p)
#define GTK_WINDOW(p) reinterpret_cast<GtkWindow*>(p)
#define WEBKIT_WEB_VIEW(p) reinterpret_cast<WebKitWebView*>(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<JOIN(gtk3_, S)>(dlsym(nullptr, #S)); \
DISTRHO_SAFE_ASSERT_RETURN(S != nullptr, false);

#define CSYM(S, NAME) \
S NAME = reinterpret_cast<S>(dlsym(nullptr, #NAME)); \
DISTRHO_SAFE_ASSERT_RETURN(NAME != nullptr, false);

#define CPPSYM(S, NAME, SN) \
S NAME = reinterpret_cast<S>(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<int>(scaleFactor + 0.5)
: static_cast<int>(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<QApplication*>(_app);
QApplication__init(app, argc, argv, 0);

uint8_t _qstrurl[32]; // sizeof(QString) == 8
QString* const qstrurl(reinterpret_cast<QString*>(_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));
QUrl__init(qurl, *qstrurl, 1 /* QUrl::StrictMode */);

uint8_t _webview[128]; // sizeof(QWebEngineView) == 56
QWebEngineView* const webview = reinterpret_cast<QWebEngineView*>(_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<QApplication*>(_app);
QApplication__init(app, argc, argv, 0);

uint8_t _qstrurl[32]; // sizeof(QString) == 8
QString* const qstrurl(reinterpret_cast<QString*>(_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));
QUrl__init(qurl, *qstrurl, 1 /* QUrl::StrictMode */);

uint8_t _webview[128]; // sizeof(QWebEngineView) == 56
QWebEngineView* const webview = reinterpret_cast<QWebEngineView*>(_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

+ 34
- 0
distrho/extra/WebViewImpl.hpp View File

@@ -0,0 +1,34 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2024 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.
*/

#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);

// -----------------------------------------------------------------------------------------------------------

+ 10
- 0
distrho/src/DistrhoUI.cpp View File

@@ -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 <winsock2.h>


+ 12
- 0
examples/WebMeters/CMakeLists.txt View File

@@ -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 ".")

+ 44
- 0
examples/WebMeters/DistrhoPluginInfo.h View File

@@ -0,0 +1,44 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2024 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_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

+ 288
- 0
examples/WebMeters/ExamplePluginWebMeters.cpp View File

@@ -0,0 +1,288 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2024 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.
*/
#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<frames; ++i)
{
// left
tmp = std::abs(inputs[0][i]);
if (tmp > 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

+ 72
- 0
examples/WebMeters/ExampleUIWebMeters.cpp View File

@@ -0,0 +1,72 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2019 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.
*/

#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

+ 50
- 0
examples/WebMeters/Makefile View File

@@ -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)

# --------------------------------------------------------------

+ 8
- 0
examples/WebMeters/README.md View File

@@ -0,0 +1,8 @@
# Meters example

This example will show how parameter outputs can be used for UI meters in DPF.<br/>
The plugin will inspect the host audio buffer but it won't change it in any way.<br/>

In this example the UI will display a simple meter based on the plugin's parameter outputs.<br/>
In order to make drawing easier the UI uses NanoVG instead of raw OpenGL.<br/>
Please see the Parameters and States examples before studying this one.<br/>

Loading…
Cancel
Save