diff --git a/resources/ui/carla_host.ui b/resources/ui/carla_host.ui index 610e46b56..90548960b 100644 --- a/resources/ui/carla_host.ui +++ b/resources/ui/carla_host.ui @@ -137,6 +137,52 @@ + + + Logs + + + + 0 + + + 0 + + + 0 + + + 0 + + + 1 + + + + + + DejaVu Sans Mono + + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOn + + + QPlainTextEdit::NoWrap + + + Loading... + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + diff --git a/source/backend/CarlaStandalone.cpp b/source/backend/CarlaStandalone.cpp index 1aa694fa7..3429af09a 100644 --- a/source/backend/CarlaStandalone.cpp +++ b/source/backend/CarlaStandalone.cpp @@ -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 diff --git a/source/backend/CarlaUtils.cpp b/source/backend/CarlaUtils.cpp index f651824bd..ff0d263c2 100644 --- a/source/backend/CarlaUtils.cpp +++ b/source/backend/CarlaUtils.cpp @@ -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); diff --git a/source/backend/CarlaUtils.h b/source/backend/CarlaUtils.h index 4ee4ede18..48697a4f6 100644 --- a/source/backend/CarlaUtils.h +++ b/source/backend/CarlaUtils.h @@ -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. */ diff --git a/source/carla_host.py b/source/carla_host.py index 46bd3e117..b7107ec9a 100644 --- a/source/carla_host.py +++ b/source/carla_host.py @@ -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 diff --git a/source/carla_utils.py b/source/carla_utils.py index 0b5a9e1fc..c7342954d 100644 --- a/source/carla_utils.py +++ b/source/carla_utils.py @@ -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")) diff --git a/source/utils/CarlaLogThread.hpp b/source/utils/CarlaLogThread.hpp index 0223f6677..ce96f8c9e 100644 --- a/source/utils/CarlaLogThread.hpp +++ b/source/utils/CarlaLogThread.hpp @@ -1,6 +1,6 @@ /* - * Carla Log thread - * Copyright (C) 2013 Filipe Coelho + * Carla Log Thread + * Copyright (C) 2013-2016 Filipe Coelho * * 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 -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(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(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(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) };