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;