diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index eedfca99..83013907 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -38,6 +38,8 @@ qt_add_library(quickshell-core STATIC qsmenuanchor.cpp clock.cpp logging.cpp + paths.cpp + filelogger.cpp ) set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}") diff --git a/src/core/filelogger.cpp b/src/core/filelogger.cpp new file mode 100644 index 00000000..279d25a7 --- /dev/null +++ b/src/core/filelogger.cpp @@ -0,0 +1,60 @@ +#include "filelogger.hpp" + +#include +#include +#include +#include +#include +#include + +#include "filelogger_p.hpp" +#include "logging.hpp" +#include "paths.hpp" + +Q_LOGGING_CATEGORY(logLogger, "quickshell.logger", QtWarningMsg); + +void FileLoggerThread::init() { + auto* thread = new FileLoggerThread(); + auto* logger = new FileLogger(); + logger->moveToThread(thread); + thread->start(); + QMetaObject::invokeMethod(logger, "init", Qt::BlockingQueuedConnection); +} + +void FileLogger::init() { + qCDebug(logLogger) << "Initializing filesystem logger..."; + auto* runDir = QsPaths::instance()->instanceRunDir(); + + if (!runDir) { + qCCritical(logLogger + ) << "Could not start filesystem logger as the runtime directory could not be created."; + return; + } + + auto path = runDir->filePath("log.log"); + auto* file = new QFile(path); + + if (!file->open(QFile::WriteOnly | QFile::Truncate)) { + qCCritical(logLogger + ) << "Could not start filesystem logger as the log file could not be created:" + << path; + return; + } + + this->fileStream.setDevice(file); + + QObject::connect( + LogManager::instance(), + &LogManager::logMessage, + this, + &FileLogger::onMessage, + Qt::QueuedConnection + ); + + qDebug(logLogger) << "Initialized filesystem logger"; +} + +void FileLogger::onMessage(const LogMessage& msg) { + LogManager::formatMessage(this->fileStream, msg, false); + this->fileStream << Qt::endl; +} diff --git a/src/core/filelogger.hpp b/src/core/filelogger.hpp new file mode 100644 index 00000000..dba7aaa2 --- /dev/null +++ b/src/core/filelogger.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +class FileLoggerThread: public QThread { + Q_OBJECT; + +public: + static void init(); + +private: + explicit FileLoggerThread() = default; +}; diff --git a/src/core/filelogger_p.hpp b/src/core/filelogger_p.hpp new file mode 100644 index 00000000..e6d7b633 --- /dev/null +++ b/src/core/filelogger_p.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +#include "logging.hpp" + +class FileLogger: public QObject { + Q_OBJECT; + +public: + explicit FileLogger() = default; + +public slots: + void init(); + +private slots: + void onMessage(const LogMessage& msg); + +private: + QTextStream fileStream; +}; diff --git a/src/core/logging.cpp b/src/core/logging.cpp index d216a980..982ee3f0 100644 --- a/src/core/logging.cpp +++ b/src/core/logging.cpp @@ -5,70 +5,61 @@ #include #include #include +#include +#include -namespace { +LogManager::LogManager(): colorLogs(qEnvironmentVariableIsEmpty("NO_COLOR")), stdoutStream(stdout) { + qInstallMessageHandler(&LogManager::messageHandler); +} -bool COLOR_LOGS = false; // NOLINT - -void formatMessage( +void LogManager::messageHandler( QtMsgType type, const QMessageLogContext& context, - const QString& msg, - bool color + const QString& msg ) { - const auto* typeString = "[log error]"; + auto message = LogMessage(type, context.category, msg.toUtf8()); + auto* self = LogManager::instance(); + + LogManager::formatMessage(self->stdoutStream, message, self->colorLogs); + self->stdoutStream << Qt::endl; + + emit self->logMessage(message); +} + +LogManager* LogManager::instance() { + static auto* instance = new LogManager(); // NOLINT + return instance; +} + +void LogManager::formatMessage(QTextStream& stream, const LogMessage& msg, bool color) { if (color) { - switch (type) { - case QtDebugMsg: typeString = "\033[34m DEBUG"; break; - case QtInfoMsg: typeString = "\033[32m INFO"; break; - case QtWarningMsg: typeString = "\033[33m WARN"; break; - case QtCriticalMsg: typeString = "\033[31m ERROR"; break; - case QtFatalMsg: typeString = "\033[31m FATAL"; break; + switch (msg.type) { + case QtDebugMsg: stream << "\033[34m DEBUG"; break; + case QtInfoMsg: stream << "\033[32m INFO"; break; + case QtWarningMsg: stream << "\033[33m WARN"; break; + case QtCriticalMsg: stream << "\033[31m ERROR"; break; + case QtFatalMsg: stream << "\033[31m FATAL"; break; } } else { - switch (type) { - case QtDebugMsg: typeString = " DEBUG"; break; - case QtInfoMsg: typeString = " INFO"; break; - case QtWarningMsg: typeString = " WARN"; break; - case QtCriticalMsg: typeString = " ERROR"; break; - case QtFatalMsg: typeString = " FATAL"; break; + switch (msg.type) { + case QtDebugMsg: stream << " DEBUG"; break; + case QtInfoMsg: stream << " INFO"; break; + case QtWarningMsg: stream << " WARN"; break; + case QtCriticalMsg: stream << " ERROR"; break; + case QtFatalMsg: stream << " FATAL"; break; } } - const auto isDefault = strcmp(context.category, "default") == 0; + const auto isDefault = strcmp(msg.category, "default") == 0; - const char* format = nullptr; + if (color && !isDefault && msg.type != QtFatalMsg) stream << "\033[97m"; - if (color) { - if (type == QtFatalMsg) { - if (isDefault) format = "%s: %s\033[0m\n"; - else format = "%s %s: %s\033[0m\n"; - } else { - if (isDefault) format = "%s\033[0m: %s\n"; - else format = "%s \033[97m%s\033[0m: %s\n"; - } - } else { - if (isDefault) format = "%s: %s\n"; - else format = "%s %s: %s\n"; + if (!isDefault) { + stream << ' ' << msg.category; } - if (isDefault) { - printf(format, typeString, msg.toStdString().c_str()); - } else { - printf(format, typeString, context.category, msg.toStdString().c_str()); - } + if (color && msg.type != QtFatalMsg) stream << "\033[0m"; - fflush(stdout); -} - -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) { - formatMessage(type, context, msg, COLOR_LOGS); -} - -} // namespace - -void LogManager::setup() { - COLOR_LOGS = qEnvironmentVariableIsEmpty("NO_COLOR"); - qInstallMessageHandler(&messageHandler); + stream << ": " << msg.body; } diff --git a/src/core/logging.hpp b/src/core/logging.hpp index c2d8d13f..03a880a1 100644 --- a/src/core/logging.hpp +++ b/src/core/logging.hpp @@ -1,6 +1,38 @@ #pragma once -class LogManager { -public: - static void setup(); +#include + +#include +#include +#include +#include + +struct LogMessage { + explicit LogMessage(QtMsgType type, const char* category, QByteArray body) + : type(type) + , category(category) + , body(std::move(body)) {} + + QtMsgType type; + const char* category; + QByteArray body; +}; + +class LogManager: public QObject { + Q_OBJECT; + +public: + static LogManager* instance(); + + static void formatMessage(QTextStream& stream, const LogMessage& msg, bool color); + +signals: + void logMessage(LogMessage msg); + +private: + explicit LogManager(); + static void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); + + bool colorLogs; + QTextStream stdoutStream; }; diff --git a/src/core/main.cpp b/src/core/main.cpp index 12f3eb38..e2bbdcbe 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -22,12 +22,14 @@ #include #include +#include "filelogger.hpp" #include "logging.hpp" +#include "paths.hpp" #include "plugin.hpp" #include "rootwrapper.hpp" int qs_main(int argc, char** argv) { - LogManager::setup(); + LogManager::instance(); QString configFilePath; QString workingDirectory; @@ -340,6 +342,8 @@ int qs_main(int argc, char** argv) { qputenv(var.toUtf8(), val.toUtf8()); } + QsPaths::init(shellId); + // While the simple animation driver can lead to better animations in some cases, // it also can cause excessive repainting at excessively high framerates which can // lead to noticeable amounts of gpu usage, including overheating on some systems. @@ -386,6 +390,8 @@ int qs_main(int argc, char** argv) { app = new QGuiApplication(argc, argv); } + FileLoggerThread::init(); + if (debugPort != -1) { QQmlDebuggingEnabler::enableDebugging(true); auto wait = waitForDebug ? QQmlDebuggingEnabler::WaitForClient diff --git a/src/core/paths.cpp b/src/core/paths.cpp new file mode 100644 index 00000000..b204f94f --- /dev/null +++ b/src/core/paths.cpp @@ -0,0 +1,91 @@ +#include "paths.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(logPaths, "quickshell.paths", QtWarningMsg); + +QsPaths* QsPaths::instance() { + static auto* instance = new QsPaths(); // NOLINT + return instance; +} + +void QsPaths::init(QString shellId) { QsPaths::instance()->shellId = std::move(shellId); } + +QDir* QsPaths::cacheDir() { + if (this->cacheState == DirState::Unknown) { + auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + dir = QDir(dir.filePath("quickshell")); + dir = QDir(dir.filePath(this->shellId)); + this->mCacheDir = dir; + + qCDebug(logPaths) << "Initialized cache path:" << dir.path(); + + if (!dir.mkpath(".")) { + qCCritical(logPaths) << "Cannot create cache directory at" << dir.path(); + + this->cacheState = DirState::Failed; + } + } + + if (this->cacheState == DirState::Failed) return nullptr; + else return &this->mCacheDir; +} + +QDir* QsPaths::runDir() { + if (this->runState == DirState::Unknown) { + auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR"); + if (runtimeDir.isEmpty()) { + runtimeDir = QString("/run/user/$1").arg(getuid()); + qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir; + } + + auto dir = QDir(runtimeDir); + dir = QDir(dir.filePath("quickshell")); + dir = QDir(dir.filePath(this->shellId)); + this->mRunDir = dir; + + qCDebug(logPaths) << "Initialized runtime path:" << dir.path(); + + if (!dir.mkpath(".")) { + qCCritical(logPaths) << "Cannot create runtime directory at" << dir.path(); + + this->runState = DirState::Failed; + } + } + + if (this->runState == DirState::Failed) return nullptr; + else return &this->mRunDir; +} + +QDir* QsPaths::instanceRunDir() { + if (this->instanceRunState == DirState::Unknown) { + auto* runtimeDir = this->runDir(); + + if (!runtimeDir) { + qCCritical(logPaths) << "Cannot create instance runtime directory as main runtim directory " + "could not be created."; + this->instanceRunState = DirState::Failed; + } else { + this->mInstanceRunDir = + runtimeDir->filePath(QString("run-%1").arg(QDateTime::currentMSecsSinceEpoch())); + + qCDebug(logPaths) << "Initialized instance runtime path:" << this->mInstanceRunDir.path(); + + if (!this->mInstanceRunDir.mkpath(".")) { + qCCritical(logPaths) << "Cannot create instance runtime directory at" + << this->mInstanceRunDir.path(); + this->instanceRunState = DirState::Failed; + } + } + } + + if (this->runState == DirState::Failed) return nullptr; + else return &this->mInstanceRunDir; +} diff --git a/src/core/paths.hpp b/src/core/paths.hpp new file mode 100644 index 00000000..b2a1c193 --- /dev/null +++ b/src/core/paths.hpp @@ -0,0 +1,27 @@ +#pragma once +#include + +class QsPaths { +public: + static QsPaths* instance(); + static void init(QString shellId); + + QDir* cacheDir(); + QDir* runDir(); + QDir* instanceRunDir(); + +private: + enum class DirState { + Unknown = 0, + Ready = 1, + Failed = 2, + }; + + QString shellId; + QDir mCacheDir; + QDir mRunDir; + QDir mInstanceRunDir; + DirState cacheState = DirState::Unknown; + DirState runState = DirState::Unknown; + DirState instanceRunState = DirState::Unknown; +};