Browse Source

Start moving files around

pull/1689/head
falkTX 1 year ago
parent
commit
5db10df8be
31 changed files with 3565 additions and 2852 deletions
  1. +6
    -1
      .gitignore
  2. +1
    -0
      source/Makefile.deps.mk
  3. +0
    -188
      source/frontend/C++/carla_database.cpp
  4. +0
    -118
      source/frontend/C++/carla_shared.cpp
  5. +0
    -48
      source/frontend/C++/carla_shared.hpp
  6. +55
    -21
      source/frontend/Makefile
  7. +15
    -2
      source/frontend/carla
  8. +2
    -1
      source/frontend/carla_app.py
  9. +0
    -2454
      source/frontend/carla_database.py
  10. +3
    -1
      source/frontend/carla_host.py
  11. +2
    -1
      source/frontend/carla_settings.py
  12. +0
    -13
      source/frontend/carla_shared.py
  13. +1
    -1
      source/frontend/patchcanvas/patchcanvas.py
  14. +20
    -0
      source/frontend/pluginlist/__init__.py
  15. +390
    -0
      source/frontend/pluginlist/discovery.py
  16. +772
    -0
      source/frontend/pluginlist/discoverythread.py
  17. +264
    -0
      source/frontend/pluginlist/jackappdialog.cpp
  18. +76
    -0
      source/frontend/pluginlist/jackappdialog.hpp
  19. +221
    -0
      source/frontend/pluginlist/jackappdialog.py
  20. +0
    -0
      source/frontend/pluginlist/jackappdialog.ui
  21. +971
    -0
      source/frontend/pluginlist/pluginlistdialog.py
  22. +0
    -0
      source/frontend/pluginlist/pluginlistdialog.ui
  23. +471
    -0
      source/frontend/pluginlist/pluginlistrefreshdialog.py
  24. +0
    -0
      source/frontend/pluginlist/pluginlistrefreshdialog.ui
  25. +19
    -0
      source/frontend/utils/__init__.py
  26. +64
    -0
      source/frontend/utils/qcarlastring.hpp
  27. +113
    -0
      source/frontend/utils/qsafesettings.cpp
  28. +60
    -0
      source/frontend/utils/qsafesettings.hpp
  29. +37
    -0
      source/frontend/utils/qsafesettings.py
  30. +2
    -2
      source/frontend/widgets/pixmapkeyboard.py
  31. +0
    -1
      source/includes/CarlaNativePrograms.hpp

+ 6
- 1
.gitignore View File

@@ -4,6 +4,7 @@
.*.kate-swp
.libmagic-tmp
.libmagic-tmp.bc
.cache/
.kdev4/
.DS_Store

@@ -17,6 +18,7 @@
*.7z
*.bz2
*.a
*.d
*.o
*.dll
*.dll.def
@@ -58,6 +60,8 @@ qrc_resources.cpp
*.pyc

# Qt files
*_ui.hpp
*_ui.py
*_rc.cpp
*_rc.py
ui_*.hpp
@@ -104,6 +108,7 @@ carla-native-plugin
carla-rest-server
zynaddsubfx-ui

compile_commands.json
stoat-output.png

