diff --git a/.gitmodules b/.gitmodules index 4a8c9adc1..94530a197 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ +[submodule "src/3rdparty/qt5-x11embed"] + path = src/3rdparty/qt5-x11embed + url = https://github.com/Lukas-W/qt5-x11embed.git [submodule "src/3rdparty/rpmalloc/rpmalloc"] path = src/3rdparty/rpmalloc/rpmalloc url = https://github.com/rampantpixels/rpmalloc.git diff --git a/.travis.yml b/.travis.yml index 3ab8dbf14..82b7bf291 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ matrix: - env: TARGET_OS=win64 - os: osx osx_image: xcode8.2 + - env: QT5= - env: QT5=True - env: QT5=True TARGET_OS=win32 - env: QT5=True TARGET_OS=win64 diff --git a/.travis/linux..before_install.sh b/.travis/linux..before_install.sh index adc892216..c2e578b54 100755 --- a/.travis/linux..before_install.sh +++ b/.travis/linux..before_install.sh @@ -2,7 +2,7 @@ set -e -sudo add-apt-repository ppa:beineri/opt-qt58-trusty -y +sudo add-apt-repository ppa:beineri/opt-qt592-trusty -y sudo add-apt-repository ppa:andrewrk/libgroove -y sudo sed -e "s/trusty/precise/" -i \ /etc/apt/sources.list.d/andrewrk-libgroove-trusty.list diff --git a/.travis/linux..install.sh b/.travis/linux..install.sh index f49f9e285..eae5507e4 100755 --- a/.travis/linux..install.sh +++ b/.travis/linux..install.sh @@ -3,21 +3,29 @@ set -e PACKAGES="cmake libsndfile-dev fftw3-dev libvorbis-dev libogg-dev libmp3lame-dev - libasound2-dev libjack-dev libsdl-dev libsamplerate0-dev libstk0-dev stk - libfluidsynth-dev portaudio19-dev wine-dev g++-multilib libfltk1.3-dev + libasound2-dev libjack-jackd2-dev libsdl-dev libsamplerate0-dev libstk0-dev stk + libfluidsynth-dev portaudio19-dev g++-multilib libfltk1.3-dev libgig-dev libsoundio-dev" # swh build dependencies SWH_PACKAGES="perl libxml2-utils libxml-perl liblist-moreutils-perl" +VST_PACKAGES="wine-dev libqt5x11extras5-dev qtbase5-private-dev libxcb-util0-dev libxcb-keysyms1-dev" + # Help with unmet dependencies -PACKAGES="$PACKAGES $SWH_PACKAGES libjack0" +PACKAGES="$PACKAGES $SWH_PACKAGES $VST_PACKAGES libjack-jackd2-0" if [ "$QT5" ]; then - PACKAGES="$PACKAGES qt58base qt58translations qt58tools" + PACKAGES="$PACKAGES qt59base qt59translations qt59tools" else PACKAGES="$PACKAGES libqt4-dev" fi # shellcheck disable=SC2086 sudo apt-get install -y $PACKAGES + +# kxstudio repo offers Carla; avoid package conflicts (wine, etc) by running last +sudo add-apt-repository -y ppa:kxstudio-debian/libs +sudo add-apt-repository -y ppa:kxstudio-debian/apps +sudo apt-get update +sudo apt-get install -y carla-git diff --git a/.travis/linux..script.sh b/.travis/linux..script.sh index d74160383..aaebd3324 100755 --- a/.travis/linux..script.sh +++ b/.travis/linux..script.sh @@ -2,7 +2,7 @@ if [ "$QT5" ]; then unset QTDIR QT_PLUGIN_PATH LD_LIBRARY_PATH # shellcheck disable=SC1091 - source /opt/qt58/bin/qt58-env.sh + source /opt/qt59/bin/qt59-env.sh fi set -e diff --git a/CMakeLists.txt b/CMakeLists.txt index 136ad1a26..50ab2caf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ SET(PROJECT_COPYRIGHT "2008-${PROJECT_YEAR} ${PROJECT_AUTHOR}") SET(VERSION_MAJOR "1") SET(VERSION_MINOR "2") SET(VERSION_RELEASE "0") -SET(VERSION_STAGE "rc4") +SET(VERSION_STAGE "rc5") SET(VERSION_BUILD "0") SET(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_RELEASE}") IF(VERSION_STAGE) @@ -160,6 +160,11 @@ IF(WANT_QT5) Qt5::Xml ) + IF(LMMS_BUILD_LINUX) + FIND_PACKAGE(Qt5X11Extras REQUIRED) + LIST(APPEND QT_LIBRARIES Qt5::X11Extras) + ENDIF() + # Resolve Qt5::qmake to full path for use in packaging scripts GET_TARGET_PROPERTY(QT_QMAKE_EXECUTABLE "${Qt5Core_QMAKE_EXECUTABLE}" IMPORTED_LOCATION) diff --git a/cmake/linux/package_linux.sh.in b/cmake/linux/package_linux.sh.in index 729169ec4..e949c4724 100644 --- a/cmake/linux/package_linux.sh.in +++ b/cmake/linux/package_linux.sh.in @@ -56,7 +56,7 @@ echo -e "\nWriting verbose output to \"${LOGFILE}\"" # Ensure linuxdeployqt uses the same qmake version as cmake export PATH -PATH="$(dirname "@QT_QMAKE_EXECUTABLE@")":$PATH +PATH="$HOME/bin:$(dirname "@QT_QMAKE_EXECUTABLE@")":$PATH # Fetch portable linuxdeployqt if cache is older than $DAYSOLD echo -e "\nDownloading linuxdeployqt to ${LINUXDEPLOYQT}..." @@ -105,6 +105,14 @@ mv "${APPDIR}usr/bin/lmms" "${APPDIR}usr/bin/lmms.real" cat >"${APPDIR}usr/bin/lmms" < /dev/null 2>&1; then + CARLAPATH="$(which carla)" + CARLAPREFIX="\${CARLAPATH%/bin*}" + echo "Carla appears to be installed on this system at \$CARLAPREFIX/lib[64]/carla so we'll use it." + export LD_LIBRARY_PATH=\$CARLAPREFIX/lib/carla:\$CARLAPREFIX/lib64/carla:\$LD_LIBRARY_PATH +else + echo "Carla does not appear to be installed. That's OK, please ignore any related library errors." +fi export LD_LIBRARY_PATH=\$DIR/usr/lib/:\$DIR/usr/lib/lmms:\$LD_LIBRARY_PATH # Prevent segfault on VirualBox if lsmod |grep vboxguest > /dev/null 2>&1; then @@ -117,7 +125,7 @@ else echo "Jack does not appear to be installed. That's OK, we'll use a dummy version instead." export LD_LIBRARY_PATH=\$DIR/usr/lib/lmms/optional:\$LD_LIBRARY_PATH fi -QT_X11_NO_NATIVE_MENUBAR=1 QT_AUTO_SCREEN_SCALE_FACTOR=1 \$DIR/usr/bin/lmms.real "\$@" +QT_X11_NO_NATIVE_MENUBAR=1 \$DIR/usr/bin/lmms.real "\$@" EOL chmod +x "${APPDIR}usr/bin/lmms" @@ -172,6 +180,9 @@ ln -sr "$VSTBIN" "$VSTLIB" # Remove wine library conflict rm -f "${APPDIR}/usr/lib/libwine.so.1" +# Use system-provided carla +rm -f "${APPDIR}usr/lib/"libcarla*.so + # Remove problematic jack library, replace with weakjack if [ -e "${APPDIR}/usr/lib/libjack.so.0" ]; then rm -f "${APPDIR}/usr/lib/libjack.so.0" diff --git a/include/AudioJack.h b/include/AudioJack.h index c3207c829..83717f251 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -37,6 +37,7 @@ #include #include #include +#include #include "AudioDevice.h" #include "AudioDeviceSetupWidget.h" @@ -107,6 +108,7 @@ private: bool m_active; bool m_stopped; + QMutex m_processingMutex; MidiJack *m_midiClient; QVector m_outputPorts; diff --git a/include/ConfigManager.h b/include/ConfigManager.h index 2510283c0..997d7f24c 100644 --- a/include/ConfigManager.h +++ b/include/ConfigManager.h @@ -32,9 +32,9 @@ #include #include #include +#include #include "export.h" -#include "MemoryManager.h" class LmmsCore; @@ -51,9 +51,9 @@ const QString TRACK_ICON_PATH = "track_icons/"; const QString LOCALE_PATH = "locale/"; -class EXPORT ConfigManager +class EXPORT ConfigManager : public QObject { - MM_OPERATORS + Q_OBJECT public: static inline ConfigManager * inst() { @@ -210,6 +210,9 @@ public: return m_recentlyOpenedProjects; } + static QStringList availabeVstEmbedMethods(); + QString vstEmbedMethod() const; + // returns true if the working dir (e.g. ~/lmms) exists on disk bool hasWorkingDir() const; @@ -242,6 +245,8 @@ public: // creates the working directory & subdirectories on disk. void createWorkingDir(); +signals: + void valueChanged( QString cls, QString attribute, QString value ); private: static ConfigManager * s_instanceOfMe; diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index bfba39de2..65f281a7e 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -415,6 +415,7 @@ private: enum RemoteMessageIDs { IdUndefined, + IdHostInfoGotten, IdInitDone, IdQuit, IdSampleRateInformation, @@ -428,6 +429,8 @@ enum RemoteMessageIDs IdChangeInputOutputCount, IdShowUI, IdHideUI, + IdToggleUI, + IdIsUIVisible, IdSaveSettingsToString, IdSaveSettingsToFile, IdLoadSettingsFromString, @@ -782,7 +785,13 @@ public: #endif } - bool init( const QString &pluginExecutable, bool waitForInitDoneMsg ); + bool init( const QString &pluginExecutable, bool waitForInitDoneMsg, QStringList extraArgs = {} ); + + inline void waitForHostInfoGotten() + { + m_failed = waitForMessage( IdHostInfoGotten ).id + != IdHostInfoGotten; + } inline void waitForInitDone( bool _busyWaiting = true ) { @@ -802,18 +811,21 @@ public: unlock(); } - void showUI() + + virtual void toggleUI() { lock(); - sendMessage( IdShowUI ); + sendMessage( IdToggleUI ); unlock(); } - void hideUI() + int isUIVisible() { lock(); - sendMessage( IdHideUI ); + sendMessage( IdIsUIVisible ); unlock(); + message m = waitForMessage( IdIsUIVisible ); + return m.id != IdIsUIVisible ? -1 : m.getInt() ? 1 : 0; } inline bool failed() const @@ -831,6 +843,9 @@ public: m_commMutex.unlock(); } +public slots: + virtual void showUI(); + virtual void hideUI(); protected: inline void setSplittedChannels( bool _on ) @@ -1207,6 +1222,7 @@ RemotePluginClient::RemotePluginClient( const char * socketPath ) : m_vstSyncData = (VstSyncData *) m_shmQtID.data(); m_bufferSize = m_vstSyncData->m_bufferSize; m_sampleRate = m_vstSyncData->m_sampleRate; + sendMessage( IdHostInfoGotten ); return; } #else @@ -1234,6 +1250,7 @@ RemotePluginClient::RemotePluginClient( const char * socketPath ) : { m_bufferSize = m_vstSyncData->m_bufferSize; m_sampleRate = m_vstSyncData->m_sampleRate; + sendMessage( IdHostInfoGotten ); // detach segment if( shmdt(m_vstSyncData) == -1 ) @@ -1249,6 +1266,12 @@ RemotePluginClient::RemotePluginClient( const char * socketPath ) : // if attaching shared memory fails sendMessage( IdSampleRateInformation ); sendMessage( IdBufferSizeInformation ); + if( waitForMessage( IdBufferSizeInformation ).id + != IdBufferSizeInformation ) + { + fprintf( stderr, "Could not get buffer size information\n" ); + } + sendMessage( IdHostInfoGotten ); } diff --git a/include/SetupDialog.h b/include/SetupDialog.h index e34035d2e..ebaa90f01 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -204,7 +204,8 @@ private: MswMap m_midiIfaceSetupWidgets; trMap m_midiIfaceNames; - + QComboBox* m_vstEmbedComboBox; + QString m_vstEmbedMethod; } ; diff --git a/include/debug.h b/include/debug.h index bb5bce557..55173f0a1 100644 --- a/include/debug.h +++ b/include/debug.h @@ -34,7 +34,9 @@ #ifdef LMMS_DEBUG #include #else - #define assert(x) ((void)(x)) + #ifndef assert + #define assert(x) ((void)(x)) + #endif #endif #include diff --git a/plugins/Eq/EqCurve.cpp b/plugins/Eq/EqCurve.cpp index 24a560b15..5d669b04a 100644 --- a/plugins/Eq/EqCurve.cpp +++ b/plugins/Eq/EqCurve.cpp @@ -32,8 +32,7 @@ EqHandle::EqHandle( int num, int x, int y ): m_width( x ), m_heigth( y ), m_mousePressed( false ), - m_active( false ), - m_handleMoved( false ) + m_active( false ) { setFlag( ItemIsMovable ); setFlag( ItemSendsGeometryChanges ); @@ -41,7 +40,6 @@ EqHandle::EqHandle( int num, int x, int y ): float totalHeight = 36; m_pixelsPerUnitHeight = ( m_heigth ) / ( totalHeight ); setMouseHover( false ); - connect( this, SIGNAL( positionChanged() ), this, SLOT( handleMoved() ) ); } @@ -93,14 +91,6 @@ float EqHandle::yPixelToGain(float y , int h, float pixelPerUnitHeight ) -void EqHandle::handleMoved() -{ - m_handleMoved = true; -} - - - - void EqHandle::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget ) { painter->setRenderHint( QPainter::Antialiasing, true ); @@ -189,6 +179,11 @@ void EqHandle::loadPixmap() m_circlePixmap = PLUGIN_NAME::getIconPixmap( fileName.toLatin1() ); } +bool EqHandle::mousePressed() const +{ + return m_mousePressed; +} + @@ -463,22 +458,6 @@ void EqHandle::setHandleActive( bool a ) -void EqHandle::setHandleMoved( bool a ) -{ - m_handleMoved = a; -} - - - - -bool EqHandle::getHandleMoved() -{ - return m_handleMoved; -} - - - - void EqHandle::sethp12() { m_hp12 = true; diff --git a/plugins/Eq/EqCurve.h b/plugins/Eq/EqCurve.h index 52e39a520..58dfa2ac9 100644 --- a/plugins/Eq/EqCurve.h +++ b/plugins/Eq/EqCurve.h @@ -71,8 +71,7 @@ public: void setMouseHover( bool d ); bool isActiveHandle(); void setHandleActive( bool a ); - void setHandleMoved(bool a); - bool getHandleMoved(); + bool mousePressed() const; void sethp12(); void sethp24(); void sethp48(); @@ -95,7 +94,6 @@ protected: private: double calculateGain( const double freq, const double a1, const double a2, const double b0, const double b1, const double b2 ); void loadPixmap(); - float m_pixelsPerUnitWidth; float m_pixelsPerUnitHeight; float m_scale; @@ -111,11 +109,7 @@ private: float m_resonance; bool m_mousePressed; bool m_active; - bool m_handleMoved; QPixmap m_circlePixmap; -private slots: - void handleMoved(); - }; diff --git a/plugins/Eq/EqParameterWidget.cpp b/plugins/Eq/EqParameterWidget.cpp index 4ccdfbb2f..be2174a2b 100644 --- a/plugins/Eq/EqParameterWidget.cpp +++ b/plugins/Eq/EqParameterWidget.cpp @@ -104,7 +104,7 @@ void EqParameterWidget::updateHandle() m_eqcurve->setModelChanged( true ); for( int i = 0 ; i < bandCount(); i++ ) { - if ( m_handleList->at( i )->getHandleMoved() == false ) //prevents a short circuit between handle and data model + if ( !m_handleList->at( i )->mousePressed() ) //prevents a short circuit between handle and data model { //sets the band on active if a fader or a knob is moved bool hover = false; // prevents an action if handle is moved @@ -126,7 +126,6 @@ void EqParameterWidget::updateHandle() else { m_handleList->at( i )->setHandleActive( m_bands[i].active->value() ); - m_handleList->at( i )->setHandleMoved( false ); } } if ( m_bands[0].hp12->value() ) m_handleList->at( 0 )->sethp12(); diff --git a/plugins/VstEffect/VstEffect.cpp b/plugins/VstEffect/VstEffect.cpp index 17d29608b..dcd7f2035 100644 --- a/plugins/VstEffect/VstEffect.cpp +++ b/plugins/VstEffect/VstEffect.cpp @@ -55,7 +55,6 @@ Plugin::Descriptor PLUGIN_EXPORT vsteffect_plugin_descriptor = VstEffect::VstEffect( Model * _parent, const Descriptor::SubPluginFeatures::Key * _key ) : Effect( &vsteffect_plugin_descriptor, _parent, _key ), - m_plugin( NULL ), m_pluginMutex(), m_key( *_key ), m_vstControls( this ) @@ -73,7 +72,6 @@ VstEffect::VstEffect( Model * _parent, VstEffect::~VstEffect() { - closePlugin(); } @@ -128,22 +126,20 @@ void VstEffect::openPlugin( const QString & _plugin ) VstPlugin::tr( "Loading plugin" ), VstPlugin::tr( "Please wait while loading VST plugin..." ), PLUGIN_NAME::getIconPixmap( "logo", 24, 24 ), 0 ); - m_pluginMutex.lock(); - m_plugin = new VstPlugin( _plugin ); + + QMutexLocker ml( &m_pluginMutex ); Q_UNUSED( ml ); + m_plugin = QSharedPointer(new VstPlugin( _plugin )); if( m_plugin->failed() ) { - m_pluginMutex.unlock(); - closePlugin(); + m_plugin.clear(); delete tf; collectErrorForUI( VstPlugin::tr( "The VST plugin %1 could not be loaded." ).arg( _plugin ) ); return; } - VstPlugin::connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), m_plugin, SLOT( setTempo( bpm_t ) ) ); + VstPlugin::connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), m_plugin.data(), SLOT( setTempo( bpm_t ) ) ); m_plugin->setTempo( Engine::getSong()->getTempo() ); - m_pluginMutex.unlock(); - delete tf; m_key.attributes["file"] = _plugin; @@ -151,21 +147,6 @@ void VstEffect::openPlugin( const QString & _plugin ) -void VstEffect::closePlugin() -{ - m_pluginMutex.lock(); - if( m_plugin && m_plugin->pluginWidget() != NULL ) - { - delete m_plugin->pluginWidget(); - } - delete m_plugin; - m_plugin = NULL; - m_pluginMutex.unlock(); -} - - - - extern "C" { diff --git a/plugins/VstEffect/VstEffect.h b/plugins/VstEffect/VstEffect.h index b678367d5..95834e862 100644 --- a/plugins/VstEffect/VstEffect.h +++ b/plugins/VstEffect/VstEffect.h @@ -25,13 +25,14 @@ #ifndef _VST_EFFECT_H #define _VST_EFFECT_H -#include +#include +#include #include "Effect.h" -#include "VstPlugin.h" #include "VstEffectControlDialog.h" #include "VstEffectControls.h" +class VstPlugin; class VstEffect : public Effect { @@ -58,7 +59,7 @@ private: void openPlugin( const QString & _plugin ); void closePlugin(); - VstPlugin * m_plugin; + QSharedPointer m_plugin; QMutex m_pluginMutex; EffectKey m_key; diff --git a/plugins/VstEffect/VstEffectControlDialog.cpp b/plugins/VstEffect/VstEffectControlDialog.cpp index b9a7d9822..cbbab0c22 100644 --- a/plugins/VstEffect/VstEffectControlDialog.cpp +++ b/plugins/VstEffect/VstEffectControlDialog.cpp @@ -30,6 +30,7 @@ #include "VstEffectControlDialog.h" #include "VstEffect.h" +#include "ConfigManager.h" #include "PixmapButton.h" #include "embed.h" #include "ToolTip.h" @@ -52,32 +53,43 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : l->setVerticalSpacing( 2 ); l->setHorizontalSpacing( 2 ); + bool embed_vst = false; + if( _ctl != NULL && _ctl->m_effect != NULL && _ctl->m_effect->m_plugin != NULL ) { m_plugin = _ctl->m_effect->m_plugin; - m_plugin->showEditor( NULL, true ); - m_pluginWidget = m_plugin->pluginWidget(); + embed_vst = m_plugin->embedMethod() != "none"; + + if (embed_vst) { + m_plugin->createUI( nullptr, true ); + m_pluginWidget = m_plugin->pluginWidget( false ); #ifdef LMMS_BUILD_WIN32 - - if( !m_pluginWidget ) - { - m_pluginWidget = m_plugin->pluginWidget( false ); - } + if( !m_pluginWidget ) + { + m_pluginWidget = m_plugin->pluginWidget( false ); + } #endif + + } } - if( m_pluginWidget ) + if ( m_plugin && (!embed_vst || m_pluginWidget) ) { - setWindowTitle( m_pluginWidget->windowTitle() ); - setMinimumWidth( 250 ); + setWindowTitle( m_plugin->name() ); QPushButton * btn = new QPushButton( tr( "Show/hide" ) ); - btn->setCheckable( true ); - connect( btn, SIGNAL( toggled( bool ) ), - m_pluginWidget, SLOT( setVisible( bool ) ) ); - emit btn->click(); + + if (embed_vst) { + btn->setCheckable( true ); + btn->setChecked( true ); + connect( btn, SIGNAL( toggled( bool ) ), + SLOT( togglePluginUI( bool ) ) ); + } else { + connect( btn, SIGNAL( clicked( bool ) ), + SLOT( togglePluginUI( bool ) ) ); + } btn->setMinimumWidth( 78 ); btn->setMaximumWidth( 78 ); @@ -206,8 +218,13 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_savePresetButton->setMinimumHeight( 21 ); m_savePresetButton->setMaximumHeight( 21 ); - int newSize = m_pluginWidget->width() + 20; - newSize = (newSize < 250) ? 250 : newSize; + int newSize = 0; + + if (embed_vst) { + newSize = m_pluginWidget->width() + 20; + } + newSize = std::max(newSize, 250); + QWidget* resize = new QWidget(this); resize->resize( newSize, 10 ); QWidget* space0 = new QWidget(this); @@ -219,7 +236,9 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : l->addItem( new QSpacerItem( newSize - 20, 30, QSizePolicy::Fixed, QSizePolicy::Fixed ), 1, 0 ); l->addWidget( resize, 2, 0, 1, 1, Qt::AlignCenter ); - l->addWidget( m_pluginWidget, 3, 0, 1, 1, Qt::AlignCenter ); + if (embed_vst) { + l->addWidget( m_pluginWidget, 3, 0, 1, 1, Qt::AlignCenter ); + } l->setRowStretch( 5, 1 ); l->setColumnStretch( 1, 1 ); @@ -264,3 +283,15 @@ VstEffectControlDialog::~VstEffectControlDialog() //delete m_pluginWidget; } + + + +void VstEffectControlDialog::togglePluginUI( bool checked ) +{ + if( !m_plugin ) { + return; + } + + m_plugin->toggleUI(); +} + diff --git a/plugins/VstEffect/VstEffectControlDialog.h b/plugins/VstEffect/VstEffectControlDialog.h index 658a3d244..ddbbef878 100644 --- a/plugins/VstEffect/VstEffectControlDialog.h +++ b/plugins/VstEffect/VstEffectControlDialog.h @@ -31,6 +31,7 @@ #include #include #include +#include class VstEffectControls; @@ -59,9 +60,12 @@ private: PixmapButton * m_managePluginButton; PixmapButton * m_savePresetButton; - VstPlugin * m_plugin; + QSharedPointer m_plugin; QLabel * tbLabel; + +private slots: + void togglePluginUI( bool checked ); } ; #endif diff --git a/plugins/carlabase/carla.cpp b/plugins/carlabase/carla.cpp index 8d46d4c1a..d55cf36d2 100644 --- a/plugins/carlabase/carla.cpp +++ b/plugins/carlabase/carla.cpp @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -150,15 +151,22 @@ CarlaInstrument::CarlaInstrument(InstrumentTrack* const instrumentTrack, const D fHost.uiName = NULL; fHost.uiParentId = 0; - // figure out prefix from dll filename + // carla/resources contains PyQt scripts required for launch QString dllName(carla_get_library_filename()); - + QString resourcesPath; #if defined(CARLA_OS_LINUX) - fHost.resourceDir = strdup(QString(dllName.split("/lib/carla")[0] + "/share/carla/resources/").toUtf8().constData()); -#else - fHost.resourceDir = NULL; + // parse prefix from dll filename + QDir path = QFileInfo(dllName).dir(); + path.cdUp(); + path.cdUp(); + resourcesPath = path.absolutePath() + "/share/carla/resources"; +#elif defined(CARLA_OS_MAC) + // assume standard install location + resourcesPath = "/Applications/Carla.app/Contents/MacOS/resources"; +#elif defined(CARLA_OS_WIN32) || defined(CARLA_OS_WIN64) + // not yet supported #endif - + fHost.resourceDir = strdup(resourcesPath.toUtf8().constData()); fHost.get_buffer_size = host_get_buffer_size; fHost.get_sample_rate = host_get_sample_rate; fHost.is_offline = host_is_offline; @@ -252,21 +260,14 @@ intptr_t CarlaInstrument::handleDispatcher(const NativeHostDispatcherOpcode opco switch (opcode) { - case NATIVE_HOST_OPCODE_NULL: - break; - case NATIVE_HOST_OPCODE_UPDATE_PARAMETER: - case NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM: - case NATIVE_HOST_OPCODE_RELOAD_PARAMETERS: - case NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS: - case NATIVE_HOST_OPCODE_RELOAD_ALL: - // nothing - break; case NATIVE_HOST_OPCODE_UI_UNAVAILABLE: handleUiClosed(); break; case NATIVE_HOST_OPCODE_HOST_IDLE: qApp->processEvents(); break; + default: + break; } return ret; diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index a3c3c66c8..a5abe2c28 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -32,6 +32,7 @@ #include #include +#include "ConfigManager.h" #include "BufferManager.h" #include "ConfigManager.h" #include "Engine.h" @@ -90,6 +91,10 @@ vestigeInstrument::vestigeInstrument( InstrumentTrack * _instrument_track ) : // now we need a play-handle which cares for calling play() InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrument_track ); Engine::mixer()->addPlayHandle( iph ); + + connect( ConfigManager::inst(), SIGNAL( valueChanged(QString,QString,QString) ), + this, SLOT( handleConfigChange(QString, QString, QString) ), + Qt::QueuedConnection ); } @@ -171,6 +176,22 @@ void vestigeInstrument::setParameter( void ) } } +void vestigeInstrument::handleConfigChange(QString cls, QString attr, QString value) +{ + Q_UNUSED(cls); Q_UNUSED(attr); Q_UNUSED(value); + // Disabled for consistency with VST effects that don't implement this. (#3786) + // if ( cls == "ui" && attr == "vstembedmethod" ) + // { + // reloadPlugin(); + // } +} + +void vestigeInstrument::reloadPlugin() +{ + closePlugin(); + loadFile( m_pluginDLL ); +} + @@ -263,7 +284,7 @@ void vestigeInstrument::loadFile( const QString & _file ) return; } - m_plugin->showEditor( NULL, false ); + m_plugin->showUI(); if( set_ch_name ) { @@ -367,10 +388,6 @@ void vestigeInstrument::closePlugin( void ) } m_pluginMutex.lock(); - if( m_plugin ) - { - delete m_plugin->pluginWidget(); - } delete m_plugin; m_plugin = NULL; m_pluginMutex.unlock(); @@ -740,19 +757,7 @@ void VestigeInstrumentView::toggleGUI( void ) { return; } - QWidget * w = m_vi->m_plugin->pluginWidget(); - if( w == NULL ) - { - return; - } - if( w->isHidden() ) - { - w->show(); - } - else - { - w->hide(); - } + m_vi->m_plugin->toggleUI(); } diff --git a/plugins/vestige/vestige.h b/plugins/vestige/vestige.h index 5faa606c2..df076bbb9 100644 --- a/plugins/vestige/vestige.h +++ b/plugins/vestige/vestige.h @@ -74,6 +74,8 @@ public: protected slots: void setParameter( void ); + void handleConfigChange( QString cls, QString attr, QString value ); + void reloadPlugin(); private: void closePlugin( void ); diff --git a/plugins/vst_base/CMakeLists.txt b/plugins/vst_base/CMakeLists.txt index afd0c7cd9..cd2f32fbd 100644 --- a/plugins/vst_base/CMakeLists.txt +++ b/plugins/vst_base/CMakeLists.txt @@ -29,6 +29,10 @@ SET(REMOTE_VST_PLUGIN_FILEPATH "RemoteVstPlugin" CACHE STRING "Relative file pat ADD_DEFINITIONS(-DREMOTE_VST_PLUGIN_FILEPATH="${REMOTE_VST_PLUGIN_FILEPATH}") BUILD_PLUGIN(vstbase vst_base.cpp VstPlugin.cpp VstPlugin.h communication.h MOCFILES VstPlugin.h) +IF(LMMS_BUILD_LINUX AND WANT_QT5) + TARGET_LINK_LIBRARIES(vstbase qx11embedcontainer) +ENDIF() + IF(LMMS_BUILD_LINUX AND NOT WANT_VST_NOWINE) IF(LMMS_HOST_X86_64) @@ -43,22 +47,44 @@ SET(WINE_CXX_FLAGS "" CACHE STRING "Extra flags passed to wineg++") STRING(REPLACE "include/wine" "include" WINE_INCLUDE_BASE_DIR ${WINE_INCLUDE_DIR}) STRING(REPLACE "lib/libwine.so" "lib" WINE_LIBRARY_DIR ${WINE_LIBRARY}) STRING(REPLACE " " ";" WINE_BUILD_FLAGS ${CMAKE_CXX_FLAGS} " " ${CMAKE_EXE_LINKER_FLAGS} " " ${WINE_CXX_FLAGS}) + +SET(WINE_CXX_ARGS + -I${CMAKE_BINARY_DIR} + -I${CMAKE_SOURCE_DIR}/include + -I${WINE_INCLUDE_BASE_DIR} + -L${WINE_LIBRARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/RemoteVstPlugin.cpp + -std=c++0x + -mwindows -lpthread ${EXTRA_FLAGS} -fno-omit-frame-pointer + ${WINE_BUILD_FLAGS} + -o ../RemoteVstPlugin +) + +# winegcc fails if winebuild is not in path +GET_FILENAME_COMPONENT(WINE_BINDIR ${WINE_CXX} PATH) +FIND_PROGRAM(WINEBUILD winebuild NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) +IF(NOT WINEBUILD) + IF(CMAKE_VERSION VERSION_LESS 3.1) + MESSAGE(WARNING "winebuild is not in PATH. Building RemoteVstPlugin may fail.") + ELSE() + SET(WINE_CXX_ARGS -E env PATH=$ENV{PATH}:${WINE_BINDIR} ${WINE_CXX} ${WINE_CXX_ARGS}) + SET(WINE_CXX "${CMAKE_COMMAND}") + ENDIF() +ENDIF() + +set(ENV{PATH} "$ENV{PATH}:${WINE_BINDIR}") + ADD_CUSTOM_COMMAND( SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/RemoteVstPlugin.cpp" + COMMAND export + ARGS "PATH=$PATH:${WINE_BINDIR}" COMMAND ${WINE_CXX} - ARGS -I${CMAKE_BINARY_DIR} - -I${CMAKE_SOURCE_DIR}/include - -I${WINE_INCLUDE_BASE_DIR} - -L${WINE_LIBRARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/RemoteVstPlugin.cpp - -ansi -mwindows -lpthread ${EXTRA_FLAGS} -fno-omit-frame-pointer - -std=c++0x - ${WINE_BUILD_FLAGS} - -o ../RemoteVstPlugin + ARGS ${WINE_CXX_ARGS} # Ensure correct file extension COMMAND sh -c "mv ../RemoteVstPlugin.exe ../RemoteVstPlugin || true" TARGET vstbase OUTPUTS ../RemoteVstPlugin + VERBATIM ) SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ../RemoteVstPlugin.exe.so) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 09f8569e9..f3a7ebc16 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -63,17 +63,10 @@ #define USE_WS_PREFIX #include -#if defined(LMMS_BUILD_WIN32) || defined(LMMS_BUILD_WIN64) -#include "basename.c" -#else -#include -#endif - - #include #include #include - +#include #include @@ -105,14 +98,18 @@ struct ERect #ifndef USE_QT_SHMEM #include #include -#include #include #include #include #endif +using namespace std; + static VstHostLanguages hlang = LanguageEnglish; +static bool EMBED = false; +static bool EMBED_X11 = false; +static bool EMBED_WIN32 = false; class RemoteVstPlugin; @@ -136,6 +133,7 @@ public: void init( const std::string & _plugin_file ); void initEditor(); + void destroyEditor(); virtual void process( const sampleFrame * _in, sampleFrame * _out ); @@ -356,6 +354,7 @@ private: int m_windowHeight; bool m_initialized; + bool m_registeredWindowClass; pthread_mutex_t m_pluginLock; bool m_processing; @@ -401,7 +400,6 @@ RemoteVstPlugin::RemoteVstPlugin( key_t _shm_in, key_t _shm_out ) : RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : RemotePluginClient( socketPath ), #endif - m_shortName( "" ), m_libInst( NULL ), m_plugin( NULL ), m_window( NULL ), @@ -409,6 +407,7 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : m_windowWidth( 0 ), m_windowHeight( 0 ), m_initialized( false ), + m_registeredWindowClass( false ), m_pluginLock(), m_processing( false ), m_messageList(), @@ -424,7 +423,6 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : m_in( NULL ), m_shmID( -1 ), m_vstSyncData( NULL ) - { pthread_mutex_init( &m_pluginLock, NULL ); pthread_mutex_init( &m_shmLock, NULL ); @@ -491,14 +489,7 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : RemoteVstPlugin::~RemoteVstPlugin() { - if( m_window != NULL ) - { - pluginDispatch( effEditClose ); -#ifdef LMMS_BUILD_LINUX - CloseWindow( m_window ); -#endif - m_window = NULL; - } + destroyEditor(); pluginDispatch( effMainsChanged, 0, 0 ); pluginDispatch( effClose ); #ifndef USE_QT_SHMEM @@ -535,22 +526,48 @@ RemoteVstPlugin::~RemoteVstPlugin() bool RemoteVstPlugin::processMessage( const message & _m ) { + if (! EMBED) + { + switch( _m.id ) + { + case IdShowUI: + initEditor(); + return true; + + case IdHideUI: + destroyEditor(); + return true; + + case IdToggleUI: + if( m_window ) + { + destroyEditor(); + } + else + { + initEditor(); + } + return true; + + case IdIsUIVisible: + sendMessage( message( IdIsUIVisible ) + .addInt( m_window ? 1 : 0 ) ); + return true; + } + } + else if (EMBED && _m.id == IdShowUI) + { + ShowWindow( m_window, SW_SHOWNORMAL ); + UpdateWindow( m_window ); + return true; + } + switch( _m.id ) { case IdVstLoadPlugin: init( _m.getString() ); break; -#ifdef LMMS_BUILD_WIN32 - case IdVstPluginWindowInformation: - { - HWND top = FindWindowEx( NULL, NULL, NULL, - _m.getString().c_str() ); - m_window = FindWindowEx( top, NULL, NULL, NULL ); - break; - } -#endif - case IdVstSetTempo: setBPM( _m.getInt() ); break; @@ -688,9 +705,20 @@ void RemoteVstPlugin::init( const std::string & _plugin_file ) +static void close_check( int fd ) +{ + if( close( fd ) ) + { + perror( "close" ); + } +} + + + + void RemoteVstPlugin::initEditor() { - if( !( m_plugin->flags & effFlagsHasEditor ) ) + if( m_window || !( m_plugin->flags & effFlagsHasEditor ) ) { return; } @@ -704,38 +732,37 @@ void RemoteVstPlugin::initEditor() } - WNDCLASS wc; - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpfnWndProc = DefWindowProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = hInst; - wc.hIcon = LoadIcon( NULL, IDI_APPLICATION ); - wc.hCursor = LoadCursor( NULL, IDC_ARROW ); - wc.hbrBackground = (HBRUSH) GetStockObject( BLACK_BRUSH ); - wc.lpszMenuName = NULL; - wc.lpszClassName = "LVSL"; - - if( !RegisterClass( &wc ) ) + if( !m_registeredWindowClass ) { - return; + WNDCLASS wc; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = DefWindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = hInst; + wc.hIcon = LoadIcon( NULL, IDI_APPLICATION ); + wc.hCursor = LoadCursor( NULL, IDC_ARROW ); + wc.hbrBackground = NULL; + wc.lpszMenuName = NULL; + wc.lpszClassName = "LVSL"; + + if( !RegisterClass( &wc ) ) + { + return; + } + m_registeredWindowClass = true; } -#ifdef LMMS_BUILD_LINUX - //m_window = CreateWindowEx( 0, "LVSL", m_shortName.c_str(), - // ( WS_OVERLAPPEDWINDOW | WS_THICKFRAME ) & ~WS_MAXIMIZEBOX, - // 0, 0, 10, 10, NULL, NULL, hInst, NULL ); + DWORD dwStyle; + if (EMBED) { + dwStyle = WS_POPUP | WS_SYSMENU | WS_BORDER; + } else { + dwStyle = WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX; + } - m_window = CreateWindowEx( 0 , "LVSL", m_shortName.c_str(), - WS_POPUP | WS_SYSMENU | WS_BORDER , 0, 0, 10, 10, NULL, NULL, hInst, NULL); -#else - m_windowID = 1; // arbitrary value on win32 to signal - // vstPlugin-class that we have an editor - - m_window = CreateWindowEx( 0, "LVSL", m_shortName.c_str(), - WS_CHILD, 0, 0, 10, 10, - m_window, NULL, hInst, NULL ); -#endif + m_window = CreateWindowEx( 0, "LVSL", pluginName(), + dwStyle, + 0, 0, 10, 10, NULL, NULL, hInst, NULL ); if( m_window == NULL ) { debugMessage( "initEditor(): cannot create editor window\n" ); @@ -756,17 +783,37 @@ void RemoteVstPlugin::initEditor() SWP_NOMOVE | SWP_NOZORDER ); pluginDispatch( effEditTop ); - ShowWindow( m_window, SW_SHOWNORMAL ); - UpdateWindow( m_window ); + if (! EMBED) { + ShowWindow( m_window, SW_SHOWNORMAL ); + } #ifdef LMMS_BUILD_LINUX m_windowID = (intptr_t) GetProp( m_window, "__wine_x11_whole_window" ); +#else + // 64-bit versions of Windows use 32-bit handles for interoperability + m_windowID = (intptr_t) m_window; #endif } +void RemoteVstPlugin::destroyEditor() +{ + if( m_window == NULL ) + { + return; + } + + pluginDispatch( effEditClose ); + // Destroying the window takes some time in Wine 1.8.5 + DestroyWindow( m_window ); + m_window = NULL; +} + + + + bool RemoteVstPlugin::load( const std::string & _plugin_file ) { if( ( m_libInst = LoadLibrary( _plugin_file.c_str() ) ) == NULL ) @@ -779,10 +826,6 @@ bool RemoteVstPlugin::load( const std::string & _plugin_file ) return false; } - char * tmp = strdup( _plugin_file.c_str() ); - m_shortName = basename( tmp ); - free( tmp ); - typedef AEffect * ( __stdcall * mainEntryPointer ) ( audioMasterCallback ); mainEntryPointer mainEntry = (mainEntryPointer) @@ -1067,7 +1110,7 @@ void RemoteVstPlugin::saveChunkToFile( const std::string & _file ) fprintf( stderr, "Error saving chunk to file.\n" ); } - close( fd ); + close_check( fd ); } } } @@ -1392,7 +1435,7 @@ void RemoteVstPlugin::loadChunkFromFile( const std::string & _file, int _len ) { fprintf( stderr, "Error loading chunk from file.\n" ); } - close( fd ); + close_check( fd ); pluginDispatch( effSetChunk, 0, _len, chunk ); @@ -1970,6 +2013,12 @@ LRESULT CALLBACK RemoteVstPlugin::messageWndProc( HWND hwnd, UINT uMsg, break; } } + else if( uMsg == WM_SYSCOMMAND && wParam == SC_CLOSE ) + { + __plugin->destroyEditor(); + return 0; + } + return DefWindowProc( hwnd, uMsg, wParam, lParam ); } @@ -1979,9 +2028,9 @@ LRESULT CALLBACK RemoteVstPlugin::messageWndProc( HWND hwnd, UINT uMsg, int main( int _argc, char * * _argv ) { #ifdef SYNC_WITH_SHM_FIFO - if( _argc < 3 ) + if( _argc < 4 ) #else - if( _argc < 2 ) + if( _argc < 3 ) #endif { fprintf( stderr, "not enough arguments\n" ); @@ -2013,6 +2062,41 @@ int main( int _argc, char * * _argv ) } #endif + { + #ifdef SYNC_WITH_SHM_FIFO + int embedMethodIndex = 3; + #else + int embedMethodIndex = 2; + #endif + std::string embedMethod = _argv[embedMethodIndex]; + + if ( embedMethod == "none" ) + { + cerr << "Starting detached." << endl; + EMBED = EMBED_X11 = EMBED_WIN32 = false; + } + else if ( embedMethod == "win32" ) + { + cerr << "Starting using Win32-native embedding." << endl; + EMBED = EMBED_WIN32 = true; EMBED_X11= false; + } + else if ( embedMethod == "qt" ) + { + cerr << "Starting using Qt-native embedding." << endl; + EMBED = true; EMBED_X11 = EMBED_WIN32 = false; + } + else if ( embedMethod == "xembed" ) + { + cerr << "Starting using X11Embed protocol." << endl; + EMBED = EMBED_X11 = true; EMBED_WIN32 = false; + } + else + { + cerr << "Unknown embed method " << embedMethod << ". Starting detached instead." << endl; + EMBED = EMBED_X11 = EMBED_WIN32 = false; + } + } + // constructor automatically will process messages until it receives // a IdVstLoadPlugin message and processes it #ifdef SYNC_WITH_SHM_FIFO diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index 9873d28e5..e291e15ba 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -2,7 +2,7 @@ * VstPlugin.cpp - implementation of VstPlugin class * * Copyright (c) 2005-2014 Tobias Doerffel - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -24,6 +24,7 @@ #include "VstPlugin.h" +#include #include #include #include @@ -31,18 +32,26 @@ #include #include #include + #ifdef LMMS_BUILD_LINUX -#if QT_VERSION < 0x050000 -#include -#include +# if QT_VERSION < 0x050000 +# include +# include +# else +# include "X11EmbedContainer.h" +# include +# endif #endif -#else -#include + +#if QT_VERSION >= 0x050000 +# include #endif + #include #ifdef LMMS_BUILD_WIN32 -#include +# include +# include #endif #include "ConfigManager.h" @@ -52,8 +61,6 @@ #include "Song.h" #include "templates.h" #include "FileDialog.h" -#include - class vstSubWin : public QMdiSubWindow { @@ -78,24 +85,13 @@ public: } ; - - VstPlugin::VstPlugin( const QString & _plugin ) : - RemotePlugin(), - JournallingObject(), m_plugin( _plugin ), - m_pluginWidget( NULL ), m_pluginWindowID( 0 ), + m_embedMethod( ConfigManager::inst()->vstEmbedMethod() ), m_badDllFormat( false ), - m_name(), m_version( 0 ), - m_vendorString(), - m_productString(), - m_currentProgramName(), - m_allProgramNames(), - p_name(), - m_currentProgram(), - m_idleTimer() + m_currentProgram() { setSplittedChannels( true ); @@ -126,6 +122,8 @@ VstPlugin::VstPlugin( const QString & _plugin ) : VstPlugin::~VstPlugin() { + delete m_pluginSubWindow; + delete m_pluginWidget; } @@ -133,29 +131,15 @@ VstPlugin::~VstPlugin() void VstPlugin::tryLoad( const QString &remoteVstPluginExecutable ) { - init( remoteVstPluginExecutable, false ); + init( remoteVstPluginExecutable, false, {m_embedMethod} ); + + waitForHostInfoGotten(); + if( failed() ) + { + return; + } lock(); -#ifdef LMMS_BUILD_WIN32 - QWidget * helper = new QWidget; - QHBoxLayout * l = new QHBoxLayout( helper ); - QWidget * target = new QWidget( helper ); - l->setSpacing( 0 ); - l->setMargin( 0 ); - l->addWidget( target ); - - static int k = 0; - const QString t = QString( "vst%1%2" ).arg( GetCurrentProcessId()<<10 ). - arg( ++k ); - helper->setWindowTitle( t ); - - // we've to call that for making sure, Qt created the windows - (void) helper->winId(); - (void) target->winId(); - - sendMessage( message( IdVstPluginWindowInformation ). - addString( QSTR_TO_STDSTR( t ) ) ); -#endif VstHostLanguages hlang = LanguageEnglish; switch( QLocale::system().language() ) @@ -183,95 +167,6 @@ void VstPlugin::tryLoad( const QString &remoteVstPluginExecutable ) waitForInitDone(); unlock(); - -#ifdef LMMS_BUILD_WIN32 - if( !failed() && m_pluginWindowID ) - { - target->setFixedSize( m_pluginGeometry ); - vstSubWin * sw = new vstSubWin( - gui->mainWindow()->workspace() ); - sw->setWidget( helper ); - helper->setWindowTitle( name() ); - m_pluginWidget = helper; - } - else - { - delete helper; - } -#endif -} - - - - -void VstPlugin::showEditor( QWidget * _parent, bool isEffect ) -{ - QWidget * w = pluginWidget(); - if( w ) - { -#ifdef LMMS_BUILD_WIN32 - // hide sw, plugin window wrapper on win32 - // this is obtained from pluginWidget() - if( isEffect ) - { - w->setWindowFlags( Qt::FramelessWindowHint ); - w->setAttribute( Qt::WA_TranslucentBackground ); - } - else - { - w->setWindowFlags( Qt::WindowCloseButtonHint ); - } -#endif - w->show(); - return; - } - -#ifdef LMMS_BUILD_LINUX - if( m_pluginWindowID == 0 ) - { - return; - } - - m_pluginWidget = new QWidget( _parent ); - m_pluginWidget->setFixedSize( m_pluginGeometry ); - m_pluginWidget->setWindowTitle( name() ); - if( _parent == NULL ) - { - vstSubWin * sw = new vstSubWin( - gui->mainWindow()->workspace() ); - if( isEffect ) - { - sw->setAttribute( Qt::WA_TranslucentBackground ); - sw->setWindowFlags( Qt::FramelessWindowHint ); - sw->setWidget( m_pluginWidget ); -#if QT_VERSION < 0x050000 - QX11EmbedContainer * xe = new QX11EmbedContainer( sw ); - xe->embedClient( m_pluginWindowID ); - xe->setFixedSize( m_pluginGeometry ); - xe->show(); -#endif - } - else - { - sw->setWindowFlags( Qt::WindowCloseButtonHint ); - sw->setWidget( m_pluginWidget ); - -#if QT_VERSION < 0x050000 - QX11EmbedContainer * xe = new QX11EmbedContainer( sw ); - xe->embedClient( m_pluginWindowID ); - xe->setFixedSize( m_pluginGeometry ); - xe->move( 4, 24 ); - xe->show(); -#endif - } - } - -#endif - - if( m_pluginWidget ) - { - m_pluginWidget->show(); - } } @@ -289,18 +184,27 @@ void VstPlugin::hideEditor() +void VstPlugin::toggleEditor() +{ + QWidget * w = pluginWidget(); + if( w ) + { + w->setVisible( !w->isVisible() ); + } +} + + + + void VstPlugin::loadSettings( const QDomElement & _this ) { - if( pluginWidget() != NULL ) + if( _this.attribute( "guivisible" ).toInt() ) { - if( _this.attribute( "guivisible" ).toInt() ) - { - showEditor( NULL, false ); - } - else - { - hideEditor(); - } + showUI(); + } + else + { + hideUI(); } const int num_params = _this.attribute( "numparams" ).toInt(); @@ -334,9 +238,20 @@ void VstPlugin::loadSettings( const QDomElement & _this ) void VstPlugin::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - if( pluginWidget() != NULL ) + if ( m_embedMethod != "none" ) { - _this.setAttribute( "guivisible", pluginWidget()->isVisible() ); + if( pluginWidget() != NULL ) + { + _this.setAttribute( "guivisible", pluginWidget()->isVisible() ); + } + } + else + { + int visible = isUIVisible(); + if ( visible != -1 ) + { + _this.setAttribute( "guivisible", visible ); + } } // try to save all settings in a chunk @@ -361,6 +276,18 @@ void VstPlugin::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "program", currentProgram() ); } +void VstPlugin::toggleUI() +{ + if ( m_embedMethod == "none" ) + { + RemotePlugin::toggleUI(); + } + else if (pluginWidget()) + { + toggleEditor(); + } +} + @@ -432,6 +359,23 @@ void VstPlugin::setParameterDump( const QMap & _pdump ) unlock(); } +QWidget *VstPlugin::pluginWidget(bool _top_widget) +{ + if ( m_embedMethod == "none" || !m_pluginWidget ) + { + return nullptr; + } + + if ( _top_widget && m_pluginWidget->parentWidget() == m_pluginSubWindow ) + { + return m_pluginSubWindow; + } + else + { + return m_pluginWidget; + } +} + @@ -439,17 +383,17 @@ bool VstPlugin::processMessage( const message & _m ) { switch( _m.id ) { - case IdVstBadDllFormat: - m_badDllFormat = true; - break; + case IdVstBadDllFormat: + m_badDllFormat = true; + break; - case IdVstPluginWindowID: - m_pluginWindowID = _m.getInt(); - break; + case IdVstPluginWindowID: + m_pluginWindowID = _m.getInt(); + break; - case IdVstPluginEditorGeometry: - m_pluginGeometry = QSize( _m.getInt( 0 ), - _m.getInt( 1 ) ); + case IdVstPluginEditorGeometry: + m_pluginGeometry = QSize( _m.getInt( 0 ), + _m.getInt( 1 ) ); break; case IdVstPluginName: @@ -624,6 +568,46 @@ void VstPlugin::idleUpdate() unlock(); } +void VstPlugin::showUI() +{ + if ( m_embedMethod == "none" ) + { + RemotePlugin::showUI(); + } + else + { + if (! pluginWidget()) { + createUI( NULL, false ); + } + + QWidget * w = pluginWidget(); + if( w ) + { + w->show(); + } + } +} + +void VstPlugin::hideUI() +{ + if ( m_embedMethod == "none" ) + { + RemotePlugin::hideUI(); + } + else if ( pluginWidget() != nullptr ) + { + hideEditor(); + } +} + +// X11Embed only +void VstPlugin::handleClientEmbed() +{ + lock(); + sendMessage( IdShowUI ); + unlock(); +} + void VstPlugin::loadChunk( const QByteArray & _chunk ) @@ -667,6 +651,114 @@ QByteArray VstPlugin::saveChunk() return a; } +void VstPlugin::createUI( QWidget * parent, bool isEffect ) +{ + if( m_pluginWindowID == 0 ) + { + return; + } + + QWidget* container = nullptr; + m_pluginSubWindow = new vstSubWin( gui->mainWindow()->workspace() ); + auto sw = m_pluginSubWindow.data(); + +#if QT_VERSION >= 0x050100 + if (m_embedMethod == "qt" ) + { + QWindow* vw = QWindow::fromWinId(m_pluginWindowID); + container = QWidget::createWindowContainer(vw, sw ); + container->installEventFilter(this); + } else +#endif + +#ifdef LMMS_BUILD_WIN32 + if (m_embedMethod == "win32" ) + { + QWidget * helper = new QWidget; + QHBoxLayout * l = new QHBoxLayout( helper ); + QWidget * target = new QWidget( helper ); + l->setSpacing( 0 ); + l->setMargin( 0 ); + l->addWidget( target ); + + // we've to call that for making sure, Qt created the windows + helper->winId(); + HWND targetHandle = (HWND)target->winId(); + HWND pluginHandle = (HWND)(intptr_t)m_pluginWindowID; + + DWORD style = GetWindowLong(pluginHandle, GWL_STYLE); + style = style & ~(WS_POPUP); + style = style | WS_CHILD; + SetWindowLong(pluginHandle, GWL_STYLE, style); + SetParent(pluginHandle, targetHandle); + + DWORD threadId = GetWindowThreadProcessId(pluginHandle, NULL); + DWORD currentThreadId = GetCurrentThreadId(); + AttachThreadInput(currentThreadId, threadId, true); + + container = helper; + RemotePlugin::showUI(); + + } else +#endif + +#ifdef LMMS_BUILD_LINUX + if (m_embedMethod == "xembed" ) + { + QX11EmbedContainer * embedContainer = new QX11EmbedContainer( sw ); + connect(embedContainer, SIGNAL(clientIsEmbedded()), this, SLOT(handleClientEmbed())); + embedContainer->embedClient( m_pluginWindowID ); + container = embedContainer; + } else +#endif + { + qCritical() << "Unknown embed method" << m_embedMethod; + delete m_pluginSubWindow; + return; + } + + container->setFixedSize( m_pluginGeometry ); + container->setWindowTitle( name() ); + + if( parent == NULL ) + { + m_pluginWidget = container; + + sw->setWidget(container); + + if( isEffect ) + { + sw->setAttribute( Qt::WA_TranslucentBackground ); + sw->setWindowFlags( Qt::FramelessWindowHint ); + } + else + { + sw->setWindowFlags( Qt::WindowCloseButtonHint ); + } + }; + + container->setFixedSize( m_pluginGeometry ); +} + +bool VstPlugin::eventFilter(QObject *obj, QEvent *event) +{ +#if QT_VERSION >= 0x050100 + if (embedMethod() == "qt" && obj == m_pluginWidget) + { + if (event->type() == QEvent::Show) { + RemotePlugin::showUI(); + } + qDebug() << obj << event; + } +#endif + return false; +} + +QString VstPlugin::embedMethod() const +{ + return m_embedMethod; +} + diff --git a/plugins/vst_base/VstPlugin.h b/plugins/vst_base/VstPlugin.h index 1b6d62ff7..f3d6bea8e 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -35,6 +35,8 @@ #include "JournallingObject.h" #include "communication.h" +class vstSubWin; + class PLUGIN_EXPORT VstPlugin : public RemotePlugin, public JournallingObject { @@ -52,8 +54,8 @@ public: return m_pluginWindowID != 0; } - void showEditor( QWidget * _parent = NULL, bool isEffect = false ); void hideEditor(); + void toggleEditor(); inline const QString & name() const { @@ -91,17 +93,7 @@ public: void setParameterDump( const QMap & _pdump ); - inline QWidget * pluginWidget( bool _top_widget = true ) - { - if( _top_widget && m_pluginWidget ) - { - if( m_pluginWidget->parentWidget() ) - { - return m_pluginWidget->parentWidget(); - } - } - return m_pluginWidget; - } + QWidget * pluginWidget( bool _top_widget = true ); virtual void loadSettings( const QDomElement & _this ); virtual void saveSettings( QDomDocument & _doc, QDomElement & _this ); @@ -111,6 +103,12 @@ public: return "vstplugin"; } + void toggleUI() override; + + void createUI( QWidget *parent, bool isEffect ); + bool eventFilter(QObject *obj, QEvent *event); + + QString embedMethod() const; public slots: void setTempo( bpm_t _bpm ); @@ -123,6 +121,10 @@ public slots: void setParam( int i, float f ); void idleUpdate(); + void showUI() override; + void hideUI() override; + + void handleClientEmbed(); private: void loadChunk( const QByteArray & _chunk ); @@ -130,8 +132,10 @@ private: QString m_plugin; QPointer m_pluginWidget; + QPointer m_pluginSubWindow; int m_pluginWindowID; QSize m_pluginGeometry; + const QString m_embedMethod; bool m_badDllFormat; diff --git a/plugins/vst_base/basename.c b/plugins/vst_base/basename.c deleted file mode 100644 index c8b4ee45c..000000000 --- a/plugins/vst_base/basename.c +++ /dev/null @@ -1,166 +0,0 @@ -/* basename.c - * - * $Id: basename.c,v 1.2 2007/03/08 23:15:58 keithmarshall Exp $ - * - * Provides an implementation of the "basename" function, conforming - * to SUSv3, with extensions to accommodate Win32 drive designators, - * and suitable for use on native Microsoft(R) Win32 platforms. - * - * Written by Keith Marshall - * - * This is free software. You may redistribute and/or modify it as you - * see fit, without restriction of copyright. - * - * This software is provided "as is", in the hope that it may be useful, - * but WITHOUT WARRANTY OF ANY KIND, not even any implied warranty of - * MERCHANTABILITY, nor of FITNESS FOR ANY PARTICULAR PURPOSE. At no - * time will the author accept any form of liability for any damages, - * however caused, resulting from the use of this software. - * - */ - -#include -#include -#include -#include - -#ifndef __cdecl /* If compiling on any non-Win32 platform ... */ -#define __cdecl /* this may not be defined. */ -#endif - -__cdecl char *basename( char *path ) -{ - size_t len; - static char *retfail = NULL; - - /* to handle path names for files in multibyte character locales, - * we need to set up LC_CTYPE to match the host file system locale - */ - - char *locale = setlocale( LC_CTYPE, NULL ); - if( locale != NULL ) locale = strdup( locale ); - setlocale( LC_CTYPE, "" ); - - if( path && *path ) - { - /* allocate sufficient local storage space, - * in which to create a wide character reference copy of path - */ - - wchar_t refcopy[1 + (len = mbstowcs( NULL, path, 0 ))]; - - /* create the wide character reference copy of path, - * and step over the drive designator, if present ... - */ - - wchar_t *refpath = refcopy; - if( ((len = mbstowcs( refpath, path, len )) > 1) && (refpath[1] == L':') ) - { - /* FIXME: maybe should confirm *refpath is a valid drive designator */ - - refpath += 2; - } - - /* ensure that our wide character reference path is NUL terminated */ - - refcopy[ len ] = L'\0'; - - /* check again, just to ensure we still have a non-empty path name ... */ - - if( *refpath ) - { - /* and, when we do, process it in the wide character domain ... - * scanning from left to right, to the char after the final dir separator - */ - - wchar_t *refname; - for( refname = refpath ; *refpath ; ++refpath ) - { - if( (*refpath == L'/') || (*refpath == L'\\') ) - { - /* we found a dir separator ... - * step over it, and any others which immediately follow it - */ - - while( (*refpath == L'/') || (*refpath == L'\\') ) - ++refpath; - - /* if we didn't reach the end of the path string ... */ - - if( *refpath ) - - /* then we have a new candidate for the base name */ - - refname = refpath; - - /* otherwise ... - * strip off any trailing dir separators which we found - */ - - else while( (refpath > refname) - && ((*--refpath == L'/') || (*refpath == L'\\')) ) - *refpath = L'\0'; - } - } - - /* in the wide character domain ... - * refname now points at the resolved base name ... - */ - - if( *refname ) - { - /* if it's not empty, - * then we transform the full normalised path back into - * the multibyte character domain, and skip over the dirname, - * to return the resolved basename. - */ - - if( (len = wcstombs( path, refcopy, len )) != (size_t)(-1) ) - path[ len ] = '\0'; - *refname = L'\0'; - if( (len = wcstombs( NULL, refcopy, 0 )) != (size_t)(-1) ) - path += len; - } - - else - { - /* the basename is empty, so return the default value of "/", - * transforming from wide char to multibyte char domain, and - * returning it in our own buffer. - */ - - retfail = (char *) realloc( retfail, len = 1 + wcstombs( NULL, L"/", 0 )); - wcstombs( path = retfail, L"/", len ); - } - - /* restore the caller's locale, clean up, and return the result */ - - setlocale( LC_CTYPE, locale ); - free( locale ); - return( path ); - } - - /* or we had an empty residual path name, after the drive designator, - * in which case we simply fall through ... - */ - } - - /* and, if we get to here ... - * the path name is either NULL, or it decomposes to an empty string; - * in either case, we return the default value of "." in our own buffer, - * reloading it with the correct value, transformed from the wide char - * to the multibyte char domain, just in case the caller trashed it - * after a previous call. - */ - - retfail = (char *) realloc( retfail, len = 1 + wcstombs( NULL, L".", 0 )); - wcstombs( retfail, L".", len ); - - /* restore the caller's locale, clean up, and return the result */ - - setlocale( LC_CTYPE, locale ); - free( locale ); - return( retfail ); -} - -/* $RCSfile: basename.c,v $$Revision: 1.2 $: end of file */ diff --git a/plugins/vst_base/communication.h b/plugins/vst_base/communication.h index 40cb4dd30..bacf43c1e 100644 --- a/plugins/vst_base/communication.h +++ b/plugins/vst_base/communication.h @@ -30,6 +30,7 @@ #include "RemotePlugin.h" + struct VstParameterDumpItem { int32_t index; @@ -56,7 +57,6 @@ enum VstRemoteMessageIDs { // vstPlugin -> remoteVstPlugin IdVstLoadPlugin = IdUserBase, - IdVstPluginWindowInformation, IdVstClosePlugin, IdVstSetTempo, IdVstSetLanguage, diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt index 3b7c62f51..66fdcf14c 100644 --- a/src/3rdparty/CMakeLists.txt +++ b/src/3rdparty/CMakeLists.txt @@ -1,5 +1,10 @@ set(CMAKE_C_FLAGS "") set(CMAKE_CXX_FLAGS "") +IF(QT5 AND LMMS_BUILD_LINUX) + set(BUILD_SHARED_LIBS OFF) + add_subdirectory(qt5-x11embed) +ENDIF() + ADD_SUBDIRECTORY(rpmalloc) ADD_SUBDIRECTORY(weakjack) diff --git a/src/3rdparty/qt5-x11embed b/src/3rdparty/qt5-x11embed new file mode 160000 index 000000000..022b39a1d --- /dev/null +++ b/src/3rdparty/qt5-x11embed @@ -0,0 +1 @@ +Subproject commit 022b39a1d496d72eb3e5b5188e5559f66afca957 diff --git a/src/3rdparty/rpmalloc/CMakeLists.txt b/src/3rdparty/rpmalloc/CMakeLists.txt index b71af279b..23d1551c2 100644 --- a/src/3rdparty/rpmalloc/CMakeLists.txt +++ b/src/3rdparty/rpmalloc/CMakeLists.txt @@ -16,8 +16,15 @@ if (NOT LMMS_BUILD_WIN32) endif() if (CMAKE_BUILD_TYPE STREQUAL "Debug") + # rpmalloc uses GCC builtin "__builtin_umull_overflow" with ENABLE_VALIDATE_ARGS, + # which is only available starting with GCC 5 + if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5) + set(ENABLE_VALIDATE_ARGS OFF) + else () + set(ENABLE_VALIDATE_ARGS ON) + endif() target_compile_definitions(rpmalloc - PRIVATE -DENABLE_ASSERTS=1 -DENABLE_VALIDATE_ARGS=1 + PRIVATE -DENABLE_ASSERTS=1 -DENABLE_VALIDATE_ARGS=${ENABLE_VALIDATE_ARGS} ) endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 42b846cf4..08fc87494 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,7 +129,7 @@ IF(LMMS_BUILD_HAIKU) SET(EXTRA_LIBRARIES "-lnetwork") ENDIF() -SET(LMMS_REQUIRED_LIBS +SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${QT_LIBRARIES} ${ASOUND_LIBRARY} diff --git a/src/core/ConfigManager.cpp b/src/core/ConfigManager.cpp index 231475002..985d5e08c 100644 --- a/src/core/ConfigManager.cpp +++ b/src/core/ConfigManager.cpp @@ -186,6 +186,39 @@ QString ConfigManager::defaultVersion() const return LMMS_VERSION; } +QStringList ConfigManager::availabeVstEmbedMethods() +{ + QStringList methods; + methods.append("none"); +#if QT_VERSION >= 0x050100 + methods.append("qt"); +#endif +#ifdef LMMS_BUILD_WIN32 + methods.append("win32"); +#endif +#ifdef LMMS_BUILD_LINUX +#if QT_VERSION >= 0x050000 + if (static_cast(QApplication::instance())-> + platformName() == "xcb") +#else + if (qgetenv("QT_QPA_PLATFORM").isNull() + || qgetenv("QT_QPA_PLATFORM") == "xcb") +#endif + { + methods.append("xembed"); + } +#endif + return methods; +} + +QString ConfigManager::vstEmbedMethod() const +{ + QStringList methods = availabeVstEmbedMethods(); + QString defaultMethod = *(methods.end() - 1); + QString currentMethod = value( "ui", "vstembedmethod", defaultMethod ); + return methods.contains(currentMethod) ? currentMethod : defaultMethod; +} + bool ConfigManager::hasWorkingDir() const { return QDir( m_workingDir ).exists(); @@ -336,12 +369,15 @@ void ConfigManager::setValue( const QString & cls, { if( m_settings.contains( cls ) ) { - for( stringPairVector::iterator it = m_settings[cls].begin(); - it != m_settings[cls].end(); ++it ) + for( QPair& pair : m_settings[cls]) { - if( ( *it ).first == attribute ) + if( pair.first == attribute ) { - ( *it ).second = value; + if ( pair.second != value ) + { + pair.second = value; + emit valueChanged( cls, attribute, value ); + } return; } } diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index df6a0d948..3c074a8f8 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -164,8 +164,8 @@ RemotePlugin::~RemotePlugin() -bool RemotePlugin::init( const QString &pluginExecutable, - bool waitForInitDoneMsg ) +bool RemotePlugin::init(const QString &pluginExecutable, + bool waitForInitDoneMsg , QStringList extraArgs) { lock(); if( m_failed ) @@ -209,6 +209,7 @@ bool RemotePlugin::init( const QString &pluginExecutable, #else args << m_socketFile; #endif + args << extraArgs; #ifndef DEBUG_REMOTE_PLUGIN m_process.setProcessChannelMode( QProcess::ForwardedChannels ); m_process.setWorkingDirectory( QCoreApplication::applicationDirPath() ); @@ -393,6 +394,20 @@ void RemotePlugin::processMidiEvent( const MidiEvent & _e, unlock(); } +void RemotePlugin::showUI() +{ + lock(); + sendMessage( IdShowUI ); + unlock(); +} + +void RemotePlugin::hideUI() +{ + lock(); + sendMessage( IdHideUI ); + unlock(); +} + diff --git a/src/core/TrackContainer.cpp b/src/core/TrackContainer.cpp index 6877e6b1f..edea9aa14 100644 --- a/src/core/TrackContainer.cpp +++ b/src/core/TrackContainer.cpp @@ -296,6 +296,9 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra continue; } MidiTime relTime = time - p->startPosition(); + if (! p->getAutoResize()) { + relTime = qMin(relTime, p->length()); + } float value = p->valueAt(relTime); for (AutomatableModel* model : p->objects()) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 24f7b2f4f..4d730eed4 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -71,6 +71,7 @@ AudioJack::AudioJack( bool & _success_ful, Mixer* _mixer ) : AudioJack::~AudioJack() { + stopProcessing(); #ifdef AUDIO_PORT_SUPPORT while( m_portMap.size() ) { @@ -200,6 +201,7 @@ bool AudioJack::initJackClient() void AudioJack::startProcessing() { + QMutexLocker m( &m_processingMutex ); m_stopped = false; if( m_active || m_client == NULL ) @@ -252,6 +254,8 @@ void AudioJack::startProcessing() void AudioJack::stopProcessing() { + QMutexLocker m( &m_processingMutex ); + m_stopped = true; } @@ -338,6 +342,8 @@ void AudioJack::renamePort( AudioPort * _port ) int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) { + QMutexLocker m( &m_processingMutex ); + // do midi processing first so that midi input can // add to the following sound processing if( m_midiClient && _nframes > 0 ) @@ -397,15 +403,15 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) if( m_framesDoneInCurBuf == m_framesToDoInCurBuf ) { m_framesToDoInCurBuf = getNextBuffer( m_outBuf ); + m_framesDoneInCurBuf = 0; if( !m_framesToDoInCurBuf ) { - m_stopped = true; + break; } - m_framesDoneInCurBuf = 0; } } - if( m_stopped == true ) + if( _nframes != done ) { for( int c = 0; c < channels(); ++c ) { diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 32d16546b..18bd49144 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -65,7 +65,7 @@ #include "lmmsversion.h" -#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BULID_APPLE) && !defined(LMMS_BUILD_HAIKU) +#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_HAIKU) && QT_VERSION >= 0x050000 //Work around an issue on KDE5 as per https://bugs.kde.org/show_bug.cgi?id=337491#c21 void disableAutoKeyAccelerators(QWidget* mainWindow) { @@ -92,7 +92,7 @@ MainWindow::MainWindow() : m_metronomeToggle( 0 ), m_session( Normal ) { -#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BULID_APPLE) && !defined(LMMS_BUILD_HAIKU) +#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_HAIKU) && QT_VERSION >= 0x050000 disableAutoKeyAccelerators(this); #endif setAttribute( Qt::WA_DeleteOnClose ); diff --git a/src/gui/SetupDialog.cpp b/src/gui/SetupDialog.cpp index 879c1b7b9..f88e1dcf6 100644 --- a/src/gui/SetupDialog.cpp +++ b/src/gui/SetupDialog.cpp @@ -67,8 +67,6 @@ #include "MidiApple.h" #include "MidiDummy.h" - - inline void labelWidget( QWidget * _w, const QString & _txt ) { QLabel * title = new QLabel( _txt, _w ); @@ -137,12 +135,13 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) : m_displayWaveform(ConfigManager::inst()->value( "ui", "displaywaveform").toInt() ), m_disableAutoQuit(ConfigManager::inst()->value( "ui", - "disableautoquit").toInt() ) + "disableautoquit").toInt() ), + m_vstEmbedMethod( ConfigManager::inst()->vstEmbedMethod() ) { setWindowIcon( embed::getIconPixmap( "setup_general" ) ); setWindowTitle( tr( "Setup LMMS" ) ); setModal( true ); - setFixedSize( 452, 520 ); + setFixedSize( 452, 570 ); Engine::projectJournal()->setJournalling( false ); @@ -159,7 +158,7 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) : m_tabBar->setFixedWidth( 72 ); QWidget * ws = new QWidget( settings ); - int wsHeight = 370; + int wsHeight = 420; #ifdef LMMS_HAVE_STK wsHeight += 50; #endif @@ -168,7 +167,7 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) : #endif ws->setFixedSize( 360, wsHeight ); QWidget * general = new QWidget( ws ); - general->setFixedSize( 360, 240 ); + general->setFixedSize( 360, 290 ); QVBoxLayout * gen_layout = new QVBoxLayout( general ); gen_layout->setSpacing( 0 ); gen_layout->setMargin( 0 ); @@ -335,6 +334,27 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) : misc_tw->setFixedHeight( YDelta*labelNumber + HeaderSize ); + TabWidget* embed_tw = new TabWidget( tr( "PLUGIN EMBEDDING" ), general); + embed_tw->setFixedHeight( 48 ); + m_vstEmbedComboBox = new QComboBox( embed_tw ); + m_vstEmbedComboBox->move( XDelta, YDelta ); + + QStringList embedMethods = ConfigManager::availabeVstEmbedMethods(); + m_vstEmbedComboBox->addItem( tr( "No embedding" ), "none" ); + if( embedMethods.contains("qt") ) + { + m_vstEmbedComboBox->addItem( tr( "Embed using Qt API" ), "qt" ); + } + if( embedMethods.contains("win32") ) + { + m_vstEmbedComboBox->addItem( tr( "Embed using native Win32 API" ), "win32" ); + } + if( embedMethods.contains("xembed") ) + { + m_vstEmbedComboBox->addItem( tr( "Embed using XEmbed protocol" ), "xembed" ); + } + m_vstEmbedComboBox->setCurrentIndex( m_vstEmbedComboBox->findData( m_vstEmbedMethod ) ); + TabWidget * lang_tw = new TabWidget( tr( "LANGUAGE" ), general ); lang_tw->setFixedHeight( 48 ); QComboBox * changeLang = new QComboBox( lang_tw ); @@ -380,13 +400,15 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) : gen_layout->addSpacing( 10 ); gen_layout->addWidget( misc_tw ); gen_layout->addSpacing( 10 ); + gen_layout->addWidget( embed_tw ); + gen_layout->addSpacing( 10 ); gen_layout->addWidget( lang_tw ); gen_layout->addStretch(); QWidget * paths = new QWidget( ws ); - int pathsHeight = 370; + int pathsHeight = 420; #ifdef LMMS_HAVE_STK pathsHeight += 55; #endif @@ -1001,6 +1023,20 @@ SetupDialog::~SetupDialog() void SetupDialog::accept() { + if( m_warnAfterSetup ) + { + QMessageBox::information( NULL, tr( "Restart LMMS" ), + tr( "Please note that most changes " + "won't take effect until " + "you restart LMMS!" ), + QMessageBox::Ok ); + } + + // Hide dialog before setting values. This prevents an obscure bug + // where non-embedded VST windows would steal focus and prevent LMMS + // from taking mouse input, rendering the application unusable. + QDialog::accept(); + ConfigManager::inst()->setValue( "mixer", "framesperaudiobuffer", QString::number( m_bufferSize ) ); ConfigManager::inst()->setValue( "mixer", "audiodev", @@ -1044,6 +1080,12 @@ void SetupDialog::accept() ConfigManager::inst()->setValue( "ui", "disableautoquit", QString::number( m_disableAutoQuit ) ); ConfigManager::inst()->setValue( "app", "language", m_lang ); + ConfigManager::inst()->setValue( "ui", "vstembedmethod", +#if QT_VERSION >= 0x050000 + m_vstEmbedComboBox->currentData().toString() ); +#else + m_vstEmbedComboBox->itemData(m_vstEmbedComboBox->currentIndex()).toString() ); +#endif ConfigManager::inst()->setWorkingDir(QDir::fromNativeSeparators(m_workingDir)); @@ -1074,16 +1116,6 @@ void SetupDialog::accept() } ConfigManager::inst()->saveConfigFile(); - - QDialog::accept(); - if( m_warnAfterSetup ) - { - QMessageBox::information( NULL, tr( "Restart LMMS" ), - tr( "Please note that most changes " - "won't take effect until " - "you restart LMMS!" ), - QMessageBox::Ok ); - } } diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index c2f071034..0abe22558 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -107,7 +107,10 @@ void SubWindow::paintEvent( QPaintEvent * ) { QPainter p( this ); QRect rect( 0, 0, width(), m_titleBarHeight ); - bool isActive = SubWindow::mdiArea()->activeSubWindow() == this; + + bool isActive = mdiArea() + ? mdiArea()->activeSubWindow() == this + : false; p.fillRect( rect, isActive ? activeColor() : p.pen().brush() ); diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index 6d1ee2583..c47a58873 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -435,9 +435,11 @@ void AutomationEditor::leaveEvent(QEvent * e ) } -void AutomationEditor::drawLine( int x0, float y0, int x1, float y1 ) +void AutomationEditor::drawLine( int x0In, float y0, int x1In, float y1 ) { - int deltax = qRound( qAbs( x1 - x0 ) ); + int x0 = Note::quantized( x0In, AutomationPattern::quantization() ); + int x1 = Note::quantized( x1In, AutomationPattern::quantization() ); + int deltax = qAbs( x1 - x0 ); float deltay = qAbs( y1 - y0 ); int x = x0; float y = y0; @@ -453,7 +455,7 @@ void AutomationEditor::drawLine( int x0, float y0, int x1, float y1 ) float yscale = deltay / ( deltax ); - if( x0 < x1) + if( x0 < x1 ) { xstep = AutomationPattern::quantization(); } @@ -462,19 +464,22 @@ void AutomationEditor::drawLine( int x0, float y0, int x1, float y1 ) xstep = -( AutomationPattern::quantization() ); } + float lineAdjust; if( y0 < y1 ) { ystep = 1; + lineAdjust = yscale; } else { ystep = -1; + lineAdjust = -( yscale ); } int i = 0; while( i < deltax ) { - y = y0 + ( ystep * yscale * i ); + y = y0 + ( ystep * yscale * i ) + lineAdjust; x += xstep; i += 1; @@ -524,7 +529,7 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) ( it+1==time_map.end() || pos_ticks <= (it+1).key() ) && ( pos_ticks<= it.key() + MidiTime::ticksPerTact() *4 / m_ppt ) && - level == it.value() ) + ( level == it.value() || mouseEvent->button() == Qt::RightButton ) ) { break; } @@ -2374,8 +2379,9 @@ AutomationEditorWindow::AutomationEditorWindow() : // copyPasteActionsToolBar->addAction( pasteAction ); - DropToolBar *timeLineToolBar = addDropToolBarToTop(tr("Timeline controls")); - m_editor->m_timeLine->addToolButtons(timeLineToolBar); + // Not implemented. + //DropToolBar *timeLineToolBar = addDropToolBarToTop(tr("Timeline controls")); + //m_editor->m_timeLine->addToolButtons(timeLineToolBar); addToolBarBreak(); diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index e3c37a6aa..8aca7596f 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -3762,7 +3762,7 @@ void PianoRoll::pasteNotes() cur_note.setSelected( true ); // add to pattern - m_pattern->addNote( cur_note ); + m_pattern->addNote( cur_note, false ); } // we only have to do the following lines if we pasted at @@ -3925,6 +3925,8 @@ void PianoRoll::quantizeNotes() return; } + m_pattern->addJournalCheckPoint(); + NoteVector notes = getSelectedNotes(); if( notes.empty() ) @@ -3950,6 +3952,7 @@ void PianoRoll::quantizeNotes() update(); gui->songEditor()->update(); + Engine::getSong()->setModified(); } diff --git a/tests/src/tracks/AutomationTrackTest.cpp b/tests/src/tracks/AutomationTrackTest.cpp index c05b09804..65c3b4874 100644 --- a/tests/src/tracks/AutomationTrackTest.cpp +++ b/tests/src/tracks/AutomationTrackTest.cpp @@ -30,6 +30,9 @@ #include "AutomationTrack.h" #include "BBTrack.h" #include "BBTrackContainer.h" +#include "DetuningHelper.h" +#include "InstrumentTrack.h" +#include "Pattern.h" #include "TrackContainer.h" #include "Engine.h" @@ -105,6 +108,55 @@ private slots: QCOMPARE(song->automatedValuesAt(150)[&model], 0.5f); } + void testLengthRespected() + { + FloatModel model; + + auto song = Engine::getSong(); + AutomationTrack track(song); + + AutomationPattern p(&track); + p.setProgressionType(AutomationPattern::LinearProgression); + p.addObject(&model); + + p.putValue(0, 0.0, false); + p.putValue(100, 1.0, false); + + p.changeLength(100); + QCOMPARE(song->automatedValuesAt( 0)[&model], 0.0f); + QCOMPARE(song->automatedValuesAt( 50)[&model], 0.5f); + QCOMPARE(song->automatedValuesAt(100)[&model], 1.0f); + + p.changeLength(50); + QCOMPARE(song->automatedValuesAt( 0)[&model], 0.0f); + QCOMPARE(song->automatedValuesAt( 50)[&model], 0.5f); + QCOMPARE(song->automatedValuesAt(100)[&model], 0.5f); + } + + void testInlineAutomation() + { + auto song = Engine::getSong(); + + InstrumentTrack* instrumentTrack = + dynamic_cast(Track::create(Track::InstrumentTrack, song)); + + Pattern* notePattern = dynamic_cast(instrumentTrack->createTCO(0)); + notePattern->changeLength(MidiTime(4, 0)); + Note* note = notePattern->addNote(Note(MidiTime(4, 0)), false); + note->createDetuning(); + + DetuningHelper* dh = note->detuning(); + auto pattern = dh->automationPattern(); + pattern->setProgressionType( AutomationPattern::LinearProgression ); + pattern->putValue(MidiTime(0, 0), 0.0); + pattern->putValue(MidiTime(4, 0), 1.0); + + QCOMPARE(pattern->valueAt(MidiTime(0, 0)), 0.0f); + QCOMPARE(pattern->valueAt(MidiTime(1, 0)), 0.25f); + QCOMPARE(pattern->valueAt(MidiTime(2, 0)), 0.5f); + QCOMPARE(pattern->valueAt(MidiTime(4, 0)), 1.0f); + } + void testBBTrack() { auto song = Engine::getSong();