crash: add crash reporter

This commit is contained in:
outfoxxed 2024-08-20 00:41:20 -07:00
parent 5040f3796c
commit fe1d15e8f6
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
23 changed files with 1118 additions and 315 deletions

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1 @@
blank_issues_enabled: true

72
.github/ISSUE_TEMPLATE/crash.yml vendored Normal file
View file

@ -0,0 +1,72 @@
name: Crash Report
description: Quickshell has crashed
labels: ["bug", "crash"]
body:
- type: textarea
id: crashinfo
attributes:
label: General crash information
description: |
Paste the contents of the `info.txt` file in your crash folder here.
value: "<details> <summary>General information</summary>
```
<Paste the contents of the file here inside of the triple backticks>
```
</details>"
validations:
required: true
- type: textarea
id: userinfo
attributes:
label: What caused the crash
description: |
Any information likely to help debug the crash. What were you doing when the crash occurred,
what changes did you make, can you get it to happen again?
- type: textarea
id: dump
attributes:
label: Minidump
description: |
Attach `minidump.dmp` here. If it is too big to upload, compress it.
You may skip this step if quickshell crashed while processing a password
or other sensitive information. If you skipped it write why instead.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Log file
description: |
Attach `log.qslog` here. If it is too big to upload, compress it.
You can preview the log if you'd like using `quickshell read-log <path-to-log>`.
validations:
required: true
- type: textarea
id: config
attributes:
label: Configuration
description: |
Attach your configuration here, preferrably in full (not just one file).
Compress it into a zip, tar, etc.
This will help us reproduce the crash ourselves.
- type: textarea
id: bt
attributes:
label: Backtrace
description: |
If you have gdb installed and use systemd, or otherwise know how to get a backtrace,
we would appreciate one. (You may have gdb installed without knowing it)
1. Run `coredumpctl debug <pid>` where `pid` is the number shown after "Crashed process ID"
in the crash reporter.
2. Once it loads, type `bt -full` (then enter)
3. Copy the output and attach it as a file or in a spoiler.

View file

@ -9,6 +9,7 @@ option(BUILD_TESTING "Build tests" OFF)
option(ASAN "Enable ASAN" OFF) # note: better output with gcc than clang option(ASAN "Enable ASAN" OFF) # note: better output with gcc than clang
option(FRAME_POINTERS "Always keep frame pointers" ${ASAN}) option(FRAME_POINTERS "Always keep frame pointers" ${ASAN})
option(CRASH_REPORTER "Enable the crash reporter" ON)
option(USE_JEMALLOC "Use jemalloc over the system malloc implementation" ON) option(USE_JEMALLOC "Use jemalloc over the system malloc implementation" ON)
option(SOCKETS "Enable unix socket support" ON) option(SOCKETS "Enable unix socket support" ON)
option(WAYLAND "Enable wayland support" ON) option(WAYLAND "Enable wayland support" ON)
@ -29,6 +30,7 @@ option(SERVICE_UPOWER "UPower service" ON)
option(SERVICE_NOTIFICATIONS "Notification server" ON) option(SERVICE_NOTIFICATIONS "Notification server" ON)
message(STATUS "Quickshell configuration") message(STATUS "Quickshell configuration")
message(STATUS " Crash reporter: ${CRASH_REPORTER}")
message(STATUS " Jemalloc: ${USE_JEMALLOC}") message(STATUS " Jemalloc: ${USE_JEMALLOC}")
message(STATUS " Build tests: ${BUILD_TESTING}") message(STATUS " Build tests: ${BUILD_TESTING}")
message(STATUS " Sockets: ${SOCKETS}") message(STATUS " Sockets: ${SOCKETS}")

View file

@ -9,6 +9,7 @@
ninja, ninja,
qt6, qt6,
cli11, cli11,
breakpad,
jemalloc, jemalloc,
wayland, wayland,
wayland-protocols, wayland-protocols,
@ -28,6 +29,7 @@
else "unknown"), else "unknown"),
debug ? false, debug ? false,
withCrashReporter ? true,
withJemalloc ? true, # masks heap fragmentation withJemalloc ? true, # masks heap fragmentation
withQtSvg ? true, withQtSvg ? true,
withWayland ? true, withWayland ? true,
@ -55,6 +57,7 @@
qt6.qtdeclarative qt6.qtdeclarative
cli11 cli11
] ]
++ (lib.optional withCrashReporter breakpad)
++ (lib.optional withJemalloc jemalloc) ++ (lib.optional withJemalloc jemalloc)
++ (lib.optional withQtSvg qt6.qtsvg) ++ (lib.optional withQtSvg qt6.qtsvg)
++ (lib.optionals withWayland [ qt6.qtwayland wayland ]) ++ (lib.optionals withWayland [ qt6.qtwayland wayland ])
@ -67,6 +70,7 @@
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo"; cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
cmakeFlags = [ "-DGIT_REVISION=${gitRev}" ] cmakeFlags = [ "-DGIT_REVISION=${gitRev}" ]
++ lib.optional (!withCrashReporter) "-DCRASH_REPORTER=OFF"
++ lib.optional (!withJemalloc) "-DUSE_JEMALLOC=OFF" ++ lib.optional (!withJemalloc) "-DUSE_JEMALLOC=OFF"
++ lib.optional (!withWayland) "-DWAYLAND=OFF" ++ lib.optional (!withWayland) "-DWAYLAND=OFF"
++ lib.optional (!withPipewire) "-DSERVICE_PIPEWIRE=OFF" ++ lib.optional (!withPipewire) "-DSERVICE_PIPEWIRE=OFF"

View file

@ -5,6 +5,10 @@ install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
add_subdirectory(core) add_subdirectory(core)
add_subdirectory(io) add_subdirectory(io)
if (CRASH_REPORTER)
add_subdirectory(crash)
endif()
if (DBUS) if (DBUS)
add_subdirectory(dbus) add_subdirectory(dbus)
endif() endif()

