From f0d0216b3d293f2813112cd74d74d4e7de57931e Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Wed, 8 Apr 2026 00:39:52 -0700 Subject: [PATCH 1/4] core: add DropExpensiveFonts pragma disabling woff and woff2 fonts --- changelog/next.md | 2 ++ src/launch/launch.cpp | 45 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/changelog/next.md b/changelog/next.md index b430a27..95de5dc 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -43,6 +43,8 @@ set shell id. - Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling. - Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link. - Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID. +- Added `DropExpensiveFonts` pragma which avoids loading fonts which may cause lag and excessive memory usage if many variants are used. +- Unrecognized pragmas are no longer a hard error for future backward compatibility. ## Bug Fixes diff --git a/src/launch/launch.cpp b/src/launch/launch.cpp index 0f5b090..de85956 100644 --- a/src/launch/launch.cpp +++ b/src/launch/launch.cpp @@ -77,6 +77,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio QString iconTheme = qEnvironmentVariable("QS_ICON_THEME"); QHash envOverrides; QString appId = qEnvironmentVariable("QS_APP_ID"); + bool dropExpensiveFonts = false; QString dataDir; QString stateDir; QString cacheDir; @@ -92,6 +93,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true; else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false; else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true; + else if (pragma == "DropExpensiveFonts") pragmas.dropExpensiveFonts = true; else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10); else if (pragma.startsWith("Env ")) { auto envPragma = pragma.sliced(4); @@ -116,8 +118,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio } else if (pragma.startsWith("CacheDir ")) { pragmas.cacheDir = pragma.sliced(9).trimmed(); } else { - qCritical() << "Unrecognized pragma" << pragma; - return -1; + qWarning() << "Unrecognized pragma" << pragma; } } else if (line.startsWith("import")) break; } @@ -168,6 +169,46 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio Common::INITIAL_ENVIRONMENT = QProcessEnvironment::systemEnvironment(); + if (pragmas.dropExpensiveFonts) { + if (auto* runDir = QsPaths::instance()->instanceRunDir()) { + auto baseConfigPath = qEnvironmentVariable("FONTCONFIG_FILE"); + if (baseConfigPath.isEmpty()) baseConfigPath = "/etc/fonts/fonts.conf"; + + auto filterPath = runDir->filePath("fonts-override.conf"); + auto filterFile = QFile(filterPath); + if (filterFile.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { + auto filterTemplate = QStringLiteral(R"( + + + %1 + + + + + woff + + + + + woff2 + + + + + +)"); + + QTextStream(&filterFile) << filterTemplate.arg(baseConfigPath); + filterFile.close(); + qputenv("FONTCONFIG_FILE", filterPath.toUtf8()); + } else { + qCritical() << "Could not write fontconfig filter to" << filterPath; + } + } else { + qCritical() << "Could not create fontconfig filter: instance run directory unavailable"; + } + } + if (!pragmas.useSystemStyle) { qunsetenv("QT_STYLE_OVERRIDE"); qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion"); From 7208f68bb7f4bf7e476b828decde1321ae544f5d Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Wed, 8 Apr 2026 01:35:15 -0700 Subject: [PATCH 2/4] core: add QS_DROP_EXPENSIVE_FONTS env var --- changelog/next.md | 2 +- src/launch/launch.cpp | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/changelog/next.md b/changelog/next.md index 95de5dc..d6dc60e 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -43,7 +43,7 @@ set shell id. - Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling. - Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link. - Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID. -- Added `DropExpensiveFonts` pragma which avoids loading fonts which may cause lag and excessive memory usage if many variants are used. +- Added `DropExpensiveFonts` pragma and `QS_DROP_EXPENSIVE_FONTS` environment variable which avoids loading fonts which may cause lag and excessive memory usage if many variants are used. - Unrecognized pragmas are no longer a hard error for future backward compatibility. ## Bug Fixes diff --git a/src/launch/launch.cpp b/src/launch/launch.cpp index de85956..dcdefa7 100644 --- a/src/launch/launch.cpp +++ b/src/launch/launch.cpp @@ -169,6 +169,17 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio Common::INITIAL_ENVIRONMENT = QProcessEnvironment::systemEnvironment(); + if (!pragmas.useSystemStyle) { + qunsetenv("QT_STYLE_OVERRIDE"); + qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion"); + } + + for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) { + qputenv(var.toUtf8(), val.toUtf8()); + } + + pragmas.dropExpensiveFonts |= qEnvironmentVariableIntValue("QS_DROP_EXPENSIVE_FONTS") == 1; + if (pragmas.dropExpensiveFonts) { if (auto* runDir = QsPaths::instance()->instanceRunDir()) { auto baseConfigPath = qEnvironmentVariable("FONTCONFIG_FILE"); @@ -209,15 +220,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio } } - if (!pragmas.useSystemStyle) { - qunsetenv("QT_STYLE_OVERRIDE"); - qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion"); - } - - for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) { - qputenv(var.toUtf8(), val.toUtf8()); - } - // The qml engine currently refuses to cache non file (qsintercept) paths. // if (auto* cacheDir = QsPaths::instance()->cacheDir()) { From 7f7ab6bc8aac6148ef0aa25a7435ee11a78dba5f Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Thu, 9 Apr 2026 00:10:49 -0700 Subject: [PATCH 3/4] launch: use dup2 to reset daemon stdio over close+open --- changelog/next.md | 1 + src/launch/main.cpp | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/changelog/next.md b/changelog/next.md index d6dc60e..761161d 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -73,6 +73,7 @@ set shell id. - Fixed JsonAdapter sending unnecessary property changes for primitive values. - Fixed JsonAdapter serialization for lists. - Fixed pipewire crashes after hotplugging devices and changing default outputs. +- Fixed launches failing for `--daemonize` on some systems. ## Packaging Changes diff --git a/src/launch/main.cpp b/src/launch/main.cpp index a324e09..efd6628 100644 --- a/src/launch/main.cpp +++ b/src/launch/main.cpp @@ -84,21 +84,29 @@ void exitDaemon(int code) { close(DAEMON_PIPE); - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - - if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT - qFatal() << "Failed to open /dev/null on stdin"; + auto fd = open("/dev/null", O_RDWR); + if (fd == -1) { + qCritical().nospace() << "Failed to open /dev/null for daemon stdio" << errno << ": " + << qt_error_string(); + return; } - if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT - qFatal() << "Failed to open /dev/null on stdout"; + if (dup2(fd, STDIN_FILENO) != STDIN_FILENO) { // NOLINT + qCritical().nospace() << "Failed to set daemon stdin to /dev/null" << errno << ": " + << qt_error_string(); } - if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT - qFatal() << "Failed to open /dev/null on stderr"; + if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) { // NOLINT + qCritical().nospace() << "Failed to set daemon stdout to /dev/null" << errno << ": " + << qt_error_string(); } + + if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) { // NOLINT + qCritical().nospace() << "Failed to set daemon stderr to /dev/null" << errno << ": " + << qt_error_string(); + } + + close(fd); } int main(int argc, char** argv) { From d4c92973b53d9fa34cc110d3b974eb6bde5b3027 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Thu, 9 Apr 2026 00:34:57 -0700 Subject: [PATCH 4/4] i3/ipc: ensure monitor/workspace pointers are nulled on destroy --- src/x11/i3/ipc/controller.cpp | 8 +------- src/x11/i3/ipc/monitor.cpp | 28 ++++++++++++++++++++++------ src/x11/i3/ipc/monitor.hpp | 7 +++++-- src/x11/i3/ipc/workspace.cpp | 26 +++++++++++++++++++++++--- src/x11/i3/ipc/workspace.hpp | 7 ++++++- 5 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/x11/i3/ipc/controller.cpp b/src/x11/i3/ipc/controller.cpp index 1a08c63..a83afd4 100644 --- a/src/x11/i3/ipc/controller.cpp +++ b/src/x11/i3/ipc/controller.cpp @@ -276,7 +276,7 @@ void I3IpcController::handleWorkspaceEvent(I3IpcEvent* event) { if (newWorkspace->bindableMonitor().value()) { auto* monitor = newWorkspace->bindableMonitor().value(); - monitor->setFocusedWorkspace(newWorkspace); + monitor->setActiveWorkspace(newWorkspace); this->bFocusedMonitor = monitor; } } else if (change == "empty") { @@ -286,13 +286,7 @@ void I3IpcController::handleWorkspaceEvent(I3IpcEvent* event) { if (oldWorkspace != nullptr) { qCInfo(logI3Ipc) << "Deleting" << oldWorkspace->bindableId().value() << name; - - if (this->bFocusedWorkspace == oldWorkspace) { - this->bFocusedMonitor->setFocusedWorkspace(nullptr); - } - this->workspaces()->removeObject(oldWorkspace); - delete oldWorkspace; } else { qCInfo(logI3Ipc) << "Workspace" << name << "has already been deleted"; diff --git a/src/x11/i3/ipc/monitor.cpp b/src/x11/i3/ipc/monitor.cpp index fb0ec86..7afb68e 100644 --- a/src/x11/i3/ipc/monitor.cpp +++ b/src/x11/i3/ipc/monitor.cpp @@ -40,21 +40,37 @@ void I3Monitor::updateFromObject(const QVariantMap& obj) { this->bHeight = rect.value("height").value(); this->bScale = obj.value("scale").value(); - if (!this->bActiveWorkspace - || activeWorkspaceName != this->bActiveWorkspace->bindableName().value()) - { + auto* activeWorkspace = this->bActiveWorkspace.value(); + if (!activeWorkspace || activeWorkspaceName != activeWorkspace->bindableName().value()) { if (activeWorkspaceName.isEmpty()) { - this->bActiveWorkspace = nullptr; + activeWorkspace = nullptr; } else { - this->bActiveWorkspace = this->ipc->findWorkspaceByName(activeWorkspaceName); + activeWorkspace = this->ipc->findWorkspaceByName(activeWorkspaceName); } }; + this->setActiveWorkspace(activeWorkspace); + Qt::endPropertyUpdateGroup(); } void I3Monitor::updateInitial(const QString& name) { this->bName = name; } -void I3Monitor::setFocusedWorkspace(I3Workspace* workspace) { this->bActiveWorkspace = workspace; }; +void I3Monitor::setActiveWorkspace(I3Workspace* workspace) { + auto* oldWorkspace = this->bActiveWorkspace.value(); + if (oldWorkspace == workspace) return; + + if (oldWorkspace) { + QObject::disconnect(oldWorkspace, nullptr, this, nullptr); + } + + if (workspace) { + QObject::connect(workspace, &QObject::destroyed, this, &I3Monitor::onActiveWorkspaceDestroyed); + } + + this->bActiveWorkspace = workspace; +} + +void I3Monitor::onActiveWorkspaceDestroyed() { this->bActiveWorkspace = nullptr; } } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/monitor.hpp b/src/x11/i3/ipc/monitor.hpp index cd348b1..c328d8b 100644 --- a/src/x11/i3/ipc/monitor.hpp +++ b/src/x11/i3/ipc/monitor.hpp @@ -55,7 +55,7 @@ public: [[nodiscard]] QBindable bindableScale() { return &this->bScale; } [[nodiscard]] QBindable bindableFocused() { return &this->bFocused; } - [[nodiscard]] QBindable bindableActiveWorkspace() { + [[nodiscard]] QBindable bindableActiveWorkspace() const { return &this->bActiveWorkspace; } @@ -64,7 +64,7 @@ public: void updateFromObject(const QVariantMap& obj); void updateInitial(const QString& name); - void setFocusedWorkspace(I3Workspace* workspace); + void setActiveWorkspace(I3Workspace* workspace); signals: void idChanged(); @@ -79,6 +79,9 @@ signals: void lastIpcObjectChanged(); void focusedChanged(); +private slots: + void onActiveWorkspaceDestroyed(); + private: I3IpcController* ipc; diff --git a/src/x11/i3/ipc/workspace.cpp b/src/x11/i3/ipc/workspace.cpp index 03fadc2..530f0a2 100644 --- a/src/x11/i3/ipc/workspace.cpp +++ b/src/x11/i3/ipc/workspace.cpp @@ -43,14 +43,17 @@ void I3Workspace::updateFromObject(const QVariantMap& obj) { auto monitorName = obj.value("output").value(); - if (!this->bMonitor || monitorName != this->bMonitor->bindableName().value()) { + auto* monitor = this->bMonitor.value(); + if (!monitor || monitorName != monitor->bindableName().value()) { if (monitorName.isEmpty()) { - this->bMonitor = nullptr; + monitor = nullptr; } else { - this->bMonitor = this->ipc->findMonitorByName(monitorName, true); + monitor = this->ipc->findMonitorByName(monitorName, true); } } + this->setMonitor(monitor); + Qt::endPropertyUpdateGroup(); } @@ -58,4 +61,21 @@ void I3Workspace::activate() { this->ipc->dispatch(QString("workspace number %1").arg(this->bNumber.value())); } +void I3Workspace::setMonitor(I3Monitor* monitor) { + auto* oldMonitor = this->bMonitor.value(); + if (oldMonitor == monitor) return; + + if (oldMonitor) { + QObject::disconnect(oldMonitor, nullptr, this, nullptr); + } + + if (monitor) { + QObject::connect(monitor, &QObject::destroyed, this, &I3Workspace::onMonitorDestroyed); + } + + this->bMonitor = monitor; +} + +void I3Workspace::onMonitorDestroyed() { this->bMonitor = nullptr; } + } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/workspace.hpp b/src/x11/i3/ipc/workspace.hpp index f540545..c08e926 100644 --- a/src/x11/i3/ipc/workspace.hpp +++ b/src/x11/i3/ipc/workspace.hpp @@ -57,11 +57,13 @@ public: [[nodiscard]] QBindable bindableActive() { return &this->bActive; } [[nodiscard]] QBindable bindableFocused() { return &this->bFocused; } [[nodiscard]] QBindable bindableUrgent() { return &this->bUrgent; } - [[nodiscard]] QBindable bindableMonitor() { return &this->bMonitor; } + [[nodiscard]] QBindable bindableMonitor() const { return &this->bMonitor; } [[nodiscard]] QVariantMap lastIpcObject() const; void updateFromObject(const QVariantMap& obj); + void setMonitor(I3Monitor* monitor); + signals: void idChanged(); void nameChanged(); @@ -72,6 +74,9 @@ signals: void monitorChanged(); void lastIpcObjectChanged(); +private slots: + void onMonitorDestroyed(); + private: I3IpcController* ipc;