Signed-off-by: falkTX <falktx@falktx.com>pull/1996/head
| @@ -428,7 +428,6 @@ ifeq ($(HAVE_FRONTEND),true) | |||
| install -d $(DESTDIR)$(DATADIR)/carla/resources/translations | |||
| install -d $(DESTDIR)$(DATADIR)/carla/common | |||
| install -d $(DESTDIR)$(DATADIR)/carla/dialogs | |||
| install -d $(DESTDIR)$(DATADIR)/carla/modgui | |||
| install -d $(DESTDIR)$(DATADIR)/carla/patchcanvas | |||
| install -d $(DESTDIR)$(DATADIR)/carla/utils | |||
| install -d $(DESTDIR)$(DATADIR)/carla/widgets | |||
| @@ -563,14 +562,6 @@ 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 \ | |||
| @@ -590,10 +581,6 @@ endif | |||
| source/frontend/dialogs/*.py \ | |||
| $(DESTDIR)$(DATADIR)/carla/dialogs/ | |||
| install -m 644 \ | |||
| source/frontend/modgui/*.py \ | |||
| $(DESTDIR)$(DATADIR)/carla/modgui/ | |||
| install -m 644 \ | |||
| source/frontend/patchcanvas/*.py \ | |||
| $(DESTDIR)$(DATADIR)/carla/patchcanvas/ | |||
| @@ -672,7 +659,6 @@ endif | |||
| # Install resources (re-use python files) | |||
| $(LINK) ../common $(DESTDIR)$(DATADIR)/carla/resources | |||
| $(LINK) ../dialogs $(DESTDIR)$(DATADIR)/carla/resources | |||
| $(LINK) ../modgui $(DESTDIR)$(DATADIR)/carla/resources | |||
| $(LINK) ../patchcanvas $(DESTDIR)$(DATADIR)/carla/resources | |||
| $(LINK) ../utils $(DESTDIR)$(DATADIR)/carla/resources | |||
| $(LINK) ../widgets $(DESTDIR)$(DATADIR)/carla/resources | |||
| @@ -780,16 +766,6 @@ ifeq ($(HAVE_FRONTEND),true) | |||
| rm -rf $(DESTDIR)$(LIBDIR)/vst/carla.vst/styles | |||
| $(LINK) ../../carla/styles $(DESTDIR)$(LIBDIR)/vst/carla.vst/styles | |||
| endif | |||
| endif | |||
| # ------------------------------------------------------------------------------------------------------------- | |||
| ifneq ($(HAVE_FRONTEND),true) | |||
| # Remove gui files for non-gui build | |||
| rm $(DESTDIR)$(LIBDIR)/carla/carla-bridge-lv2-modgui | |||
| ifeq ($(CAN_GENERATE_LV2_TTL),true) | |||
| rm $(DESTDIR)$(LIBDIR)/lv2/carla.lv2/carla-bridge-lv2-modgui | |||
| endif | |||
| endif | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| @@ -1,11 +0,0 @@ | |||
| #!/bin/sh | |||
| 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 "$@" | |||
| @@ -1940,11 +1940,6 @@ public: | |||
| fPipeServer.syncMessages(); | |||
| } | |||
| #ifndef BUILD_BRIDGE | |||
| if (fUI.rdfDescriptor->Type == LV2_UI_MOD) | |||
| pData->tryTransient(); | |||
| #endif | |||
| } | |||
| else | |||
| { | |||
| @@ -5394,9 +5389,6 @@ 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: | |||
| @@ -6990,8 +6982,8 @@ public: | |||
| // --------------------------------------------------------------- | |||
| // find the most appropriate ui | |||
| 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; | |||
| 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; | |||
| #if defined(LV2_UIS_ONLY_BRIDGES) | |||
| const bool preferUiBridges = true; | |||
| @@ -7049,9 +7041,6 @@ public: | |||
| case LV2_UI_OLD_EXTERNAL: | |||
| iExt = ii; | |||
| break; | |||
| case LV2_UI_MOD: | |||
| eMod = ii; | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| @@ -7118,14 +7107,8 @@ public: | |||
| if (iFinal < 0) | |||
| { | |||
| if (eMod < 0) | |||
| { | |||
| carla_stderr("Failed to find an appropriate LV2 UI for this plugin"); | |||
| return; | |||
| } | |||
| // use MODGUI as last resort | |||
| iFinal = eMod; | |||
| carla_stderr("Failed to find an appropriate LV2 UI for this plugin"); | |||
| return; | |||
| } | |||
| } | |||
| @@ -7179,8 +7162,7 @@ public: | |||
| iFinal == eGtk3 || | |||
| iFinal == eCocoa || | |||
| iFinal == eWindows || | |||
| iFinal == eX11 || | |||
| iFinal == eMod) | |||
| iFinal == eX11) | |||
| #ifdef BUILD_BRIDGE | |||
| && ! hasShowInterface | |||
| #endif | |||
| @@ -7214,7 +7196,7 @@ public: | |||
| return; | |||
| } | |||
| if (iFinal == eQt4 || iFinal == eQt5 || iFinal == eGtk2 || iFinal == eGtk3 || iFinal == eMod) | |||
| if (iFinal == eQt4 || iFinal == eQt5 || iFinal == eGtk2 || iFinal == eGtk3) | |||
| { | |||
| carla_stderr2("Failed to find UI bridge binary for '%s', cannot use UI", pData->name); | |||
| fUI.rdfDescriptor = nullptr; | |||
| @@ -69,7 +69,6 @@ OBJS = $(CPP_FILES:%=$(OBJDIR)/%.o) | |||
| RES = \ | |||
| qt_config.py \ | |||
| resources_rc.py \ | |||
| $(BINDIR)/resources/modgui \ | |||
| $(BINDIR)/resources/patchcanvas \ | |||
| $(BINDIR)/resources/widgets \ | |||
| $(BINDIR)/resources/bigmeter-ui \ | |||
| @@ -80,7 +79,6 @@ RES = \ | |||
| $(BINDIR)/resources/carla_backend_qt.py \ | |||
| $(BINDIR)/resources/carla_host.py \ | |||
| $(BINDIR)/resources/carla_host_control.py \ | |||
| $(BINDIR)/resources/carla_modgui.py \ | |||
| $(BINDIR)/resources/carla_settings.py \ | |||
| $(BINDIR)/resources/carla_skin.py \ | |||
| $(BINDIR)/resources/carla_shared.py \ | |||
| @@ -1,77 +0,0 @@ | |||
| #!/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 | |||
| import sys | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # Imports (local) | |||
| from carla_app import CarlaApplication, gCarla, getPaths | |||
| from carla_shared import DLL_EXTENSION, setUpSignals | |||
| from carla_utils import CarlaUtils | |||
| from modgui.host import HostWindow | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # Main | |||
| if __name__ == '__main__': | |||
| # ------------------------------------------------------------- | |||
| # Read CLI args | |||
| if len(sys.argv) < 2: | |||
| print("usage: %s <plugin-uri>" % sys.argv[0]) | |||
| sys.exit(1) | |||
| libPrefix = os.getenv("CARLA_LIB_PREFIX") | |||
| # ------------------------------------------------------------- | |||
| # App initialization | |||
| app = CarlaApplication("Carla2-MODGUI", libPrefix) | |||
| # ------------------------------------------------------------- | |||
| # Init utils | |||
| pathBinaries, pathResources = getPaths(libPrefix) | |||
| utilsname = "libcarla_utils.%s" % (DLL_EXTENSION) | |||
| gCarla.utils = CarlaUtils(os.path.join(pathBinaries, utilsname)) | |||
| gCarla.utils.set_process_name("carla-bridge-lv2-modgui") | |||
| # ------------------------------------------------------------- | |||
| # Set-up custom signal handling | |||
| setUpSignals() | |||
| # ------------------------------------------------------------- | |||
| # Create GUI | |||
| gui = HostWindow() | |||
| # -------------------------------------------------------------------------------------------------------- | |||
| # App-Loop | |||
| app.exit_exec() | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| @@ -1,546 +0,0 @@ | |||
| #!/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 modtools.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 = {} | |||
| self.fParamTypes = {} | |||
| self.fParamValues = {} | |||
| 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'] | |||
| for parameter in self.fPlugin['parameters']: | |||
| if parameter['ranges'] is None: | |||
| continue | |||
| if parameter['type'] == "http://lv2plug.in/ns/ext/atom#Bool": | |||
| paramtype = 'b' | |||
| elif parameter['type'] == "http://lv2plug.in/ns/ext/atom#Int": | |||
| paramtype = 'i' | |||
| elif parameter['type'] == "http://lv2plug.in/ns/ext/atom#Long": | |||
| paramtype = 'l' | |||
| elif parameter['type'] == "http://lv2plug.in/ns/ext/atom#Float": | |||
| paramtype = 'f' | |||
| elif parameter['type'] == "http://lv2plug.in/ns/ext/atom#Double": | |||
| paramtype = 'g' | |||
| elif parameter['type'] == "http://lv2plug.in/ns/ext/atom#String": | |||
| paramtype = 's' | |||
| elif parameter['type'] == "http://lv2plug.in/ns/ext/atom#Path": | |||
| paramtype = 'p' | |||
| elif parameter['type'] == "http://lv2plug.in/ns/ext/atom#URI": | |||
| paramtype = 'u' | |||
| else: | |||
| continue | |||
| if paramtype not in ('s','p','u') and parameter['ranges']['minimum'] == parameter['ranges']['maximum']: | |||
| continue | |||
| self.fParamTypes [parameter['uri']] = paramtype | |||
| self.fParamValues[parameter['uri']] = parameter['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.setPortWidgetsValue(':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.setPortWidgetsValue('%s', %f, null)" % (symbol, value)) | |||
| for uri in self.fParamValues.keys(): | |||
| ptype = self.fParamTypes[uri] | |||
| value = str(self.fParamValues[uri]) | |||
| print("icongui.setWritableParameterValue('%s', '%c', %s, 'from-carla')" % (uri, ptype, value)) | |||
| self.fCurrentFrame.evaluateJavaScript("icongui.setWritableParameterValue('%s', '%c', %s, 'from-carla')" % ( | |||
| uri, ptype, 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.controls['%s'].value" % (symbol,)) | |||
| if oldValue != newValue: | |||
| self.fPortValues[index] = newValue | |||
| self.send(["control", index, newValue]) | |||
| for uri in self.fParamValues.keys(): | |||
| oldValue = self.fParamValues[uri] | |||
| newValue = self.fCurrentFrame.evaluateJavaScript("icongui.parameters['%s'].value" % (uri,)) | |||
| if oldValue != newValue: | |||
| self.fParamValues[uri] = newValue | |||
| self.send(["pcontrol", uri, 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 = self.readlineblock_int() | |||
| value = self.readlineblock_float() | |||
| self.dspControlChanged(index, value) | |||
| elif msg == "parameter": | |||
| uri = self.readlineblock() | |||
| value = self.readlineblock_float() | |||
| self.dspParameterChanged(uri, value) | |||
| elif msg == "program": | |||
| index = self.readlineblock_int() | |||
| self.dspProgramChanged(index) | |||
| elif msg == "midiprogram": | |||
| bank = self.readlineblock_int() | |||
| program = self.readlineblock_int() | |||
| self.dspMidiProgramChanged(bank, program) | |||
| elif msg == "configure": | |||
| key = self.readlineblock() | |||
| value = self.readlineblock() | |||
| self.dspStateChanged(key, value) | |||
| elif msg == "note": | |||
| onOff = self.readlineblock_bool() | |||
| channel = self.readlineblock_int() | |||
| note = self.readlineblock_int() | |||
| velocity = self.readlineblock_int() | |||
| self.dspNoteReceived(onOff, channel, note, velocity) | |||
| elif msg == "atom": | |||
| index = self.readlineblock_int() | |||
| atomsize = self.readlineblock_int() | |||
| base64size = self.readlineblock_int() | |||
| base64atom = self.readlineblock() | |||
| # nothing to do yet | |||
| elif msg == "urid": | |||
| urid = self.readlineblock_int() | |||
| size = self.readlineblock_int() | |||
| uri = self.readlineblock() | |||
| # nothing to do yet | |||
| elif msg == "uiOptions": | |||
| sampleRate = self.readlineblock_float() | |||
| bgColor = self.readlineblock_int() | |||
| fgColor = self.readlineblock_int() | |||
| uiScale = self.readlineblock_float() | |||
| useTheme = self.readlineblock_bool() | |||
| useThemeColors = self.readlineblock_bool() | |||
| windowTitle = self.readlineblock() | |||
| transWindowId = self.readlineblock_int() | |||
| 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 dspControlChanged(self, index, value): | |||
| self.fPortValues[index] = value | |||
| if self.fCurrentFrame is None or not self.fCanSetValues: | |||
| return | |||
| symbol, isOutput = self.fPortSymbols[index] | |||
| if isOutput: | |||
| self.fCurrentFrame.evaluateJavaScript("icongui.setOutputPortValue('%s', %f)" % (symbol, value)) | |||
| else: | |||
| self.fCurrentFrame.evaluateJavaScript("icongui.setPortWidgetsValue('%s', %f, null)" % (symbol, value)) | |||
| def dspParameterChanged(self, uri, value): | |||
| print("dspParameterChanged", uri, value) | |||
| if uri not in self.fParamValues: | |||
| return | |||
| self.fParamValues[uri] = value | |||
| if self.fCurrentFrame is None or not self.fCanSetValues: | |||
| return | |||
| ptype = self.fParamTypes[uri] | |||
| self.fCurrentFrame.evaluateJavaScript("icongui.setWritableParameterValue('%s', '%c', %f, 'from-carla')" % ( | |||
| uri, ptype, 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 readlineblock_bool(self): | |||
| if self.fPipeClient is None: | |||
| return False | |||
| return gCarla.utils.pipe_client_readlineblock_bool(self.fPipeClient, 5000) | |||
| def readlineblock_int(self): | |||
| if self.fPipeClient is None: | |||
| return 0 | |||
| return gCarla.utils.pipe_client_readlineblock_int(self.fPipeClient, 5000) | |||
| def readlineblock_float(self): | |||
| if self.fPipeClient is None: | |||
| return 0.0 | |||
| return gCarla.utils.pipe_client_readlineblock_float(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) | |||
| @@ -1,225 +0,0 @@ | |||
| #!/usr/bin/env python3 | |||
| # -*- coding: utf-8 -*- | |||
| # Carla bridge for LV2 modguis | |||
| # Copyright (C) 2015-2020 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 (asyncio) | |||
| try: | |||
| from asyncio import new_event_loop, set_event_loop | |||
| haveAsyncIO = True | |||
| except: | |||
| haveAsyncIO = False | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # 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 modtools.utils import get_plugin_info, get_plugin_gui, get_plugin_gui_mini | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # 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 | |||
| self.fEventLoop = None | |||
| def run(self): | |||
| if not self.fPrepareWasCalled: | |||
| self.fPrepareWasCalled = True | |||
| if haveAsyncIO: | |||
| self.fEventLoop = new_event_loop() | |||
| set_event_loop(self.fEventLoop) | |||
| 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() | |||
| if self.fEventLoop is not None: | |||
| self.fEventLoop.call_soon_threadsafe(self.fEventLoop.stop) | |||
| return self.wait(5000) | |||
| @@ -242,7 +242,6 @@ 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) | |||
| @@ -253,7 +252,6 @@ 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 | |||
| @@ -104,7 +104,6 @@ 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" | |||
| @@ -2914,15 +2913,9 @@ const LV2_RDF_Descriptor* lv2_rdf_new(const LV2_URI uri, const bool loadPresets) | |||
| // ---------------------------------------------------------------------------------------------------------------- | |||
| // Set Plugin UIs | |||
| { | |||
| #ifdef CARLA_OS_LINUX | |||
| const bool hasMODGui = lilvPlugin.get_modgui_resources_directory().as_uri() != nullptr; | |||
| #else | |||
| const bool hasMODGui = false; | |||
| #endif | |||
| Lilv::UIs lilvUIs(lilvPlugin.get_uis()); | |||
| const uint numUIs = lilvUIs.size() + (hasMODGui ? 1 : 0); | |||
| const uint numUIs = lilvUIs.size(); | |||
| if (numUIs > 0) | |||
| { | |||
| @@ -3119,29 +3112,6 @@ 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; | |||
| } | |||