Skip to content

Commit

Permalink
Initial implementation for DISTRHO_UI_USE_WEB_VIEW
Browse files Browse the repository at this point in the history
Signed-off-by: falkTX <falktx@falktx.com>
  • Loading branch information
falkTX committed May 15, 2024
1 parent 472cda8 commit bcd834c
Show file tree
Hide file tree
Showing 16 changed files with 895 additions and 37 deletions.
31 changes: 28 additions & 3 deletions Makefile.plugins.mk
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ endif

endif

# ---------------------------------------------------------------------------------------------------------------------
# Check for proper UI_TYPE parameter

ifeq ($(UI_TYPE),)
else ifeq ($(UI_TYPE),generic)
else ifeq ($(UI_TYPE),external)
else ifeq ($(UI_TYPE),cairo)
else ifeq ($(UI_TYPE),opengl)
else ifeq ($(UI_TYPE),opengl3)
USE_OPENGL3 = true
else ifeq ($(UI_TYPE),vulkan)
else ifeq ($(UI_TYPE),webview)
USE_WEB_VIEW = true
else
$(error unknown UI_TYPE $(UI_TYPE))
endif

# ---------------------------------------------------------------------------------------------------------------------
# Include DPF base setup

include $(DPF_PATH)/Makefile.base.mk

# ---------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -251,11 +271,16 @@ HAVE_DGL = false
endif
endif

ifeq ($(UI_TYPE),web)
DGL_FLAGS += -DDGL_WEB -DHAVE_DGL
ifeq ($(UI_TYPE),webview)
DGL_FLAGS += -DDGL_EXTERNAL -DHAVE_DGL
ifeq ($(HAVE_STUB),true)
DGL_FLAGS += $(STUB_FLAGS)
DGL_LIBS += $(STUB_LIBS)
DGL_LIB = $(DGL_BUILD_DIR)/libdgl-stub.a
HAVE_DGL = true
USE_WEB_VIEW = true
else
HAVE_DGL = false
endif
endif

