Add back modgui support

Signed-off-by: falkTX <falktx@falktx.com>
This commit is contained in:
falkTX 2019-03-19 23:40:32 +01:00
parent 0c197e12b1
commit d0610840bd
No known key found for this signature in database
GPG Key ID: CDBAA37ABC74FBA0
11 changed files with 762 additions and 8 deletions

View File

@ -522,6 +522,14 @@ ifeq ($(HAVE_LIBLO),true)
$(DESTDIR)$(BINDIR)/carla-control
endif
# Install the real modgui bridge
install -m 755 \
data/carla-bridge-lv2-modgui \
$(DESTDIR)$(LIBDIR)/carla
sed $(SED_ARGS) 's?X-PREFIX-X?$(PREFIX)?' \
$(DESTDIR)$(LIBDIR)/carla/carla-bridge-lv2-modgui
# Install frontend
install -m 644 \
source/frontend/carla \
@ -592,6 +600,7 @@ endif
install -m 644 resources/scalable/carla-control.svg $(DESTDIR)$(DATADIR)/icons/hicolor/scalable/apps
# Install resources (re-use python files)
$(LINK) ../modgui $(DESTDIR)$(DATADIR)/carla/resources
$(LINK) ../patchcanvas $(DESTDIR)$(DATADIR)/carla/resources
$(LINK) ../widgets $(DESTDIR)$(DATADIR)/carla/resources
$(LINK) ../carla_app.py $(DESTDIR)$(DATADIR)/carla/resources
@ -690,6 +699,16 @@ ifeq ($(HAVE_PYQT),true)
endif
endif
# -------------------------------------------------------------------------------------------------------------
ifneq ($(HAVE_PYQT),true)
# Remove gui files for non-gui build
rm $(DESTDIR)$(LIBDIR)/carla/carla-bridge-lv2-modgui
rm $(DESTDIR)$(LIBDIR)/lv2/carla.lv2/carla-bridge-lv2-modgui
endif
# ---------------------------------------------------------------------------------------------------------------------
ifneq ($(EXTERNAL_PLUGINS),true)
install_external_plugins:
endif

6
bin/carla-bridge-lv2-modgui Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
ASPATH=$(readlink -f $0)
BINDIR=$(dirname $ASPATH)
exec python3 $BINDIR/../source/frontend/carla_modgui.py "$@"

View File

@ -0,0 +1,11 @@
#!/bin/bash
PYTHON=$(which python3 2>/dev/null)
if [ ! -f ${PYTHON} ]; then
PYTHON=python
fi
INSTALL_PREFIX="X-PREFIX-X"
export CARLA_LIB_PREFIX="$INSTALL_PREFIX"
exec $PYTHON "$INSTALL_PREFIX"/share/carla/carla_modgui.py "$@"

View File

