diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 342fa059b..39d638200 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -244,7 +244,8 @@ set(obs_SOURCES source-label.cpp remote-text.cpp audio-encoders.cpp - qt-wrappers.cpp) + qt-wrappers.cpp + log-viewer.cpp) set(obs_HEADERS ${obs_PLATFORM_HEADERS} @@ -308,7 +309,8 @@ set(obs_HEADERS remote-text.hpp audio-encoders.hpp qt-wrappers.hpp - clickable-label.hpp) + clickable-label.hpp + log-viewer.hpp) set(obs_importers_HEADERS importers/importers.hpp) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 50453d5c7..a2db938b0 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -94,6 +94,8 @@ Windowed="Windowed" Percent="Percent" AspectRatio="Aspect Ratio %1:%2" LockVolume="Lock Volume" +LogViewer="Log Viewer" +ShowOnStartup="Show on startup" # warning if program already open AlreadyRunning.Title="OBS is already running" diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index b7eb0da61..b62de67f3 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -1768,6 +1768,17 @@ Basic.MainMenu.View.SourceIcons + + + true + + + true + + + LogViewer + + diff --git a/UI/log-viewer.cpp b/UI/log-viewer.cpp new file mode 100644 index 000000000..554c0535c --- /dev/null +++ b/UI/log-viewer.cpp @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log-viewer.hpp" +#include "qt-wrappers.hpp" + +OBSLogViewer::OBSLogViewer(QWidget *parent) : QDialog(parent) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + + const QFont fixedFont = + QFontDatabase::systemFont(QFontDatabase::FixedFont); + + textArea = new QTextEdit(); + textArea->setReadOnly(true); + textArea->setFont(fixedFont); + + QHBoxLayout *buttonLayout = new QHBoxLayout(); + QPushButton *clearButton = new QPushButton(QTStr("Clear")); + connect(clearButton, &QPushButton::clicked, this, + &OBSLogViewer::ClearText); + QPushButton *closeButton = new QPushButton(QTStr("Close")); + connect(closeButton, &QPushButton::clicked, this, &QDialog::hide); + + QCheckBox *showStartup = new QCheckBox(QTStr("ShowOnStartup")); + showStartup->setChecked(ShowOnStartup()); + connect(showStartup, SIGNAL(toggled(bool)), this, + SLOT(ToggleShowStartup(bool))); + + buttonLayout->addSpacing(10); + buttonLayout->addWidget(showStartup); + buttonLayout->addStretch(); + buttonLayout->addWidget(clearButton); + buttonLayout->addWidget(closeButton); + buttonLayout->addSpacing(10); + buttonLayout->setContentsMargins(0, 0, 0, 4); + + layout->addWidget(textArea); + layout->addLayout(buttonLayout); + setLayout(layout); + + setWindowTitle(QTStr("LogViewer")); + resize(800, 300); + + const char *geom = config_get_string(App()->GlobalConfig(), "LogViewer", + "geometry"); + + if (geom != nullptr) { + QByteArray ba = QByteArray::fromBase64(QByteArray(geom)); + restoreGeometry(ba); + } + + InitLog(); +} + +OBSLogViewer::~OBSLogViewer() +{ + config_set_string(App()->GlobalConfig(), "LogViewer", "geometry", + saveGeometry().toBase64().constData()); +} + +void OBSLogViewer::ToggleShowStartup(bool checked) +{ + config_set_bool(App()->GlobalConfig(), "LogViewer", "ShowLogStartup", + checked); +} + +bool OBSLogViewer::ShowOnStartup() +{ + return config_get_bool(App()->GlobalConfig(), "LogViewer", + "ShowLogStartup"); +} + +extern QPointer obsLogViewer; + +void OBSLogViewer::InitLog() +{ + char logDir[512]; + std::string path; + + if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs")) { + path += logDir; + path += "/"; + path += App()->GetCurrentLog(); + } + + QFile file(QT_UTF8(path.c_str())); + + if (file.open(QIODevice::ReadOnly)) { + QTextStream in(&file); + + while (!in.atEnd()) { + QString line = in.readLine(); + AddLine(LOG_INFO, line); + } + + file.close(); + } + + obsLogViewer = this; +} + +void OBSLogViewer::AddLine(int type, const QString &str) +{ + QString msg = str.toHtmlEscaped(); + + switch (type) { + case LOG_WARNING: + msg = QStringLiteral("") + msg + + QStringLiteral(""); + break; + case LOG_ERROR: + msg = QStringLiteral("") + msg + + QStringLiteral(""); + break; + } + + QScrollBar *scroll = textArea->verticalScrollBar(); + bool bottomScrolled = scroll->value() >= scroll->maximum() - 10; + + if (bottomScrolled) + scroll->setValue(scroll->maximum()); + + QTextCursor newCursor = textArea->textCursor(); + newCursor.movePosition(QTextCursor::End); + newCursor.insertHtml(msg + QStringLiteral("
")); + + if (bottomScrolled) + scroll->setValue(scroll->maximum()); +} + +void OBSLogViewer::ClearText() +{ + textArea->clear(); +} diff --git a/UI/log-viewer.hpp b/UI/log-viewer.hpp new file mode 100644 index 000000000..3a73c8f2f --- /dev/null +++ b/UI/log-viewer.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include "obs-app.hpp" + +class OBSLogViewer : public QDialog { + Q_OBJECT + + QPointer textArea; + + void InitLog(); + +private slots: + void AddLine(int type, const QString &text); + void ClearText(); + void ToggleShowStartup(bool checked); + +public: + OBSLogViewer(QWidget *parent = 0); + ~OBSLogViewer(); + + bool ShowOnStartup(); +}; diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 25722473c..27849c8b9 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -38,6 +38,7 @@ #include "qt-wrappers.hpp" #include "obs-app.hpp" +#include "log-viewer.hpp" #include "window-basic-main.hpp" #include "window-basic-settings.hpp" #include "crash-report.hpp" @@ -89,6 +90,8 @@ string remuxFilename; bool restart = false; +QPointer obsLogViewer; + // GPU hint exports for AMD/NVIDIA laptops #ifdef _MSC_VER extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 1; @@ -250,12 +253,22 @@ string CurrentDateTimeString() } static inline void LogString(fstream &logFile, const char *timeString, - char *str) + char *str, int log_level) { - logFile << timeString << str << endl; + string msg; + msg += timeString; + msg += str; + + logFile << msg << endl; + + if (!!obsLogViewer) + QMetaObject::invokeMethod(obsLogViewer.data(), "AddLine", + Qt::QueuedConnection, + Q_ARG(int, log_level), + Q_ARG(QString, QString(msg.c_str()))); } -static inline void LogStringChunk(fstream &logFile, char *str) +static inline void LogStringChunk(fstream &logFile, char *str, int log_level) { char *nextLine = str; string timeString = CurrentTimeString(); @@ -272,12 +285,12 @@ static inline void LogStringChunk(fstream &logFile, char *str) nextLine[0] = 0; } - LogString(logFile, timeString.c_str(), str); + LogString(logFile, timeString.c_str(), str, log_level); nextLine++; str = nextLine; } - LogString(logFile, timeString.c_str(), str); + LogString(logFile, timeString.c_str(), str, log_level); } #define MAX_REPEATED_LINES 30 @@ -368,7 +381,7 @@ static void do_log(int log_level, const char *msg, va_list args, void *param) if (log_level <= LOG_INFO || log_verbose) { if (too_many_repeated_entries(logFile, msg, str)) return; - LogStringChunk(logFile, str); + LogStringChunk(logFile, str, log_level); } #if defined(_WIN32) && defined(OBS_DEBUGBREAK_ON_ERROR) diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 150651541..382a4cab6 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -190,6 +190,9 @@ extern void RegisterRestreamAuth(); OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), ui(new Ui::OBSBasic) { + /* setup log viewer */ + logView = new OBSLogViewer(); + qRegisterMetaTypeStreamOperators>( "SignalContainer"); @@ -377,6 +380,7 @@ OBSBasic::OBSBasic(QWidget *parent) } QPoint curSize(width(), height()); + QPoint statsDockSize(statsDock->width(), statsDock->height()); QPoint statsDockPos = curSize / 2 - statsDockSize / 2; QPoint newPos = curPos + statsDockPos; @@ -1926,6 +1930,9 @@ void OBSBasic::OnFirstLoad() #endif Auth::Load(); + + if (logView && logView->ShowOnStartup()) + logView->show(); } void OBSBasic::DeferredSysTrayLoad(int requeueCount) @@ -2398,6 +2405,7 @@ OBSBasic::~OBSBasic() updateCheckThread->wait(); delete screenshotData; + delete logView; delete multiviewProjectorMenu; delete previewProjector; delete studioProgramProjector; @@ -5183,18 +5191,7 @@ void OBSBasic::on_actionUploadLastLog_triggered() void OBSBasic::on_actionViewCurrentLog_triggered() { - char logDir[512]; - if (GetConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - const char *log = App()->GetCurrentLog(); - - string path = logDir; - path += "/"; - path += log; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(path.c_str())); - QDesktopServices::openUrl(url); + logView->setVisible(!logView->isVisible()); } void OBSBasic::on_actionShowCrashLogs_triggered() diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 5c14f7a7c..9acf2d5a5 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -34,6 +34,7 @@ #include "window-projector.hpp" #include "window-basic-about.hpp" #include "auth-base.hpp" +#include "log-viewer.hpp" #include @@ -210,6 +211,8 @@ private: QPointer statsDock; QPointer about; + OBSLogViewer *logView; + QPointer cpuUsageTimer; QPointer diskFullTimer;