source/tests/ansi-pedantic-test_*
@@ -143,7 +148,7 @@ bin/resources/widgets
source/native-plugins/resources/*.py

# Other
source/frontend/carla_config.py
source/frontend/pluginlist/jackappdialog
source/includes/asio/
source/includes/rewire/
source/includes/vst2


+ 1
- 0
source/Makefile.deps.mk View File

@@ -294,6 +294,7 @@ ifeq ($(HAVE_QT5),true)
QT5_HOSTBINS = $(shell $(PKG_CONFIG) --variable=host_bins Qt5Core)
MOC_QT5 ?= $(QT5_HOSTBINS)/moc
RCC_QT5 ?= $(QT5_HOSTBINS)/rcc
UIC_QT5 ?= $(QT5_HOSTBINS)/uic
ifeq (,$(wildcard $(MOC_QT5)))
HAVE_QT5 = false
endif


+ 0
- 188
source/frontend/C++/carla_database.cpp View File

@@ -315,191 +315,3 @@ void PluginDatabaseW::slot_saveSettings()
}

// --------------------------------------------------------------------------------------------------------------------
// Jack Application Dialog

// NOTE: index matches the one in the UI
enum UiSessionManager {
UI_SESSION_NONE,
UI_SESSION_LADISH,
UI_SESSION_NSM
};

struct JackApplicationW::PrivateData {
Ui::Dialog ui;

const QString fProjectFilename;

PrivateData(JackApplicationW* const dialog, const QString& projectFilename)
: ui(),
fProjectFilename(projectFilename)
{
ui.setupUi(dialog);

ui.group_error->setVisible(false);

// ------------------------------------------------------------------------------------------------------------
// Load settings

loadSettings();
}

void checkIfButtonBoxShouldBeEnabled(int index, const QString text)
{
static QList<QChar> badFirstChars = { '.', '/' };

bool enabled = text.length() > 0;
QCarlaString showErr;

// NSM applications must not be abstract or absolute paths, and must not contain arguments
if (enabled && index == UI_SESSION_NSM)
{
if (badFirstChars.contains(text[0]))
showErr = tr("NSM applications cannot use abstract or absolute paths");
else if (text.contains(' ') || text.contains(';') || text.contains('&'))
showErr = tr("NSM applications cannot use CLI arguments");
else if (fProjectFilename.isEmpty())
showErr = tr("You need to save the current Carla project before NSM can be used");
}

if (showErr.isNotEmpty())
{
enabled = false;
ui.l_error->setText(showErr);
ui.group_error->setVisible(true);
}
else
{
ui.group_error->setVisible(false);
}

if (QPushButton* const button = ui.buttonBox->button(QDialogButtonBox::Ok))
button->setEnabled(enabled);
}

void loadSettings()
{
const QSafeSettings settings("falkTX", "CarlaAddJackApp");

const QString smName = settings.valueString("SessionManager", "");

if (smName == "LADISH (SIGUSR1)")
ui.cb_session_mgr->setCurrentIndex(UI_SESSION_LADISH);
else if (smName == "NSM")
ui.cb_session_mgr->setCurrentIndex(UI_SESSION_NSM);
else
ui.cb_session_mgr->setCurrentIndex(UI_SESSION_NONE);

ui.le_command->setText(settings.valueString("Command", ""));
ui.le_name->setText(settings.valueString("Name", ""));
ui.sb_audio_ins->setValue(settings.valueIntPositive("NumAudioIns", 2));
ui.sb_audio_ins->setValue(settings.valueIntPositive("NumAudioIns", 2));
ui.sb_audio_outs->setValue(settings.valueIntPositive("NumAudioOuts", 2));
ui.sb_midi_ins->setValue(settings.valueIntPositive("NumMidiIns", 0));
ui.sb_midi_outs->setValue(settings.valueIntPositive("NumMidiOuts", 0));
ui.cb_manage_window->setChecked(settings.valueBool("ManageWindow", true));
ui.cb_capture_first_window->setChecked(settings.valueBool("CaptureFirstWindow", false));
ui.cb_out_midi_mixdown->setChecked(settings.valueBool("MidiOutMixdown", false));

checkIfButtonBoxShouldBeEnabled(ui.cb_session_mgr->currentIndex(), ui.le_command->text());
}

void saveSettings()
{
QSafeSettings settings("falkTX", "CarlaAddJackApp");
settings.setValue("Command", ui.le_command->text());
settings.setValue("Name", ui.le_name->text());
settings.setValue("SessionManager", ui.cb_session_mgr->currentText());
settings.setValue("NumAudioIns", ui.sb_audio_ins->value());
settings.setValue("NumAudioOuts", ui.sb_audio_outs->value());
settings.setValue("NumMidiIns", ui.sb_midi_ins->value());
settings.setValue("NumMidiOuts", ui.sb_midi_outs->value());
settings.setValue("ManageWindow", ui.cb_manage_window->isChecked());
settings.setValue("CaptureFirstWindow", ui.cb_capture_first_window->isChecked());
settings.setValue("MidiOutMixdown", ui.cb_out_midi_mixdown->isChecked());
}

CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData)
};

JackApplicationW::JackApplicationW(QWidget* parent, const QString& projectFilename)
: QDialog(parent),
self(new PrivateData(this, projectFilename))
{
adjustSize();
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

// ----------------------------------------------------------------------------------------------------------------
// Set-up connections

connect(this, SIGNAL(finished(int)), SLOT(slot_saveSettings()));
connect(self->ui.cb_session_mgr, SIGNAL(currentIndexChanged(int)), SLOT(slot_sessionManagerChanged(int)));
connect(self->ui.le_command, SIGNAL(textChanged(QString)), SLOT(slot_commandChanged(QString)));
}

JackApplicationW::~JackApplicationW()
{
delete self;
}

void JackApplicationW::getCommandAndFlags(QString& command, QString& name, QString& labelSetup)
{
name = self->ui.le_name->text();
command = self->ui.le_command->text();

if (name.isEmpty())
{
name = QFileInfo(command.split(' ').first()).baseName();
// FIXME
name[0] = name[0].toTitleCase();
}

SessionManager smgr;
switch (self->ui.cb_session_mgr->currentIndex())
{
case UI_SESSION_LADISH:
smgr = LIBJACK_SESSION_MANAGER_LADISH;
break;
case UI_SESSION_NSM:
smgr = LIBJACK_SESSION_MANAGER_NSM;
break;
default:
smgr = LIBJACK_SESSION_MANAGER_NONE;
break;
}

uint flags = 0x0;
if (self->ui.cb_manage_window->isChecked())
flags |= LIBJACK_FLAG_CONTROL_WINDOW;
if (self->ui.cb_capture_first_window->isChecked())
flags |= LIBJACK_FLAG_CAPTURE_FIRST_WINDOW;
if (self->ui.cb_buffers_addition_mode->isChecked())
flags |= LIBJACK_FLAG_AUDIO_BUFFERS_ADDITION;
if (self->ui.cb_out_midi_mixdown->isChecked())
flags |= LIBJACK_FLAG_MIDI_OUTPUT_CHANNEL_MIXDOWN;
if (self->ui.cb_external_start->isChecked())
flags |= LIBJACK_FLAG_EXTERNAL_START;

labelSetup = QString("%1%2%3%4%5%6").arg(QChar('0' + self->ui.sb_audio_ins->value()))
.arg(QChar('0' + self->ui.sb_audio_outs->value()))
.arg(QChar('0' + self->ui.sb_midi_ins->value()))
.arg(QChar('0' + self->ui.sb_midi_outs->value()))
.arg(QChar('0' + smgr))
.arg(QChar('0' + flags));
}

void JackApplicationW::slot_commandChanged(const QString text)
{
self->checkIfButtonBoxShouldBeEnabled(self->ui.cb_session_mgr->currentIndex(), text);
}

void JackApplicationW::slot_sessionManagerChanged(const int index)
{
self->checkIfButtonBoxShouldBeEnabled(index, self->ui.le_command->text());
}

void JackApplicationW::slot_saveSettings()
{
self->saveSettings();
}

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

+ 0
- 118
source/frontend/C++/carla_shared.cpp View File

@@ -370,124 +370,6 @@ void QMessageBoxWithBetterWidth::showEvent(QShowEvent* const event)
QMessageBox::showEvent(event);
}

//---------------------------------------------------------------------------------------------------------------------
// Safer QSettings class, which does not throw if type mismatches

bool QSafeSettings::valueBool(const QString key, const bool defaultValue) const
{
QVariant var(value(key, defaultValue));

if (var.isNull())
return defaultValue;

CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::Bool), defaultValue);

return var.isValid() ? var.toBool() : defaultValue;
}

Qt::CheckState QSafeSettings::valueCheckState(const QString key, const Qt::CheckState defaultValue) const
{
QVariant var(value(key, defaultValue));

if (var.isNull())
return defaultValue;

CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::UInt), defaultValue);

if (! var.isValid())
return defaultValue;

const uint value = var.toUInt();

switch (value)
{
case Qt::Unchecked:
case Qt::PartiallyChecked:
case Qt::Checked:
return static_cast<Qt::CheckState>(value);
default:
return defaultValue;
}
}

int QSafeSettings::valueIntPositive(const QString key, const int defaultValue) const
{
CARLA_SAFE_ASSERT_INT(defaultValue >= 0, defaultValue);

QVariant var(value(key, defaultValue));

if (var.isNull())
return defaultValue;

CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::Int), defaultValue);
CARLA_SAFE_ASSERT_RETURN(var.isValid(), defaultValue);

const int value = var.toInt();
CARLA_SAFE_ASSERT_RETURN(value >= 0, defaultValue);

return value;
}

uint QSafeSettings::valueUInt(const QString key, const uint defaultValue) const
{
QVariant var(value(key, defaultValue));

if (var.isNull())
return defaultValue;

CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::UInt), defaultValue);

return var.isValid() ? var.toUInt() : defaultValue;
}

double QSafeSettings::valueDouble(const QString key, const double defaultValue) const
{
QVariant var(value(key, defaultValue));

if (var.isNull())
return defaultValue;

CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::Double), defaultValue);

return var.isValid() ? var.toDouble() : defaultValue;
}

QString QSafeSettings::valueString(const QString key, const QString defaultValue) const
{
QVariant var(value(key, defaultValue));

if (var.isNull())
return defaultValue;

CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::String), defaultValue);

return var.isValid() ? var.toString() : defaultValue;
}

QByteArray QSafeSettings::valueByteArray(const QString key, const QByteArray defaultValue) const
{
QVariant var(value(key, defaultValue));

if (var.isNull())
return defaultValue;

CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::ByteArray), defaultValue);

return var.isValid() ? var.toByteArray() : defaultValue;
}

QStringList QSafeSettings::valueStringList(const QString key, const QStringList defaultValue) const
{
QVariant var(value(key, defaultValue));

if (var.isNull())
return defaultValue;

CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::StringList), defaultValue);

return var.isValid() ? var.toStringList() : defaultValue;
}

//---------------------------------------------------------------------------------------------------------------------
// Custom MessageBox



+ 0
- 48
source/frontend/C++/carla_shared.hpp View File

@@ -437,32 +437,6 @@ int getIndexOfQDoubleListValue(const QList<double>& list, const double value);

bool isQDoubleListEqual(const QList<double>& list1, const QList<double>& list2);

//---------------------------------------------------------------------------------------------------------------------
// Custom QString class with a few extra methods

class QCarlaString : public QString
{
public:
inline QCarlaString()
: QString() {}

inline QCarlaString(const char* const ch)
: QString(ch) {}

inline QCarlaString(const QString& s)
: QString(s) {}

inline bool isNotEmpty() const
{
return !isEmpty();
}

inline QCarlaString& operator=(const char* const ch)
{
return (*this = fromUtf8(ch));
}
};

//---------------------------------------------------------------------------------------------------------------------
// Custom QMessageBox which resizes itself to fit text

@@ -476,28 +450,6 @@ protected:
void showEvent(QShowEvent* event);
};

//---------------------------------------------------------------------------------------------------------------------
// Safer QSettings class, which does not throw if type mismatches

class QSafeSettings : public QSettings
{
public:
inline QSafeSettings()
: QSettings() {}

inline QSafeSettings(const QString organizationName, const QString applicationName)
: QSettings(organizationName, applicationName) {}

bool valueBool(const QString key, const bool defaultValue) const;
Qt::CheckState valueCheckState(const QString key, const Qt::CheckState defaultValue) const;
int valueIntPositive(const QString key, const int defaultValue) const;
uint valueUInt(const QString key, const uint defaultValue) const;
double valueDouble(const QString key, const double defaultValue) const;
QString valueString(const QString key, const QString defaultValue) const;
QByteArray valueByteArray(const QString key, const QByteArray defaultValue = QByteArray()) const;
QStringList valueStringList(const QString key, const QStringList defaultValue = QStringList()) const;
};

//---------------------------------------------------------------------------------------------------------------------
// Custom MessageBox



+ 55
- 21
source/frontend/Makefile View File

@@ -12,6 +12,24 @@ include $(CWD)/Makefile.mk
BINDIR := $(CWD)/../bin
RESDIR := $(CWD)/../resources

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

ifeq ($(WINDOWS),true)
QT5_LINK_FLAGS = $(shell echo $(LINK_FLAGS) | awk 'sub(" -static","")') -static-libgcc
else
QT5_LINK_FLAGS = $(LINK_FLAGS)
endif

ifeq ($(HAVE_QT5),true)
QT5_PREFIX = $(shell pkg-config --variable=prefix Qt5Core)
BUILD_CXX_FLAGS += $(shell pkg-config --cflags Qt5Core Qt5Gui Qt5Widgets)
QT5_LINK_FLAGS += -Wl,-rpath,$(QT5_PREFIX)/lib $(shell pkg-config --libs Qt5Core Qt5Gui Qt5Widgets)
else ifeq ($(HAVE_QT5PKG),true)
QT5_PREFIX = $(shell pkg-config --variable=prefix Qt5OpenGLExtensions)
BUILD_CXX_FLAGS += -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -I $(QT5_PREFIX)/include/qt5
QT5_LINK_FLAGS += -Wl,-rpath,$(QT5_PREFIX)/lib -F $(QT5_PREFIX)/lib -framework QtCore -framework QtGui -framework QtWidgets
endif

# ---------------------------------------------------------------------------------------------------------------------
# Translations

@@ -77,26 +95,29 @@ endif
# ---------------------------------------------------------------------------------------------------------------------
# UI code

UIs = \
ui_carla_about.py \
ui_carla_about_juce.py \
ui_carla_add_jack.py \
ui_carla_database.py \
ui_carla_edit.py \
ui_carla_host.py \
ui_carla_osc_connect.py \
ui_carla_parameter.py \
ui_carla_plugin_calf.py \
ui_carla_plugin_classic.py \
ui_carla_plugin_compact.py \
ui_carla_plugin_default.py \
ui_carla_plugin_presets.py \
ui_carla_refresh.py \
ui_carla_settings.py \
ui_carla_settings_driver.py \
ui_inputdialog_value.py \
ui_midipattern.py \
ui_xycontroller.py
UI_FILES = $(wildcard pluginlist/*.ui)

UIs = $(UI_FILES:%.ui=%_ui.hpp)
UIs += $(UI_FILES:%.ui=%_ui.py)

# ui_carla_about.py \
# ui_carla_about_juce.py \
# ui_carla_database.py \
# ui_carla_edit.py \
# ui_carla_host.py \
# ui_carla_osc_connect.py \
# ui_carla_parameter.py \
# ui_carla_plugin_calf.py \
# ui_carla_plugin_classic.py \
# ui_carla_plugin_compact.py \
# ui_carla_plugin_default.py \
# ui_carla_plugin_presets.py \
# ui_carla_refresh.py \
# ui_carla_settings.py \
# ui_carla_settings_driver.py \
# ui_inputdialog_value.py \
# ui_midipattern.py \
# ui_xycontroller.py

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

@@ -104,7 +125,10 @@ all: $(QMs) $(RES) $(UIs)

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

ui_%.py: $(RESDIR)/ui/%.ui
%_ui.hpp: %.ui
$(UIC_QT5) $< -o $@

%_ui.py: %.ui
$(PYUIC) $< -o $@

resources_rc.py: $(RESDIR)/resources.qrc $(RESDIR)/*/*.png $(RESDIR)/*/*.svg $(RESDIR)/*/*.svgz
@@ -132,6 +156,16 @@ debug:

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

