22 Commits
Author SHA1 Message Date
outfoxxed 4f508be500 version: 0.3.2 2026-10-08 01:42:54 -07:00
outfoxxed 11ca60be22 docs: add PR template and clarify AI usage in pr descriptions 2026-10-05 21:20:58 -07:00
Carson Powers 8fd0e25844 networking: prefer AP security 2026-10-05 11:03:58 -07:00
Cloud0310 5d5d49873f all: use MOC includes instead of opaque pointer defiinitions
Fixes linking issues and Qt 6.12 compatibility.
2026-10-03 04:43:26 -07:00
ayandexyz 41651d7dcd io/socket: discard sockets which have failed to connect
Fixes future connection attempts.
2026-09-25 06:40:25 -07:00
bbedward 4594680eb4 core/generation: destroy the engine after the root's children 2026-09-25 00:15:27 -07:00
faithleysath 25da30db02 pipewire: write route volumes without volume step 2026-09-25 00:11:17 -07:00
outfoxxed fae96f1a5b hyprland/ipc: support workspace addresses 2026-09-20 22:04:49 -07:00
outfoxxed c6a516096d core/scriptmodel: avoid comparing native types as equal by fields 2026-09-15 01:37:42 -07:00
ZANX3Y 86b4275879 services/notifications: fix action text updates 2026-09-13 18:59:02 -07:00
qaqland 9b4b206961 x11/i3: build the module without requiring X11
BUILD.md documents i3/Sway as having no x11 dependency, but the module was
only added from src/x11/CMakeLists.txt, so -DI3=ON -DX11=OFF silently lost
Quickshell.I3.
2026-09-13 18:59:02 -07:00
outfoxxed 92cb8909eb all: ensure deleteLater'd QObjects are disconnected 2026-09-13 18:59:00 -07:00
outfoxxed 2d3b3e9c70 core/generation: destroy generation extensions after js interruption 2026-08-29 02:11:43 -07:00
outfoxxed ec0be5b36f launch: initialize QuickshellTracked before almost anything else
This fixes consumers like WlSessionLock getting connections to
QGuiApplication::screenAdded before QuickshellTracked::updateScreens
can run if the user never references the Quickshell singleton from QML
first.
2026-08-28 19:27:13 -07:00
outfoxxed fd116897cf launch: create a QCoreApplication for non launch command paths
Things that need an event loop break without this.
2026-08-28 19:21:49 -07:00
outfoxxed 916a0dd90c launch: avoid creating multiple QApplications
Accessibility hooks created by QQuick module init are destroyed by the
QApplication destructor and not recreated by a new QApplication.
2026-08-28 00:48:19 -07:00
Carson Powers 0f9939ca4f networking: add NetworkManager service watcher 2026-08-25 23:36:53 -07:00
Carson Powers 052059fb7c networking: handle omitted mode setting in NM 2026-08-25 23:14:48 -07:00
outfoxxed 39e14d3627 service/tray: support /NO_DBUSMENU as a marker for absence of a menu
I couldn't find any documentation for this, but it was requested and
KDE also does it.
2026-08-25 23:03:30 -07:00
outfoxxed 9f59d0a05d wayland/lock: add warning on live session lock destruction 2026-08-25 17:22:27 -07:00
outfoxxed afb2c27cd6 wayland/lock: guard against reentrancy during surface creation 2026-08-25 02:14:21 -07:00
outfoxxed 0fed22a2c4 service/pam: fix free of wrong pointer in pam subprocess messaging 2026-08-22 16:08:41 -07:00
53 changed files with 483 additions and 264 deletions

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
<!--
Read https://github.com/quickshell-mirror/quickshell/blob/master/CONTRIBUTING.md before opening a PR.
Failing to do so may result in a ban from opening PRs.
-->
### Describe your changes
<!-- High level overview of what you are changing and why. AI may not be used to write this. -->
### Details and descisions
<!--
Put details of why you took a given approach to problems here if its not obvious.
Reviewing PRs is easier if we don't have to reverse engineer your reasoning.
Consider if things you put here should be code comments instead.
If an AI was heavily used to make the change and its context might be useful,
put it in a spoiler under your own reasoning.
-->
### Open concerns
<!-- Anything you're not sure of or want help with? -->
### Participation
<!--
With the advent of AI, we get a lot more drive-bys that don't care about participating
in a traditional review process, and just want to get a change in.
If you care about your code or learning how the project works to contribute in the future,
please say so. If not, we'll assume you don't particularly care.
-->
+1 -1
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(quickshell VERSION "0.3.1" LANGUAGES CXX C)
project(quickshell VERSION "0.3.2" LANGUAGES CXX C)
set(UNRELEASED_FEATURES)
+3
View File
@@ -9,6 +9,9 @@ To ensure nobody's time is wasted, please follow the rules below.
your change works, do not submit it to be merged. You must be able to explain your reasoning
for every change.
- Change descriptions MUST NOT be written by AI. Summarize what you changed in your own words.
If you think an AI summary of a chat session etc may provide useful context, include it in a spoiler.
- Changes MUST be submitted by a human who will be responsible for them. Changes submitted without
a human in the loop such as automated tooling and AI Agents are **strictly disallowed**. Accounts
responsible for such contribution attempts **will be banned**.
+17
View File
@@ -0,0 +1,17 @@
## Other Changes
- Added support for Qt 6.12
- Added support for Hyprland's new workspace address format.
## Bug Fixes
- Fixed main process crashes on pam subprocess misbehavior.
- Fixed crashes when attempting to create or modify session locks reentrantly.
- Fixed networking state breaking after restarting NetworkManager.
- Fixed accessibility information not being provided for Quickshell windows.
- Fixed `WlSessionLockSurface.screen` being null and never updating with a real screen on monitor plug.
- Fixed `NotificationAction.text` not accepting text updates.
- Fixed PipeWire route volume writes for devices without a reported volume step.
- Fixed a crash when quitting or reloading while a `Loader` was still loading asynchronously.
- Fixed future `Socket` connection attempts after a failed connection attempt.
- Fixed Open wifi networks showing as Unknown security.
+1 -1
View File
@@ -56,7 +56,7 @@
unwrapped = stdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.3.1";
version = "0.3.2";
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
dontWrapQtApps = true; # see wrappers
+4
View File
@@ -29,6 +29,10 @@ if (X11)
add_subdirectory(x11)
endif()
if (I3)
add_subdirectory(x11/i3)
endif()
add_subdirectory(services)
if (BLUETOOTH)
+32 -19
View File
@@ -11,6 +11,7 @@
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qqmlcontext.h>
#include <qqmlengine.h>
@@ -62,6 +63,10 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
EngineGeneration::EngineGeneration(): EngineGeneration(QDir(), QmlScanner()) {}
EngineGeneration::~EngineGeneration() {
for (auto* extension: this->extensions.values()) {
delete extension;
}
if (this->engine != nullptr) {
qFatal() << this << "destroyed without calling destroy()";
}
@@ -78,30 +83,37 @@ void EngineGeneration::destroy() {
this->watcher = nullptr;
}
for (auto* extension: this->extensions.values()) {
delete extension;
}
if (this->root != nullptr) {
// prevent further js execution during teardown of the root's children, gc and the engine.
QObject::connect(this->root, &QObject::destroyed, this, [this]() {
// prevent further js execution between garbage collection and engine destruction.
this->engine->setInterrupted(true);
g_generations.remove(this->engine);
// Garbage is not collected during engine destruction.
this->engine->collectGarbage();
delete this->engine;
this->engine = nullptr;
auto terminate = this->shouldTerminate;
auto code = this->exitCode;
delete this;
if (terminate) QCoreApplication::exit(code);
});
// QObject::destroyed is emitted before the root's children are destroyed.
// The engine must outlive them, as objects such as Loaders with in-flight
// incubations access it in their destructors.
QObject::connect(
this->root,
&QObject::destroyed,
this,
[this]() {
g_generations.remove(this->engine);
// Garbage is not collected during engine destruction.
this->engine->collectGarbage();
delete this->engine;
this->engine = nullptr;
auto terminate = this->shouldTerminate;
auto code = this->exitCode;
delete this;
if (terminate) QCoreApplication::exit(code);
},
Qt::QueuedConnection
);
this->root->deleteLater();
this->root = nullptr;
} else {
@@ -198,6 +210,7 @@ void EngineGeneration::setWatchingFiles(bool watching) {
}
} else {
if (this->watcher != nullptr) {
QObject::disconnect(this->watcher, nullptr, this, nullptr);
this->watcher->deleteLater();
this->watcher = nullptr;
}
+17 -19
View File
@@ -335,28 +335,33 @@ void LogManager::init(
qInstallMessageHandler(&LogManager::messageHandler);
qCDebug(logLogging) << "Creating offthread logger...";
auto* thread = new QThread();
instance->threadProxy.moveToThread(thread);
thread->start();
QMetaObject::invokeMethod(
&instance->threadProxy,
&LoggingThreadProxy::initInThread,
Qt::BlockingQueuedConnection
);
instance->threadLogging = new ThreadLogging(instance);
instance->threadLogging->init();
qCDebug(logLogging) << "Logger initialized.";
}
void LogManager::initThreadLogging() {
auto* instance = LogManager::instance();
qCDebug(logLogging) << "Moving logger to dedicated thread...";
auto* thread = new QThread();
instance->threadLogging->setParent(nullptr);
instance->threadLogging->moveToThread(thread);
thread->start();
qCDebug(logLogging) << "Logger thread initialized.";
}
void initLogCategoryLevel(const char* name, QtMsgType defaultLevel) {
LogManager::instance()->defaultLevels.insert(QLatin1StringView(name), defaultLevel);
}
void LogManager::initFs() {
QMetaObject::invokeMethod(
&LogManager::instance()->threadProxy,
"initFs",
LogManager::instance()->threadLogging,
&ThreadLogging::initFs,
Qt::BlockingQueuedConnection
);
}
@@ -369,13 +374,6 @@ CategoryFilter LogManager::getFilter(QLatin1StringView category) {
return this->allFilters.value(category);
}
void LoggingThreadProxy::initInThread() {
this->logging = new ThreadLogging(this);
this->logging->init();
}
void LoggingThreadProxy::initFs() { this->logging->initFs(); }
void ThreadLogging::init() {
auto logMfd = memfd_create("quickshell:logs", 0);
+2 -15
View File
@@ -55,20 +55,6 @@ size_t qHash(const LogMessage& message);
class ThreadLogging;
class LoggingThreadProxy: public QObject {
Q_OBJECT;
public:
explicit LoggingThreadProxy() = default;
public slots:
void initInThread();
void initFs();
private:
ThreadLogging* logging = nullptr;
};
namespace qt_logging_registry {
class QLoggingRule;
}
@@ -104,6 +90,7 @@ public:
const QString& prefix = ""
);
static void initThreadLogging();
static void initFs();
static LogManager* instance();
@@ -137,7 +124,7 @@ private:
QTextStream stdoutStream;
QMutex stdoutMutex;
LoggingThreadProxy threadProxy;
ThreadLogging* threadLogging = nullptr;
friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel);
};
+3 -1
View File
@@ -114,9 +114,11 @@ public:
explicit ThreadLogging(QObject* parent): QObject(parent) {}
void init();
void initFs();
void setupFileLogging();
public slots:
void initFs();
private slots:
void onMessage(const LogMessage& msg, bool showInSparse);
+14 -6
View File
@@ -88,6 +88,8 @@ QuickshellTracked::QuickshellTracked() {
}
}
// FIXME: Some callers can potentially pass a freed QScreen here which is why we don't init a new one.
// The QuickshellTracked::init() in launch.cpp makes sure our screenAdded handler runs first for now.
QuickshellScreenInfo* QuickshellTracked::screenInfo(QScreen* screen) const {
for (auto* info: this->screens) {
if (info->screen == screen) return info;
@@ -96,13 +98,19 @@ QuickshellScreenInfo* QuickshellTracked::screenInfo(QScreen* screen) const {
return nullptr;
}
namespace {
static QuickshellTracked* qsTrackedInstance = nullptr; // NOLINT
}
void QuickshellTracked::init() {
if (qsTrackedInstance) qFatal() << "Tried to reinitialize QuickshellTracked";
qsTrackedInstance = new QuickshellTracked();
QJSEngine::setObjectOwnership(qsTrackedInstance, QJSEngine::CppOwnership);
}
QuickshellTracked* QuickshellTracked::instance() {
static QuickshellTracked* instance = nullptr; // NOLINT
if (instance == nullptr) {
QJSEngine::setObjectOwnership(instance, QJSEngine::CppOwnership);
instance = new QuickshellTracked();
}
return instance;
if (!qsTrackedInstance) qFatal() << "Tried to get QuickshellTracked instance before init";
return qsTrackedInstance;
}
void QuickshellTracked::updateScreens() {
+1
View File
@@ -70,6 +70,7 @@ public:
QVector<QuickshellScreenInfo*> screens;
QuickshellScreenInfo* screenInfo(QScreen* screen) const;
static void init();
static QuickshellTracked* instance();
private slots:
+7 -1
View File
@@ -74,6 +74,7 @@ void QsMenuAnchor::onClosed() {
this->mOpen = false;
if (this->platformMenu) {
QObject::disconnect(this->platformMenu, nullptr, this, nullptr);
this->platformMenu->deleteLater();
this->platformMenu = nullptr;
}
@@ -101,7 +102,12 @@ void QsMenuAnchor::setMenu(QsMenuHandle* menu) {
if (menu == this->mMenu) return;
if (this->mMenu != nullptr) {
if (this->platformMenu != nullptr) this->platformMenu->deleteLater();
if (this->platformMenu != nullptr) {
QObject::disconnect(this->platformMenu, nullptr, this, nullptr);
this->platformMenu->deleteLater();
this->platformMenu = nullptr;
}
QObject::disconnect(this->mMenu, nullptr, this, nullptr);
}
+8 -1
View File
@@ -27,7 +27,14 @@ bool qjsValueStructuralEq(
const RecursionPair* activePair = nullptr
) {
if (a.strictlyEquals(b)) return true;
if (!a.isObject() || !b.isObject() || a.isArray() != b.isArray()) return false;
const auto jsValueType = QMetaType::fromType<QJSValue>();
const auto aIsNative = a.toVariant(QJSValue::RetainJSObjects).metaType() != jsValueType;
const auto bIsNative = b.toVariant(QJSValue::RetainJSObjects).metaType() != jsValueType;
if (aIsNative || bIsNative) return false;
if (!a.isObject() || !b.isObject() || a.isCallable() || b.isCallable()) return false;
if (a.isArray() != b.isArray()) return false;
for (const auto* pair = activePair; pair != nullptr; pair = pair->previous) {
if (a.strictlyEquals(*pair->a) || b.strictlyEquals(*pair->b)) return false;
+2
View File
@@ -272,6 +272,7 @@ void Process::onStarted() {
}
void Process::onFinished(qint32 exitCode, QProcess::ExitStatus exitStatus) {
QObject::disconnect(this->process, nullptr, this, nullptr);
this->process->deleteLater();
this->process = nullptr;
if (this->mStdoutParser) this->mStdoutParser->streamEnded(this->stdoutBuffer);
@@ -290,6 +291,7 @@ void Process::onErrorOccurred(QProcess::ProcessError error) {
if (error == QProcess::FailedToStart) { // other cases should be covered by other events
qWarning() << "Process failed to start, likely because the binary could not be found. Command:"
<< this->mCommand;
QObject::disconnect(this->process, nullptr, this, nullptr);
this->process->deleteLater();
this->process = nullptr;
emit this->runningChanged();
+14 -1
View File
@@ -17,7 +17,11 @@
QS_LOGGING_CATEGORY(logSocket, "quickshell.io.socket", QtWarningMsg);
void Socket::setSocket(QLocalSocket* socket) {
if (this->socket != nullptr) this->socket->deleteLater();
if (this->socket != nullptr) {
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->socket->deleteLater();
}
this->socket = socket;
if (socket != nullptr) {
@@ -57,6 +61,7 @@ void Socket::onSocketDisconnected() {
qCDebug(logSocket) << "Socket disconnected:" << this;
this->connected = false;
this->disconnecting = false;
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->socket->deleteLater();
this->socket = nullptr;
this->buffer.clear();
@@ -68,6 +73,13 @@ void Socket::onSocketDisconnected() {
void Socket::onSocketError(QLocalSocket::LocalSocketError error) {
qCWarning(logSocket) << "Socket error for" << this << error;
emit this->error(error);
if (!this->connected) {
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->socket->deleteLater();
this->socket = nullptr;
this->disconnecting = false;
}
}
bool Socket::isConnected() const { return this->connected; }
@@ -191,6 +203,7 @@ void SocketServer::disableServer() {
}
this->mSockets.clear();
QObject::disconnect(this->server, nullptr, this, nullptr);
this->server->close();
this->server->deleteLater();
this->server = nullptr;
+1
View File
@@ -62,6 +62,7 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server
void IpcServerConnection::onDisconnected() {
qCInfo(logIpc) << "IPC connection disconnected" << this;
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->deleteLater();
}
+16 -10
View File
@@ -2,6 +2,7 @@
#include <array>
#include <cerrno>
#include <cstring>
#include <functional>
#include <utility>
#include <qconfig.h>
@@ -465,7 +466,7 @@ int ipcCommand(CommandState& cmd) {
});
}
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
int launchFromCommand(CommandState& cmd) {
QString configPath;
auto r = locateConfigFile(cmd, configPath);
@@ -485,14 +486,13 @@ int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
.debugPort = cmd.debug.port,
.waitForDebug = cmd.debug.wait,
},
cmd.exec.argv,
coreApplication
cmd.exec.argv
);
}
} // namespace
int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
int runCommand(int argc, char** argv) {
auto state = CommandState();
if (auto ret = parseCommand(argc, argv, state); ret != 65535) return ret;
@@ -555,20 +555,24 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
);
}
std::function<int(CommandState&)> continuation;
if (state.misc.printVersion) {
if (state.log.verbosity == 0) {
qCInfo(logBare).noquote() << "Quickshell" << qs::debuginfo::qsVersion();
} else {
qCInfo(logBare).noquote() << qs::debuginfo::combinedInfo();
}
return 0;
} else if (*state.subcommand.log) {
return readLogFile(state);
continuation = readLogFile;
} else if (*state.subcommand.list) {
return listInstances(state);
continuation = listInstances;
} else if (*state.subcommand.kill) {
return killInstances(state);
continuation = killInstances;
} else if (*state.subcommand.msg || *state.ipc.ipc) {
return ipcCommand(state);
continuation = ipcCommand;
} else {
if (strcmp(qVersion(), QT_VERSION_STR) != 0) {
qWarning() << "\033[31mQuickshell was built against Qt" << QT_VERSION_STR
@@ -577,10 +581,12 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
"the quickshell package must be rebuilt.\n";
}
return launchFromCommand(state, coreApplication);
return launchFromCommand(state);
}
return 0;
int qArgc = 1;
auto coreApp = QCoreApplication(qArgc, argv);
return continuation(state);
}
QString getDisplayConnection() {
+9 -4
View File
@@ -22,6 +22,7 @@
#include "../core/logging.hpp"
#include "../core/paths.hpp"
#include "../core/plugin.hpp"
#include "../core/qmlglobal.hpp"
#include "../core/rootwrapper.hpp"
#include "../ipc/ipc.hpp"
#include "build.hpp"
@@ -57,7 +58,7 @@ QString base36Encode(T number) {
} // namespace
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication) {
int launch(const LaunchArgs& args, char** argv) {
auto pathId = QCryptographicHash::hash(args.configPath.toUtf8(), QCryptographicHash::Md5).toHex();
auto shellId = QString(pathId);
@@ -171,7 +172,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir, pragmas.cacheDir);
QsPaths::instance()->linkRunDir();
QsPaths::instance()->linkPathDir();
LogManager::initFs();
Common::INITIAL_ENVIRONMENT = QProcessEnvironment::systemEnvironment();
@@ -279,8 +279,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
QGuiApplication::setDesktopSettingsAware(pragmas.desktopSettingsAware);
delete coreApplication;
QGuiApplication* app = nullptr;
auto qArgC = 0;
@@ -290,6 +288,9 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
app = new QGuiApplication(qArgC, argv);
}
LogManager::initThreadLogging();
LogManager::initFs();
QGuiApplication::setDesktopFileName(appId);
if (args.debugPort != -1) {
@@ -299,6 +300,10 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
QQmlDebuggingEnabler::startTcpDebugServer(args.debugPort, wait);
}
// This needs to run early to get the first connection to QGuiApplication::screenAdded() in Qs.
// If we don't do that, attempts to get a QuickshellScreenInfo from a QScreen will fail in screenAdded bindings.
QuickshellTracked::init();
QsEnginePlugin::initPlugins();
// Base window transparency appears to be additive.
+2 -2
View File
@@ -107,10 +107,10 @@ struct LaunchArgs {
void exitDaemon(int code);
int parseCommand(int argc, char** argv, CommandState& state);
int runCommand(int argc, char** argv, QCoreApplication* coreApplication);
int runCommand(int argc, char** argv);
QString getDisplayConnection();
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication);
int launch(const LaunchArgs& args, char** argv);
} // namespace qs::launch
+4 -7
View File
@@ -24,7 +24,7 @@ namespace qs::launch {
namespace {
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
void checkCrashRelaunch(char** argv) {
#if CRASH_HANDLER
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
auto dumpPid = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
@@ -71,7 +71,7 @@ void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
} else {
qCritical() << "Quickshell has been restarted.";
launch({.configPath = info.instance.configPath}, argv, coreApplication);
launch({.configPath = info.instance.configPath}, argv);
}
}
#endif
@@ -123,11 +123,8 @@ int main(int argc, char** argv) {
qsCheckCrash(argc, argv);
#endif
auto qArgC = 1;
auto* coreApplication = new QCoreApplication(qArgC, argv);
checkCrashRelaunch(argv, coreApplication);
auto code = runCommand(argc, argv, coreApplication);
checkCrashRelaunch(argv);
auto code = runCommand(argc, argv);
exitDaemon(code);
return code;
+40 -12
View File
@@ -6,6 +6,7 @@
#include <qdbusmetatype.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qdbusservicewatcher.h>
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
@@ -42,6 +43,17 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
return;
}
// clang-format off
QObject::connect(&this->serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &NetworkManager::onServiceRegistered);
QObject::connect(&this->serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &NetworkManager::onServiceUnregistered);
// clang-format on
this->serviceWatcher.setWatchMode(
QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration
);
this->serviceWatcher.addWatchedService("org.freedesktop.NetworkManager");
this->serviceWatcher.setConnection(bus);
this->proxy = new DBusNetworkManagerProxy(
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager",
@@ -49,25 +61,20 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
this
);
if (!this->proxy->isValid()) {
qCDebug(
logNetworkManager
) << "NetworkManager is not currently running. This network backend will not work";
} else {
this->init();
}
}
void NetworkManager::init() {
// clang-format off
QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceAdded, this, &NetworkManager::onDevicePathAdded);
QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceRemoved, this, &NetworkManager::onDevicePathRemoved);
// clang-format on
this->dbusProperties.setInterface(this->proxy);
this->dbusProperties.updateAllViaGetAll();
this->registerDevices();
if (!this->proxy->isValid()) {
qCDebug(
logNetworkManager
) << "NetworkManager is not currently running. This network backend will not work";
} else {
this->onServiceRegistered();
}
}
void NetworkManager::checkConnectivity() {
@@ -230,6 +237,27 @@ void NetworkManager::setWifiEnabled(bool enabled) {
this->pWifiEnabled.write();
}
void NetworkManager::onServiceRegistered() {
qCDebug(logNetworkManager) << "NetworkManager service registered";
this->dbusProperties.updateAllViaGetAll();
this->registerDevices();
}
void NetworkManager::onServiceUnregistered() {
qCDebug(logNetworkManager) << "NetworkManager service unregistered";
// Clear all remaining devices
const auto devices = this->mDevices;
this->mDevices.clear();
for (auto* device: devices) {
if (!device) continue;
qCDebug(logNetworkManager) << "Device removed:" << device->path();
emit this->deviceRemoved(device->frontend());
delete device;
}
}
bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); };
} // namespace qs::network
+4 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <qdbusextratypes.h>
#include <qdbusservicewatcher.h>
#include <qhash.h>
#include <qobject.h>
#include <qproperty.h>
@@ -59,6 +60,8 @@ public slots:
private slots:
void onDevicePathAdded(const QDBusObjectPath& path);
void onDevicePathRemoved(const QDBusObjectPath& path);
void onServiceRegistered();
void onServiceUnregistered();
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
void addAndActivateConnection(
const NMSettingsMap& settings,
@@ -67,7 +70,6 @@ private slots:
);
private:
void init();
void registerDevices();
void registerDevice(const QString& path);
@@ -88,6 +90,7 @@ private:
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckEnabled, bConnectivityCheckEnabled, dbusProperties, "ConnectivityCheckEnabled");
// clang-format on
DBusNetworkManagerProxy* proxy = nullptr;
QDBusServiceWatcher serviceWatcher;
};
} // namespace qs::network
+3 -3
View File
@@ -186,10 +186,10 @@ NMWirelessNetwork::NMWirelessNetwork(const QString& ssid, NetworkDevice* device,
, bSecurity(WifiSecurityType::Unknown) {
auto updateSecurity = [this]() {
if (NMSettings* settings = this->bReferenceSettings) {
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
} else if (NMAccessPoint* ap = this->bReferenceAp) {
if (NMAccessPoint* ap = this->bReferenceAp) {
this->bSecurity.setBinding([ap]() { return ap->security(); });
} else if (NMSettings* settings = this->bReferenceSettings) {
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
} else {
this->bSecurity = WifiSecurityType::Unknown;
}
+1 -1
View File
@@ -20,7 +20,7 @@ namespace qs::network {
WifiSecurityType::Enum securityFromSettingsMap(const NMSettingsMap& settings) {
const QString mapName = "802-11-wireless-security";
if (!settings.contains(mapName)) return WifiSecurityType::Unknown;
if (!settings.contains(mapName)) return WifiSecurityType::Open;
const QVariantMap& security = settings.value(mapName);
if (security.isEmpty()) return WifiSecurityType::Open;
+2 -1
View File
@@ -124,7 +124,8 @@ void NMWirelessDevice::onSettingsLoaded(NMSettings* settings) {
const auto ssid = map["802-11-wireless"]["ssid"].toString();
const auto mode = map["802-11-wireless"]["mode"].toString();
if (mode == "infrastructure") {
// Omitted mode is assumed to be "infrastructure".
if (mode.isEmpty() || mode == "infrastructure") {
auto* net = this->mNetworks.value(ssid);
if (!net) net = this->registerNetwork(ssid);
net->addSettings(settings);
+3 -1
View File
@@ -65,8 +65,10 @@ void MprisWatcher::onServiceRegistered(const QString& service) {
void MprisWatcher::onServiceUnregistered(const QString& service) {
if (auto* player = this->mPlayers.value(service)) {
player->deleteLater();
QObject::disconnect(player, nullptr, this, nullptr);
this->mPlayers.remove(service);
this->readyPlayers.removeObject(player);
player->deleteLater();
qCDebug(logMprisWatcher) << "Unregistered MprisPlayer" << service;
} else {
qCWarning(logMprisWatcher) << "Got service unregister event for untracked service" << service;
+1 -1
View File
@@ -56,7 +56,7 @@ void NotificationAction::invoke() {
}
void NotificationAction::setText(const QString& text) {
if (text != this->mText) return;
if (text == this->mText) return;
this->mText = text;
emit this->textChanged();
+1 -1
View File
@@ -201,6 +201,6 @@ int PamSubprocess::conversation(
return PAM_SUCCESS;
fail:
free(responseArray); // NOLINT
free(responses); // NOLINT
_exit(1);
}
+1
View File
@@ -102,6 +102,7 @@ void PwConnection::startSocketWatcher() {
void PwConnection::stopSocketWatcher() {
if (this->socketWatcher == nullptr) return;
QObject::disconnect(this->socketWatcher, nullptr, this, nullptr);
this->socketWatcher->deleteLater();
this->socketWatcher = nullptr;
}
+18 -18
View File
@@ -486,8 +486,8 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
<< "via device";
this->waitingVolumes = realVolumes;
} else {
auto significantChange = this->mServerVolumes.isEmpty() || this->volumeStep == -1;
if (this->volumeStep != -1) {
auto significantChange = this->mServerVolumes.isEmpty();
for (auto i = 0; i < this->mServerVolumes.length(); i++) {
auto serverVolume = this->mServerVolumes.value(i);
auto targetVolume = realVolumes.value(i);
@@ -496,25 +496,25 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
break;
}
}
}
if (significantChange) {
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes
<< "via device";
if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) {
return;
}
this->mDeviceVolumes = realVolumes;
this->node->device->waitForDevice();
} else {
// Insignificant changes won't cause an info event on the device, leaving qs hung in the
// "waiting for acknowledgement" state forever.
qCInfo(logNode).nospace()
<< "Ignoring volume change for " << this->node << " to " << realVolumes << " from "
<< this->mServerVolumes
<< " as it is a device node and the change is too small (min step: "
<< this->volumeStep << ").";
if (significantChange) {
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes
<< "via device";
if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) {
return;
}
this->mDeviceVolumes = realVolumes;
this->node->device->waitForDevice();
} else {
// Insignificant changes won't cause an info event on the device, leaving qs hung in the
// "waiting for acknowledgement" state forever.
qCInfo(logNode).nospace()
<< "Ignoring volume change for " << this->node << " to " << realVolumes << " from "
<< this->mServerVolumes
<< " as it is a device node and the change is too small (min step: " << this->volumeStep
<< ").";
}
}
} else {
+1 -7
View File
@@ -8,18 +8,12 @@
#include <qvector.h>
#include "node.hpp"
#include "qml.hpp"
namespace qs::service::pipewire {
class PwNodeIface;
class PwPeakStream;
} // namespace qs::service::pipewire
Q_DECLARE_OPAQUE_POINTER(qs::service::pipewire::PwNodeIface*);
namespace qs::service::pipewire {
///! Monitors peak levels of an audio node.
/// Tracks volume peaks for a node across all its channels.
///
+4 -2
View File
@@ -43,6 +43,8 @@ void PolkitAgentImpl::cancelAllRequests(const QString& reason) {
auto* flow = this->bActiveFlow.value();
if (flow) {
QObject::disconnect(flow, nullptr, this, nullptr);
this->bActiveFlow = nullptr;
flow->cancelAuthenticationRequest();
flow->deleteLater();
}
@@ -169,12 +171,12 @@ void PolkitAgentImpl::finishAuthenticationRequest() {
qCDebug(logPolkit) << "finishing authentication request for action"
<< this->bActiveFlow.value()->actionId();
QObject::disconnect(this->bActiveFlow.value(), nullptr, this, nullptr);
this->bActiveFlow.value()->deleteLater();
this->bActiveFlow = nullptr;
if (!this->queuedRequests.empty()) {
this->activateAuthenticationRequest();
} else {
this->bActiveFlow = nullptr;
}
}
} // namespace qs::service::polkit
+10 -3
View File
@@ -103,7 +103,12 @@ StatusNotifierItem::StatusNotifierItem(const QString& address, QObject* parent)
return this->imageHandle.url() % "/" % QString::number(this->pixmapIndex);
});
this->bHasMenu.setBinding([this]() { return !this->bMenuPath.value().path().isEmpty(); });
this->bHasMenu.setBinding([this]() {
// Could not find any documentation about No_DBUSMENU but KDE checks for it
// and it was requested.
return !this->bMenuPath.value().path().isEmpty()
&& this->bMenuPath.value().path() != "/NO_DBUSMENU";
});
QObject::connect(
this->item,
@@ -270,11 +275,13 @@ void StatusNotifierItem::scroll(qint32 delta, bool horizontal) const {
void StatusNotifierItem::updatePixmapIndex() { this->pixmapIndex = this->pixmapIndex + 1; }
DBusMenuHandle* StatusNotifierItem::menuHandle() {
return this->bMenuPath.value().path().isEmpty() ? nullptr : &this->mMenuHandle;
return !this->bHasMenu ? nullptr : &this->mMenuHandle;
}
void StatusNotifierItem::onMenuPathChanged() {
this->mMenuHandle.setAddress(this->item->service(), this->bMenuPath.value().path());
auto path = this->bMenuPath.value().path();
if (!this->bHasMenu) path = "";
this->mMenuHandle.setAddress(this->item->service(), path);
}
void StatusNotifierItem::onGetAllFinished() {
+55 -36
View File
@@ -37,6 +37,18 @@ QS_LOGGING_CATEGORY(logHyprlandIpc, "quickshell.hyprland.ipc", QtWarningMsg);
QS_LOGGING_CATEGORY(logHyprlandIpcEvents, "quickshell.hyprland.ipc.events", QtWarningMsg);
} // namespace
QBindable<HyprlandMonitor*> HyprlandIpc::bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
}
QBindable<HyprlandWorkspace*> HyprlandIpc::bindableFocusedWorkspace() const {
return &this->bFocusedWorkspace;
}
QBindable<HyprlandToplevel*> HyprlandIpc::bindableActiveToplevel() const {
return &this->bActiveToplevel;
}
HyprlandIpc::HyprlandIpc() {
auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
if (his.isEmpty()) {
@@ -187,11 +199,13 @@ void HyprlandIpc::makeRequest(
requestSocket->flush();
};
auto errorCallback = [=](QLocalSocket::LocalSocketError error) {
qCWarning(logHyprlandIpc) << "Error making request:" << error << "request:" << request;
requestSocket->deleteLater();
callback(false, {});
};
auto errorCallback =
[this, requestSocket, request, callback](QLocalSocket::LocalSocketError error) {
qCWarning(logHyprlandIpc) << "Error making request:" << error << "request:" << request;
QObject::disconnect(requestSocket, nullptr, this, nullptr);
requestSocket->deleteLater();
callback(false, {});
};
QObject::connect(requestSocket, &QLocalSocket::connected, this, connectedCallback);
QObject::connect(requestSocket, &QLocalSocket::errorOccurred, this, errorCallback);
@@ -305,7 +319,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
const auto& mList = this->mMonitors.valueList();
auto name = QString::fromUtf8(event->data);
auto monitorIter = std::ranges::find_if(mList, [name](HyprlandMonitor* m) {
auto monitorIter = std::ranges::find_if(mList, [&](HyprlandMonitor* m) {
return m->bindableName().value() == name;
});
@@ -330,10 +344,10 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
} else if (event->name == "createworkspacev2") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto address = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
qCDebug(logHyprlandIpc) << "Workspace created with id" << id << "name" << name;
qCDebug(logHyprlandIpc) << "Workspace created with address" << address << "name" << name;
auto* workspace = this->findWorkspaceByName(name, false);
auto existed = workspace != nullptr;
@@ -342,7 +356,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
workspace = new HyprlandWorkspace(this);
}
workspace->updateInitial(id, name);
workspace->updateInitial(address, name);
if (!existed) {
this->refreshWorkspaces(false);
@@ -351,17 +365,17 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
} else if (event->name == "destroyworkspacev2") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto address = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
const auto& mList = this->mWorkspaces.valueList();
auto workspaceIter = std::ranges::find_if(mList, [id](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableAddress().value() == address;
});
if (workspaceIter == mList.end()) {
qCWarning(logHyprlandIpc) << "Got removal for workspace id" << id << "name" << name
qCWarning(logHyprlandIpc) << "Got removal for workspace id" << address << "name" << name
<< "which was not previously tracked.";
return;
}
@@ -369,7 +383,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
auto index = workspaceIter - mList.begin();
auto* workspace = *workspaceIter;
qCDebug(logHyprlandIpc) << "Workspace removed with id" << id << "name" << name;
qCDebug(logHyprlandIpc) << "Workspace removed with id" << address << "name" << name;
this->mWorkspaces.removeAt(index);
// workspaces have not been observed to be referenced after deletion
@@ -398,10 +412,10 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
this->setFocusedMonitor(monitor);
monitor->setActiveWorkspace(workspace);
qCDebug(logHyprlandIpc) << "Monitor" << name << "focused with workspace"
<< (workspace ? workspace->bindableId().value() : -1);
<< (workspace ? workspace->bindableAddress().value() : "<unk>");
} else if (event->name == "workspacev2") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto id = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
if (this->bFocusedMonitor != nullptr) {
@@ -412,7 +426,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
}
} else if (event->name == "moveworkspacev2") {
auto args = event->parseView(3);
auto id = args.at(0).toInt();
auto id = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
auto monitorName = QString::fromUtf8(args.at(2));
@@ -423,18 +437,18 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
workspace->setMonitor(monitor);
} else if (event->name == "renameworkspace") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto address = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
const auto& mList = this->mWorkspaces.valueList();
auto workspaceIter = std::ranges::find_if(mList, [id](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableAddress().value() == address;
});
if (workspaceIter == mList.end()) return;
qCDebug(logHyprlandIpc) << "Workspace with id" << id << "renamed from"
qCDebug(logHyprlandIpc) << "Workspace with address" << address << "renamed from"
<< (*workspaceIter)->bindableName().value() << "to" << name;
(*workspaceIter)->bindableName().setValue(name);
@@ -486,7 +500,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
if (!ok) return;
const auto& mList = this->mToplevels.valueList();
auto toplevelIter = std::ranges::find_if(mList, [windowAddress](HyprlandToplevel* m) {
auto toplevelIter = std::ranges::find_if(mList, [&](HyprlandToplevel* m) {
return m->address() == windowAddress;
});
@@ -578,14 +592,17 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
}
}
HyprlandWorkspace*
HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id) {
HyprlandWorkspace* HyprlandIpc::findWorkspaceByName(
const QString& name,
bool createIfMissing,
const QString& address
) {
const auto& mList = this->mWorkspaces.valueList();
HyprlandWorkspace* workspace = nullptr;
if (id != -1) {
if (!address.isEmpty()) {
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
return m->bindableAddress().value() == address;
});
workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
@@ -603,10 +620,11 @@ HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint
return workspace;
} else if (createIfMissing) {
qCDebug(logHyprlandIpc) << "Workspace" << name
<< "requested before creation, performing early init with id" << id;
<< "requested before creation, performing early init with address"
<< address;
auto* workspace = new HyprlandWorkspace(this);
workspace->updateInitial(id, name);
workspace->updateInitial(address, name);
this->mWorkspaces.insertObjectSorted(workspace, &HyprlandIpc::compareWorkspaces);
return workspace;
} else {
@@ -626,15 +644,16 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
auto json = QJsonDocument::fromJson(resp).array();
const auto& mList = this->mWorkspaces.valueList();
auto ids = QVector<quint32>();
auto addresses = QVector<QString>();
for (auto entry: json) {
auto object = entry.toObject().toVariantMap();
auto id = object.value("id").toInt();
auto address = object.value("address").toString();
if (address.isEmpty()) address = QString::number(object.value("id").toInt());
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
return m->bindableAddress().value() == address;
});
// Only fall back to name-based filtering as a last resort, for workspaces where
@@ -643,7 +662,7 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
auto name = object.value("name").toString();
workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableId().value() == -1 && m->bindableName().value() == name;
return m->bindableAddress().value().isEmpty() && m->bindableName().value() == name;
});
}
@@ -661,14 +680,14 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
this->mWorkspaces.insertObjectSorted(workspace, &HyprlandIpc::compareWorkspaces);
}
ids.push_back(id);
addresses.push_back(address);
}
if (canCreate) {
auto removedWorkspaces = QVector<HyprlandWorkspace*>();
for (auto* workspace: mList) {
if (!ids.contains(workspace->bindableId().value())) {
if (!addresses.contains(workspace->bindableAddress().value())) {
removedWorkspaces.push_back(workspace);
}
}
@@ -750,7 +769,7 @@ HyprlandMonitor*
HyprlandIpc::findMonitorByName(const QString& name, bool createIfMissing, qint32 id) {
const auto& mList = this->mMonitors.valueList();
auto monitorIter = std::ranges::find_if(mList, [name](HyprlandMonitor* m) {
auto monitorIter = std::ranges::find_if(mList, [&](HyprlandMonitor* m) {
return m->bindableName().value() == name;
});
@@ -818,7 +837,7 @@ void HyprlandIpc::refreshMonitors(bool canCreate) {
auto object = entry.toObject().toVariantMap();
auto name = object.value("name").toString();
auto monitorIter = std::ranges::find_if(mList, [name](HyprlandMonitor* m) {
auto monitorIter = std::ranges::find_if(mList, [&](HyprlandMonitor* m) {
return m->bindableName().value() == name;
});
+11 -15
View File
@@ -25,9 +25,9 @@ class HyprlandToplevel;
} // namespace qs::hyprland::ipc
Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandWorkspace*);
Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandMonitor*);
Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandToplevel*);
Q_MOC_INCLUDE("hyprland_toplevel.hpp")
Q_MOC_INCLUDE("monitor.hpp")
Q_MOC_INCLUDE("workspace.hpp")
namespace qs::hyprland::ipc {
@@ -83,17 +83,9 @@ public:
[[nodiscard]] QBindable<bool> bindableUsingLua() const { return &this->bUsingLua; }
[[nodiscard]] QBindable<HyprlandMonitor*> bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
}
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableFocusedWorkspace() const {
return &this->bFocusedWorkspace;
}
[[nodiscard]] QBindable<HyprlandToplevel*> bindableActiveToplevel() const {
return &this->bActiveToplevel;
}
[[nodiscard]] QBindable<HyprlandMonitor*> bindableFocusedMonitor() const;
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableFocusedWorkspace() const;
[[nodiscard]] QBindable<HyprlandToplevel*> bindableActiveToplevel() const;
void setFocusedMonitor(HyprlandMonitor* monitor);
@@ -102,7 +94,11 @@ public:
[[nodiscard]] ObjectModel<HyprlandToplevel>* toplevels();
// No byId because these preemptively create objects. The given id is set if created.
HyprlandWorkspace* findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id = -1);
HyprlandWorkspace* findWorkspaceByName(
const QString& name,
bool createIfMissing,
const QString& address = QString()
);
HyprlandMonitor* findMonitorByName(const QString& name, bool createIfMissing, qint32 id = -1);
HyprlandToplevel* findToplevelByAddress(quint64 address, bool createIfMissing);
@@ -8,6 +8,7 @@
#include "../../toplevel/qml.hpp"
#include "connection.hpp"
#include "monitor.hpp"
#include "toplevel_mapping.hpp"
#include "workspace.hpp"
@@ -15,6 +16,10 @@ using namespace qs::wayland::toplevel;
namespace qs::hyprland::ipc {
QBindable<HyprlandWorkspace*> HyprlandToplevel::bindableWorkspace() { return &this->bWorkspace; }
QBindable<HyprlandMonitor*> HyprlandToplevel::bindableMonitor() { return &this->bMonitor; }
HyprlandToplevel::HyprlandToplevel(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
this->bMonitor.setBinding([this]() {
return this->bWorkspace ? this->bWorkspace->bindableMonitor().value() : nullptr;
@@ -81,10 +81,10 @@ public:
return &this->bLastIpcObject;
};
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableWorkspace() { return &this->bWorkspace; }
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableWorkspace();
void setWorkspace(HyprlandWorkspace* workspace);
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor() { return &this->bMonitor; }
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor();
signals:
void addressChanged();
+8 -1
View File
@@ -13,6 +13,10 @@ namespace qs::hyprland::ipc {
QVariantMap HyprlandMonitor::lastIpcObject() const { return this->mLastIpcObject; }
QBindable<HyprlandWorkspace*> HyprlandMonitor::bindableActiveWorkspace() const {
return &this->bActiveWorkspace;
}
void HyprlandMonitor::updateInitial(qint32 id, const QString& name, const QString& description) {
Qt::beginPropertyUpdateGroup();
this->bId = id;
@@ -28,7 +32,9 @@ void HyprlandMonitor::updateInitial(qint32 id, const QString& name, const QStrin
void HyprlandMonitor::updateFromObject(QVariantMap object) {
auto activeWorkspaceObj = object.value("activeWorkspace").value<QVariantMap>();
auto activeWorkspaceAddress = activeWorkspaceObj.value("address").value<QString>();
auto activeWorkspaceId = activeWorkspaceObj.value("id").value<qint32>();
if (activeWorkspaceAddress.isEmpty()) activeWorkspaceAddress = QString::number(activeWorkspaceId);
auto activeWorkspaceName = activeWorkspaceObj.value("name").value<QString>();
auto focused = object.value("focused").value<bool>();
@@ -46,7 +52,8 @@ void HyprlandMonitor::updateFromObject(QVariantMap object) {
if (this->bActiveWorkspace == nullptr
|| this->bActiveWorkspace->bindableName().value() != activeWorkspaceName)
{
auto* workspace = this->ipc->findWorkspaceByName(activeWorkspaceName, true, activeWorkspaceId);
auto* workspace =
this->ipc->findWorkspaceByName(activeWorkspaceName, true, activeWorkspaceAddress);
workspace->setMonitor(this);
this->setActiveWorkspace(workspace);
}
+1 -3
View File
@@ -54,9 +54,7 @@ public:
[[nodiscard]] QBindable<qint32> bindableHeight() { return &this->bHeight; }
[[nodiscard]] QBindable<qreal> bindableScale() { return &this->bScale; }
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableActiveWorkspace() const {
return &this->bActiveWorkspace;
}
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableActiveWorkspace() const;
[[nodiscard]] QBindable<bool> bindableFocused() const { return &this->bFocused; }
+19 -7
View File
@@ -15,14 +15,23 @@ namespace qs::hyprland::ipc {
QVariantMap HyprlandWorkspace::lastIpcObject() const { return this->mLastIpcObject; }
QBindable<HyprlandMonitor*> HyprlandWorkspace::bindableMonitor() { return &this->bMonitor; }
HyprlandWorkspace::HyprlandWorkspace(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
Qt::beginPropertyUpdateGroup();
this->bActive.setBinding([this]() {
this->bId.setBinding([this] {
auto idOk = false;
auto id = this->bAddress.value().toInt(&idOk);
if (!idOk) id = -1;
return id;
});
this->bActive.setBinding([this] {
return this->bMonitor.value() && this->bMonitor->bindableActiveWorkspace().value() == this;
});
this->bFocused.setBinding([this]() {
this->bFocused.setBinding([this] {
return this->ipc->bindableFocusedWorkspace().value() == this;
});
@@ -35,9 +44,9 @@ HyprlandWorkspace::HyprlandWorkspace(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
Qt::endPropertyUpdateGroup();
}
void HyprlandWorkspace::updateInitial(qint32 id, const QString& name) {
void HyprlandWorkspace::updateInitial(const QString& address, const QString& name) {
Qt::beginPropertyUpdateGroup();
this->bId = id;
this->bAddress = address;
this->bName = name;
Qt::endPropertyUpdateGroup();
}
@@ -47,11 +56,14 @@ void HyprlandWorkspace::updateFromObject(QVariantMap object) {
auto monitorName = object.value("monitor").value<QString>();
auto hasFullscreen = object.value("hasfullscreen").value<bool>();
auto initial = this->bId == -1;
auto initial = this->bAddress.value().isEmpty();
// ID cannot be updated after creation
// Address cannot be updated after creation
if (initial) {
this->bId = object.value("id").value<qint32>();
auto address = object.value("address").value<QString>();
auto id = object.value("id").value<qint32>();
if (address.isEmpty()) address = QString::number(id);
this->bAddress = address;
}
// No events we currently handle give a workspace id but not a name,
+14 -2
View File
@@ -1,6 +1,7 @@
#pragma once
#include <qbytearrayview.h>
#include <qcontainerfwd.h>
#include <qjsonobject.h>
#include <qobject.h>
#include <qproperty.h>
@@ -18,6 +19,13 @@ class HyprlandMonitor;
class HyprlandWorkspace: public QObject {
Q_OBJECT;
// clang-format off
// > [!NOTE] On Hyprland versions prior to 0.57, this property is set to @@id as a string for compatibility.
// >
// > Prefer this property over @@id for new code.
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
// > [!NOTE] On Hyprland versions 0.57 and later, this property is set to @@address as an integer for compatibility.
// >
// > After 0.57, this property will equal `-1` for non-numbered workspaces.
Q_PROPERTY(qint32 id READ default NOTIFY idChanged BINDABLE bindableId);
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
/// If this workspace is currently active on its monitor. See also @@focused.
@@ -47,7 +55,7 @@ class HyprlandWorkspace: public QObject {
public:
explicit HyprlandWorkspace(HyprlandIpc* ipc);
void updateInitial(qint32 id, const QString& name);
void updateInitial(const QString& address, const QString& name);
void updateFromObject(QVariantMap object);
/// Activate the workspace.
@@ -58,13 +66,14 @@ public:
/// > ```
Q_INVOKABLE void activate();
[[nodiscard]] QBindable<QString> bindableAddress() { return &this->bAddress; }
[[nodiscard]] QBindable<qint32> bindableId() { return &this->bId; }
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] QBindable<bool> bindableActive() { return &this->bActive; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] QBindable<bool> bindableUrgent() { return &this->bUrgent; }
[[nodiscard]] QBindable<bool> bindableHasFullscreen() { return &this->bHasFullscreen; }
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor() { return &this->bMonitor; }
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor();
[[nodiscard]] ObjectModel<HyprlandToplevel>* toplevels() { return &this->mToplevels; }
[[nodiscard]] QVariantMap lastIpcObject() const;
@@ -75,7 +84,9 @@ public:
void removeToplevel(HyprlandToplevel* toplevel);
signals:
void addressChanged();
void idChanged();
void typeChanged();
void nameChanged();
void activeChanged();
void focusedChanged();
@@ -97,6 +108,7 @@ private:
ObjectModel<HyprlandToplevel> mToplevels {this};
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, QString, bAddress, &HyprlandWorkspace::addressChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(HyprlandWorkspace, qint32, bId, -1, &HyprlandWorkspace::idChanged);
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, QString, bName, &HyprlandWorkspace::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, bool, bActive, &HyprlandWorkspace::activeChanged);
@@ -16,7 +16,7 @@ FloatingWindow {
color: "lightgray"
ColumnLayout {
Text { text: `Workspace ${wsDelegate.modelData.id} on ${wsDelegate.modelData.monitor} | urgent: ${wsDelegate.modelData.urgent}`}
Text { text: `Workspace (addr ${wsDelegate.modelData.address}; id ${wsDelegate.modelData.id}; name: ${wsDelegate.modelData.name}) on ${wsDelegate.modelData.monitor} | urgent: ${wsDelegate.modelData.urgent}`}
ColumnLayout {
Repeater {
+63 -40
View File
@@ -22,6 +22,15 @@
#include "../window/proxywindow.hpp"
#include "session_lock/session_lock.hpp"
WlSessionLock::~WlSessionLock() {
if (this->isLocked()) {
qCritical() << "Session lock object was destroyed without unlocking. The session will stay "
"locked in case this was accidental.";
qCritical() << "Component.onDestruction may be used if this behavior is required, but it may "
"result in a lock failing open.";
}
}
void WlSessionLock::onReload(QObject* oldInstance) {
auto* old = qobject_cast<WlSessionLock*>(oldInstance);
@@ -113,70 +122,84 @@ void WlSessionLock::updateSurfaces(bool show, WlSessionLock* old) {
}
void WlSessionLock::realizeLockTarget(WlSessionLock* old) {
if (this->lockTarget) {
if (!SessionLockManager::lockAvailable()) {
qCritical() << "Cannot start session lock: The current compositor does not support the "
"ext-session-lock-v1 protocol.";
this->unlock();
return;
if (this->realizing || !this->manager) {
this->awaitingRealization = true;
return;
}
this->realizing = true;
[&, this]() {
if (this->lockTarget) {
if (!SessionLockManager::lockAvailable()) {
qCritical() << "Cannot start session lock: The current compositor does not support the "
"ext-session-lock-v1 protocol.";
this->unlock();
return;
}
if (this->mSurfaceComponent == nullptr) {
qWarning() << "WlSessionLock.surface is null. Aborting lock.";
this->unlock();
return;
}
// preload initial surfaces to make the chance of the compositor displaying a blank
// frame before the lock surfaces are shown as low as possible.
this->updateSurfaces(false);
auto wasLocked = this->isLocked();
if (!this->manager->lock()) {
qWarning() << "Failed to acquire session lock.";
this->unlock();
return;
}
this->updateSurfaces(true, old);
if (!wasLocked) emit this->lockStateChanged();
} else {
this->unlock(); // emits lockStateChanged
}
}();
if (this->mSurfaceComponent == nullptr) {
qWarning() << "WlSessionLock.surface is null. Aborting lock.";
this->unlock();
return;
}
this->realizing = false;
// preload initial surfaces to make the chance of the compositor displaying a blank
// frame before the lock surfaces are shown as low as possible.
this->updateSurfaces(false);
if (!this->manager->lock()) this->lockTarget = false;
this->updateSurfaces(true, old);
} else {
this->unlock(); // emits lockStateChanged
if (this->awaitingRealization) {
this->awaitingRealization = false;
this->realizeLockTarget();
}
}
void WlSessionLock::unlock() {
if (this->isLocked()) {
this->lockTarget = false;
this->manager->unlock();
this->lockTarget = false;
for (auto* surface: this->surfaces) {
surface->deleteLater();
}
this->surfaces.clear();
emit this->lockStateChanged();
for (auto* surface: this->surfaces) {
surface->deleteLater();
}
this->surfaces.clear();
if (this->manager) this->manager->unlock();
if (this->isLocked()) emit this->lockStateChanged();
}
void WlSessionLock::onScreensChanged() {
if (this->manager != nullptr && this->manager->isLocked()) {
// Theoretically screens shouldnt be invalidated while realizing, instead in later event loop cycles.
if (this->isLocked() && !this->realizing) {
this->updateSurfaces(true);
}
}
bool WlSessionLock::isLocked() const {
return this->manager == nullptr ? this->lockTarget : this->manager->isLocked();
}
bool WlSessionLock::isLocked() const { return this->manager && this->manager->isLocked(); }
bool WlSessionLock::isSecure() const {
return this->manager != nullptr && SessionLockManager::isSecure();
}
void WlSessionLock::setLocked(bool locked) {
if (this->isLocked() == locked) return;
if (this->lockTarget == locked) return;
this->lockTarget = locked;
if (this->manager == nullptr) {
emit this->lockStateChanged();
return;
}
this->realizeLockTarget();
}
+4
View File
@@ -73,6 +73,8 @@ class WlSessionLock: public Reloadable {
public:
explicit WlSessionLock(QObject* parent = nullptr): Reloadable(parent) {}
~WlSessionLock() override;
Q_DISABLE_COPY_MOVE(WlSessionLock);
void onReload(QObject* oldInstance) override;
@@ -101,6 +103,8 @@ private:
QQmlComponent* mSurfaceComponent = nullptr;
QMap<QScreen*, WlSessionLockSurface*> surfaces;
bool lockTarget = false;
bool realizing = false;
bool awaitingRealization = false;
friend class WlSessionLockSurface;
};
+2 -1
View File
@@ -25,7 +25,8 @@ QSWaylandSessionLockManager* manager() {
bool SessionLockManager::lockAvailable() { return manager()->isActive(); }
bool SessionLockManager::lock() {
if (this->isLocked() || SessionLockManager::sessionLocked()) return false;
if (this->isLocked()) return true;
if (SessionLockManager::sessionLocked()) return false;
this->mLock = manager()->acquireLock();
this->mLock->setParent(this);
-4
View File
@@ -11,10 +11,6 @@ qt_add_qml_module(quickshell-x11
DEPENDENCIES QtQuick
)
if(I3)
add_subdirectory(i3)
endif()
install_qml_module(quickshell-x11)
add_library(quickshell-x11-init OBJECT init.cpp)
+8
View File
@@ -31,6 +31,14 @@ QS_LOGGING_CATEGORY(logI3Ipc, "quickshell.I3.ipc", QtWarningMsg);
QS_LOGGING_CATEGORY(logI3IpcEvents, "quickshell.I3.ipc.events", QtWarningMsg);
} // namespace
QBindable<I3Monitor*> I3IpcController::bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
}
QBindable<I3Workspace*> I3IpcController::bindableFocusedWorkspace() const {
return &this->bFocusedWorkspace;
}
I3IpcController::I3IpcController(): I3Ipc({"workspace", "output"}) {
// bind focused workspace to focused monitor's active workspace
this->bFocusedWorkspace.setBinding([this]() -> I3Workspace* {
+4 -9
View File
@@ -20,8 +20,8 @@ class I3Workspace;
class I3Monitor;
} // namespace qs::i3::ipc
Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Workspace*);
Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Monitor*);
Q_MOC_INCLUDE("monitor.hpp")
Q_MOC_INCLUDE("workspace.hpp")
namespace qs::i3::ipc {
@@ -43,13 +43,8 @@ public:
I3Monitor* monitorFor(QuickshellScreenInfo* screen);
[[nodiscard]] QBindable<I3Monitor*> bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
};
[[nodiscard]] QBindable<I3Workspace*> bindableFocusedWorkspace() const {
return &this->bFocusedWorkspace;
};
[[nodiscard]] QBindable<I3Monitor*> bindableFocusedMonitor() const;
[[nodiscard]] QBindable<I3Workspace*> bindableFocusedWorkspace() const;
[[nodiscard]] ObjectModel<I3Monitor>* monitors();
[[nodiscard]] ObjectModel<I3Workspace>* workspaces();
+4
View File
@@ -12,6 +12,10 @@
namespace qs::i3::ipc {
QBindable<I3Workspace*> I3Monitor::bindableActiveWorkspace() const {
return &this->bActiveWorkspace;
}
I3Monitor::I3Monitor(I3IpcController* ipc): QObject(ipc), ipc(ipc) {
// clang-format off
this->bFocused.setBinding([this]() { return this->ipc->bindableFocusedMonitor().value() == this; });
+1 -3
View File
@@ -55,9 +55,7 @@ public:
[[nodiscard]] QBindable<qreal> bindableScale() { return &this->bScale; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] QBindable<I3Workspace*> bindableActiveWorkspace() const {
return &this->bActiveWorkspace;
}
[[nodiscard]] QBindable<I3Workspace*> bindableActiveWorkspace() const;
[[nodiscard]] QVariantMap lastIpcObject() const;
+2
View File
@@ -12,6 +12,8 @@
namespace qs::i3::ipc {
QBindable<I3Monitor*> I3Workspace::bindableMonitor() const { return &this->bMonitor; }
I3Workspace::I3Workspace(I3IpcController* ipc): QObject(ipc), ipc(ipc) {
Qt::beginPropertyUpdateGroup();
+1 -1
View File
@@ -57,7 +57,7 @@ public:
[[nodiscard]] QBindable<bool> bindableActive() { return &this->bActive; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] QBindable<bool> bindableUrgent() { return &this->bUrgent; }
[[nodiscard]] QBindable<I3Monitor*> bindableMonitor() const { return &this->bMonitor; }
[[nodiscard]] QBindable<I3Monitor*> bindableMonitor() const;
[[nodiscard]] QVariantMap lastIpcObject() const;
void updateFromObject(const QVariantMap& obj);