Browse Source

Add back modgui support

Signed-off-by: falkTX <falktx@falktx.com>
tags/v2.1-rc1
falkTX 5 years ago
parent
commit
d0610840bd
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
11 changed files with 762 additions and 8 deletions
  1. +19
    -0
      Makefile
  2. +6
    -0
      bin/carla-bridge-lv2-modgui
  3. +11
    -0
      data/carla-bridge-lv2-modgui
  4. +24
    -6
      source/backend/plugin/CarlaPluginLV2.cpp
  5. +1
    -1
      source/backend/utils/CachedPlugins.cpp
  6. +1
    -0
      source/frontend/Makefile
  7. +0
    -0
      source/frontend/modgui/__init__.py
  8. +459
    -0
      source/frontend/modgui/host.py
  9. +210
    -0
      source/frontend/modgui/webserver.py
  10. +2
    -0
      source/includes/lv2_rdf.hpp
  11. +29
    -1
      source/utils/CarlaLv2Utils.hpp

+ 19
- 0
Makefile 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
- 0
bin/carla-bridge-lv2-modgui View File

@@ -0,0 +1,6 @@
#!/bin/bash

ASPATH=$(readlink -f $0)
BINDIR=$(dirname $ASPATH)

exec python3 $BINDIR/../source/frontend/carla_modgui.py "$@"

+ 11
- 0
data/carla-bridge-lv2-modgui 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 "$@"

+ 24
- 6
source/backend/plugin/CarlaPluginLV2.cpp 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;


+ 1
- 1
source/backend/utils/CachedPlugins.cpp 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;

{


+ 1
- 0
source/frontend/Makefile 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 \


+ 0
- 0
source/frontend/modgui/__init__.py View File


+ 459
- 0
source/frontend/modgui/host.py 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)

+ 210
- 0
source/frontend/modgui/webserver.py 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)

+ 2
- 0
source/includes/lv2_rdf.hpp 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


+ 29
- 1
source/utils/CarlaLv2Utils.hpp 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;
}



Loading…
Cancel
Save