@@ -137,6 +137,52 @@ | |||||
</item> | </item> | ||||
</layout> | </layout> | ||||
</widget> | </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> | </widget> | ||||
</item> | </item> | ||||
</layout> | </layout> | ||||
@@ -27,6 +27,10 @@ | |||||
#include "CarlaBackendUtils.hpp" | #include "CarlaBackendUtils.hpp" | ||||
#include "CarlaBase64Utils.hpp" | #include "CarlaBase64Utils.hpp" | ||||
#ifndef BUILD_BRIDGE | |||||
# include "CarlaLogThread.hpp" | |||||
#endif | |||||
#include "juce_audio_formats/juce_audio_formats.h" | #include "juce_audio_formats/juce_audio_formats.h" | ||||
#if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) | #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) | ||||
@@ -49,6 +53,7 @@ struct CarlaBackendStandalone { | |||||
void* engineCallbackPtr; | void* engineCallbackPtr; | ||||
#ifndef BUILD_BRIDGE | #ifndef BUILD_BRIDGE | ||||
EngineOptions engineOptions; | EngineOptions engineOptions; | ||||
CarlaLogThread logThread; | |||||
#endif | #endif | ||||
FileCallbackFunc fileCallback; | FileCallbackFunc fileCallback; | ||||
@@ -62,6 +67,7 @@ struct CarlaBackendStandalone { | |||||
engineCallbackPtr(nullptr), | engineCallbackPtr(nullptr), | ||||
#ifndef BUILD_BRIDGE | #ifndef BUILD_BRIDGE | ||||
engineOptions(), | engineOptions(), | ||||
logThread(), | |||||
#endif | #endif | ||||
fileCallback(nullptr), | fileCallback(nullptr), | ||||
fileCallbackPtr(nullptr), | fileCallbackPtr(nullptr), | ||||
@@ -314,6 +320,7 @@ bool carla_engine_init(const char* driverName, const char* clientName) | |||||
{ | { | ||||
#ifndef BUILD_BRIDGE | #ifndef BUILD_BRIDGE | ||||
juce::initialiseJuce_GUI(); | juce::initialiseJuce_GUI(); | ||||
gStandalone.logThread.init(); | |||||
#endif | #endif | ||||
gStandalone.lastError = "No error"; | gStandalone.lastError = "No error"; | ||||
return true; | return true; | ||||
@@ -402,7 +409,9 @@ bool carla_engine_close() | |||||
#ifndef BUILD_BRIDGE | #ifndef BUILD_BRIDGE | ||||
juce::shutdownJuce_GUI(); | juce::shutdownJuce_GUI(); | ||||
gStandalone.logThread.stop(); | |||||
#endif | #endif | ||||
delete gStandalone.engine; | delete gStandalone.engine; | ||||
gStandalone.engine = nullptr; | gStandalone.engine = nullptr; | ||||
@@ -436,12 +445,12 @@ void carla_set_engine_callback(EngineCallbackFunc func, void* ptr) | |||||
gStandalone.engineCallback = func; | gStandalone.engineCallback = func; | ||||
gStandalone.engineCallbackPtr = ptr; | gStandalone.engineCallbackPtr = ptr; | ||||
#ifndef BUILD_BRIDGE | |||||
gStandalone.logThread.setCallback(func, ptr); | |||||
#endif | |||||
if (gStandalone.engine != nullptr) | if (gStandalone.engine != nullptr) | ||||
gStandalone.engine->setCallback(func, ptr); | gStandalone.engine->setCallback(func, ptr); | ||||
//#ifdef WANT_LOGS | |||||
// gLogThread.setCallback(func, ptr); | |||||
//#endif | |||||
} | } | ||||
#ifndef BUILD_BRIDGE | #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) | void carla_set_process_name(const char* name) | ||||
{ | { | ||||
carla_debug("carla_set_process_name(\"%s\")", 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 */ | * 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. | * 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") | LADISH_APP_NAME = os.getenv("LADISH_APP_NAME") | ||||
NSM_URL = os.getenv("NSM_URL") | 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 | # Host Window | ||||
@@ -167,6 +180,7 @@ class HostWindow(QMainWindow): | |||||
self.ui.menu_Engine.setEnabled(False) | self.ui.menu_Engine.setEnabled(False) | ||||
self.ui.menu_Engine.setVisible(False) | self.ui.menu_Engine.setVisible(False) | ||||
self.ui.menu_Engine.menuAction().setVisible(False) | self.ui.menu_Engine.menuAction().setVisible(False) | ||||
self.ui.tabWidget.removeTab(2) | |||||
if self.host.isControl: | if self.host.isControl: | ||||
self.ui.act_file_new.setVisible(False) | self.ui.act_file_new.setVisible(False) | ||||
@@ -443,6 +457,7 @@ class HostWindow(QMainWindow): | |||||
# ---------------------------------------------------------------------------------------------------- | # ---------------------------------------------------------------------------------------------------- | ||||
# Final setup | # Final setup | ||||
self.ui.text_logs.clear() | |||||
self.setProperWindowTitle() | self.setProperWindowTitle() | ||||
# Qt needs this so it properly creates & resizes the canvas | # Qt needs this so it properly creates & resizes the canvas | ||||
@@ -613,8 +628,14 @@ class HostWindow(QMainWindow): | |||||
firstInit = self.fFirstEngineInit | firstInit = self.fFirstEngineInit | ||||
self.fFirstEngineInit = False | 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 | return | ||||
audioError = self.host.get_last_error() | audioError = self.host.get_last_error() | ||||
@@ -626,6 +647,8 @@ class HostWindow(QMainWindow): | |||||
@pyqtSlot() | @pyqtSlot() | ||||
def slot_engineStop(self, forced = False): | def slot_engineStop(self, forced = False): | ||||
self.ui.text_logs.appendPlainText("======= Stopping engine =======") | |||||
if self.fPluginCount == 0: | if self.fPluginCount == 0: | ||||
self.engineStopFinal() | self.engineStopFinal() | ||||
return True | return True | ||||
@@ -655,8 +678,11 @@ class HostWindow(QMainWindow): | |||||
if self.host.is_engine_running(): | if self.host.is_engine_running(): | ||||
self.host.remove_all_plugins() | 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: | if self.fCustomStopAction == 1: | ||||
self.close() | self.close() | ||||
@@ -1601,7 +1627,9 @@ class HostWindow(QMainWindow): | |||||
if self.fCanvasWidth == 0 or self.fCanvasHeight == 0: | if self.fCanvasWidth == 0 or self.fCanvasHeight == 0: | ||||
return | return | ||||
if self.ui.tabWidget.currentIndex() == 1: | |||||
currentIndex = self.ui.tabWidget.currentIndex() | |||||
if currentIndex == 1: | |||||
width = self.ui.graphicsView.width() | width = self.ui.graphicsView.width() | ||||
height = self.ui.graphicsView.height() | height = self.ui.graphicsView.height() | ||||
else: | else: | ||||
@@ -1609,7 +1637,7 @@ class HostWindow(QMainWindow): | |||||
self.ui.tabWidget.setCurrentIndex(1) | self.ui.tabWidget.setCurrentIndex(1) | ||||
width = self.ui.graphicsView.width() | width = self.ui.graphicsView.width() | ||||
height = self.ui.graphicsView.height() | height = self.ui.graphicsView.height() | ||||
self.ui.tabWidget.setCurrentIndex(0) | |||||
self.ui.tabWidget.setCurrentIndex(currentIndex) | |||||
self.ui.tabWidget.blockSignals(False) | self.ui.tabWidget.blockSignals(False) | ||||
self.ui.miniCanvasPreview.setViewSize(float(width)/self.fCanvasWidth, float(height)/self.fCanvasHeight) | 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) | @pyqtSlot(int, int, int, float, str) | ||||
def slot_handleDebugCallback(self, pluginId, value1, value2, value3, valueStr): | 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) | @pyqtSlot(str) | ||||
def slot_handleInfoCallback(self, info): | 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 = CarlaUtils(os.path.join(pathBinaries, utilsname)) | ||||
gCarla.utils.set_process_name(os.path.basename(initName)) | 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 | # 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.argtypes = [c_enum, c_uint] | ||||
self.lib.carla_get_cached_plugin_info.restype = POINTER(CarlaCachedPluginInfo) | 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.argtypes = [c_char_p] | ||||
self.lib.carla_set_process_name.restype = None | self.lib.carla_set_process_name.restype = None | ||||
@@ -279,6 +285,12 @@ class CarlaUtils(object): | |||||
def get_cached_plugin_info(self, ptype, index): | def get_cached_plugin_info(self, ptype, index): | ||||
return structToDict(self.lib.carla_get_cached_plugin_info(ptype, index).contents) | 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): | def set_process_name(self, name): | ||||
self.lib.carla_set_process_name(name.encode("utf-8")) | 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 | * This program is free software; you can redistribute it and/or | ||||
* modify it under the terms of the GNU General Public License as | * modify it under the terms of the GNU General Public License as | ||||
@@ -24,51 +24,67 @@ | |||||
#include <fcntl.h> | #include <fcntl.h> | ||||
using CarlaBackend::CallbackFunc; | |||||
using CarlaBackend::EngineCallbackFunc; | |||||
// ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
// Log thread | // Log thread | ||||
class CarlaLogThread : public CarlaThread | |||||
class CarlaLogThread : private CarlaThread | |||||
{ | { | ||||
public: | public: | ||||
CarlaLogThread() | CarlaLogThread() | ||||
: CarlaThread("CarlaLogThread"), | : CarlaThread("CarlaLogThread"), | ||||
fStdOut(-1), | |||||
fStdErr(-1), | |||||
fCallback(nullptr), | 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], STDOUT_FILENO); | ||||
dup2(fPipe[1], STDERR_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[0]); | ||||
close(fPipe[1]); | 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; | fCallback = callback; | ||||
fCallbackPtr = callbackPtr; | fCallbackPtr = callbackPtr; | ||||
@@ -77,56 +93,62 @@ public: | |||||
protected: | protected: | ||||
void run() | 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'; | bufRead[r] = '\0'; | ||||
lastRead = 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: | 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) | CARLA_DECLARE_NON_COPY_CLASS(CarlaLogThread) | ||||
}; | }; | ||||