View file

@ -41,9 +41,19 @@ qt_add_library(quickshell-core STATIC
clock.cpp clock.cpp
logging.cpp logging.cpp
paths.cpp paths.cpp
crashinfo.cpp
common.cpp
) )
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}") if (CRASH_REPORTER)
set(CRASH_REPORTER_DEF 1)
endif()
add_library(quickshell-build INTERFACE)
configure_file(build.hpp.in build.hpp)
target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(quickshell-core PRIVATE quickshell-build)
qt_add_qml_module(quickshell-core URI Quickshell VERSION 0.1) qt_add_qml_module(quickshell-core URI Quickshell VERSION 0.1)
target_link_libraries(quickshell-core PRIVATE ${QT_DEPS} Qt6::QuickPrivate CLI11::CLI11) target_link_libraries(quickshell-core PRIVATE ${QT_DEPS} Qt6::QuickPrivate CLI11::CLI11)

6
src/core/build.hpp.in Normal file
View file

@ -0,0 +1,6 @@
#pragma once
// NOLINTBEGIN
#define GIT_REVISION "@GIT_REVISION@"
#define CRASH_REPORTER @CRASH_REPORTER_DEF@
// NOLINTEND

9
src/core/common.cpp Normal file
View file

@ -0,0 +1,9 @@
#include "common.hpp"
#include <qdatetime.h>
namespace qs {
const QDateTime Common::LAUNCH_TIME = QDateTime::currentDateTime();
}

11
src/core/common.hpp Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <qdatetime.h>
namespace qs {
struct Common {
static const QDateTime LAUNCH_TIME;
};
} // namespace qs

19
src/core/crashinfo.cpp Normal file
View file

@ -0,0 +1,19 @@
#include "crashinfo.hpp"
#include <qdatastream.h>
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
stream << info.configPath << info.shellId << info.launchTime << info.noColor;
return stream;
}
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
stream >> info.configPath >> info.shellId >> info.launchTime >> info.noColor;
return stream;
}
namespace qs::crash {
CrashInfo CrashInfo::INSTANCE = {}; // NOLINT
}

26
src/core/crashinfo.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <qdatetime.h>
#include <qstring.h>
struct InstanceInfo {
QString configPath;
QString shellId;
QString initialWorkdir;
QDateTime launchTime;
bool noColor = false;
bool sparseLogsOnly = false;
};
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info);
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info);
namespace qs::crash {
struct CrashInfo {
int logFd = -1;
static CrashInfo INSTANCE; // NOLINT
};
} // namespace qs::crash

View file