ifeq ($(HAVE_DGL)$(LINUX)$(USE_WEB_VIEW),truetruetrue)
Expand Down
2 changes: 1 addition & 1 deletion distrho/DistrhoPluginUtils.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
* 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
Expand Down
3 changes: 1 addition & 2 deletions distrho/DistrhoUI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ class UI : public UIWidget
This function does not block the event loop.
@note This is exactly the same API as provided by the Window class,
but redeclared here so that non-embed/DGL based UIs can still use file browser related functions.
@note This is exactly the same API as provided by the Window class, but redeclared here for convenience.
*/
bool openFileBrowser(const DISTRHO_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions());
#endif
Expand Down
12 changes: 6 additions & 6 deletions distrho/extra/WebViewImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,10 +415,10 @@ WebViewHandle webViewCreate(const char* const url,

SetParent(hwnd, reinterpret_cast<HWND>(windowId));
SetWindowPos(hwnd, nullptr,
options.offset.x * scaleFactor,
options.offset.y * scaleFactor,
(initialWidth - options.offset.x) * scaleFactor,
(initialHeight - options.offset.y) * scaleFactor,
options.offset.x,
options.offset.y,
initialWidth - options.offset.x,
initialHeight - options.offset.y,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
ShowWindow(hwnd, SW_SHOW);
#endif
Expand Down Expand Up @@ -448,8 +448,8 @@ WebViewHandle webViewCreate(const char* const url,

const CGRect rect = CGRectMake(options.offset.x / scaleFactor,
options.offset.y / scaleFactor,
initialWidth,
initialHeight);
initialWidth / scaleFactor,
initialHeight / scaleFactor);

WKWebView* const webview = [[WKWebView alloc] initWithFrame:rect
configuration:config];
Expand Down
2 changes: 1 addition & 1 deletion distrho/extra/WebViewImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ struct WebViewOptions {
This means it will draw on top of whatever is below it,
something to take into consideration if mixing regular widgets with web views.
Provided metrics must not have scale factor pre-applied.
Provided metrics must have scale factor pre-applied.
@p windowId: The native window id to attach this view to (X11 Window, HWND or NSView*)
@p scaleFactor: Scale factor in use
Expand Down
2 changes: 1 addition & 1 deletion distrho/src/DistrhoPluginChecks.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
# error invalid build config: file browser requested but `USE_FILE_BROWSER` build option is not set
#endif

#if DISTRHO_UI_USE_WEB_VIEW && !defined(DGL_UI_USE_WEB_VIEW)
#if DISTRHO_UI_USE_WEB_VIEW && !defined(DGL_USE_WEB_VIEW)
# error invalid build config: web view requested but `USE_WEB_VIEW` build option is not set
#endif

Expand Down
210 changes: 195 additions & 15 deletions distrho/src/DistrhoUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,6 @@ END_NAMESPACE_DISTRHO

START_NAMESPACE_DISTRHO

/* ------------------------------------------------------------------------------------------------------------
* Static data, see DistrhoUIInternal.hpp */

const char* g_nextBundlePath = nullptr;

/* ------------------------------------------------------------------------------------------------------------
* get global scale factor */

Expand Down Expand Up @@ -178,8 +173,8 @@ UI::PrivateData* UI::PrivateData::s_nextPrivateData = nullptr;

PluginWindow& UI::PrivateData::createNextWindow(UI* const ui, uint width, uint height, const bool adjustForScaleFactor)
{
UI::PrivateData* const pData = s_nextPrivateData;
const double scaleFactor = d_isNotZero(pData->scaleFactor) ? pData->scaleFactor : getDesktopScaleFactor(pData->winId);
UI::PrivateData* const uiData = s_nextPrivateData;
const double scaleFactor = d_isNotZero(uiData->scaleFactor) ? uiData->scaleFactor : getDesktopScaleFactor(uiData->winId);

if (adjustForScaleFactor && d_isNotZero(scaleFactor) && d_isNotEqual(scaleFactor, 1.0))
{
Expand All @@ -188,15 +183,132 @@ PluginWindow& UI::PrivateData::createNextWindow(UI* const ui, uint width, uint h
}

d_stdout("createNextWindow %u %u %f %d", width, height, scaleFactor, adjustForScaleFactor);
pData->window = new PluginWindow(ui, pData->app, pData->winId, width, height, scaleFactor);
uiData->window = new PluginWindow(ui, uiData->app, uiData->winId, width, height, scaleFactor);

if (uiData->callbacksPtr != nullptr)
{
#if DISTRHO_UI_USE_WEB_VIEW
String path;
if (uiData->bundlePath != nullptr)
{
path = getResourcePath(uiData->bundlePath);
}
else
{
path = getBinaryFilename();
path.truncate(path.rfind(DISTRHO_OS_SEP));
path += "/resources";
}

// TODO convert win32 paths to web
// TODO encode paths (e.g. %20 for space)

WebViewOptions opts;
opts.initialJS = ""
"editParameter = function(index, started){ postMessage('editparam '+index+' '+(started ? 1 : 0)) };"
"setParameterValue = function(index, value){ postMessage('setparam '+index+' '+value) };"
#if DISTRHO_PLUGIN_WANT_STATE
"setState = function(key, value){ postMessage('setstate '+key+' '+value) };"
"requestStateFile = function(key){ postMessage('reqstatefile '+key) };"
#endif
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
"sendNote = function(channel, note, velocity){ postMessage('sendnote '+channel+' '+note+' '+velocity) };"
#endif
;
opts.callback = webViewMessageCallback;
opts.callbackPtr = uiData;
uiData->webview = webViewCreate("file://" + path + "/index.html", uiData->winId, width, height, scaleFactor, opts);
#endif
}
// If there are no callbacks, this is most likely a temporary window, so ignore idle callbacks
if (pData->callbacksPtr == nullptr)
pData->window->setIgnoreIdleCallbacks();
else
{
uiData->window->setIgnoreIdleCallbacks();
}

return pData->window.getObject();
return uiData->window.getObject();
}

#if DISTRHO_UI_USE_WEB_VIEW
void UI::PrivateData::webViewMessageCallback(void* const arg, char* const msg)
{
UI::PrivateData* const uiData = static_cast<UI::PrivateData*>(arg);

if (std::strncmp(msg, "setparam ", 9) == 0)
{
const char* const strindex = msg + 9;
char* strvalue = nullptr;
const ulong index = std::strtoul(strindex, &strvalue, 10);
DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,);

float value;
{
const ScopedSafeLocale ssl;
value = std::atof(strvalue);
}
uiData->setParamCallback(index + uiData->parameterOffset, value);
return;
}

if (std::strncmp(msg, "editparam ", 10) == 0)
{
const char* const strindex = msg + 10;
char* strvalue = nullptr;
const ulong index = std::strtoul(strindex, &strvalue, 10);
DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,);

const bool started = strvalue[0] != '0';
uiData->editParamCallback(index + uiData->parameterOffset, started);
return;
}

#if DISTRHO_PLUGIN_WANT_STATE
if (std::strncmp(msg, "setstate ", 9) == 0)
{
char* const key = msg + 9;
char* const sep = std::strchr(key, ' ');
DISTRHO_SAFE_ASSERT_RETURN(sep != nullptr,);
*sep = 0;
char* const value = sep + 1;

uiData->setStateCallback(key, value);
return;
}

if (std::strncmp(msg, "reqstatefile ", 13) == 0)
{
const char* const key = msg + 13;
uiData->fileRequestCallback(key);
return;
}
#endif

#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
if (std::strncmp(msg, "sendnote ", 9) == 0)
{
const char* const strchannel = msg + 9;
char* strnote = nullptr;
char* strvelocity = nullptr;
char* end = nullptr;

const ulong channel = std::strtoul(strchannel, &strnote, 10);
DISTRHO_SAFE_ASSERT_RETURN(strnote != nullptr && strchannel != strnote,);

const ulong note = std::strtoul(strnote, &strvelocity, 10);
DISTRHO_SAFE_ASSERT_RETURN(strvelocity != nullptr && strchannel != strvelocity,);

const ulong velocity = std::strtoul(strvelocity, &end, 10);
DISTRHO_SAFE_ASSERT_RETURN(end != nullptr && strvelocity != end,);

uiData->sendNoteCallback(channel, note, velocity);
return;
}
#endif

d_stderr("UI received unknown message '%s'", msg);
}
#endif

/* ------------------------------------------------------------------------------------------------------------
* UI */

Expand Down Expand Up @@ -238,6 +350,10 @@ UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetA

UI::~UI()
{
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
webViewDestroy(uiData->webview);
#endif
}

/* ------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -323,27 +439,91 @@ void* UI::getPluginInstancePointer() const noexcept
/* ------------------------------------------------------------------------------------------------------------
* DSP/Plugin Callbacks */

void UI::parameterChanged(uint32_t, float)
void UI::parameterChanged(const uint32_t index, const float value)
{
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
{
char msg[128];
{
const ScopedSafeLocale ssl;
std::snprintf(msg, sizeof(msg) - 1,
"typeof(parameterChanged) === 'function' && parameterChanged(%u,%f)", index, value);
}
webViewEvaluateJS(uiData->webview, msg);
}
#else
// unused
(void)index;
(void)value;
#endif
}

#if DISTRHO_PLUGIN_WANT_PROGRAMS
void UI::programLoaded(uint32_t)
void UI::programLoaded(const uint32_t index)
{
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
{
char msg[128];
std::snprintf(msg, sizeof(msg) - 1,
"typeof(programLoaded) === 'function' && programLoaded(%u)", index);
webViewEvaluateJS(uiData->webview, msg);
}
#else
// unused
(void)index;
#endif
}
#endif

#if DISTRHO_PLUGIN_WANT_STATE
void UI::stateChanged(const char*, const char*)
void UI::stateChanged(const char* const key, const char* const value)
{
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
{
const size_t keylen = std::strlen(key);
const size_t valuelen = std::strlen(value);
const size_t msglen = keylen + valuelen + 60;
if (char* const msg = static_cast<char*>(std::malloc(msglen)))
{
// TODO escape \\'
std::snprintf(msg, sizeof(msglen) - 1,
"typeof(stateChanged) === 'function' && stateChanged('%s','%s')", key, value);
msg[msglen - 1] = '\0';
webViewEvaluateJS(uiData->webview, msg);
std::free(msg);
}
}
#else
// unused
(void)key;
(void)value;
#endif
}
#endif

/* ------------------------------------------------------------------------------------------------------------
* DSP/Plugin Callbacks (optional) */

void UI::sampleRateChanged(double)
void UI::sampleRateChanged(const double sampleRate)
{
#if DISTRHO_UI_USE_WEB_VIEW
if (uiData->webview != nullptr)
{
char msg[128];
{
const ScopedSafeLocale ssl;
std::snprintf(msg, sizeof(msg) - 1,
"typeof(sampleRateChanged) === 'function' && sampleRateChanged(%f)", sampleRate);
}
webViewEvaluateJS(uiData->webview, msg);
}
#else
// unused
(void)sampleRate;
#endif
}

/* ------------------------------------------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit bcd834c

Please sign in to comment.