@ -1512,6 +1512,11 @@ public:
fPipeServer.flushMessages();
}
#ifndef BUILD_BRIDGE
if (fUI.rdfDescriptor->Type == LV2_UI_MOD)
pData->tryTransient();
#endif
}
else
{
@ -4640,6 +4645,9 @@ public:
case LV2_UI_X11:
bridgeBinary += CARLA_OS_SEP_STR "carla-bridge-lv2-x11";
break;
case LV2_UI_MOD:
bridgeBinary += CARLA_OS_SEP_STR "carla-bridge-lv2-modgui";
break;
#if 0
case LV2_UI_EXTERNAL:
case LV2_UI_OLD_EXTERNAL:
@ -5691,8 +5699,8 @@ public:
// ---------------------------------------------------------------
// find the most appropriate ui
int eQt4, eQt5, eGtk2, eGtk3, eCocoa, eWindows, eX11, iCocoa, iWindows, iX11, iExt, iFinal;
eQt4 = eQt5 = eGtk2 = eGtk3 = eCocoa = eWindows = eX11 = iCocoa = iWindows = iX11 = iExt = iFinal = -1;
int eQt4, eQt5, eGtk2, eGtk3, eCocoa, eWindows, eX11, eMod, iCocoa, iWindows, iX11, iExt, iFinal;
eQt4 = eQt5 = eGtk2 = eGtk3 = eCocoa = eWindows = eX11 = eMod = iCocoa = iWindows = iX11 = iExt = iFinal = -1;
#if defined(LV2_UIS_ONLY_BRIDGES)
const bool preferUiBridges = true;
@ -5750,6 +5758,9 @@ public:
case LV2_UI_OLD_EXTERNAL:
iExt = ii;
break;
case LV2_UI_MOD:
eMod = ii;
break;
default:
break;
}
@ -5816,8 +5827,14 @@ public:
if (iFinal < 0)
{
carla_stderr("Failed to find an appropriate LV2 UI for this plugin");
return;
if (eMod < 0)
{
carla_stderr("Failed to find an appropriate LV2 UI for this plugin");
return;
}
// use MODGUI as last resort
iFinal = eMod;
}
}
@ -5867,7 +5884,8 @@ public:
iFinal == eGtk3 ||
iFinal == eCocoa ||
iFinal == eWindows ||
iFinal == eX11)
iFinal == eX11 ||
iFinal == eMod)
#ifdef BUILD_BRIDGE
&& ! hasShowInterface
#endif
@ -5891,7 +5909,7 @@ public:
return;
}
if (iFinal == eQt4 || iFinal == eQt5 || iFinal == eGtk2 || iFinal == eGtk3)
if (iFinal == eQt4 || iFinal == eQt5 || iFinal == eGtk2 || iFinal == eGtk3 || iFinal == eMod)
{
carla_stderr2("Failed to find UI bridge binary for '%s', cannot use UI", pData->name);
fUI.rdfDescriptor = nullptr;

View File

@ -187,7 +187,7 @@ static const CarlaCachedPluginInfo* get_cached_plugin_lv2(Lv2WorldClass& lv2Worl
info.hints = 0x0;
if (lilvPlugin.get_uis().size() > 0)
if (lilvPlugin.get_uis().size() > 0 || lilvPlugin.get_modgui_resources_directory().as_uri() != nullptr)
info.hints |= CB::PLUGIN_HAS_CUSTOM_UI;
{

View File

@ -56,6 +56,7 @@ RES = \
$(BINDIR)/resources/carla_control.py \
$(BINDIR)/resources/carla_database.py \
$(BINDIR)/resources/carla_host.py \
$(BINDIR)/resources/carla_modgui.py \
$(BINDIR)/resources/carla_settings.py \
$(BINDIR)/resources/carla_skin.py \
$(BINDIR)/resources/carla_shared.py \

View File

View File

@ -0,0 +1,459 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Carla bridge for LV2 modguis
# Copyright (C) 2015-2019 Filipe Coelho <falktx@falktx.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# For a full copy of the GNU General Public License see the doc/GPL.txt file.
# ------------------------------------------------------------------------------------------------------------
# Imports (Global)
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QSize, QUrl
from PyQt5.QtGui import QImage, QPainter, QPalette
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebView
import sys
# ------------------------------------------------------------------------------------------------------------
# Imports (Custom)
from carla_host import charPtrToString, gCarla
from .webserver import WebServerThread, PORT
# ------------------------------------------------------------------------------------------------------------
# Imports (MOD)
from mod.utils import get_plugin_info, init as lv2_init
# ------------------------------------------------------------------------------------------------------------
# Host Window
class HostWindow(QMainWindow):
# signals
SIGTERM = pyqtSignal()
SIGUSR1 = pyqtSignal()
# --------------------------------------------------------------------------------------------------------
def __init__(self):
QMainWindow.__init__(self)
gCarla.gui = self
URI = sys.argv[1]
# ----------------------------------------------------------------------------------------------------
# Internal stuff
self.fCurrentFrame = None
self.fDocElemement = None
self.fCanSetValues = False
self.fNeedsShow = False
self.fSizeSetup = False
self.fQuitReceived = False
self.fWasRepainted = False
lv2_init()
self.fPlugin = get_plugin_info(URI)
self.fPorts = self.fPlugin['ports']
self.fPortSymbols = {}
self.fPortValues = {}
for port in self.fPorts['control']['input']:
self.fPortSymbols[port['index']] = (port['symbol'], False)
self.fPortValues [port['index']] = port['ranges']['default']
for port in self.fPorts['control']['output']:
self.fPortSymbols[port['index']] = (port['symbol'], True)
self.fPortValues [port['index']] = port['ranges']['default']
# ----------------------------------------------------------------------------------------------------
# Init pipe
if len(sys.argv) == 7:
self.fPipeClient = gCarla.utils.pipe_client_new(lambda s,msg: self.msgCallback(msg))
else:
self.fPipeClient = None
# ----------------------------------------------------------------------------------------------------
# Init Web server
self.fWebServerThread = WebServerThread(self)
self.fWebServerThread.start()
# ----------------------------------------------------------------------------------------------------
# Set up GUI
self.setContentsMargins(0, 0, 0, 0)
self.fWebview = QWebView(self)
#self.fWebview.setAttribute(Qt.WA_OpaquePaintEvent, False)
#self.fWebview.setAttribute(Qt.WA_TranslucentBackground, True)
self.setCentralWidget(self.fWebview)
page = self.fWebview.page()
page.setViewportSize(QSize(980, 600))
mainFrame = page.mainFrame()
mainFrame.setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
mainFrame.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
palette = self.fWebview.palette()
palette.setBrush(QPalette.Base, palette.brush(QPalette.Window))
page.setPalette(palette)
self.fWebview.setPalette(palette)
settings = self.fWebview.settings()
settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
self.fWebview.loadFinished.connect(self.slot_webviewLoadFinished)
url = "http://127.0.0.1:%s/icon.html#%s" % (PORT, URI)
print("url:", url)
self.fWebview.load(QUrl(url))
# ----------------------------------------------------------------------------------------------------
# Connect actions to functions
self.SIGTERM.connect(self.slot_handleSIGTERM)
# ----------------------------------------------------------------------------------------------------
# Final setup
self.fIdleTimer = self.startTimer(30)
if self.fPipeClient is None:
# testing, show UI only
self.setWindowTitle("TestUI")
self.fNeedsShow = True
# --------------------------------------------------------------------------------------------------------
def closeExternalUI(self):
self.fWebServerThread.stopWait()
if self.fPipeClient is None:
return
if not self.fQuitReceived:
self.send(["exiting"])
gCarla.utils.pipe_client_destroy(self.fPipeClient)
self.fPipeClient = None
def idleStuff(self):
if self.fPipeClient is not None:
gCarla.utils.pipe_client_idle(self.fPipeClient)
self.checkForRepaintChanges()
if self.fSizeSetup:
return
if self.fDocElemement is None or self.fDocElemement.isNull():
return
pedal = self.fDocElemement.findFirst(".mod-pedal")
if pedal.isNull():
return
size = pedal.geometry().size()
if size.width() <= 10 or size.height() <= 10:
return
# render web frame to image
image = QImage(self.fWebview.page().viewportSize(), QImage.Format_ARGB32_Premultiplied)
image.fill(Qt.transparent)
painter = QPainter(image)
self.fCurrentFrame.render(painter)
painter.end()
#image.save("/tmp/test.png")
# get coordinates and size from image
#x = -1
#y = -1
#lastx = -1
#lasty = -1
#bgcol = self.fHostColor.rgba()
#for h in range(0, image.height()):
#hasNonTransPixels = False
#for w in range(0, image.width()):
#if image.pixel(w, h) not in (0, bgcol): # 0xff070707):
#hasNonTransPixels = True
#if x == -1 or x > w:
#x = w
#lastx = max(lastx, w)
#if hasNonTransPixels:
##if y == -1:
##y = h
#lasty = h
# set size and position accordingly
#if -1 not in (x, lastx, lasty):
#self.setFixedSize(lastx-x, lasty)
#self.fCurrentFrame.setScrollPosition(QPoint(x, 0))
#else:
# TODO that^ needs work
if True:
self.setFixedSize(size)
# set initial values
self.fCurrentFrame.evaluateJavaScript("icongui.setPortValue(':bypass', 0, null)")
for index in self.fPortValues.keys():
symbol, isOutput = self.fPortSymbols[index]
value = self.fPortValues[index]
if isOutput:
self.fCurrentFrame.evaluateJavaScript("icongui.setOutputPortValue('%s', %f)" % (symbol, value))
else:
self.fCurrentFrame.evaluateJavaScript("icongui.setPortValue('%s', %f, null)" % (symbol, value))
# final setup
self.fCanSetValues = True
self.fSizeSetup = True
self.fDocElemement = None
if self.fNeedsShow:
self.show()
def checkForRepaintChanges(self):
if not self.fWasRepainted:
return
self.fWasRepainted = False
if not self.fCanSetValues:
return
for index in self.fPortValues.keys():
symbol, isOutput = self.fPortSymbols[index]
if isOutput:
continue
oldValue = self.fPortValues[index]
newValue = self.fCurrentFrame.evaluateJavaScript("icongui.getPortValue('%s')" % (symbol,))
if oldValue != newValue:
self.fPortValues[index] = newValue
self.send(["control", index, newValue])
# --------------------------------------------------------------------------------------------------------
@pyqtSlot(bool)
def slot_webviewLoadFinished(self, ok):
page = self.fWebview.page()
page.repaintRequested.connect(self.slot_repaintRequested)
self.fCurrentFrame = page.currentFrame()
self.fDocElemement = self.fCurrentFrame.documentElement()
def slot_repaintRequested(self):
if self.fCanSetValues:
self.fWasRepainted = True
# --------------------------------------------------------------------------------------------------------
# Callback
def msgCallback(self, msg):
msg = charPtrToString(msg)
if msg == "control":
index = int(self.readlineblock())
value = float(self.readlineblock())
self.dspParameterChanged(index, value)
elif msg == "program":
index = int(self.readlineblock())
self.dspProgramChanged(index)
elif msg == "midiprogram":
bank = int(self.readlineblock())
program = float(self.readlineblock())
self.dspMidiProgramChanged(bank, program)
elif msg == "configure":
key = self.readlineblock()
value = self.readlineblock()
self.dspStateChanged(key, value)
elif msg == "note":
onOff = bool(self.readlineblock() == "true")
channel = int(self.readlineblock())
note = int(self.readlineblock())
velocity = int(self.readlineblock())
self.dspNoteReceived(onOff, channel, note, velocity)
elif msg == "atom":
index = int(self.readlineblock())
size = int(self.readlineblock())
base64atom = self.readlineblock()
# nothing to do yet
elif msg == "urid":
urid = int(self.readlineblock())
uri = self.readlineblock()
# nothing to do yet
elif msg == "uiOptions":
sampleRate = float(self.readlineblock())
useTheme = bool(self.readlineblock() == "true")
useThemeColors = bool(self.readlineblock() == "true")
windowTitle = self.readlineblock()
transWindowId = int(self.readlineblock())
self.uiTitleChanged(windowTitle)
elif msg == "show":
self.uiShow()
elif msg == "focus":
self.uiFocus()
elif msg == "hide":
self.uiHide()
elif msg == "quit":
self.fQuitReceived = True
self.uiQuit()
elif msg == "uiTitle":
uiTitle = self.readlineblock()
self.uiTitleChanged(uiTitle)
else:
print("unknown message: \"" + msg + "\"")
# --------------------------------------------------------------------------------------------------------
def dspParameterChanged(self, index, value):
self.fPortValues[index] = value
if self.fCurrentFrame is not None and self.fCanSetValues:
symbol, isOutput = self.fPortSymbols[index]
if isOutput:
self.fPortValues[index] = value
self.fCurrentFrame.evaluateJavaScript("icongui.setOutputPortValue('%s', %f)" % (symbol, value))
else:
self.fCurrentFrame.evaluateJavaScript("icongui.setPortValue('%s', %f, null)" % (symbol, value))
def dspProgramChanged(self, index):
return
def dspMidiProgramChanged(self, bank, program):
return
def dspStateChanged(self, key, value):
return
def dspNoteReceived(self, onOff, channel, note, velocity):
return
# --------------------------------------------------------------------------------------------------------
def uiShow(self):
if self.fSizeSetup:
self.show()
else:
self.fNeedsShow = True
def uiFocus(self):
if not self.fSizeSetup:
return
self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive)
self.show()
self.raise_()
self.activateWindow()
def uiHide(self):
self.hide()
def uiQuit(self):
self.closeExternalUI()
self.close()
QApplication.instance().quit()
def uiTitleChanged(self, uiTitle):
self.setWindowTitle(uiTitle)
# --------------------------------------------------------------------------------------------------------
# Qt events
def closeEvent(self, event):
self.closeExternalUI()
QMainWindow.closeEvent(self, event)
# there might be other qt windows open which will block carla-modgui from quitting
QApplication.instance().quit()
def timerEvent(self, event):
if event.timerId() == self.fIdleTimer:
self.idleStuff()
QMainWindow.timerEvent(self, event)
# --------------------------------------------------------------------------------------------------------
@pyqtSlot()
def slot_handleSIGTERM(self):
print("Got SIGTERM -> Closing now")
self.close()
# --------------------------------------------------------------------------------------------------------
# Internal stuff
def readlineblock(self):
if self.fPipeClient is None:
return ""
return gCarla.utils.pipe_client_readlineblock(self.fPipeClient, 5000)
def send(self, lines):
if self.fPipeClient is None or len(lines) == 0:
return
gCarla.utils.pipe_client_lock(self.fPipeClient)
# this must never fail, we need to unlock at the end
try:
for line in lines:
if line is None:
line2 = "(null)"
elif isinstance(line, str):
line2 = line.replace("\n", "\r")
elif isinstance(line, bool):
line2 = "true" if line else "false"
elif isinstance(line, int):
line2 = "%i" % line
elif isinstance(line, float):
line2 = "%.10f" % line
else:
print("unknown data type to send:", type(line))
return
gCarla.utils.pipe_client_write_msg(self.fPipeClient, line2 + "\n")
except:
pass
gCarla.utils.pipe_client_flush_and_unlock(self.fPipeClient)

View File

@ -0,0 +1,210 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Carla bridge for LV2 modguis
# Copyright (C) 2015-2019 Filipe Coelho <falktx@falktx.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# For a full copy of the GNU General Public License see the doc/GPL.txt file.
# ------------------------------------------------------------------------------------------------------------
# Imports (Global)
import os
from PyQt5.QtCore import pyqtSignal, QThread
# ------------------------------------------------------------------------------------------------------------
# Generate a random port number between 9000 and 18000
from random import random
PORTn = 8998 + int(random()*9000)
# ------------------------------------------------------------------------------------------------------------
# Imports (tornado)
from tornado.log import enable_pretty_logging
from tornado.ioloop import IOLoop
from tornado.util import unicode_type
from tornado.web import HTTPError
from tornado.web import Application, RequestHandler, StaticFileHandler
# ------------------------------------------------------------------------------------------------------------
# Set up environment for the webserver
PORT = str(PORTn)
ROOT = "/usr/share/mod"
DATA_DIR = os.path.expanduser("~/.local/share/mod-data/")
HTML_DIR = os.path.join(ROOT, "html")
os.environ['MOD_DEV_HOST'] = "1"
os.environ['MOD_DEV_HMI'] = "1"
os.environ['MOD_DESKTOP'] = "1"
os.environ['MOD_DATA_DIR'] = DATA_DIR
os.environ['MOD_HTML_DIR'] = HTML_DIR
os.environ['MOD_KEY_PATH'] = os.path.join(DATA_DIR, "keys")
os.environ['MOD_CLOUD_PUB'] = os.path.join(ROOT, "keys", "cloud_key.pub")
os.environ['MOD_PLUGIN_LIBRARY_DIR'] = os.path.join(DATA_DIR, "lib")
os.environ['MOD_PHANTOM_BINARY'] = "/usr/bin/phantomjs"
os.environ['MOD_SCREENSHOT_JS'] = os.path.join(ROOT, "screenshot.js")
os.environ['MOD_DEVICE_WEBSERVER_PORT'] = PORT
# ------------------------------------------------------------------------------------------------------------
# Imports (MOD)
from mod.utils import get_plugin_info, get_plugin_gui, get_plugin_gui_mini, init as lv2_init
# ------------------------------------------------------------------------------------------------------------
# MOD related classes
class JsonRequestHandler(RequestHandler):
def write(self, data):
if isinstance(data, (bytes, unicode_type, dict)):
RequestHandler.write(self, data)
self.finish()
return
elif data is True:
data = "true"
self.set_header("Content-Type", "application/json; charset=UTF-8")
elif data is False:
data = "false"
self.set_header("Content-Type", "application/json; charset=UTF-8")
else:
data = json.dumps(data)
self.set_header("Content-Type", "application/json; charset=UTF-8")
RequestHandler.write(self, data)
self.finish()
class EffectGet(JsonRequestHandler):
def get(self):
uri = self.get_argument('uri')
try:
data = get_plugin_info(uri)
except:
print("ERROR: get_plugin_info for '%s' failed" % uri)
raise HTTPError(404)
self.write(data)
class EffectFile(StaticFileHandler):
def initialize(self):
# return custom type directly. The browser will do the parsing
self.custom_type = None
uri = self.get_argument('uri')
try:
self.modgui = get_plugin_gui(uri)
except:
raise HTTPError(404)
try:
root = self.modgui['resourcesDirectory']
except:
raise HTTPError(404)
return StaticFileHandler.initialize(self, root)
def parse_url_path(self, prop):
try:
path = self.modgui[prop]
except:
raise HTTPError(404)
if prop in ("iconTemplate", "settingsTemplate", "stylesheet", "javascript"):
self.custom_type = "text/plain"
return path
def get_content_type(self):
if self.custom_type is not None:
return self.custom_type
return StaticFileHandler.get_content_type(self)
class EffectResource(StaticFileHandler):
def initialize(self):
# Overrides StaticFileHandler initialize
pass
def get(self, path):
try:
uri = self.get_argument('uri')
except:
return self.shared_resource(path)
try:
modgui = get_plugin_gui_mini(uri)
except:
raise HTTPError(404)
try:
root = modgui['resourcesDirectory']
except:
raise HTTPError(404)
try:
super(EffectResource, self).initialize(root)
return super(EffectResource, self).get(path)
except HTTPError as e:
if e.status_code != 404:
raise e
return self.shared_resource(path)
except IOError:
raise HTTPError(404)
def shared_resource(self, path):
super(EffectResource, self).initialize(os.path.join(HTML_DIR, 'resources'))
return super(EffectResource, self).get(path)
# ------------------------------------------------------------------------------------------------------------
# WebServer Thread
class WebServerThread(QThread):
# signals
running = pyqtSignal()
def __init__(self, parent=None):
QThread.__init__(self, parent)
self.fApplication = Application(
[
(r"/effect/get/?", EffectGet),
(r"/effect/file/(.*)", EffectFile),
(r"/resources/(.*)", EffectResource),
(r"/(.*)", StaticFileHandler, {"path": HTML_DIR}),
],
debug=True)
self.fPrepareWasCalled = False
def run(self):
if not self.fPrepareWasCalled:
self.fPrepareWasCalled = True
self.fApplication.listen(PORT, address="0.0.0.0")
if int(os.getenv("MOD_LOG", "0")):
enable_pretty_logging()
self.running.emit()
IOLoop.instance().start()
def stopWait(self):
IOLoop.instance().stop()
return self.wait(5000)

View File

@ -234,6 +234,7 @@ typedef uint32_t LV2_Property;
#define LV2_UI_X11 7
#define LV2_UI_EXTERNAL 8
#define LV2_UI_OLD_EXTERNAL 9
#define LV2_UI_MOD 10
#define LV2_IS_UI_GTK2(x) ((x) == LV2_UI_GTK2)
#define LV2_IS_UI_GTK3(x) ((x) == LV2_UI_GTK3)
@ -244,6 +245,7 @@ typedef uint32_t LV2_Property;
#define LV2_IS_UI_X11(x) ((x) == LV2_UI_X11)
#define LV2_IS_UI_EXTERNAL(x) ((x) == LV2_UI_EXTERNAL)
#define LV2_IS_UI_OLD_EXTERNAL(x) ((x) == LV2_UI_OLD_EXTERNAL)
#define LV2_IS_UI_MOD(x) ((x) == LV2_UI_MOD)
// Plugin Types
#define LV2_PLUGIN_DELAY 0x000001

View File

@ -99,6 +99,7 @@ typedef std::map<double,const LilvScalePoint*> LilvScalePointMap;
#define NS_rdfs "http://www.w3.org/2000/01/rdf-schema#"
#define NS_llmm "http://ll-plugins.nongnu.org/lv2/ext/midimap#"
#define NS_devp "http://lv2plug.in/ns/dev/extportinfo#"
#define NS_mod "http://moddevices.com/ns/modgui#"
#define LV2_MIDI_Map__CC "http://ll-plugins.nongnu.org/lv2/namespace#CC"
#define LV2_MIDI_Map__NRPN "http://ll-plugins.nongnu.org/lv2/namespace#NRPN"
@ -2624,9 +2625,13 @@ const LV2_RDF_Descriptor* lv2_rdf_new(const LV2_URI uri, const bool loadPresets)
// ----------------------------------------------------------------------------------------------------------------
// Set Plugin UIs
{
const bool hasMODGui(lilvPlugin.get_modgui_resources_directory().as_uri() != nullptr);
Lilv::UIs lilvUIs(lilvPlugin.get_uis());
if (const uint numUIs = lilvUIs.size())
const uint numUIs = lilvUIs.size() + (hasMODGui ? 1 : 0);
if (numUIs > 0)
{
rdfDescriptor->UIs = new LV2_RDF_UI[numUIs];
@ -2821,6 +2826,29 @@ const LV2_RDF_Descriptor* lv2_rdf_new(const LV2_URI uri, const bool loadPresets)
}
}
for (; hasMODGui;)
{
CARLA_SAFE_ASSERT_BREAK(numUsed == numUIs-1);
LV2_RDF_UI* const rdfUI(&rdfDescriptor->UIs[numUsed++]);
// -------------------------------------------------------
// Set UI Type
rdfUI->Type = LV2_UI_MOD;
// -------------------------------------------------------
// Set UI Information
if (const char* const resDir = lilvPlugin.get_modgui_resources_directory().as_uri())
rdfUI->URI = carla_strdup_free(lilv_file_uri_parse(resDir, nullptr));
if (rdfDescriptor->Bundle != nullptr)
rdfUI->Bundle = carla_strdup(rdfDescriptor->Bundle);
break;
}
rdfDescriptor->UICount = numUsed;
}