@ -24,6 +24,7 @@
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/sendfile.h> #include <sys/sendfile.h>
#include "crashinfo.hpp"
#include "logging_p.hpp" #include "logging_p.hpp"
#include "logging_qtprivate.cpp" // NOLINT #include "logging_qtprivate.cpp" // NOLINT
#include "paths.hpp" #include "paths.hpp"
@ -198,14 +199,16 @@ void ThreadLogging::init() {
if (logMfd != -1) { if (logMfd != -1) {
this->file = new QFile(); this->file = new QFile();
this->file->open(logMfd, QFile::WriteOnly, QFile::AutoCloseHandle); this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle);
this->fileStream.setDevice(this->file); this->fileStream.setDevice(this->file);
} }
if (dlogMfd != -1) { if (dlogMfd != -1) {
crash::CrashInfo::INSTANCE.logFd = dlogMfd;
this->detailedFile = new QFile(); this->detailedFile = new QFile();
// buffered by WriteBuffer // buffered by WriteBuffer
this->detailedFile->open(dlogMfd, QFile::WriteOnly | QFile::Unbuffered, QFile::AutoCloseHandle); this->detailedFile->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle);
this->detailedWriter.setDevice(this->detailedFile); this->detailedWriter.setDevice(this->detailedFile);
if (!this->detailedWriter.writeHeader()) { if (!this->detailedWriter.writeHeader()) {
@ -245,7 +248,7 @@ void ThreadLogging::initFs() {
auto* file = new QFile(path); auto* file = new QFile(path);
auto* detailedFile = new QFile(detailedPath); auto* detailedFile = new QFile(detailedPath);
if (!file->open(QFile::WriteOnly | QFile::Truncate)) { if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
qCCritical(logLogging qCCritical(logLogging
) << "Could not start filesystem logger as the log file could not be created:" ) << "Could not start filesystem logger as the log file could not be created:"
<< path; << path;
@ -256,7 +259,7 @@ void ThreadLogging::initFs() {
} }
// buffered by WriteBuffer // buffered by WriteBuffer
if (!detailedFile->open(QFile::WriteOnly | QFile::Truncate | QFile::Unbuffered)) { if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) {
qCCritical(logLogging qCCritical(logLogging
) << "Could not start detailed filesystem logger as the log file could not be created:" ) << "Could not start detailed filesystem logger as the log file could not be created:"
<< detailedPath; << detailedPath;
@ -287,6 +290,8 @@ void ThreadLogging::initFs() {
sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size()); sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size());
} }
crash::CrashInfo::INSTANCE.logFd = detailedFile->handle();
this->detailedFile = detailedFile; this->detailedFile = detailedFile;
this->detailedWriter.setDevice(detailedFile); this->detailedWriter.setDevice(detailedFile);

View file

@ -1,13 +1,17 @@
#include "main.hpp" #include "main.hpp"
#include <cstdlib>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <CLI/App.hpp> #include <CLI/App.hpp>
#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes #include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes
#include <CLI/Error.hpp>
#include <CLI/Validators.hpp> #include <CLI/Validators.hpp>
#include <qapplication.h> #include <qapplication.h>
#include <qcoreapplication.h> #include <qcoreapplication.h>
#include <qcryptographichash.h> #include <qcryptographichash.h>
#include <qdatastream.h>
#include <qdatetime.h>
#include <qdir.h> #include <qdir.h>
#include <qfileinfo.h> #include <qfileinfo.h>
#include <qguiapplication.h> #include <qguiapplication.h>
@ -24,32 +28,31 @@
#include <qtextstream.h> #include <qtextstream.h>
#include <qtpreprocessorsupport.h> #include <qtpreprocessorsupport.h>
#include "build.hpp"
#include "common.hpp"
#include "crashinfo.hpp"
#include "logging.hpp" #include "logging.hpp"
#include "paths.hpp" #include "paths.hpp"
#include "plugin.hpp" #include "plugin.hpp"
#include "rootwrapper.hpp" #include "rootwrapper.hpp"
#if CRASH_REPORTER
#include "../crash/handler.hpp"
#include "../crash/main.hpp"
#endif
int qs_main(int argc, char** argv) { struct CommandInfo {
QString configPath;
QString manifestPath;
QString configName;
QString& initialWorkdir;
int& debugPort;
bool& waitForDebug;
bool& printInfo;
bool& noColor;
bool& sparseLogsOnly;
};
auto qArgC = 1; void processCommand(int argc, char** argv, CommandInfo& info) {
auto* qArgV = argv;
auto noColor = !qEnvironmentVariableIsEmpty("NO_COLOR");
QString workingDirectory;
QString configFilePath;
QString shellId;
auto printInfo = false;
auto debugPort = -1;
auto waitForDebug = false;
auto useQApplication = false;
auto nativeTextRendering = false;
auto desktopSettingsAware = true;
QHash<QString, QString> envOverrides;
{
auto app = CLI::App(""); auto app = CLI::App("");
class QStringOption { class QStringOption {
@ -79,10 +82,10 @@ int qs_main(int argc, char** argv) {
}; };
/// --- /// ---
QStringOption path; QStringRefOption path(&info.configPath);
QStringOption manifest; QStringRefOption manifest(&info.manifestPath);
QStringOption config; QStringRefOption config(&info.configName);
QStringRefOption workdirRef(&workingDirectory); QStringRefOption workdirRef(&info.initialWorkdir);
auto* selection = app.add_option_group( auto* selection = app.add_option_group(
"Config Selection", "Config Selection",
@ -115,7 +118,7 @@ int qs_main(int argc, char** argv) {
auto* debugPortArg = debug auto* debugPortArg = debug
->add_option( ->add_option(
"--debugport", "--debugport",
debugPort, info.debugPort,
"Open the given port for a QML debugger to connect to." "Open the given port for a QML debugger to connect to."
) )
->check(CLI::Range(0, 65535)); ->check(CLI::Range(0, 65535));
@ -123,20 +126,20 @@ int qs_main(int argc, char** argv) {
debug debug
->add_flag( ->add_flag(
"--waitfordebug", "--waitfordebug",
waitForDebug, info.waitForDebug,
"Wait for a debugger to attach to the given port before launching." "Wait for a debugger to attach to the given port before launching."
) )
->needs(debugPortArg); ->needs(debugPortArg);
/// --- /// ---
auto sparseLogsOnly = false; app.add_flag("--info", info.printInfo, "Print information about the shell")
app.add_flag("--info", printInfo, "Print information about the shell")->excludes(debugPortArg); ->excludes(debugPortArg);
app.add_flag("--no-color", noColor, "Do not color the log output. (Env:NO_COLOR)"); app.add_flag("--no-color", info.noColor, "Do not color the log output. (Env:NO_COLOR)");
auto* printVersion = app.add_flag("-V,--version", "Print quickshell's version, then exit."); auto* printVersion = app.add_flag("-V,--version", "Print quickshell's version, then exit.");
app.add_flag( app.add_flag(
"--no-detailed-logs", "--no-detailed-logs",
sparseLogsOnly, info.sparseLogsOnly,
"Do not enable this unless you know what you are doing." "Do not enable this unless you know what you are doing."
); );
@ -155,30 +158,33 @@ int qs_main(int argc, char** argv) {
); );
readLog->add_flag("--no-time", logNoTime, "Do not print timestamps of log messages."); readLog->add_flag("--no-time", logNoTime, "Do not print timestamps of log messages.");
readLog->add_flag("--no-color", noColor, "Do not color the log output. (Env:NO_COLOR)"); readLog->add_flag("--no-color", info.noColor, "Do not color the log output. (Env:NO_COLOR)");
CLI11_PARSE(app, argc, argv); try {
app.parse(argc, argv);
const auto qApplication = QCoreApplication(qArgC, qArgV); } catch (const CLI::ParseError& e) {
exit(app.exit(e)); // NOLINT
// Start log manager - has to happen with an active event loop or offthread can't be started. };
LogManager::init(!noColor, sparseLogsOnly);
if (*printVersion) { if (*printVersion) {
std::cout << "quickshell pre-release, revision: " << GIT_REVISION << std::endl; std::cout << "quickshell pre-release, revision: " << GIT_REVISION << std::endl;
return 0; exit(0); // NOLINT
} if (*readLog) { } else if (*readLog) {
auto file = QFile(*logPath); auto file = QFile(*logPath);
if (!file.open(QFile::ReadOnly)) { if (!file.open(QFile::ReadOnly)) {
qCritical() << "Failed to open log for reading:" << *logPath; qCritical() << "Failed to open log for reading:" << *logPath;
return -1; exit(-1); // NOLINT
} else { } else {
qInfo() << "Reading log" << *logPath; qInfo() << "Reading log" << *logPath;
} }
return qs::log::readEncodedLogs(&file, !logNoTime, *logFilter) ? 0 : -1; exit( // NOLINT
} else { qs::log::readEncodedLogs(&file, !logNoTime, *logFilter) ? 0 : -1
);
}
}
QString commandConfigPath(QString path, QString manifest, QString config, bool printInfo) {
// NOLINTBEGIN // NOLINTBEGIN
#define CHECK(rname, name, level, label, expr) \ #define CHECK(rname, name, level, label, expr) \
QString name = expr; \ QString name = expr; \
@ -216,7 +222,7 @@ int qs_main(int argc, char** argv) {
int configPathLevel = 10; int configPathLevel = 10;
{ {
// NOLINTBEGIN // NOLINTBEGIN
CHECK(configPath, optionConfigPath, 0, foundpath, *path); CHECK(configPath, optionConfigPath, 0, foundpath, path);
CHECK(configPath, envConfigPath, 1, foundpath, qEnvironmentVariable("QS_CONFIG_PATH")); CHECK(configPath, envConfigPath, 1, foundpath, qEnvironmentVariable("QS_CONFIG_PATH"));
// NOLINTEND // NOLINTEND
@ -235,7 +241,7 @@ int qs_main(int argc, char** argv) {
{ {
// NOLINTBEGIN // NOLINTBEGIN
// clang-format off // clang-format off
CHECK(manifestPath, optionManifestPath, 0, foundmf, *manifest); CHECK(manifestPath, optionManifestPath, 0, foundmf, manifest);
CHECK(manifestPath, envManifestPath, 1, foundmf, qEnvironmentVariable("QS_MANIFEST")); CHECK(manifestPath, envManifestPath, 1, foundmf, qEnvironmentVariable("QS_MANIFEST"));
CHECK(manifestPath, defaultManifestPath, 2, foundmf, QDir(basePath).filePath("manifest.conf")); CHECK(manifestPath, defaultManifestPath, 2, foundmf, QDir(basePath).filePath("manifest.conf"));
// clang-format on // clang-format on
@ -256,7 +262,7 @@ int qs_main(int argc, char** argv) {
int configNameLevel = 10; int configNameLevel = 10;
{ {
// NOLINTBEGIN // NOLINTBEGIN
CHECK(configName, optionConfigName, 0, foundname, *config); CHECK(configName, optionConfigName, 0, foundname, config);
CHECK(configName, envConfigName, 1, foundname, qEnvironmentVariable("QS_CONFIG_NAME")); CHECK(configName, envConfigName, 1, foundname, qEnvironmentVariable("QS_CONFIG_NAME"));
// NOLINTEND // NOLINTEND
@ -270,6 +276,8 @@ int qs_main(int argc, char** argv) {
} }
foundname:; foundname:;
QString configFilePath;
if (!configPath.isEmpty() && configPathLevel <= configNameLevel) { if (!configPath.isEmpty() && configPathLevel <= configNameLevel) {
configFilePath = configPath; configFilePath = configPath;
} else if (!configName.isEmpty()) { } else if (!configName.isEmpty()) {
@ -284,22 +292,21 @@ int qs_main(int argc, char** argv) {
auto split = line.split('='); auto split = line.split('=');
if (split.length() != 2) { if (split.length() != 2) {
qCritical() << "manifest line not in expected format 'name = relativepath':" qCritical() << "manifest line not in expected format 'name = relativepath':" << line;
<< line; exit(-1); // NOLINT
return -1;
} }
if (split[0].trimmed() == configName) { if (split[0].trimmed() == configName) {
configFilePath = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed()); configFilePath = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed());
goto haspath; // NOLINT goto foundp;
} }
} }
qCritical() << "configuration" << configName << "not found in manifest" << manifestPath; qCritical() << "configuration" << configName << "not found in manifest" << manifestPath;
return -1; exit(-1); // NOLINT
} else if (manifestPathLevel < 2) { } else if (manifestPathLevel < 2) {
qCritical() << "cannot open config manifest at" << manifestPath; qCritical() << "cannot open config manifest at" << manifestPath;
return -1; exit(-1); // NOLINT
} }
} }
@ -307,32 +314,32 @@ int qs_main(int argc, char** argv) {
auto basePathInfo = QFileInfo(basePath); auto basePathInfo = QFileInfo(basePath);
if (!basePathInfo.exists()) { if (!basePathInfo.exists()) {
qCritical() << "base path does not exist:" << basePath; qCritical() << "base path does not exist:" << basePath;
return -1; exit(-1); // NOLINT
} else if (!QFileInfo(basePathInfo.canonicalFilePath()).isDir()) { } else if (!QFileInfo(basePathInfo.canonicalFilePath()).isDir()) {
qCritical() << "base path is not a directory" << basePath; qCritical() << "base path is not a directory" << basePath;
return -1; exit(-1); // NOLINT
} }
auto dir = QDir(basePath); auto dir = QDir(basePath);
for (auto& entry: dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { for (auto& entry: dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) {
if (entry == configName) { if (entry == configName) {
configFilePath = dir.filePath(entry); configFilePath = dir.filePath(entry);
goto haspath; // NOLINT goto foundp;
} }
} }
qCritical() << "no directory named " << configName << "found in base path" << basePath; qCritical() << "no directory named " << configName << "found in base path" << basePath;
return -1; exit(-1); // NOLINT
} }
haspath:;
} else { } else {
configFilePath = basePath; configFilePath = basePath;
} }
foundp:;
auto configFile = QFileInfo(configFilePath); auto configFile = QFileInfo(configFilePath);
if (!configFile.exists()) { if (!configFile.exists()) {
qCritical() << "config path does not exist:" << configFilePath; qCritical() << "config path does not exist:" << configFilePath;
return -1; exit(-1); // NOLINT
} }
if (configFile.isDir()) { if (configFile.isDir()) {
@ -342,25 +349,124 @@ int qs_main(int argc, char** argv) {
configFile = QFileInfo(configFilePath); configFile = QFileInfo(configFilePath);
if (!configFile.exists()) { if (!configFile.exists()) {
qCritical() << "no shell.qml found in config path:" << configFilePath; qCritical() << "no shell.qml found in config path:" << configFilePath;
return -1; exit(-1); // NOLINT
} else if (configFile.isDir()) { } else if (configFile.isDir()) {
qCritical() << "shell.qml is a directory:" << configFilePath; qCritical() << "shell.qml is a directory:" << configFilePath;
return -1; exit(-1); // NOLINT
} }
configFilePath = QFileInfo(configFilePath).canonicalFilePath(); configFilePath = QFileInfo(configFilePath).canonicalFilePath();
configFile = QFileInfo(configFilePath); configFile = QFileInfo(configFilePath);
if (!configFile.exists()) { if (!configFile.exists()) {
qCritical() << "config file does not exist:" << configFilePath; qCritical() << "config file does not exist:" << configFilePath;
return -1; exit(-1); // NOLINT
} else if (configFile.isDir()) { } else if (configFile.isDir()) {
qCritical() << "config file is a directory:" << configFilePath; qCritical() << "config file is a directory:" << configFilePath;
return -1; exit(-1); // NOLINT
} }
#undef CHECK #undef CHECK
#undef OPTSTR #undef OPTSTR
return configFilePath;
}
int qs_main(int argc, char** argv) {
#if CRASH_REPORTER
qsCheckCrash(argc, argv);
auto crashHandler = qs::crash::CrashHandler();
#endif
auto qArgC = 1;
auto* qArgV = argv;
QString configFilePath;
QString initialWorkdir;
QString shellId;
int debugPort = -1;
bool waitForDebug = false;
bool printInfo = false;
bool noColor = !qEnvironmentVariableIsEmpty("NO_COLOR");
bool sparseLogsOnly = false;
auto useQApplication = false;
auto nativeTextRendering = false;
auto desktopSettingsAware = true;
QHash<QString, QString> envOverrides;
{
const auto qApplication = QCoreApplication(qArgC, qArgV);
#if CRASH_REPORTER
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
if (!lastInfoFdStr.isEmpty()) {
auto lastInfoFd = lastInfoFdStr.toInt();
QFile file;
file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle);
file.seek(0);
auto ds = QDataStream(&file);
InstanceInfo info;
ds >> info;
configFilePath = info.configPath;
initialWorkdir = info.initialWorkdir;
noColor = info.noColor;
sparseLogsOnly = info.sparseLogsOnly;
LogManager::init(!noColor, sparseLogsOnly);
qCritical().nospace() << "Quickshell has crashed under pid "
<< qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt()
<< " (Coredumps will be available under that pid.)";
qCritical() << "Further crash information is stored under"
<< QsPaths::crashDir(info.shellId, info.launchTime).path();
if (info.launchTime.msecsTo(QDateTime::currentDateTime()) < 10000) {
qCritical() << "Quickshell crashed within 10 seconds of launching. Not restarting to avoid "
"a crash loop.";
return 0;
} else {
qCritical() << "Quickshell has been restarted.";
}
crashHandler.init();
} else
#endif
{
auto command = CommandInfo {
.initialWorkdir = initialWorkdir,
.debugPort = debugPort,
.waitForDebug = waitForDebug,
.printInfo = printInfo,
.noColor = noColor,
.sparseLogsOnly = sparseLogsOnly,
};
processCommand(argc, argv, command);
// Start log manager - has to happen with an active event loop or offthread can't be started.
LogManager::init(!noColor, sparseLogsOnly);
#if CRASH_REPORTER
// Started after log manager for pretty debug logs. Unlikely anything will crash before this point, but
// this can be moved if it happens.
crashHandler.init();
#endif
configFilePath = commandConfigPath(
command.configPath,
command.manifestPath,
command.configName,
command.printInfo
);
}
shellId = QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex(); shellId = QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex();
qInfo() << "Config file path:" << configFilePath; qInfo() << "Config file path:" << configFilePath;
@ -408,12 +514,22 @@ int qs_main(int argc, char** argv) {
file.close(); file.close();
} }
}
qInfo() << "Shell ID:" << shellId; qInfo() << "Shell ID:" << shellId;
if (printInfo) return 0; if (printInfo) return 0;
#if CRASH_REPORTER
crashHandler.setInstanceInfo(InstanceInfo {
.configPath = configFilePath,
.shellId = shellId,
.initialWorkdir = initialWorkdir,
.launchTime = qs::Common::LAUNCH_TIME,
.noColor = noColor,
.sparseLogsOnly = sparseLogsOnly,
});
#endif
for (auto [var, val]: envOverrides.asKeyValueRange()) { for (auto [var, val]: envOverrides.asKeyValueRange()) {
qputenv(var.toUtf8(), val.toUtf8()); qputenv(var.toUtf8(), val.toUtf8());
} }
@ -484,8 +600,8 @@ int qs_main(int argc, char** argv) {
QQmlDebuggingEnabler::startTcpDebugServer(debugPort, wait); QQmlDebuggingEnabler::startTcpDebugServer(debugPort, wait);
} }
if (!workingDirectory.isEmpty()) { if (!initialWorkdir.isEmpty()) {
QDir::setCurrent(workingDirectory); QDir::setCurrent(initialWorkdir);
} }
QuickshellPlugin::initPlugins(); QuickshellPlugin::initPlugins();

View file

@ -9,6 +9,8 @@
#include <qtenvironmentvariables.h> #include <qtenvironmentvariables.h>
#include <unistd.h> #include <unistd.h>
#include "common.hpp"
Q_LOGGING_CATEGORY(logPaths, "quickshell.paths", QtWarningMsg); Q_LOGGING_CATEGORY(logPaths, "quickshell.paths", QtWarningMsg);
QsPaths* QsPaths::instance() { QsPaths* QsPaths::instance() {
@ -18,6 +20,15 @@ QsPaths* QsPaths::instance() {
void QsPaths::init(QString shellId) { QsPaths::instance()->shellId = std::move(shellId); } void QsPaths::init(QString shellId) { QsPaths::instance()->shellId = std::move(shellId); }
QDir QsPaths::crashDir(const QString& shellId, const QDateTime& launchTime) {
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
dir = QDir(dir.filePath("crashes"));
dir = QDir(dir.filePath(shellId));
dir = QDir(dir.filePath(QString("run-%1").arg(launchTime.toMSecsSinceEpoch())));
return dir;
}
QDir* QsPaths::cacheDir() { QDir* QsPaths::cacheDir() {
if (this->cacheState == DirState::Unknown) { if (this->cacheState == DirState::Unknown) {
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
@ -73,7 +84,7 @@ QDir* QsPaths::instanceRunDir() {
this->instanceRunState = DirState::Failed; this->instanceRunState = DirState::Failed;
} else { } else {
this->mInstanceRunDir = this->mInstanceRunDir =
runtimeDir->filePath(QString("run-%1").arg(QDateTime::currentMSecsSinceEpoch())); runtimeDir->filePath(QString("run-%1").arg(qs::Common::LAUNCH_TIME.toMSecsSinceEpoch()));
qCDebug(logPaths) << "Initialized instance runtime path:" << this->mInstanceRunDir.path(); qCDebug(logPaths) << "Initialized instance runtime path:" << this->mInstanceRunDir.path();

View file

@ -1,10 +1,12 @@
#pragma once #pragma once
#include <qdatetime.h>
#include <qdir.h> #include <qdir.h>
class QsPaths { class QsPaths {
public: public:
static QsPaths* instance(); static QsPaths* instance();
static void init(QString shellId); static void init(QString shellId);
static QDir crashDir(const QString& shellId, const QDateTime& launchTime);
QDir* cacheDir(); QDir* cacheDir();
QDir* runDir(); QDir* runDir();

16
src/crash/CMakeLists.txt Normal file
View file

@ -0,0 +1,16 @@
qt_add_library(quickshell-crash STATIC
main.cpp
interface.cpp
handler.cpp
)
qs_pch(quickshell-crash)
find_package(PkgConfig REQUIRED)
pkg_check_modules(breakpad REQUIRED IMPORTED_TARGET breakpad)
# only need client?? take only includes from pkg config todo
target_link_libraries(quickshell-crash PRIVATE PkgConfig::breakpad -lbreakpad_client)
target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt6::Widgets)
target_link_libraries(quickshell-core PRIVATE quickshell-crash)

180
src/crash/handler.cpp Normal file
View file

@ -0,0 +1,180 @@
#include "handler.hpp"
#include <array>
#include <cstdio>
#include <cstring>
#include <bits/types/sigset_t.h>
#include <breakpad/client/linux/handler/exception_handler.h>
#include <breakpad/client/linux/handler/minidump_descriptor.h>
#include <breakpad/common/linux/linux_libc_support.h>
#include <qdatastream.h>
#include <qfile.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <sys/mman.h>
#include <unistd.h>
#include "../core/crashinfo.hpp"
extern char** environ; // NOLINT
using namespace google_breakpad;
namespace qs::crash {
Q_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
struct CrashHandlerPrivate {
ExceptionHandler* exceptionHandler = nullptr;
int minidumpFd = -1;
int infoFd = -1;
static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded);
};
CrashHandler::CrashHandler(): d(new CrashHandlerPrivate()) {}
void CrashHandler::init() {
// MinidumpDescriptor has no move constructor and the copy constructor breaks fds.
auto createHandler = [this](const MinidumpDescriptor& desc) {
this->d->exceptionHandler = new ExceptionHandler(
desc,
nullptr,
&CrashHandlerPrivate::minidumpCallback,
this->d,
true,
-1
);
};
qCDebug(logCrashHandler) << "Starting crash handler...";
this->d->minidumpFd = memfd_create("quickshell:minidump", MFD_CLOEXEC);
if (this->d->minidumpFd == -1) {
qCCritical(logCrashHandler
) << "Failed to allocate minidump memfd, minidumps will be saved in the working directory.";
createHandler(MinidumpDescriptor("."));
} else {
qCDebug(logCrashHandler) << "Created memfd" << this->d->minidumpFd
<< "for holding possible minidumps.";
createHandler(MinidumpDescriptor(this->d->minidumpFd));
}
qCInfo(logCrashHandler) << "Crash handler initialized.";
}
void CrashHandler::setInstanceInfo(const InstanceInfo& info) {
this->d->infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
if (this->d->infoFd == -1) {
qCCritical(logCrashHandler
) << "Failed to allocate instance info memfd, crash recovery will not work.";
return;
}
QFile file;
file.open(this->d->infoFd, QFile::ReadWrite);
QDataStream ds(&file);
ds << info;
file.flush();
qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd;
}
CrashHandler::~CrashHandler() {
delete this->d->exceptionHandler;
delete this->d;
}
bool CrashHandlerPrivate::minidumpCallback(
const MinidumpDescriptor& /*descriptor*/,
void* context,
bool /*success*/
) {
// A fork that just dies to ensure the coredump is caught by the system.
auto coredumpPid = fork();
if (coredumpPid == 0) {
return false;
}
auto* self = static_cast<CrashHandlerPrivate*>(context);
auto exe = std::array<char, 4096>();
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
perror("Failed to find crash reporter executable.\n");
_exit(-1);
}
auto arg = std::array<char*, 2> {exe.data(), nullptr};
auto env = std::array<char*, 4096>();
auto envi = 0;
auto infoFd = dup(self->infoFd);
auto infoFdStr = std::array<char, 38>();
memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30);
if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10);
env[envi++] = infoFdStr.data();
auto corePidStr = std::array<char, 39>();
memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31);
if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10);
env[envi++] = corePidStr.data();
auto populateEnv = [&]() {
auto senvi = 0;
while (envi < 4095) {
env[envi++] = environ[senvi++]; // NOLINT
}
env[envi] = nullptr;
};
sigset_t sigset;
sigemptyset(&sigset); // NOLINT (include)
sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT
auto pid = fork();
if (pid == -1) {
perror("Failed to fork and launch crash reporter.\n");
return false;
} else if (pid == 0) {
// dup to remove CLOEXEC
// if already -1 will return -1
auto dumpFd = dup(self->minidumpFd);
auto logFd = dup(CrashInfo::INSTANCE.logFd);
// allow up to 10 digits, which should never happen
auto dumpFdStr = std::array<char, 38>();
auto logFdStr = std::array<char, 37>();
memcpy(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD=-1" /*\0*/, 30);
memcpy(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD=-1" /*\0*/, 29);
if (dumpFd != -1) my_uitos(&dumpFdStr[27], dumpFd, 10);
if (logFd != -1) my_uitos(&logFdStr[26], logFd, 10);
env[envi++] = dumpFdStr.data();
env[envi++] = logFdStr.data();
populateEnv();
execve(exe.data(), arg.data(), env.data());
perror("Failed to launch crash reporter.\n");
_exit(-1);
} else {
populateEnv();
execve(exe.data(), arg.data(), env.data());
perror("Failed to relaunch quickshell.\n");
_exit(-1);
}
return false; // should make sure it hits the system coredump handler
}
} // namespace qs::crash

23
src/crash/handler.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <qtclasshelpermacros.h>
#include "../core/crashinfo.hpp"
namespace qs::crash {
struct CrashHandlerPrivate;
class CrashHandler {
public:
explicit CrashHandler();
~CrashHandler();
Q_DISABLE_COPY_MOVE(CrashHandler);
void init();
void setInstanceInfo(const InstanceInfo& info);
private:
CrashHandlerPrivate* d;
};
} // namespace qs::crash

97
src/crash/interface.cpp Normal file
View file

@ -0,0 +1,97 @@
#include "interface.hpp"
#include <utility>
#include <qapplication.h>
#include <qboxlayout.h>
#include <qdesktopservices.h>
#include <qfont.h>
#include <qfontinfo.h>
#include <qlabel.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qpushbutton.h>
#include <qwidget.h>
#include "build.hpp"
class ReportLabel: public QWidget {
public:
ReportLabel(const QString& label, const QString& content, QWidget* parent): QWidget(parent) {
auto* layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(new QLabel(label, this));
auto* cl = new QLabel(content, this);
cl->setTextInteractionFlags(Qt::TextSelectableByMouse);
layout->addWidget(cl);
layout->addStretch();
}
};
CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
: reportFolder(std::move(reportFolder)) {
this->setWindowFlags(Qt::Window);
auto textHeight = QFontInfo(QFont()).pixelSize();
auto* mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(new QLabel(
"<u>Quickshell has crashed. Please submit a bug report to help us fix it.</u>",
this
));
mainLayout->addSpacing(textHeight);
mainLayout->addWidget(new QLabel("General information", this));
mainLayout->addWidget(new ReportLabel("Git Revision:", GIT_REVISION, this));
mainLayout->addWidget(new ReportLabel("Crashed process ID:", QString::number(pid), this));
mainLayout->addWidget(new ReportLabel("Crash report folder:", this->reportFolder, this));
mainLayout->addSpacing(textHeight);
mainLayout->addWidget(new QLabel("Please open a bug report for this issue via github or email."));
mainLayout->addWidget(new ReportLabel(
"Github:",
"https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml",
this
));
mainLayout->addWidget(new ReportLabel("Email:", "quickshell-bugs@outfoxxed.me", this));
auto* buttons = new QWidget(this);
buttons->setMinimumWidth(900);
auto* buttonLayout = new QHBoxLayout(buttons);
buttonLayout->setContentsMargins(0, 0, 0, 0);
auto* reportButton = new QPushButton("Open report page", buttons);
reportButton->setDefault(true);
QObject::connect(reportButton, &QPushButton::clicked, this, &CrashReporterGui::openReportUrl);
buttonLayout->addWidget(reportButton);
auto* openFolderButton = new QPushButton("Open crash folder", buttons);
QObject::connect(openFolderButton, &QPushButton::clicked, this, &CrashReporterGui::openFolder);
buttonLayout->addWidget(openFolderButton);
auto* cancelButton = new QPushButton("Exit", buttons);
QObject::connect(cancelButton, &QPushButton::clicked, this, &CrashReporterGui::cancel);
buttonLayout->addWidget(cancelButton);
mainLayout->addWidget(buttons);
mainLayout->addStretch();
this->setFixedSize(this->sizeHint());
}
void CrashReporterGui::openFolder() {
QDesktopServices::openUrl(QUrl::fromLocalFile(this->reportFolder));
}
void CrashReporterGui::openReportUrl() {
QDesktopServices::openUrl(
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml")
);
}
void CrashReporterGui::cancel() { QApplication::quit(); }

17
src/crash/interface.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <qwidget.h>
class CrashReporterGui: public QWidget {
public:
CrashReporterGui(QString reportFolder, int pid);
private slots:
void openFolder();
static void openReportUrl();
static void cancel();
private:
QString reportFolder;
};

165
src/crash/main.cpp Normal file
View file

@ -0,0 +1,165 @@
#include "main.hpp"
#include <cerrno>
#include <cstdlib>
#include <qapplication.h>
#include <qconfig.h>
#include <qdatastream.h>
#include <qdatetime.h>
#include <qdir.h>
#include <qfile.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qtenvironmentvariables.h>
#include <qtextstream.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include "../core/crashinfo.hpp"
#include "../core/logging.hpp"
#include "../core/paths.hpp"
#include "build.hpp"
#include "interface.hpp"
Q_LOGGING_CATEGORY(logCrashReporter, "quickshell.crashreporter", QtWarningMsg);
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instanceInfo);
void qsCheckCrash(int argc, char** argv) {
auto fd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD");
if (fd.isEmpty()) return;
auto app = QApplication(argc, argv);
InstanceInfo instance;
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
{
auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt();
QFile file;
file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle);
file.seek(0);
auto ds = QDataStream(&file);
ds >> instance;
}
LogManager::init(!instance.noColor, false);
auto crashDir = QsPaths::crashDir(instance.shellId, instance.launchTime);
qCInfo(logCrashReporter) << "Starting crash reporter...";
recordCrashInfo(crashDir, instance);
auto gui = CrashReporterGui(crashDir.path(), crashProc);
gui.show();
exit(QApplication::exec()); // NOLINT
}
int tryDup(int fd, const QString& path) {
QFile sourceFile;
if (!sourceFile.open(fd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qCCritical(logCrashReporter) << "Failed to open source fd for duplication.";
return 1;
}
auto destFile = QFile(path);
if (!destFile.open(QFile::WriteOnly)) {
qCCritical(logCrashReporter) << "Failed to open dest file for duplication.";
return 2;
}
auto size = sourceFile.size();
off_t offset = 0;
ssize_t count = 0;
sourceFile.seek(0);
while (count != size) {
auto r = sendfile(destFile.handle(), sourceFile.handle(), &offset, sourceFile.size());
if (r == -1) {
qCCritical(logCrashReporter).nospace()
<< "Failed to duplicate fd " << fd << " with error code " << errno
<< ". Error: " << qt_error_string();
return 3;
} else {
count += r;
}
}
return 0;
}
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path();
if (!crashDir.mkpath(".")) {
qCCritical(logCrashReporter) << "Failed to create folder" << crashDir
<< "to save crash information.";
return;
}
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt();
auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt();
qCDebug(logCrashReporter) << "Saving minidump from fd" << dumpFd;
auto dumpDupStatus = tryDup(dumpFd, crashDir.filePath("minidump.dmp"));
if (dumpDupStatus != 0) {
qCCritical(logCrashReporter) << "Failed to write minidump:" << dumpDupStatus;
}
qCDebug(logCrashReporter) << "Saving log from fd" << logFd;
auto logDupStatus = tryDup(logFd, crashDir.filePath("log.qslog"));
if (logDupStatus != 0) {
qCCritical(logCrashReporter) << "Failed to save log:" << logDupStatus;
}
{
auto extraInfoFile = QFile(crashDir.filePath("info.txt"));
if (!extraInfoFile.open(QFile::WriteOnly)) {
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
} else {
auto stream = QTextStream(&extraInfoFile);
stream << "===== Quickshell Crash =====\n";
stream << "Git Revision: " << GIT_REVISION << '\n';
stream << "Crashed process ID: " << crashProc << '\n';
stream << "Run ID: " << QString("run-%1").arg(instance.launchTime.toMSecsSinceEpoch())
<< '\n';
stream << "\n===== Shell Information =====\n";
stream << "Shell ID: " << instance.shellId << '\n';
stream << "Config Path: " << instance.configPath << '\n';
stream << "\n===== Report Integrity =====\n";
stream << "Minidump save status: " << dumpDupStatus << '\n';
stream << "Log save status: " << logDupStatus << '\n';
stream << "\n===== System Information =====\n";
stream << "Qt Version: " << QT_VERSION_STR << "\n\n";
stream << "/etc/os-release:";
auto osReleaseFile = QFile("/etc/os-release");
if (osReleaseFile.open(QFile::ReadOnly)) {
stream << '\n' << osReleaseFile.readAll() << '\n';
osReleaseFile.close();
} else {
stream << "FAILED TO OPEN\n";
}
stream << "/etc/lsb-release:";
auto lsbReleaseFile = QFile("/etc/lsb-release");
if (lsbReleaseFile.open(QFile::ReadOnly)) {
stream << '\n' << lsbReleaseFile.readAll() << '\n';
lsbReleaseFile.close();
} else {
stream << "FAILED TO OPEN\n";
}
extraInfoFile.close();
}
}
qCDebug(logCrashReporter) << "Recorded crash information.";
}

3
src/crash/main.hpp Normal file
View file

@ -0,0 +1,3 @@
#pragma once
void qsCheckCrash(int argc, char** argv);

View file

@ -11,6 +11,7 @@
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <unistd.h> #include <unistd.h>
#include "../../core/common.hpp"
#include "../../dbus/properties.hpp" #include "../../dbus/properties.hpp"
#include "dbus_watcher_interface.h" #include "dbus_watcher_interface.h"
#include "item.hpp" #include "item.hpp"
@ -31,7 +32,10 @@ StatusNotifierHost::StatusNotifierHost(QObject* parent): QObject(parent) {
return; return;
} }
this->hostId = QString("org.kde.StatusNotifierHost-") + QString::number(getpid()); this->hostId = QString("org.kde.StatusNotifierHost-%1-%2")
.arg(QString::number(getpid()))
.arg(QString::number(qs::Common::LAUNCH_TIME.toMSecsSinceEpoch()));
auto success = bus.registerService(this->hostId); auto success = bus.registerService(this->hostId);
if (!success) { if (!success) {
@ -98,7 +102,7 @@ void StatusNotifierHost::connectToWatcher() {
[this](QStringList value, QDBusError error) { // NOLINT [this](QStringList value, QDBusError error) { // NOLINT
if (error.isValid()) { if (error.isValid()) {
qCWarning(logStatusNotifierHost).noquote() qCWarning(logStatusNotifierHost).noquote()
<< "Error reading \"RegisteredStatusNotifierITems\" property of watcher" << "Error reading \"RegisteredStatusNotifierItems\" property of watcher"
<< this->watcher->service(); << this->watcher->service();
qCWarning(logStatusNotifierHost) << error; qCWarning(logStatusNotifierHost) << error;