tests: $(UI_FILES:%.ui=%$(APP_EXT))

pluginlist/jackappdialog$(APP_EXT): pluginlist/jackappdialog.cpp pluginlist/jackappdialog.ui pluginlist/jackappdialog_ui.hpp
$(CXX) $< $(BUILD_CXX_FLAGS) $(QT5_LINK_FLAGS) -o $@

pluginlist/pluginlistdialog$(APP_EXT): pluginlist/pluginlistdialog.cpp pluginlist/pluginlistdialog.ui pluginlist/pluginlistdialog_ui.hpp
$(CXX) $< $(BUILD_CXX_FLAGS) $(QT5_LINK_FLAGS) -o $@

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

lint:
pylint \
--extension-pkg-whitelist=PyQt5 \


+ 15
- 2
source/frontend/carla View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-

# Carla plugin host
# Copyright (C) 2011-2017 Filipe Coelho <falktx@falktx.com>
# Copyright (C) 2011-2022 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
@@ -19,7 +19,20 @@
# ----------------------------------------------------------------------------------------------------------------------
# Imports (Custom Stuff)

from carla_host import *
from carla_app import (
CarlaApplication,
)

from carla_host import (
HostWindow,
initHost,
loadHostSettings,
)

from carla_shared import (
handleInitialCommandLineArguments,
setUpSignals,
)

# ----------------------------------------------------------------------------------------------------------------------
# Main


+ 2
- 1
source/frontend/carla_app.py View File

@@ -46,10 +46,11 @@ from carla_shared import (
CARLA_DEFAULT_MAIN_PRO_THEME_COLOR,
CWD, VERSION,
getPaths,
QSafeSettings,
gCarla
)

from utils import QSafeSettings

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

class CarlaApplication():


+ 0
- 2454
source/frontend/carla_database.py
File diff suppressed because it is too large
View File


+ 3
- 1
source/frontend/carla_host.py View File

