| @@ -137,6 +137,52 @@ | |||
| </item> | |||
| </layout> | |||
| </widget> | |||
| <widget class="QWidget" name="logs"> | |||
| <attribute name="title"> | |||
| <string>Logs</string> | |||
| </attribute> | |||
| <layout class="QHBoxLayout" name="horizontalLayout_4"> | |||
| <property name="spacing"> | |||
| <number>0</number> | |||
| </property> | |||
| <property name="leftMargin"> | |||
| <number>0</number> | |||
| </property> | |||
| <property name="topMargin"> | |||
| <number>0</number> | |||
| </property> | |||
| <property name="rightMargin"> | |||
| <number>0</number> | |||
| </property> | |||
| <property name="bottomMargin"> | |||
| <number>1</number> | |||
| </property> | |||
| <item> | |||
| <widget class="QPlainTextEdit" name="text_logs"> | |||
| <property name="font"> | |||
| <font> | |||
| <family>DejaVu Sans Mono</family> | |||
| </font> | |||
| </property> | |||
| <property name="verticalScrollBarPolicy"> | |||
| <enum>Qt::ScrollBarAlwaysOn</enum> | |||
| </property> | |||
| <property name="horizontalScrollBarPolicy"> | |||
| <enum>Qt::ScrollBarAlwaysOn</enum> | |||
| </property> | |||
| <property name="lineWrapMode"> | |||
| <enum>QPlainTextEdit::NoWrap</enum> | |||
| </property> | |||
| <property name="plainText"> | |||
| <string>Loading...</string> | |||
| </property> | |||
| <property name="textInteractionFlags"> | |||
| <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | |||
| </property> | |||
| </widget> | |||
| </item> | |||
| </layout> | |||
| </widget> | |||
| </widget> | |||
| </item> | |||
| </layout> | |||
| @@ -27,6 +27,10 @@ | |||
| #include "CarlaBackendUtils.hpp" | |||
| #include "CarlaBase64Utils.hpp" | |||
| #ifndef BUILD_BRIDGE | |||
| # include "CarlaLogThread.hpp" | |||
| #endif | |||
| #include "juce_audio_formats/juce_audio_formats.h" | |||
| #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) | |||
| @@ -49,6 +53,7 @@ struct CarlaBackendStandalone { | |||
| void* engineCallbackPtr; | |||
| #ifndef BUILD_BRIDGE | |||
| EngineOptions engineOptions; | |||
| CarlaLogThread logThread; | |||
| #endif | |||
| FileCallbackFunc fileCallback; | |||
| @@ -62,6 +67,7 @@ struct CarlaBackendStandalone { | |||
| engineCallbackPtr(nullptr), | |||
| #ifndef BUILD_BRIDGE | |||
| engineOptions(), | |||
| logThread(), | |||
| #endif | |||
| fileCallback(nullptr), | |||
| fileCallbackPtr(nullptr), | |||
| @@ -314,6 +320,7 @@ bool carla_engine_init(const char* driverName, const char* clientName) | |||
| { | |||
| #ifndef BUILD_BRIDGE | |||
| juce::initialiseJuce_GUI(); | |||
| gStandalone.logThread.init(); | |||
| #endif | |||
| gStandalone.lastError = "No error"; | |||
| return true; | |||
| @@ -402,7 +409,9 @@ bool carla_engine_close() | |||
| #ifndef BUILD_BRIDGE | |||
| juce::shutdownJuce_GUI(); | |||
| gStandalone.logThread.stop(); | |||
| #endif | |||
| delete gStandalone.engine; | |||
| gStandalone.engine = nullptr; | |||
| @@ -436,12 +445,12 @@ void carla_set_engine_callback(EngineCallbackFunc func, void* ptr) | |||
| gStandalone.engineCallback = func; | |||
| gStandalone.engineCallbackPtr = ptr; | |||
| #ifndef BUILD_BRIDGE | |||
| gStandalone.logThread.setCallback(func, ptr); | |||
| #endif | |||
| if (gStandalone.engine != nullptr) | |||
| gStandalone.engine->setCallback(func, ptr); | |||
| //#ifdef WANT_LOGS | |||
| // gLogThread.setCallback(func, ptr); | |||
| //#endif | |||
| } | |||
| #ifndef BUILD_BRIDGE | |||
| @@ -654,6 +654,16 @@ const char* carla_get_supported_file_extensions() | |||
| // ------------------------------------------------------------------------------------------------------------------- | |||
| void carla_fflush(bool err) | |||
| { | |||
| std::fflush(err ? stderr : stdout); | |||
| } | |||
| void carla_fputs(bool err, const char* string) | |||
| { | |||
| std::fputs(string, err ? stderr : stdout); | |||
| } | |||
| void carla_set_process_name(const char* name) | |||
| { | |||
| carla_debug("carla_set_process_name(\"%s\")", name); | |||
| @@ -158,6 +158,16 @@ CARLA_EXPORT const CarlaCachedPluginInfo* carla_get_cached_plugin_info(PluginTyp | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * set stuff */ | |||
| /*! | |||
| * Flush stdout or stderr. | |||
| */ | |||
| CARLA_EXPORT void carla_fflush(bool err); | |||
| /*! | |||
| * Print the string @a string to stdout or stderr. | |||
| */ | |||
| CARLA_EXPORT void carla_fputs(bool err, const char* string); | |||
| /*! | |||
| * Set the current process name to @a name. | |||
| */ | |||
| @@ -68,6 +68,19 @@ CARLA_CLIENT_NAME = os.getenv("CARLA_CLIENT_NAME") | |||
| LADISH_APP_NAME = os.getenv("LADISH_APP_NAME") | |||
| NSM_URL = os.getenv("NSM_URL") | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # Carla Print class | |||
| class CarlaPrint: | |||
| def __init__(self, err): | |||
| self.err = err | |||
| def flush(self): | |||
| gCarla.utils.fflush(self.err) | |||
| def write(self, string): | |||
| gCarla.utils.fputs(self.err, string) | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # Host Window | |||
| @@ -167,6 +180,7 @@ class HostWindow(QMainWindow): | |||
| self.ui.menu_Engine.setEnabled(False) | |||
| self.ui.menu_Engine.setVisible(False) | |||
| self.ui.menu_Engine.menuAction().setVisible(False) | |||
| self.ui.tabWidget.removeTab(2) | |||
| if self.host.isControl: | |||
| self.ui.act_file_new.setVisible(False) | |||
| @@ -443,6 +457,7 @@ class HostWindow(QMainWindow): | |||
| # ---------------------------------------------------------------------------------------------------- | |||
| # Final setup | |||
| self.ui.text_logs.clear() | |||
| self.setProperWindowTitle() | |||
| # Qt needs this so it properly creates & resizes the canvas | |||
| @@ -613,8 +628,14 @@ class HostWindow(QMainWindow): | |||
| firstInit = self.fFirstEngineInit | |||
| self.fFirstEngineInit = False | |||
| self.ui.text_logs.appendPlainText("======= Starting engine =======") | |||
| if self.host.engine_init(audioDriver, self.fClientName): | |||
| self.ui.text_logs.appendPlainText("======= Engine started ========") | |||
| return | |||
| if self.host.engine_init(audioDriver, self.fClientName) or firstInit: | |||
| elif firstInit: | |||
| self.ui.text_logs.appendPlainText("Failed to start engine on first try, ignored") | |||
| return | |||
| audioError = self.host.get_last_error() | |||
| @@ -626,6 +647,8 @@ class HostWindow(QMainWindow): | |||
| @pyqtSlot() | |||
| def slot_engineStop(self, forced = False): | |||
| self.ui.text_logs.appendPlainText("======= Stopping engine =======") | |||
| if self.fPluginCount == 0: | |||
| self.engineStopFinal() | |||
| return True | |||
| @@ -655,8 +678,11 @@ class HostWindow(QMainWindow): | |||
| if self.host.is_engine_running(): | |||
| self.host.remove_all_plugins() | |||
| if not self.host.engine_close(): | |||
| print(self.host.get_last_error()) | |||
| if self.host.engine_close(): | |||
| self.ui.text_logs.appendPlainText("======= Engine stopped ========") | |||
| else: | |||
| self.ui.text_logs.appendPlainText("Failed to stop engine, error was:") | |||
| self.ui.text_logs.appendPlainText(self.host.get_last_error()) | |||
| if self.fCustomStopAction == 1: | |||
| self.close() | |||
| @@ -1601,7 +1627,9 @@ class HostWindow(QMainWindow): | |||
| if self.fCanvasWidth == 0 or self.fCanvasHeight == 0: | |||
| return | |||
| if self.ui.tabWidget.currentIndex() == 1: | |||
| currentIndex = self.ui.tabWidget.currentIndex() | |||
| if currentIndex == 1: | |||
| width = self.ui.graphicsView.width() | |||
| height = self.ui.graphicsView.height() | |||
| else: | |||
| @@ -1609,7 +1637,7 @@ class HostWindow(QMainWindow): | |||
| self.ui.tabWidget.setCurrentIndex(1) | |||
| width = self.ui.graphicsView.width() | |||
| height = self.ui.graphicsView.height() | |||
| self.ui.tabWidget.setCurrentIndex(0) | |||
| self.ui.tabWidget.setCurrentIndex(currentIndex) | |||
| self.ui.tabWidget.blockSignals(False) | |||
| self.ui.miniCanvasPreview.setViewSize(float(width)/self.fCanvasWidth, float(height)/self.fCanvasHeight) | |||
| @@ -1719,9 +1747,12 @@ class HostWindow(QMainWindow): | |||
| # -------------------------------------------------------------------------------------------------------- | |||
| def fixLogText(self, text): | |||
| return text.replace("\x1b[30;1m", "").replace("\x1b[31m", "").replace("\x1b[0m", "") | |||
| @pyqtSlot(int, int, int, float, str) | |||
| def slot_handleDebugCallback(self, pluginId, value1, value2, value3, valueStr): | |||
| print("DEBUG:", pluginId, value1, value2, value3, valueStr) | |||
| self.ui.text_logs.appendPlainText(self.fixLogText(valueStr)) | |||
| @pyqtSlot(str) | |||
| def slot_handleInfoCallback(self, info): | |||
| @@ -2224,7 +2255,14 @@ def initHost(initName, libPrefix, isControl, isPlugin, failError, HostClass = No | |||
| gCarla.utils = CarlaUtils(os.path.join(pathBinaries, utilsname)) | |||
| gCarla.utils.set_process_name(os.path.basename(initName)) | |||
| #gCarla.utils.set_locale_C() | |||
| try: | |||
| sys.stdout.flush() | |||
| except: | |||
| pass | |||
| sys.stdout = CarlaPrint(False) | |||
| sys.stderr = CarlaPrint(True) | |||
| # -------------------------------------------------------------------------------------------------------- | |||
| # Done | |||
| @@ -179,6 +179,12 @@ class CarlaUtils(object): | |||
| self.lib.carla_get_cached_plugin_info.argtypes = [c_enum, c_uint] | |||
| self.lib.carla_get_cached_plugin_info.restype = POINTER(CarlaCachedPluginInfo) | |||
| self.lib.carla_fflush.argtypes = [c_bool] | |||
| self.lib.carla_fflush.restype = None | |||
| self.lib.carla_fputs.argtypes = [c_bool, c_char_p] | |||
| self.lib.carla_fputs.restype = None | |||
| self.lib.carla_set_process_name.argtypes = [c_char_p] | |||
| self.lib.carla_set_process_name.restype = None | |||
| @@ -279,6 +285,12 @@ class CarlaUtils(object): | |||
| def get_cached_plugin_info(self, ptype, index): | |||
| return structToDict(self.lib.carla_get_cached_plugin_info(ptype, index).contents) | |||
| def fflush(self, err): | |||
| self.lib.carla_fflush(err) | |||
| def fputs(self, err, string): | |||
| self.lib.carla_fputs(err, string.encode("utf-8")) | |||
| def set_process_name(self, name): | |||
| self.lib.carla_set_process_name(name.encode("utf-8")) | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * Carla Log thread | |||
| * Copyright (C) 2013 Filipe Coelho <falktx@falktx.com> | |||
| * Carla Log Thread | |||
| * Copyright (C) 2013-2016 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 | |||
| @@ -24,51 +24,67 @@ | |||
| #include <fcntl.h> | |||
| using CarlaBackend::CallbackFunc; | |||
| using CarlaBackend::EngineCallbackFunc; | |||
| // ----------------------------------------------------------------------- | |||
| // Log thread | |||
| class CarlaLogThread : public CarlaThread | |||
| class CarlaLogThread : private CarlaThread | |||
| { | |||
| public: | |||
| CarlaLogThread() | |||
| : CarlaThread("CarlaLogThread"), | |||
| fStdOut(-1), | |||
| fStdErr(-1), | |||
| fCallback(nullptr), | |||
| fCallbackPtr(nullptr) | |||
| fCallbackPtr(nullptr) {} | |||
| ~CarlaLogThread() | |||
| { | |||
| stop(); | |||
| } | |||
| void init() | |||
| { | |||
| pipe(fPipe); | |||
| CARLA_SAFE_ASSERT_RETURN(pipe(fPipe) == 0,); | |||
| CARLA_SAFE_ASSERT_RETURN(fcntl(fPipe[0], F_SETFL, O_NONBLOCK) == 0,); | |||
| fflush(stdout); | |||
| fflush(stderr); | |||
| std::fflush(stdout); | |||
| std::fflush(stderr); | |||
| fStdOut = dup(STDOUT_FILENO); | |||
| fStdErr = dup(STDERR_FILENO); | |||
| //fPipe[1] = ::dup(STDOUT_FILENO); | |||
| //fPipe[1] = ::dup(STDERR_FILENO); | |||
| dup2(fPipe[1], STDOUT_FILENO); | |||
| dup2(fPipe[1], STDERR_FILENO); | |||
| fcntl(fPipe[0], F_SETFL, O_NONBLOCK); | |||
| start(2); | |||
| startThread(); | |||
| } | |||
| ~CarlaLogThread() | |||
| void stop() | |||
| { | |||
| fCallback = nullptr; | |||
| fCallbackPtr = nullptr; | |||
| if (fStdOut != -1) | |||
| return; | |||
| stop(5000); | |||
| stopThread(5000); | |||
| fflush(stdout); | |||
| fflush(stderr); | |||
| std::fflush(stdout); | |||
| std::fflush(stderr); | |||
| close(fPipe[0]); | |||
| close(fPipe[1]); | |||
| dup2(fStdOut, STDOUT_FILENO); | |||
| dup2(fStdErr, STDERR_FILENO); | |||
| close(fStdOut); | |||
| close(fStdErr); | |||
| fStdOut = -1; | |||
| fStdErr = -1; | |||
| } | |||
| void setCallback(CallbackFunc callback, void* callbackPtr) | |||
| void setCallback(EngineCallbackFunc callback, void* callbackPtr) | |||
| { | |||
| CARLA_ASSERT(callback != nullptr); | |||
| CARLA_SAFE_ASSERT_RETURN(callback != nullptr,); | |||
| fCallback = callback; | |||
| fCallbackPtr = callbackPtr; | |||
| @@ -77,56 +93,62 @@ public: | |||
| protected: | |||
| void run() | |||
| { | |||
| while (! shouldExit()) | |||
| { | |||
| size_t r, lastRead; | |||
| ssize_t r2; // to avoid sign/unsign conversions | |||
| CARLA_SAFE_ASSERT_RETURN(fCallback != nullptr,); | |||
| static char bufTemp[1024+1] = { '\0' }; | |||
| static char bufRead[1024+1]; | |||
| static char bufSend[2048+1]; | |||
| size_t k, bufTempPos; | |||
| ssize_t r, lastRead; | |||
| char bufTemp[1024+1]; | |||
| char bufRead[1024+1]; | |||
| char bufSend[2048+1]; | |||
| while ((r2 = read(fPipe[0], bufRead, sizeof(char)*1024)) > 0) | |||
| bufTemp[0] = '\0'; | |||
| bufTempPos = 0; | |||
| while (! shouldThreadExit()) | |||
| { | |||
| bufRead[0] = '\0'; | |||
| while ((r = read(fPipe[0], bufRead, 1024)) > 0) | |||
| { | |||
| r = static_cast<size_t>(r2); | |||
| CARLA_SAFE_ASSERT_CONTINUE(r <= 1024); | |||
| bufRead[r] = '\0'; | |||
| lastRead = 0; | |||
| for (size_t i=0; i < r; ++i) | |||
| for (ssize_t i=0; i<r; ++i) | |||
| { | |||
| CARLA_ASSERT(bufRead[i] != '\0'); | |||
| CARLA_SAFE_ASSERT_BREAK(bufRead[i] != '\0'); | |||
| if (bufRead[i] != '\n') | |||
| continue; | |||
| k = static_cast<size_t>(i-lastRead); | |||
| if (bufRead[i] == '\n') | |||
| if (bufTempPos != 0) | |||
| { | |||
| std::strcpy(bufSend, bufTemp); | |||
| std::strncat(bufSend, bufRead+lastRead, i-lastRead); | |||
| bufSend[std::strlen(bufTemp)+i-lastRead] = '\0'; | |||
| lastRead = i; | |||
| bufTemp[0] = '\0'; | |||
| if (fCallback != nullptr) | |||
| { | |||
| if (fOldBuffer.isNotEmpty()) | |||
| { | |||
| fCallback(fCallbackPtr, CarlaBackend::CALLBACK_DEBUG, 0, 0, 0, 0.0f, fOldBuffer); | |||
| fOldBuffer = nullptr; | |||
| } | |||
| fCallback(fCallbackPtr, CarlaBackend::CALLBACK_DEBUG, 0, 0, 0, 0.0f, bufSend); | |||
| } | |||
| else | |||
| fOldBuffer += bufSend; | |||
| std::memcpy(bufSend, bufTemp, bufTempPos); | |||
| std::memcpy(bufSend+bufTempPos, bufRead+lastRead, k); | |||
| k += bufTempPos; | |||
| } | |||
| else | |||
| { | |||
| std::memcpy(bufSend, bufRead+lastRead, k); | |||
| } | |||
| } | |||
| CARLA_ASSERT(lastRead < r); | |||
| lastRead = i+1; | |||
| bufSend[k] = '\0'; | |||
| bufTemp[0] = '\0'; | |||
| bufTempPos = 0; | |||
| fCallback(fCallbackPtr, CarlaBackend::ENGINE_CALLBACK_DEBUG, 0, 0, 0, 0.0f, bufSend); | |||
| } | |||
| if (lastRead > 0 && r > 0 && lastRead+1 < r) | |||
| if (lastRead > 0 && lastRead != r) | |||
| { | |||
| std::strncpy(bufTemp, bufRead+lastRead, r-lastRead); | |||
| bufTemp[r-lastRead] = '\0'; | |||
| k = static_cast<size_t>(r-lastRead); | |||
| std::memcpy(bufTemp, bufRead+lastRead, k); | |||
| bufTemp[k] = '\0'; | |||
| bufTempPos = k; | |||
| } | |||
| } | |||
| @@ -135,13 +157,14 @@ protected: | |||
| } | |||
| private: | |||
| int fPipe[2]; | |||
| int fPipe[2]; | |||
| int fStdOut; | |||
| int fStdErr; | |||
| CallbackFunc fCallback; | |||
| void* fCallbackPtr; | |||
| CarlaString fOldBuffer; | |||
| EngineCallbackFunc fCallback; | |||
| void* fCallbackPtr; | |||
| CARLA_PREVENT_HEAP_ALLOCATION | |||
| //CARLA_PREVENT_HEAP_ALLOCATION | |||
| CARLA_DECLARE_NON_COPY_CLASS(CarlaLogThread) | |||
| }; | |||