@@ -53,13 +53,15 @@ from PyQt5.QtWidgets import (
import ui_carla_host

from carla_app import *
from carla_backend import *
from carla_backend_qt import CarlaHostQtDLL, CarlaHostQtNull
from carla_database import *
from carla_shared import *
from carla_settings import *
from carla_utils import *
from carla_widgets import *

from patchcanvas import patchcanvas
from pluginlist import PluginDatabaseW
from widgets.digitalpeakmeter import DigitalPeakMeter
from widgets.pixmapkeyboard import PixmapKeyboardHArea



+ 2
- 1
source/frontend/carla_settings.py View File

@@ -178,11 +178,12 @@ from carla_shared import (
getIcon,
fontMetricsHorizontalAdvance,
splitter,
QSafeSettings
)

from patchcanvas.theme import Theme, getThemeName

from utils import QSafeSettings

# ---------------------------------------------------------------------------------------------------------------------
# ...



+ 0
- 13
source/frontend/carla_shared.py View File

@@ -870,19 +870,6 @@ class QMessageBoxWithBetterWidth(QMessageBox):

QMessageBox.showEvent(self, event)

# ------------------------------------------------------------------------------------------------------------
# Safer QSettings class, which does not throw if type mismatches

class QSafeSettings(QSettings):
def value(self, key, defaultValue, valueType):
if not isinstance(defaultValue, valueType):
print("QSafeSettings.value() - defaultValue type mismatch for key", key)

try:
return QSettings.value(self, key, defaultValue, valueType)
except:
return defaultValue

# ------------------------------------------------------------------------------------------------------------
# Custom MessageBox



+ 1
- 1
source/frontend/patchcanvas/patchcanvas.py View File

@@ -63,7 +63,7 @@ from .utils import CanvasCallback, CanvasGetNewGroupPos, CanvasItemFX, CanvasRem
from . import *
from .scene import PatchScene

from carla_shared import QSafeSettings
from utils import QSafeSettings

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



+ 20
- 0
source/frontend/pluginlist/__init__.py View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Carla plugin host
# Copyright (C) 2011-2022 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.

from .jackappdialog import JackApplicationW
from .pluginlistdialog import PluginDatabaseW

+ 390
- 0
source/frontend/pluginlist/discovery.py View File

@@ -0,0 +1,390 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Carla plugin list code
# Copyright (C) 2011-2022 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 copy import deepcopy
from subprocess import Popen, PIPE
from PyQt5.QtCore import qWarning

# ---------------------------------------------------------------------------------------------------------------------
# Imports (Carla)

from carla_backend import (
BINARY_NATIVE,
BINARY_NONE,
PLUGIN_AU,
PLUGIN_DSSI,
PLUGIN_LADSPA,
PLUGIN_LV2,
PLUGIN_NONE,
PLUGIN_SF2,
PLUGIN_SFZ,
PLUGIN_VST2,
PLUGIN_VST3,
)

from carla_shared import (
LINUX,
MACOS,
WINDOWS,
)

from carla_utils import getPluginCategoryAsString

# ---------------------------------------------------------------------------------------------------------------------
# Plugin Query (helper functions)

def findBinaries(binPath, pluginType, OS):
binaries = []

if OS == "HAIKU":
extensions = ("") if pluginType == PLUGIN_VST2 else (".so",)
elif OS == "MACOS":
extensions = (".dylib", ".so")
elif OS == "WINDOWS":
extensions = (".dll",)
else:
extensions = (".so",)

for root, _, files in os.walk(binPath):
for name in tuple(name for name in files if name.lower().endswith(extensions)):
binaries.append(os.path.join(root, name))

return binaries

def findVST3Binaries(binPath):
binaries = []

for root, dirs, files in os.walk(binPath):
for name in tuple(name for name in (files+dirs) if name.lower().endswith(".vst3")):
binaries.append(os.path.join(root, name))

return binaries

def findLV2Bundles(bundlePath):
bundles = []

for root, _, _2 in os.walk(bundlePath, followlinks=True):
if root == bundlePath:
continue
if os.path.exists(os.path.join(root, "manifest.ttl")):
bundles.append(root)

return bundles

def findMacVSTBundles(bundlePath, isVST3):
bundles = []
extension = ".vst3" if isVST3 else ".vst"

for root, dirs, _ in os.walk(bundlePath, followlinks=True):
for name in tuple(name for name in dirs if name.lower().endswith(extension)):
bundles.append(os.path.join(root, name))

return bundles

def findFilenames(filePath, stype):
filenames = []

if stype == "sf2":
extensions = (".sf2",".sf3",)
else:
return []

for root, _, files in os.walk(filePath):
for name in tuple(name for name in files if name.lower().endswith(extensions)):
filenames.append(os.path.join(root, name))

return filenames

# ---------------------------------------------------------------------------------------------------------------------
# Plugin Query

# NOTE: this code is ugly, it is meant to be replaced, so let it be as-is for now

PLUGIN_QUERY_API_VERSION = 12

PyPluginInfo = {
'API': PLUGIN_QUERY_API_VERSION,
'valid': False,
'build': BINARY_NONE,
'type': PLUGIN_NONE,
'hints': 0x0,
'category': "",
'filename': "",
'name': "",
'label': "",
'maker': "",
'uniqueId': 0,
'audio.ins': 0,
'audio.outs': 0,
'cv.ins': 0,
'cv.outs': 0,
'midi.ins': 0,
'midi.outs': 0,
'parameters.ins': 0,
'parameters.outs': 0
}

gDiscoveryProcess = None

def findWinePrefix(filename, recursionLimit = 10):
if recursionLimit == 0 or len(filename) < 5 or "/" not in filename:
return ""

path = filename[:filename.rfind("/")]

if os.path.isdir(path + "/dosdevices"):
return path

return findWinePrefix(path, recursionLimit-1)

def runCarlaDiscovery(itype, stype, filename, tool, wineSettings=None):
if not os.path.exists(tool):
qWarning(f"runCarlaDiscovery() - tool '{tool}' does not exist")
return []

command = []

if LINUX or MACOS:
command.append("env")
command.append("LANG=C")
command.append("LD_PRELOAD=")
if wineSettings is not None:
command.append("WINEDEBUG=-all")

if wineSettings['autoPrefix']:
winePrefix = findWinePrefix(filename)
else:
winePrefix = ""

if not winePrefix:
envWinePrefix = os.getenv("WINEPREFIX")

if envWinePrefix:
winePrefix = envWinePrefix
elif wineSettings['fallbackPrefix']:
winePrefix = os.path.expanduser(wineSettings['fallbackPrefix'])
else:
winePrefix = os.path.expanduser("~/.wine")

wineCMD = wineSettings['executable'] if wineSettings['executable'] else "wine"

if tool.endswith("64.exe") and os.path.exists(wineCMD + "64"):
wineCMD += "64"

command.append("WINEPREFIX=" + winePrefix)
command.append(wineCMD)

command.append(tool)
command.append(stype)
command.append(filename)

# pylint: disable=global-statement
global gDiscoveryProcess
# pylint: enable=global-statement

# pylint: disable=consider-using-with
gDiscoveryProcess = Popen(command, stdout=PIPE)
# pylint: enable=consider-using-with

pinfo = None
plugins = []
fakeLabel = os.path.basename(filename).rsplit(".", 1)[0]

while True:
try:
line = gDiscoveryProcess.stdout.readline().decode("utf-8", errors="ignore")
except:
print("ERROR: discovery readline failed")
break

# line is valid, strip it
if line:
line = line.strip()

# line is invalid, try poll() again
elif gDiscoveryProcess.poll() is None:
continue

# line is invalid and poll() failed, stop here
else:
break

if line == "carla-discovery::init::-----------":
pinfo = deepcopy(PyPluginInfo)
pinfo['type'] = itype
pinfo['filename'] = filename if filename != ":all" else ""

elif line == "carla-discovery::end::------------":
if pinfo is not None:
plugins.append(pinfo)
del pinfo
pinfo = None

elif line == "Segmentation fault":
print(f"carla-discovery::crash::{filename} crashed during discovery")

elif line.startswith("err:module:import_dll Library"):
print(line)

elif line.startswith("carla-discovery::info::"):
print(f"{line} - {filename}")

elif line.startswith("carla-discovery::warning::"):
print(f"{line} - {filename}")

elif line.startswith("carla-discovery::error::"):
print(f"{line} - {filename}")

elif line.startswith("carla-discovery::"):
if pinfo is None:
continue

try:
prop, value = line.replace("carla-discovery::", "").split("::", 1)
except:
continue

# pylint: disable=unsupported-assignment-operation
if prop == "build":
if value.isdigit():
pinfo['build'] = int(value)
elif prop == "name":
pinfo['name'] = value if value else fakeLabel
elif prop == "label":
pinfo['label'] = value if value else fakeLabel
elif prop == "filename":
pinfo['filename'] = value
elif prop == "maker":
pinfo['maker'] = value
elif prop == "category":
pinfo['category'] = value
elif prop == "uniqueId":
if value.isdigit():
pinfo['uniqueId'] = int(value)
elif prop == "hints":
if value.isdigit():
pinfo['hints'] = int(value)
elif prop == "audio.ins":
if value.isdigit():
pinfo['audio.ins'] = int(value)
elif prop == "audio.outs":
if value.isdigit():
pinfo['audio.outs'] = int(value)
elif prop == "cv.ins":
if value.isdigit():
pinfo['cv.ins'] = int(value)
elif prop == "cv.outs":
if value.isdigit():
pinfo['cv.outs'] = int(value)
elif prop == "midi.ins":
if value.isdigit():
pinfo['midi.ins'] = int(value)
elif prop == "midi.outs":
if value.isdigit():
pinfo['midi.outs'] = int(value)
elif prop == "parameters.ins":
if value.isdigit():
pinfo['parameters.ins'] = int(value)
elif prop == "parameters.outs":
if value.isdigit():
pinfo['parameters.outs'] = int(value)
elif prop == "uri":
if value:
pinfo['label'] = value
else:
# cannot use empty URIs
del pinfo
pinfo = None
continue
else:
print(f"{line} - {filename} (unknown property)")
# pylint: enable=unsupported-assignment-operation

tmp = gDiscoveryProcess
gDiscoveryProcess = None
del tmp

return plugins

def killDiscovery():
# pylint: disable=global-variable-not-assigned
global gDiscoveryProcess
# pylint: enable=global-variable-not-assigned

if gDiscoveryProcess is not None:
gDiscoveryProcess.kill()

def checkPluginCached(desc, ptype):
pinfo = deepcopy(PyPluginInfo)
pinfo['build'] = BINARY_NATIVE
pinfo['type'] = ptype
pinfo['hints'] = desc['hints']
pinfo['name'] = desc['name']
pinfo['label'] = desc['label']
pinfo['maker'] = desc['maker']
pinfo['category'] = getPluginCategoryAsString(desc['category'])

pinfo['audio.ins'] = desc['audioIns']
pinfo['audio.outs'] = desc['audioOuts']

pinfo['cv.ins'] = desc['cvIns']
pinfo['cv.outs'] = desc['cvOuts']

pinfo['midi.ins'] = desc['midiIns']
pinfo['midi.outs'] = desc['midiOuts']

pinfo['parameters.ins'] = desc['parameterIns']
pinfo['parameters.outs'] = desc['parameterOuts']

if ptype == PLUGIN_LV2:
pinfo['filename'], pinfo['label'] = pinfo['label'].split('\\' if WINDOWS else '/',1)

elif ptype == PLUGIN_SFZ:
pinfo['filename'] = pinfo['label']
pinfo['label'] = pinfo['name']

return pinfo

def checkPluginLADSPA(filename, tool, wineSettings=None):
return runCarlaDiscovery(PLUGIN_LADSPA, "LADSPA", filename, tool, wineSettings)

def checkPluginDSSI(filename, tool, wineSettings=None):
return runCarlaDiscovery(PLUGIN_DSSI, "DSSI", filename, tool, wineSettings)

def checkPluginLV2(filename, tool, wineSettings=None):
return runCarlaDiscovery(PLUGIN_LV2, "LV2", filename, tool, wineSettings)

def checkPluginVST2(filename, tool, wineSettings=None):
return runCarlaDiscovery(PLUGIN_VST2, "VST2", filename, tool, wineSettings)

def checkPluginVST3(filename, tool, wineSettings=None):
return runCarlaDiscovery(PLUGIN_VST3, "VST3", filename, tool, wineSettings)

def checkFileSF2(filename, tool):
return runCarlaDiscovery(PLUGIN_SF2, "SF2", filename, tool)

def checkFileSFZ(filename, tool):
return runCarlaDiscovery(PLUGIN_SFZ, "SFZ", filename, tool)

def checkAllPluginsAU(tool):
return runCarlaDiscovery(PLUGIN_AU, "AU", ":all", tool)

+ 772
- 0
source/frontend/pluginlist/discoverythread.py View File

@@ -0,0 +1,772 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Carla plugin host
# Copyright (C) 2011-2022 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
from PyQt5.QtWidgets import QWidget

# ---------------------------------------------------------------------------------------------------------------------
# Imports (Custom)

from carla_backend import (
PLUGIN_AU,
PLUGIN_DSSI,
PLUGIN_JSFX,
PLUGIN_LADSPA,
PLUGIN_LV2,
PLUGIN_SFZ,
PLUGIN_VST2,
)

from carla_shared import (
CARLA_DEFAULT_DSSI_PATH,
CARLA_DEFAULT_JSFX_PATH,
CARLA_DEFAULT_LADSPA_PATH,
CARLA_DEFAULT_LV2_PATH,
CARLA_DEFAULT_SF2_PATH,
CARLA_DEFAULT_SFZ_PATH,
CARLA_DEFAULT_VST2_PATH,
CARLA_DEFAULT_VST3_PATH,
CARLA_DEFAULT_WINE_AUTO_PREFIX,
CARLA_DEFAULT_WINE_EXECUTABLE,
CARLA_DEFAULT_WINE_FALLBACK_PREFIX,
CARLA_KEY_PATHS_DSSI,
CARLA_KEY_PATHS_JSFX,
CARLA_KEY_PATHS_LADSPA,
CARLA_KEY_PATHS_LV2,
CARLA_KEY_PATHS_SF2,
CARLA_KEY_PATHS_SFZ,
CARLA_KEY_PATHS_VST2,
CARLA_KEY_PATHS_VST3,
CARLA_KEY_WINE_AUTO_PREFIX,
CARLA_KEY_WINE_EXECUTABLE,
CARLA_KEY_WINE_FALLBACK_PREFIX,
HAIKU,
LINUX,
MACOS,
WINDOWS,
gCarla,
splitter,
)

from utils import QSafeSettings

from .discovery import (
checkAllPluginsAU,
checkFileSF2,
checkPluginCached,
checkPluginDSSI,
checkPluginLADSPA,
checkPluginVST2,
checkPluginVST3,
findBinaries,
findFilenames,
findMacVSTBundles,
findVST3Binaries
)

# ---------------------------------------------------------------------------------------------------------------------
# Separate Thread for Plugin Search

class SearchPluginsThread(QThread):
pluginLook = pyqtSignal(float, str)

def __init__(self, parent: QWidget, pathBinaries: str):
QThread.__init__(self, parent)

self.fContinueChecking = False
self.fPathBinaries = pathBinaries

self.fCheckNative = False
self.fCheckPosix32 = False
self.fCheckPosix64 = False
self.fCheckWin32 = False
self.fCheckWin64 = False

self.fCheckLADSPA = False
self.fCheckDSSI = False
self.fCheckLV2 = False
self.fCheckVST2 = False
self.fCheckVST3 = False
self.fCheckAU = False
self.fCheckSF2 = False
self.fCheckSFZ = False
self.fCheckJSFX = False

if WINDOWS:
toolNative = "carla-discovery-native.exe"
self.fWineSettings = None

else:
toolNative = "carla-discovery-native"
settings = QSafeSettings("falkTX", "Carla2")
self.fWineSettings = {
'executable' : settings.value(CARLA_KEY_WINE_EXECUTABLE,
CARLA_DEFAULT_WINE_EXECUTABLE, str),
'autoPrefix' : settings.value(CARLA_KEY_WINE_AUTO_PREFIX,
CARLA_DEFAULT_WINE_AUTO_PREFIX, bool),
'fallbackPrefix': settings.value(CARLA_KEY_WINE_FALLBACK_PREFIX,
CARLA_DEFAULT_WINE_FALLBACK_PREFIX, str),
}
del settings

self.fToolNative = os.path.join(pathBinaries, toolNative)

if not os.path.exists(self.fToolNative):
self.fToolNative = ""

self.fCurCount = 0
self.fCurPercentValue = 0
self.fLastCheckValue = 0
self.fSomethingChanged = False

# -----------------------------------------------------------------------------------------------------------------
# public methods

def hasSomethingChanged(self):
return self.fSomethingChanged

def setSearchBinaryTypes(self, native: bool, posix32: bool, posix64: bool, win32: bool, win64: bool):
self.fCheckNative = native
self.fCheckPosix32 = posix32
self.fCheckPosix64 = posix64
self.fCheckWin32 = win32
self.fCheckWin64 = win64

def setSearchPluginTypes(self, ladspa: bool, dssi: bool, lv2: bool, vst2: bool, vst3: bool, au: bool,
sf2: bool, sfz: bool, jsfx: bool):
self.fCheckLADSPA = ladspa
self.fCheckDSSI = dssi
self.fCheckLV2 = lv2
self.fCheckVST2 = vst2
self.fCheckVST3 = vst3 and (LINUX or MACOS or WINDOWS)
self.fCheckAU = au and MACOS
self.fCheckSF2 = sf2
self.fCheckSFZ = sfz
self.fCheckJSFX = jsfx

def stop(self):
self.fContinueChecking = False

# -----------------------------------------------------------------------------------------------------------------
# protected reimplemented methods

def run(self):
settingsDB = QSafeSettings("falkTX", "CarlaPlugins5")

self.fContinueChecking = True
self.fCurCount = 0

if self.fCheckNative and not self.fToolNative:
self.fCheckNative = False

# looking for plugins via external discovery
pluginCount = 0

if self.fCheckLADSPA:
pluginCount += 1
if self.fCheckDSSI:
pluginCount += 1
if self.fCheckVST2:
pluginCount += 1
if self.fCheckVST3:
pluginCount += 1

# Increase count by the number of externally discoverable plugin types
if self.fCheckNative:
self.fCurCount += pluginCount
# Linux, MacOS and Windows are the only VST3 supported OSes
if self.fCheckVST3 and not (LINUX or MACOS or WINDOWS):
self.fCurCount -= 1

if self.fCheckPosix32:
self.fCurCount += pluginCount
if self.fCheckVST3 and not (LINUX or MACOS):
self.fCurCount -= 1

if self.fCheckPosix64:
self.fCurCount += pluginCount
if self.fCheckVST3 and not (LINUX or MACOS):
self.fCurCount -= 1

if self.fCheckWin32:
self.fCurCount += pluginCount

if self.fCheckWin64:
self.fCurCount += pluginCount

if self.fCheckLV2:
if self.fCheckNative:
self.fCurCount += 1
else:
self.fCheckLV2 = False

if self.fCheckAU:
if self.fCheckNative or self.fCheckPosix32:
self.fCurCount += int(self.fCheckNative) + int(self.fCheckPosix32)
else:
self.fCheckAU = False

if self.fCheckSF2:
if self.fCheckNative:
self.fCurCount += 1
else:
self.fCheckSF2 = False

if self.fCheckSFZ:
if self.fCheckNative:
self.fCurCount += 1
else:
self.fCheckSFZ = False

if self.fCheckJSFX:
if self.fCheckNative:
self.fCurCount += 1
else:
self.fCheckJSFX = False

if self.fCurCount == 0:
return

self.fCurPercentValue = 100.0 / self.fCurCount
self.fLastCheckValue = 0.0

del pluginCount

if HAIKU:
OS = "HAIKU"
elif LINUX:
OS = "LINUX"
elif MACOS:
OS = "MACOS"
elif WINDOWS:
OS = "WINDOWS"
else:
OS = "UNKNOWN"

if not self.fContinueChecking:
return

self.fSomethingChanged = True

if self.fCheckLADSPA:
if self.fCheckNative:
plugins = self._checkLADSPA(OS, self.fToolNative)
settingsDB.setValue("Plugins/LADSPA_native", plugins)
if not self.fContinueChecking:
return

if self.fCheckPosix32:
tool = os.path.join(self.fPathBinaries, "carla-discovery-posix32")
plugins = self._checkLADSPA(OS, tool)
settingsDB.setValue("Plugins/LADSPA_posix32", plugins)
if not self.fContinueChecking:
return

if self.fCheckPosix64:
tool = os.path.join(self.fPathBinaries, "carla-discovery-posix64")
plugins = self._checkLADSPA(OS, tool)
settingsDB.setValue("Plugins/LADSPA_posix64", plugins)
if not self.fContinueChecking:
return

if self.fCheckWin32:
tool = os.path.join(self.fPathBinaries, "carla-discovery-win32.exe")
plugins = self._checkLADSPA("WINDOWS", tool, not WINDOWS)
settingsDB.setValue("Plugins/LADSPA_win32", plugins)
if not self.fContinueChecking:
return

if self.fCheckWin64:
tool = os.path.join(self.fPathBinaries, "carla-discovery-win64.exe")
plugins = self._checkLADSPA("WINDOWS", tool, not WINDOWS)
settingsDB.setValue("Plugins/LADSPA_win64", plugins)

settingsDB.sync()
if not self.fContinueChecking:
return

if self.fCheckDSSI:
if self.fCheckNative:
plugins = self._checkDSSI(OS, self.fToolNative)
settingsDB.setValue("Plugins/DSSI_native", plugins)
if not self.fContinueChecking:
return

if self.fCheckPosix32:
plugins = self._checkDSSI(OS, os.path.join(self.fPathBinaries, "carla-discovery-posix32"))
settingsDB.setValue("Plugins/DSSI_posix32", plugins)
if not self.fContinueChecking:
return

if self.fCheckPosix64:
plugins = self._checkDSSI(OS, os.path.join(self.fPathBinaries, "carla-discovery-posix64"))
settingsDB.setValue("Plugins/DSSI_posix64", plugins)
if not self.fContinueChecking:
return

if self.fCheckWin32:
tool = os.path.join(self.fPathBinaries, "carla-discovery-win32.exe")
plugins = self._checkDSSI("WINDOWS", tool, not WINDOWS)
settingsDB.setValue("Plugins/DSSI_win32", plugins)
if not self.fContinueChecking:
return

if self.fCheckWin64:
tool = os.path.join(self.fPathBinaries, "carla-discovery-win64.exe")
plugins = self._checkDSSI("WINDOWS", tool, not WINDOWS)
settingsDB.setValue("Plugins/DSSI_win64", plugins)

settingsDB.sync()
if not self.fContinueChecking:
return

if self.fCheckLV2:
plugins = self._checkCached(True)
settingsDB.setValue("Plugins/LV2", plugins)
settingsDB.sync()
if not self.fContinueChecking:
return

if self.fCheckVST2:
if self.fCheckNative:
plugins = self._checkVST2(OS, self.fToolNative)
settingsDB.setValue("Plugins/VST2_native", plugins)
if not self.fContinueChecking:
return

if self.fCheckPosix32:
plugins = self._checkVST2(OS, os.path.join(self.fPathBinaries, "carla-discovery-posix32"))
settingsDB.setValue("Plugins/VST2_posix32", plugins)
if not self.fContinueChecking:
return

if self.fCheckPosix64:
plugins = self._checkVST2(OS, os.path.join(self.fPathBinaries, "carla-discovery-posix64"))
settingsDB.setValue("Plugins/VST2_posix64", plugins)
if not self.fContinueChecking:
return

if self.fCheckWin32:
tool = os.path.join(self.fPathBinaries, "carla-discovery-win32.exe")
plugins = self._checkVST2("WINDOWS", tool, not WINDOWS)
settingsDB.setValue("Plugins/VST2_win32", plugins)
if not self.fContinueChecking:
return

if self.fCheckWin64:
tool = os.path.join(self.fPathBinaries, "carla-discovery-win64.exe")
plugins = self._checkVST2("WINDOWS", tool, not WINDOWS)
settingsDB.setValue("Plugins/VST2_win64", plugins)
if not self.fContinueChecking:
return

settingsDB.sync()
if not self.fContinueChecking:
return

if self.fCheckVST3:
if self.fCheckNative and (LINUX or MACOS or WINDOWS):
plugins = self._checkVST3(self.fToolNative)
settingsDB.setValue("Plugins/VST3_native", plugins)
if not self.fContinueChecking:
return

if self.fCheckPosix32:
plugins = self._checkVST3(os.path.join(self.fPathBinaries, "carla-discovery-posix32"))
settingsDB.setValue("Plugins/VST3_posix32", plugins)
if not self.fContinueChecking:
return

if self.fCheckPosix64:
plugins = self._checkVST3(os.path.join(self.fPathBinaries, "carla-discovery-posix64"))
settingsDB.setValue("Plugins/VST3_posix64", plugins)
if not self.fContinueChecking:
return

if self.fCheckWin32:
tool = os.path.join(self.fPathBinaries, "carla-discovery-win32.exe")
plugins = self._checkVST3(tool, not WINDOWS)
settingsDB.setValue("Plugins/VST3_win32", plugins)
if not self.fContinueChecking:
return

if self.fCheckWin64:
tool = os.path.join(self.fPathBinaries, "carla-discovery-win64.exe")
plugins = self._checkVST3(tool, not WINDOWS)
settingsDB.setValue("Plugins/VST3_win64", plugins)
if not self.fContinueChecking:
return

settingsDB.sync()
if not self.fContinueChecking:
return

if self.fCheckAU:
if self.fCheckNative:
plugins = self._checkCached(False)
settingsDB.setValue("Plugins/AU", plugins)
settingsDB.sync()
if not self.fContinueChecking:
return

if self.fCheckPosix32:
plugins = self._checkAU(os.path.join(self.fPathBinaries, "carla-discovery-posix32"))
settingsDB.setValue("Plugins/AU_posix32", self.fAuPlugins)
if not self.fContinueChecking:
return

settingsDB.sync()
if not self.fContinueChecking:
return

if self.fCheckSF2:
settings = QSafeSettings("falkTX", "Carla2")
SF2_PATH = settings.value(CARLA_KEY_PATHS_SF2, CARLA_DEFAULT_SF2_PATH, list)
del settings

kits = self._checkKIT(SF2_PATH, "sf2")
settingsDB.setValue("Plugins/SF2", kits)
settingsDB.sync()
if not self.fContinueChecking:
return

if self.fCheckSFZ:
kits = self._checkSfzCached()
settingsDB.setValue("Plugins/SFZ", kits)
settingsDB.sync()

if self.fCheckJSFX:
kits = self._checkJsfxCached()
settingsDB.setValue("Plugins/JSFX", kits)
settingsDB.sync()

# -----------------------------------------------------------------------------------------------------------------
# private methods

def _checkLADSPA(self, OS, tool, isWine=False):
ladspaBinaries = []
ladspaPlugins = []

self._pluginLook(self.fLastCheckValue, "LADSPA plugins...")

settings = QSafeSettings("falkTX", "Carla2")
LADSPA_PATH = settings.value(CARLA_KEY_PATHS_LADSPA, CARLA_DEFAULT_LADSPA_PATH, list)
del settings

for iPATH in LADSPA_PATH:
binaries = findBinaries(iPATH, PLUGIN_LADSPA, OS)
for binary in binaries:
if binary not in ladspaBinaries:
ladspaBinaries.append(binary)

ladspaBinaries.sort()

if not self.fContinueChecking:
return ladspaPlugins

for i in range(len(ladspaBinaries)):
ladspa = ladspaBinaries[i]
percent = ( float(i) / len(ladspaBinaries) ) * self.fCurPercentValue
self._pluginLook((self.fLastCheckValue + percent) * 0.9, ladspa)

plugins = checkPluginLADSPA(ladspa, tool, self.fWineSettings if isWine else None)
if plugins:
ladspaPlugins.append(plugins)

if not self.fContinueChecking:
break

self.fLastCheckValue += self.fCurPercentValue
return ladspaPlugins

def _checkDSSI(self, OS, tool, isWine=False):
dssiBinaries = []
dssiPlugins = []

self._pluginLook(self.fLastCheckValue, "DSSI plugins...")

settings = QSafeSettings("falkTX", "Carla2")
DSSI_PATH = settings.value(CARLA_KEY_PATHS_DSSI, CARLA_DEFAULT_DSSI_PATH, list)
del settings

for iPATH in DSSI_PATH:
binaries = findBinaries(iPATH, PLUGIN_DSSI, OS)
for binary in binaries:
if binary not in dssiBinaries:
dssiBinaries.append(binary)

dssiBinaries.sort()

if not self.fContinueChecking:
return dssiPlugins

for i in range(len(dssiBinaries)):
dssi = dssiBinaries[i]
percent = ( float(i) / len(dssiBinaries) ) * self.fCurPercentValue
self._pluginLook(self.fLastCheckValue + percent, dssi)

plugins = checkPluginDSSI(dssi, tool, self.fWineSettings if isWine else None)
if plugins:
dssiPlugins.append(plugins)

if not self.fContinueChecking:
break

self.fLastCheckValue += self.fCurPercentValue
return dssiPlugins

def _checkVST2(self, OS, tool, isWine=False):
vst2Binaries = []
vst2Plugins = []

if MACOS and not isWine:
self._pluginLook(self.fLastCheckValue, "VST2 bundles...")
else:
self._pluginLook(self.fLastCheckValue, "VST2 plugins...")

settings = QSafeSettings("falkTX", "Carla2")
VST2_PATH = settings.value(CARLA_KEY_PATHS_VST2, CARLA_DEFAULT_VST2_PATH, list)
del settings

for iPATH in VST2_PATH:
if MACOS and not isWine:
binaries = findMacVSTBundles(iPATH, False)
else:
binaries = findBinaries(iPATH, PLUGIN_VST2, OS)
for binary in binaries:
if binary not in vst2Binaries:
vst2Binaries.append(binary)

vst2Binaries.sort()

if not self.fContinueChecking:
return vst2Plugins

for i in range(len(vst2Binaries)):
vst2 = vst2Binaries[i]
percent = ( float(i) / len(vst2Binaries) ) * self.fCurPercentValue
self._pluginLook(self.fLastCheckValue + percent, vst2)

plugins = checkPluginVST2(vst2, tool, self.fWineSettings if isWine else None)
if plugins:
vst2Plugins.append(plugins)

if not self.fContinueChecking:
break

self.fLastCheckValue += self.fCurPercentValue
return vst2Plugins

def _checkVST3(self, tool, isWine=False):
vst3Binaries = []
vst3Plugins = []

if MACOS and not isWine:
self._pluginLook(self.fLastCheckValue, "VST3 bundles...")
else:
self._pluginLook(self.fLastCheckValue, "VST3 plugins...")

settings = QSafeSettings("falkTX", "Carla2")
VST3_PATH = settings.value(CARLA_KEY_PATHS_VST3, CARLA_DEFAULT_VST3_PATH, list)
del settings

for iPATH in VST3_PATH:
if MACOS and not isWine:
binaries = findMacVSTBundles(iPATH, True)
else:
binaries = findVST3Binaries(iPATH)
for binary in binaries:
if binary not in vst3Binaries:
vst3Binaries.append(binary)

vst3Binaries.sort()

if not self.fContinueChecking:
return vst3Plugins

for i in range(len(vst3Binaries)):
vst3 = vst3Binaries[i]
percent = ( float(i) / len(vst3Binaries) ) * self.fCurPercentValue
self._pluginLook(self.fLastCheckValue + percent, vst3)

plugins = checkPluginVST3(vst3, tool, self.fWineSettings if isWine else None)
if plugins:
vst3Plugins.append(plugins)

if not self.fContinueChecking:
break

self.fLastCheckValue += self.fCurPercentValue
return vst3Plugins

def _checkAU(self, tool):
auPlugins = []

plugins = checkAllPluginsAU(tool)
if plugins:
auPlugins.append(plugins)

self.fLastCheckValue += self.fCurPercentValue
return auPlugins

def _checkKIT(self, kitPATH, kitExtension):
kitFiles = []
kitPlugins = []

for iPATH in kitPATH:
files = findFilenames(iPATH, kitExtension)
for file_ in files:
if file_ not in kitFiles:
kitFiles.append(file_)

kitFiles.sort()

if not self.fContinueChecking:
return kitPlugins

for i in range(len(kitFiles)):
kit = kitFiles[i]
percent = ( float(i) / len(kitFiles) ) * self.fCurPercentValue
self._pluginLook(self.fLastCheckValue + percent, kit)

if kitExtension == "sf2":
plugins = checkFileSF2(kit, self.fToolNative)
else:
plugins = None

if plugins:
kitPlugins.append(plugins)

if not self.fContinueChecking:
break

self.fLastCheckValue += self.fCurPercentValue
return kitPlugins

def _checkCached(self, isLV2):
if isLV2:
settings = QSafeSettings("falkTX", "Carla2")
PLUG_PATH = splitter.join(settings.value(CARLA_KEY_PATHS_LV2, CARLA_DEFAULT_LV2_PATH, list))
del settings
PLUG_TEXT = "LV2"
PLUG_TYPE = PLUGIN_LV2
else: # AU
PLUG_PATH = ""
PLUG_TEXT = "AU"
PLUG_TYPE = PLUGIN_AU

plugins = []
self._pluginLook(self.fLastCheckValue, f"{PLUG_TEXT} plugins...")

if not isLV2:
gCarla.utils.juce_init()

count = gCarla.utils.get_cached_plugin_count(PLUG_TYPE, PLUG_PATH)

if not self.fContinueChecking:
return plugins

for i in range(count):
descInfo = gCarla.utils.get_cached_plugin_info(PLUG_TYPE, i)

percent = ( float(i) / count ) * self.fCurPercentValue
self._pluginLook(self.fLastCheckValue + percent, descInfo['label'])

if not descInfo['valid']:
continue

plugins.append(checkPluginCached(descInfo, PLUG_TYPE))

if not self.fContinueChecking:
break

if not isLV2:
gCarla.utils.juce_cleanup()

self.fLastCheckValue += self.fCurPercentValue
return plugins

def _checkSfzCached(self):
settings = QSafeSettings("falkTX", "Carla2")
PLUG_PATH = splitter.join(settings.value(CARLA_KEY_PATHS_SFZ, CARLA_DEFAULT_SFZ_PATH, list))
del settings

sfzKits = []
self._pluginLook(self.fLastCheckValue, "SFZ kits...")

count = gCarla.utils.get_cached_plugin_count(PLUGIN_SFZ, PLUG_PATH)

if not self.fContinueChecking:
return sfzKits

for i in range(count):
descInfo = gCarla.utils.get_cached_plugin_info(PLUGIN_SFZ, i)

percent = ( float(i) / count ) * self.fCurPercentValue
self._pluginLook(self.fLastCheckValue + percent, descInfo['label'])

if not descInfo['valid']:
continue

sfzKits.append(checkPluginCached(descInfo, PLUGIN_SFZ))

if not self.fContinueChecking:
break

self.fLastCheckValue += self.fCurPercentValue
return sfzKits

def _checkJsfxCached(self):
settings = QSafeSettings("falkTX", "Carla2")
PLUG_PATH = splitter.join(settings.value(CARLA_KEY_PATHS_JSFX, CARLA_DEFAULT_JSFX_PATH, list))
del settings

jsfxPlugins = []
self._pluginLook(self.fLastCheckValue, "JSFX plugins...")

count = gCarla.utils.get_cached_plugin_count(PLUGIN_JSFX, PLUG_PATH)

if not self.fContinueChecking:
return jsfxPlugins

for i in range(count):
descInfo = gCarla.utils.get_cached_plugin_info(PLUGIN_JSFX, i)

percent = ( float(i) / count ) * self.fCurPercentValue
self._pluginLook(self.fLastCheckValue + percent, descInfo['label'])

if not descInfo['valid']:
continue

jsfxPlugins.append(checkPluginCached(descInfo, PLUGIN_JSFX))

if not self.fContinueChecking:
break

self.fLastCheckValue += self.fCurPercentValue
return jsfxPlugins

def _pluginLook(self, percent, plugin):
self.pluginLook.emit(percent, plugin)

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

+ 264
- 0
source/frontend/pluginlist/jackappdialog.cpp View File

@@ -0,0 +1,264 @@
/*
* Carla plugin host
* Copyright (C) 2011-2022 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.
*/

#include "jackappdialog.hpp"

#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
# pragma clang diagnostic ignored "-Wdeprecated-register"
#elif defined(__GNUC__) && __GNUC__ >= 8
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wclass-memaccess"
# pragma GCC diagnostic ignored "-Wdeprecated-copy"
#endif

#include "jackappdialog_ui.hpp"
#include <QtCore/QFileInfo>
#include <QtCore/QVector>
#include <QtWidgets/QPushButton>

#ifdef __clang__
# pragma clang diagnostic pop
#elif defined(__GNUC__) && __GNUC__ >= 8
# pragma GCC diagnostic pop
#endif

#include "../utils/qsafesettings.hpp"
#include "../../includes/CarlaLibJackHints.h"

// --------------------------------------------------------------------------------------------------------------------
// Jack Application Dialog

enum {
UI_SESSION_NONE = 0,
UI_SESSION_LADISH = 1,
UI_SESSION_NSM = 2,
};

struct JackApplicationW::Self {
Ui_Dialog ui;
const QString fProjectFilename;

Self(const char* const projectFilename)
: fProjectFilename(projectFilename) {}

static Self& create(const char* const projectFilename)
{
Self* const self = new Self(projectFilename);
return *self;
}
};

JackApplicationW::JackApplicationW(QWidget* const parent, const char* const projectFilename)
: QDialog(parent),
self(Self::create(projectFilename))
{
self.ui.setupUi(this);

// -------------------------------------------------------------------------------------------------------------
// UI setup

self.ui.group_error->setVisible(false);
adjustSize();
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

// -------------------------------------------------------------------------------------------------------------
// Load settings

loadSettings();

// -------------------------------------------------------------------------------------------------------------
// Set-up connections

connect(this, &QDialog::finished,
this, &JackApplicationW::slot_saveSettings);
connect(self.ui.cb_session_mgr, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &JackApplicationW::slot_sessionManagerChanged);
connect(self.ui.le_command, &QLineEdit::textChanged,
this, &JackApplicationW::slot_commandChanged);
}

JackApplicationW::~JackApplicationW()
{
delete &self;
}

// -----------------------------------------------------------------------------------------------------------------
// public methods

JackApplicationW::CommandAndFlags JackApplicationW::getCommandAndFlags() const
{
const QString command = self.ui.le_command->text();
QString name = self.ui.le_name->text();

if (name.isEmpty())
{
name = QFileInfo(command.split(' ').first()).baseName();
name[0] = name[0].toTitleCase();
}

SessionManager smgr;
switch(self.ui.cb_session_mgr->currentIndex())
{
case UI_SESSION_LADISH:
smgr = LIBJACK_SESSION_MANAGER_LADISH;
break;
case UI_SESSION_NSM:
smgr = LIBJACK_SESSION_MANAGER_NSM;
break;
default:
smgr = LIBJACK_SESSION_MANAGER_NONE;
break;
}

uint flags = 0x0;
if (self.ui.cb_manage_window->isChecked())
flags |= LIBJACK_FLAG_CONTROL_WINDOW;
if (self.ui.cb_capture_first_window->isChecked())
flags |= LIBJACK_FLAG_CAPTURE_FIRST_WINDOW;
if (self.ui.cb_buffers_addition_mode->isChecked())
flags |= LIBJACK_FLAG_AUDIO_BUFFERS_ADDITION;
if (self.ui.cb_out_midi_mixdown->isChecked())
flags |= LIBJACK_FLAG_MIDI_OUTPUT_CHANNEL_MIXDOWN;
if (self.ui.cb_external_start->isChecked())
flags |= LIBJACK_FLAG_EXTERNAL_START;

const QString labelSetup(QString("%1%2%3%4%5%6").arg(QChar('0' + self.ui.sb_audio_ins->value()))
.arg(QChar('0' + self.ui.sb_audio_outs->value()))
.arg(QChar('0' + self.ui.sb_midi_ins->value()))
.arg(QChar('0' + self.ui.sb_midi_outs->value()))
.arg(QChar('0' + smgr))
.arg(QChar('0' + flags)));

return {command, name, labelSetup};
}

// -----------------------------------------------------------------------------------------------------------------
// private methods

void JackApplicationW::checkIfButtonBoxShouldBeEnabled(const int index, const QCarlaString& command)
{
bool enabled = command.isNotEmpty();
QCarlaString showErr;

// NSM applications must not be abstract or absolute paths, and must not contain arguments
if (enabled and index == UI_SESSION_NSM)
{
if (QVector<QChar>{'.', '/'}.contains(command[0]))
showErr = tr("NSM applications cannot use abstract or absolute paths");
else if (command.contains(' ') or command.contains(';') or command.contains('&'))
showErr = tr("NSM applications cannot use CLI arguments");
else if (self.fProjectFilename.isEmpty())
showErr = tr("You need to save the current Carla project before NSM can be used");
}

if (showErr.isNotEmpty())
{
enabled = false;
self.ui.l_error->setText(showErr);
self.ui.group_error->setVisible(true);
}
else
{
self.ui.group_error->setVisible(false);
}

if (QPushButton* const button = self.ui.buttonBox->button(QDialogButtonBox::Ok))
button->setEnabled(enabled);
}

void JackApplicationW::loadSettings()
{
const QSafeSettings settings("falkTX", "CarlaAddJackApp");

const QString smName = settings.valueString("SessionManager", "");

if (smName == "LADISH (SIGUSR1)")
self.ui.cb_session_mgr->setCurrentIndex(UI_SESSION_LADISH);
else if (smName == "NSM")
self.ui.cb_session_mgr->setCurrentIndex(UI_SESSION_NSM);
else
self.ui.cb_session_mgr->setCurrentIndex(UI_SESSION_NONE);

self.ui.le_command->setText(settings.valueString("Command", ""));
self.ui.le_name->setText(settings.valueString("Name", ""));
self.ui.sb_audio_ins->setValue(settings.valueIntPositive("NumAudioIns", 2));
self.ui.sb_audio_ins->setValue(settings.valueIntPositive("NumAudioIns", 2));
self.ui.sb_audio_outs->setValue(settings.valueIntPositive("NumAudioOuts", 2));
self.ui.sb_midi_ins->setValue(settings.valueIntPositive("NumMidiIns", 0));
self.ui.sb_midi_outs->setValue(settings.valueIntPositive("NumMidiOuts", 0));
self.ui.cb_manage_window->setChecked(settings.valueBool("ManageWindow", true));
self.ui.cb_capture_first_window->setChecked(settings.valueBool("CaptureFirstWindow", false));
self.ui.cb_out_midi_mixdown->setChecked(settings.valueBool("MidiOutMixdown", false));

checkIfButtonBoxShouldBeEnabled(self.ui.cb_session_mgr->currentIndex(),
self.ui.le_command->text());
}

// -----------------------------------------------------------------------------------------------------------------
// private slots

void JackApplicationW::slot_commandChanged(const QString& command)
{
checkIfButtonBoxShouldBeEnabled(self.ui.cb_session_mgr->currentIndex(), command);
}

void JackApplicationW::slot_sessionManagerChanged(const int index)
{
checkIfButtonBoxShouldBeEnabled(index, self.ui.le_command->text());
}

void JackApplicationW::slot_saveSettings()
{
QSafeSettings settings("falkTX", "CarlaAddJackApp");