33 Commits
Author SHA1 Message Date
Fredrik Averpil 9b0f339b19 launch: set desktop file name before constructing the app 2026-10-21 23:48:02 -07:00
Domen Kožar 3c5323bcf5 service/tray: forward Wayland activation tokens on primary clicks 2026-10-21 23:47:48 -07:00
bbedward 5c4f4c7148 service/greetd: discard responses to cancelled sessions 2026-10-21 17:11:13 -07:00
Jeff Zhou 1039e2b9d5 service/pipewire: call pw_loop_enter/leave on the SPA event loop 2026-10-21 17:11:09 -07:00
2 * r + 2 * t 2b4501a282 build: expose dependency metatypes in light dep
If not Qt will generate stubs for unknown types, which qmllint reads and
treats as the real type
2026-10-16 22:20:46 -07:00
Alberto Cerqua 95a1f91125 wayland/screencopy: avoid crash when capturing null screen
Prevent null pointer dereference in IccOutputSourceManager, WlrScreencopyManager, and ScreencopyManager when captureSource references a null screen.
2026-10-16 20:21:29 -07:00
Dmitry Litoshik 18c95fae4a wayland/lock: emit lockStateChanged on unlock 2026-10-16 18:13:15 -07:00
2 * r + 2 * t 1c1cc91533 all: include type namespaces in MOC'd members 2026-10-16 18:04:32 -07:00
2 * r + 2 * t 30a5d60a31 core/desktopentry: trim lines in desktop entries when parsing 2026-10-16 17:59:45 -07:00
Ryan Hughes 4befab5bc6 hyprland/ipc: follow workspace id changes 2026-10-16 17:51:32 -07:00
outfoxxed d7ee6d125c core/scriptmodel: emit row updates for rows changed by only identity
Fixes updates not being emitted when using objectProp.
Changes that compared structurally equal are also reported,
which might cover some additional JS equality cases.
2026-10-16 17:50:59 -07:00
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
126 changed files with 1038 additions and 425 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**.
+14
View File
@@ -0,0 +1,14 @@
## Other Changes
- `SystemTrayItem.activate()` now forwards Wayland activation tokens to applications.
## Bug Fixes
- Fixed ScriptModel not sending role updates on changed data that compares equal by key or structure.
- Fixed Hyprland workspaces keeping their old id after Hyprland changes it.
- Fixed desktop entries being parsed incorrectly with trailing whitespace or CRLF line endings.
- Fixed `WlSessionLock.locked` changes not being emitted on unlock.
- Fixed a crash when unplugging a screen being captured by ScreencopyView or trying to capture the fallback screen.
- Fixed most broken qml types in qmlls/qmllint for Qt 6.12.
- Fixed missing thread entry for Pipewire event loop resulting in Pipewire connection failing.
- Fixed Greetd treating responses for a cancelled session as responses for the next one.
+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.
+21 -2
View File
@@ -21,8 +21,27 @@ endfunction()
# DEPENDENCIES introduces a cmake dependency which we don't need with static modules.
# This greatly improves comp speed by not introducing those dependencies.
function (qs_add_module_deps_light target)
foreach (dep IN LISTS ARGN)
string(APPEND qmldir_extra "depends ${dep}\n")
cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "URIS")
set(uris)
# Add dep metatypes files to target so it picks up the dep type data
foreach (dep IN LISTS arg_UNPARSED_ARGUMENTS)
get_target_property(uri ${dep} QT_QML_MODULE_URI)
get_target_property(metatypes ${dep} INTERFACE_QT_META_TYPES_BUILD_FILE)
if (NOT uri OR NOT metatypes)
message(FATAL_ERROR "${dep} must be a qml module defined before ${target}")
endif()
list(APPEND uris ${uri})
target_sources(${target} PRIVATE ${metatypes})
endforeach()
list(APPEND uris ${arg_URIS})
foreach (uri IN LISTS uris)
string(APPEND qmldir_extra "depends ${uri}\n")
endforeach()
qs_append_qmldir(${target} "${qmldir_extra}")
+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)
+2
View File
@@ -29,6 +29,8 @@ qt_add_qml_module(quickshell-bluetooth
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-bluetooth quickshell-core)
install_qml_module(quickshell-bluetooth)
# dbus headers
+2 -2
View File
@@ -32,7 +32,7 @@ public:
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(BluetoothAdapterState::Enum state);
Q_INVOKABLE static QString toString(qs::bluetooth::BluetoothAdapterState::Enum state);
};
} // namespace qs::bluetooth
@@ -64,7 +64,7 @@ class BluetoothAdapter: public QObject {
/// True if the adapter is currently enabled. More detailed state is available from @@state.
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
/// Detailed power state of the adapter.
Q_PROPERTY(BluetoothAdapterState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
Q_PROPERTY(qs::bluetooth::BluetoothAdapterState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// True if the adapter can be discovered by other bluetooth devices.
Q_PROPERTY(bool discoverable READ discoverable WRITE setDiscoverable NOTIFY discoverableChanged);
/// Timeout in seconds for how long the adapter stays discoverable after @@discoverable is set to true.
+1 -1
View File
@@ -66,7 +66,7 @@ class BluezQml: public QObject {
QML_SINGLETON;
// clang-format off
/// The default bluetooth adapter. Usually there is only one.
Q_PROPERTY(BluetoothAdapter* defaultAdapter READ default NOTIFY defaultAdapterChanged BINDABLE bindableDefaultAdapter);
Q_PROPERTY(qs::bluetooth::BluetoothAdapter* defaultAdapter READ default NOTIFY defaultAdapterChanged BINDABLE bindableDefaultAdapter);
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::bluetooth::BluetoothAdapter>*);
/// A list of all bluetooth adapters. See @@defaultAdapter for the default.
Q_PROPERTY(UntypedObjectModel* adapters READ adapters CONSTANT);
+3 -3
View File
@@ -29,7 +29,7 @@ public:
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(BluetoothDeviceState::Enum state);
Q_INVOKABLE static QString toString(qs::bluetooth::BluetoothDeviceState::Enum state);
};
struct BatteryPercentage {};
@@ -70,7 +70,7 @@ class BluetoothDevice: public QObject {
/// System icon representing the device type. Use @@Quickshell.Quickshell.iconPath() to display this in an image.
Q_PROPERTY(QString icon READ default NOTIFY iconChanged BINDABLE bindableIcon);
/// Connection state of the device.
Q_PROPERTY(BluetoothDeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
Q_PROPERTY(qs::bluetooth::BluetoothDeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// True if the device is currently connected to the computer.
///
/// Setting this property is equivalent to calling @@connect() and @@disconnect().
@@ -100,7 +100,7 @@ class BluetoothDevice: public QObject {
/// Battery level of the connected device, from `0.0` to `1.0`. Only valid if @@batteryAvailable is true.
Q_PROPERTY(qreal battery READ default NOTIFY batteryChanged BINDABLE bindableBattery);
/// The Bluetooth adapter this device belongs to.
Q_PROPERTY(BluetoothAdapter* adapter READ adapter NOTIFY adapterChanged);
Q_PROPERTY(qs::bluetooth::BluetoothAdapter* adapter READ adapter NOTIFY adapterChanged);
/// DBus path of the device under the `org.bluez` system service.
Q_PROPERTY(QString dbusPath READ path CONSTANT);
// clang-format on
+7 -6
View File
@@ -158,22 +158,23 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
};
for (auto& line: text.split(u'\n', Qt::SkipEmptyParts)) {
if (line.startsWith(u'#')) continue;
const auto trimmed = line.trimmed();
if (trimmed.isEmpty() || trimmed.startsWith(u'#')) continue;
if (line.startsWith(u'[') && line.endsWith(u']')) {
if (trimmed.startsWith(u'[') && trimmed.endsWith(u']')) {
finishCategory();
groupName = line.sliced(1, line.length() - 2);
groupName = trimmed.sliced(1, trimmed.length() - 2);
continue;
}
auto splitIdx = line.indexOf(u'=');
auto splitIdx = trimmed.indexOf(u'=');
if (splitIdx == -1) {
qCWarning(logDesktopEntry) << "Encountered invalid line in desktop entry (no =)" << line;
continue;
}
auto key = line.sliced(0, splitIdx);
const auto& value = line.sliced(splitIdx + 1);
auto key = trimmed.sliced(0, splitIdx);
const auto& value = trimmed.sliced(splitIdx + 1);
auto localeIdx = key.indexOf('[');
Locale locale;
+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);
};
+4 -2
View File
@@ -114,11 +114,13 @@ 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);
void onMessage(const qs::log::LogMessage& msg, bool showInSparse);
private:
QFile* file = nullptr;
+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);
}
+30 -20
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;
@@ -192,31 +199,34 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
this->endInsertRows();
anyChanges = true;
}
} else if (!newIter->strictlyEquals(*iter)) {
auto first = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
auto index = first;
do {
this->mValues.replace(index, *newIter);
++iter;
++newIter;
++index;
} while (iter != this->mValues.end() && newIter != newValues.end()
&& !newIter->strictlyEquals(*iter));
this->dataChanged(
this->index(first, 0, QModelIndex()),
this->index(index - 1, 0, QModelIndex()),
{Qt::UserRole}
);
anyChanges = true;
} else {
++iter;
++newIter;
}
}
for (auto i = 0; i != this->mValues.length();) {
if (this->mValues.at(i).strictlyEquals(newValues.at(i))) {
++i;
continue;
}
auto first = i;
do {
this->mValues.replace(i, newValues.at(i));
++i;
} while (i != this->mValues.length() && !this->mValues.at(i).strictlyEquals(newValues.at(i)));
this->dataChanged(
this->index(first, 0, QModelIndex()),
this->index(i - 1, 0, QModelIndex()),
{Qt::UserRole}
);
anyChanges = true;
}
this->isModifying = false;
if (this->stagedValues.has_value()) {
+103
View File
@@ -4,6 +4,7 @@
#include <qabstractitemmodeltester.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qhash.h>
#include <qjsengine.h>
#include <qjsvalue.h>
#include <qlist.h>
@@ -47,6 +48,7 @@ ChangeObserver::ChangeObserver(QAbstractItemModel* model) {
QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ChangeObserver::onInsert);
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ChangeObserver::onRemove);
QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ChangeObserver::onMove);
QObject::connect(model, &QAbstractItemModel::dataChanged, this, &ChangeObserver::onDataChanged);
}
void ChangeObserver::onInsert(const QModelIndex& parent, int first, int last) {
@@ -72,6 +74,10 @@ void ChangeObserver::onMove(
.emplaceBack(ModelOperation::Move, sourceStart, sourceEnd - sourceStart + 1, destStart);
}
void ChangeObserver::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) {
this->mChanges.emplaceBack(topLeft.row(), bottomRight.row());
}
// NOLINTNEXTLINE(misc-use-internal-linkage)
QDebug& operator<<(QDebug& debug, const QJSValueList& list) {
QString str;
@@ -186,6 +192,7 @@ void TestScriptModel::unique() {
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), operations);
QCOMPARE_EQ(observer.changes(), ChangeList());
}
void TestScriptModel::structuralEquality() {
@@ -280,4 +287,100 @@ void TestScriptModel::comparisonModes() {
QCOMPARE_EQ(observer.operations(), OpList());
}
void TestScriptModel::dataUpdated_data() {
QTest::addColumn<QString>("oldstr");
QTest::addColumn<QString>("newstr");
QTest::addColumn<OpList>("operations");
QTest::addColumn<ChangeList>("changes");
// Uppercase = same data, Lowercase = data updated
QTest::addRow("insert") << "ABC" << "AXBC" << OpList({{ModelOperation::Insert, 1, 1}})
<< ChangeList();
QTest::addRow("remove") << "AXBC" << "ABC" << OpList({{ModelOperation::Remove, 1, 1}})
<< ChangeList();
QTest::addRow("move") << "ABCD" << "ACBD" << OpList({{ModelOperation::Move, 2, 1, 1}})
<< ChangeList();
QTest::addRow("move_range") << "ABCDEF" << "ADEBCF" << OpList({{ModelOperation::Move, 3, 2, 1}})
<< ChangeList();
QTest::addRow("state_ok") << "ABCDEFG" << "ABEFXYCDG"
<< OpList({
{ModelOperation::Move, 4, 2, 2}, // ABEFCDG
{ModelOperation::Insert, 4, 2}, // ABEFXYCDG
})
<< ChangeList();
QTest::addRow("insert_all_updated") << "ABC" << "aXbc" << OpList({{ModelOperation::Insert, 1, 1}})
<< ChangeList({{0, 0}, {2, 3}});
QTest::addRow("remove_all_updated")
<< "AXBC" << "abc" << OpList({{ModelOperation::Remove, 1, 1}}) << ChangeList({{0, 2}});
QTest::addRow("move_all_updated")
<< "ABCD" << "acbd" << OpList({{ModelOperation::Move, 2, 1, 1}}) << ChangeList({{0, 3}});
QTest::addRow("move_range_all_updated")
<< "ABCDEF" << "adebcf" << OpList({{ModelOperation::Move, 3, 2, 1}}) << ChangeList({{0, 5}});
QTest::addRow("state_ok_all_updated") << "ABCDEFG" << "abefXYcdg"
<< OpList({
{ModelOperation::Move, 4, 2, 2}, // abefCDG
{ModelOperation::Insert, 4, 2}, // abefXYCDG
})
<< ChangeList({{0, 3}, {6, 8}});
QTest::addRow("update_in_place") << "ABC" << "aBc" << OpList() << ChangeList({{0, 0}, {2, 2}});
QTest::addRow("move_updated") << "ABCDEF" << "AdeBCF" << OpList({{ModelOperation::Move, 3, 2, 1}})
<< ChangeList({{1, 2}});
QTest::addRow("move_neighbours_updated")
<< "ABCD" << "aCbd" << OpList({{ModelOperation::Move, 2, 1, 1}})
<< ChangeList({{0, 0}, {2, 3}});
QTest::addRow("move_partially_updated")
<< "ABCDEF" << "aCdEbf" << OpList({{ModelOperation::Move, 2, 3, 1}})
<< ChangeList({{0, 0}, {2, 2}, {4, 5}});
}
void TestScriptModel::dataUpdated() {
QFETCH(const QString, oldstr);
QFETCH(const QString, newstr);
QFETCH(const OpList, operations);
QFETCH(const ChangeList, changes);
QJSEngine engine;
auto version = 0;
QHash<QString, QJSValue> oldValues;
auto strToJsValueList = [&](const QString& str) -> QJSValueList {
QJSValueList list;
for (auto c: str) {
auto key = QString(c.toUpper());
if (c.isUpper() && oldValues.contains(key)) {
list.emplace_back(oldValues.value(key));
continue;
}
auto value = engine.newObject();
value.setProperty("key", key);
value.setProperty("version", version);
list.emplace_back(value);
}
version++;
return list;
};
auto oldList = strToJsValueList(oldstr);
for (const auto& value: oldList) oldValues.insert(value.property("key").toString(), value);
auto newList = strToJsValueList(newstr);
ScriptModel model;
model.setObjectProp("key");
auto modelTester = QAbstractItemModelTester(&model);
model.setValues(oldList);
auto observer = ChangeObserver(&model);
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), operations);
QCOMPARE_EQ(observer.changes(), changes);
}
QTEST_MAIN(TestScriptModel);
+12 -1
View File
@@ -4,6 +4,7 @@
#include <qdebug.h>
#include <qlist.h>
#include <qobject.h>
#include <qpair.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -29,13 +30,19 @@ struct ModelOperation {
};
using OpList = QList<ModelOperation>;
// First and last row of each dataChanged, in emission order.
using ChangeList = QList<QPair<qint32, qint32>>;
class ChangeObserver: public QObject {
public:
explicit ChangeObserver(QAbstractItemModel* model);
[[nodiscard]] const OpList& operations() const { return this->mOperations; }
void clear() { this->mOperations.clear(); }
[[nodiscard]] const ChangeList& changes() const { return this->mChanges; }
void clear() {
this->mOperations.clear();
this->mChanges.clear();
}
private:
void onInsert(const QModelIndex& parent, int first, int last);
@@ -47,8 +54,10 @@ private:
const QModelIndex& destParent,
int destStart
);
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
OpList mOperations;
ChangeList mChanges;
};
QDebug& operator<<(QDebug& debug, const ModelOperation& op);
@@ -61,4 +70,6 @@ private slots:
static void unique();
static void structuralEquality();
static void comparisonModes();
static void dataUpdated_data(); // NOLINT
static void dataUpdated();
};
+1 -1
View File
@@ -20,7 +20,7 @@ qt_add_qml_module(quickshell-dbusmenu
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-dbusmenu Quickshell)
qs_add_module_deps_light(quickshell-dbusmenu quickshell-core)
install_qml_module(quickshell-dbusmenu)
+1 -1
View File
@@ -21,7 +21,7 @@ qt_add_qml_module(quickshell-io
FileView.qml
)
qs_add_module_deps_light(quickshell-io Quickshell)
qs_add_module_deps_light(quickshell-io quickshell-core)
install_qml_module(quickshell-io)
target_link_libraries(quickshell-io PRIVATE Qt::Quick quickshell-ipc)
+1 -1
View File
@@ -227,7 +227,7 @@ class FileView: public QObject {
/// Its state may be saved with @@writeAdapter().
///
/// Currently the only adapter is @@JsonAdapter.
Q_PROPERTY(FileViewAdapter* adapter READ adapter WRITE setAdapter NOTIFY adapterChanged);
Q_PROPERTY(qs::io::FileViewAdapter* adapter READ adapter WRITE setAdapter NOTIFY adapterChanged);
QSDOC_HIDE Q_PROPERTY(QString __path READ path WRITE setPath NOTIFY pathChanged);
QSDOC_HIDE Q_PROPERTY(QString __text READ text NOTIFY internalTextChanged);
+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 -5
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();
@@ -277,10 +277,9 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
QIcon::setFallbackSearchPaths(fallbackPaths);
}
QGuiApplication::setDesktopFileName(appId);
QGuiApplication::setDesktopSettingsAware(pragmas.desktopSettingsAware);
delete coreApplication;
QGuiApplication* app = nullptr;
auto qArgC = 0;
@@ -290,7 +289,8 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
app = new QGuiApplication(qArgC, argv);
}
QGuiApplication::setDesktopFileName(appId);
LogManager::initThreadLogging();
LogManager::initFs();
if (args.debugPort != -1) {
QQmlDebuggingEnabler::enableDebugging(true);
@@ -299,6 +299,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;
+1 -1
View File
@@ -19,7 +19,7 @@ qt_add_qml_module(quickshell-network
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-network Quickshell)
qs_add_module_deps_light(quickshell-network quickshell-core)
install_qml_module(quickshell-network)
target_link_libraries(quickshell-network PRIVATE quickshell-network-nm Qt::Qml Qt::DBus)
qs_add_link_dependencies(quickshell-network quickshell-dbus)
+1 -1
View File
@@ -25,7 +25,7 @@ class NetworkDevice: public QObject {
/// When the device type is `Wifi`, the device object is a @@WifiDevice.
/// When the device type is `Wired`, the device object is a @@WiredDevice.
/// connection and scanning.
Q_PROPERTY(DeviceType::Enum type READ type CONSTANT);
Q_PROPERTY(qs::network::DeviceType::Enum type READ type CONSTANT);
/// The name of the device's control interface.
Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName);
/// A list of available or connected networks for this device.
+7 -7
View File
@@ -27,7 +27,7 @@ public:
Full = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NetworkConnectivity::Enum conn);
Q_INVOKABLE static QString toString(qs::network::NetworkConnectivity::Enum conn);
};
///! The backend supplying the Network service.
@@ -42,7 +42,7 @@ public:
NetworkManager = 1,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NetworkBackendType::Enum type);
Q_INVOKABLE static QString toString(qs::network::NetworkBackendType::Enum type);
};
///! The connection state of a device or network.
@@ -60,7 +60,7 @@ public:
Disconnected = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(ConnectionState::Enum state);
Q_INVOKABLE static QString toString(qs::network::ConnectionState::Enum state);
};
///! The reason a connection failed.
@@ -85,7 +85,7 @@ public:
WifiNetworkLost = 5,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(ConnectionFailReason::Enum reason);
Q_INVOKABLE static QString toString(qs::network::ConnectionFailReason::Enum reason);
};
///! Type of a @@NetworkDevice.
@@ -101,7 +101,7 @@ public:
Wired = 2,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceType::Enum type);
Q_INVOKABLE static QString toString(qs::network::DeviceType::Enum type);
};
///! The security type of a @@WifiNetwork.
@@ -126,7 +126,7 @@ public:
Unknown = 11,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(WifiSecurityType::Enum type);
Q_INVOKABLE static QString toString(qs::network::WifiSecurityType::Enum type);
};
///! The 802.11 mode of a @@WifiDevice.
@@ -149,7 +149,7 @@ public:
Unknown = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode);
Q_INVOKABLE static QString toString(qs::network::WifiDeviceMode::Enum mode);
};
} // namespace qs::network
+6 -6
View File
@@ -27,17 +27,17 @@ class Network: public QObject {
/// The name of the network.
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
/// The device this network belongs to.
Q_PROPERTY(NetworkDevice* device READ device CONSTANT);
Q_PROPERTY(qs::network::NetworkDevice* device READ device CONSTANT);
/// A list of NetworkManager connection settings profiles for this network.
///
/// > [!WARNING] Only valid for the NetworkManager backend.
Q_PROPERTY(QList<NMSettings*> nmSettings READ default NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
Q_PROPERTY(QList<qs::network::NMSettings*> nmSettings READ default NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
/// True if the network is connected.
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
/// True if the wifi network has known connection settings saved.
Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown);
/// The connectivity state of the network.
Q_PROPERTY(ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
Q_PROPERTY(qs::network::ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// If the network is currently connecting or disconnecting. Shorthand for checking @@state.
Q_PROPERTY(bool stateChanging READ default NOTIFY stateChangingChanged BINDABLE bindableStateChanging);
// clang-format on
@@ -53,7 +53,7 @@ public:
/// Attempt to connect to the network with a specific @@nmSettings entry.
///
/// > [!WARNING] Only valid for the NetworkManager backend.
Q_INVOKABLE void connectWithSettings(NMSettings* settings);
Q_INVOKABLE void connectWithSettings(qs::network::NMSettings* settings);
/// Disconnect from the network.
Q_INVOKABLE void disconnect();
/// Forget all connection settings for this network.
@@ -77,7 +77,7 @@ public:
signals:
/// Signals that a connection to the network has failed because of the given @@ConnectionFailReason.
void connectionFailed(ConnectionFailReason::Enum reason);
void connectionFailed(qs::network::ConnectionFailReason::Enum reason);
void nameChanged();
void connectedChanged();
@@ -86,7 +86,7 @@ signals:
void stateChangingChanged();
void nmSettingsChanged();
QSDOC_HIDE void requestConnect();
QSDOC_HIDE void requestConnectWithSettings(NMSettings* settings);
QSDOC_HIDE void requestConnectWithSettings(qs::network::NMSettings* settings);
QSDOC_HIDE void requestDisconnect();
QSDOC_HIDE void requestForget();
+5 -5
View File
@@ -61,11 +61,11 @@ signals:
void loaded();
void ssidChanged(const QByteArray& ssid);
void signalStrengthChanged(quint8 signal);
void flagsChanged(NM80211ApFlags::Enum flags);
void wpaFlagsChanged(NM80211ApSecurityFlags::Enum wpaFlags);
void rsnFlagsChanged(NM80211ApSecurityFlags::Enum rsnFlags);
void modeChanged(NM80211Mode::Enum mode);
void securityChanged(WifiSecurityType::Enum security);
void flagsChanged(qs::network::NM80211ApFlags::Enum flags);
void wpaFlagsChanged(qs::network::NM80211ApSecurityFlags::Enum wpaFlags);
void rsnFlagsChanged(qs::network::NM80211ApSecurityFlags::Enum rsnFlags);
void modeChanged(qs::network::NM80211Mode::Enum mode);
void securityChanged(qs::network::WifiSecurityType::Enum security);
private:
// clang-format off
+2 -2
View File
@@ -43,8 +43,8 @@ public:
signals:
void loaded();
void connectionChanged(QDBusObjectPath path);
void stateChanged(NMConnectionState::Enum state);
void stateReasonChanged(NMConnectionStateReason::Enum reason);
void stateChanged(qs::network::NMConnectionState::Enum state);
void stateReasonChanged(qs::network::NMConnectionStateReason::Enum reason);
private slots:
void onStateChanged(quint32 state, quint32 reason);
+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
+8 -5
View File
@@ -1,6 +1,7 @@
#pragma once
#include <qdbusextratypes.h>
#include <qdbusservicewatcher.h>
#include <qhash.h>
#include <qobject.h>
#include <qproperty.h>
@@ -43,11 +44,11 @@ public:
[[nodiscard]] NMConnectivityState::Enum connectivity() const { return this->bConnectivity; }
signals:
void deviceAdded(NetworkDevice* device);
void deviceRemoved(NetworkDevice* device);
void deviceAdded(qs::network::NetworkDevice* device);
void deviceRemoved(qs::network::NetworkDevice* device);
void wifiEnabledChanged(bool enabled);
void wifiHardwareEnabledChanged(bool enabled);
void connectivityStateChanged(NMConnectivityState::Enum state);
void connectivityStateChanged(qs::network::NMConnectivityState::Enum state);
void connectivityCheckAvailableChanged(bool available);
void connectivityCheckEnabledChanged(bool enabled);
@@ -59,15 +60,16 @@ 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,
const qs::network::NMSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& specificObjectPath
);
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
+10 -10
View File
@@ -64,25 +64,25 @@ signals:
void loaded();
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
void addAndActivateConnection(
const NMSettingsMap& settings,
const qs::network::NMSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& specificObjectPath
);
void networkAdded(Network* net);
void networkRemoved(Network* net);
void settingsLoaded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void networkAdded(qs::network::Network* net);
void networkRemoved(qs::network::Network* net);
void settingsLoaded(qs::network::NMSettings* settings);
void settingsRemoved(qs::network::NMSettings* settings);
void availableSettingsPathsChanged(QList<QDBusObjectPath> paths);
void activeConnectionPathChanged(const QDBusObjectPath& connection);
void activeConnectionLoaded(NMActiveConnection* active);
void activeConnectionLoaded(qs::network::NMActiveConnection* active);
void interfaceChanged(const QString& interface);
void hwAddressChanged(const QString& hwAddress);
void managedChanged(bool managed);
void stateChanged(NMDeviceState::Enum state);
void stateReasonChanged(NMDeviceStateReason::Enum reason);
void lastFailReasonChanged(NMDeviceStateReason::Enum reason);
void stateChanged(qs::network::NMDeviceState::Enum state);
void stateReasonChanged(qs::network::NMDeviceStateReason::Enum reason);
void lastFailReasonChanged(qs::network::NMDeviceStateReason::Enum reason);
void autoconnectChanged(bool autoconnect);
void interfaceFlagsChanged(NMDeviceInterfaceFlags::Enum flags);
void interfaceFlagsChanged(qs::network::NMDeviceInterfaceFlags::Enum flags);
public slots:
void disconnect();
+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;
}
+14 -12
View File
@@ -41,16 +41,18 @@ public:
signals:
void requestDisconnect();
void requestActivateConnection(const QString& settingsPath);
void
requestAddAndActivateConnection(const NMSettingsMap& settingsMap, const QString& specificObject);
void requestAddAndActivateConnection(
const qs::network::NMSettingsMap& settingsMap,
const QString& specificObject
);
void settingsAdded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void stateChanged(NMConnectionState::Enum state);
void settingsAdded(qs::network::NMSettings* settings);
void settingsRemoved(qs::network::NMSettings* settings);
void stateChanged(qs::network::NMConnectionState::Enum state);
void knownChanged(bool known);
void reasonChanged(NMConnectionStateReason::Enum reason);
void deviceFailReasonChanged(NMDeviceStateReason::Enum reason);
void referenceSettingsChanged(NMSettings* settings);
void reasonChanged(qs::network::NMConnectionStateReason::Enum reason);
void deviceFailReasonChanged(qs::network::NMDeviceStateReason::Enum reason);
void referenceSettingsChanged(qs::network::NMSettings* settings);
void visibilityChanged(bool visible);
protected:
@@ -113,11 +115,11 @@ public:
signals:
void disappeared();
void signalStrengthChanged(quint8 signal);
void securityChanged(WifiSecurityType::Enum security);
void securityChanged(qs::network::WifiSecurityType::Enum security);
void activeApPathChanged(QString path);
void referenceApChanged(NMAccessPoint* ap);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
void apRemoved(NMAccessPoint* ap);
void referenceApChanged(qs::network::NMAccessPoint* ap);
void capabilitiesChanged(qs::network::NMWirelessCapabilities::Enum caps);
void apRemoved(qs::network::NMAccessPoint* ap);
private:
void updateReferenceAp();
+1 -1
View File
@@ -61,7 +61,7 @@ public:
signals:
void loaded();
void settingsChanged(NMSettingsMap settings);
void settingsChanged(qs::network::NMSettingsMap settings);
void idChanged(QString id);
void uuidChanged(QString uuid);
+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 -2
View File
@@ -32,8 +32,8 @@ signals:
void speedChanged(quint32 speed);
private slots:
void onSettingsLoaded(NMSettings* settings);
void onActiveConnectionLoaded(NMActiveConnection* active);
void onSettingsLoaded(qs::network::NMSettings* settings);
void onActiveConnectionLoaded(qs::network::NMActiveConnection* active);
private:
void initWired();
+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);
+7 -7
View File
@@ -51,20 +51,20 @@ public:
[[nodiscard]] WifiDevice* frontend() override { return this->mFrontend; };
signals:
void accessPointLoaded(NMAccessPoint* ap);
void accessPointRemoved(NMAccessPoint* ap);
void accessPointLoaded(qs::network::NMAccessPoint* ap);
void accessPointRemoved(qs::network::NMAccessPoint* ap);
void lastScanChanged(QDateTime lastScan);
void scanningChanged(bool scanning);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
void capabilitiesChanged(qs::network::NMWirelessCapabilities::Enum caps);
void activeAccessPointChanged(const QDBusObjectPath& path);
void modeChanged(NM80211Mode::Enum mode);
void modeChanged(qs::network::NM80211Mode::Enum mode);
private slots:
void onAccessPointAdded(const QDBusObjectPath& path);
void onAccessPointRemoved(const QDBusObjectPath& path);
void onAccessPointLoaded(NMAccessPoint* ap);
void onSettingsLoaded(NMSettings* settings);
void onActiveConnectionLoaded(NMActiveConnection* active);
void onAccessPointLoaded(qs::network::NMAccessPoint* ap);
void onSettingsLoaded(qs::network::NMSettings* settings);
void onActiveConnectionLoaded(qs::network::NMActiveConnection* active);
void onScanTimeout();
void onScanningChanged(bool scanning);
+2 -2
View File
@@ -55,8 +55,8 @@ signals:
void connectivityChanged();
private slots:
void deviceAdded(NetworkDevice* dev);
void deviceRemoved(NetworkDevice* dev);
void deviceAdded(qs::network::NetworkDevice* dev);
void deviceRemoved(qs::network::NetworkDevice* dev);
private:
explicit Networking(QObject* parent = nullptr);
+2 -2
View File
@@ -22,7 +22,7 @@ class WifiNetwork: public Network {
/// The current signal strength of the network, from 0.0 to 1.0.
Q_PROPERTY(qreal signalStrength READ default NOTIFY signalStrengthChanged BINDABLE bindableSignalStrength);
/// The security type of the wifi network.
Q_PROPERTY(WifiSecurityType::Enum security READ default NOTIFY securityChanged BINDABLE bindableSecurity);
Q_PROPERTY(qs::network::WifiSecurityType::Enum security READ default NOTIFY securityChanged BINDABLE bindableSecurity);
// clang-format on
public:
@@ -64,7 +64,7 @@ class WifiDevice: public NetworkDevice {
/// When enabled, the scanner populates the device with an active list of available wifi networks.
Q_PROPERTY(bool scannerEnabled READ scannerEnabled WRITE setScannerEnabled NOTIFY scannerEnabledChanged BINDABLE bindableScannerEnabled);
/// The 802.11 mode the device is in.
Q_PROPERTY(WifiDeviceMode::Enum mode READ default NOTIFY modeChanged BINDABLE bindableMode);
Q_PROPERTY(qs::network::WifiDeviceMode::Enum mode READ default NOTIFY modeChanged BINDABLE bindableMode);
// clang-format on
public:
+3 -1
View File
@@ -20,7 +20,9 @@ class WiredDevice: public NetworkDevice {
/// The wired network for this device or `null`.
///
/// > [!NOTE] This network is only available when @@hasLink is `true`.
Q_PROPERTY(Network* network READ network NOTIFY networkChanged BINDABLE bindableNetwork);
Q_PROPERTY(
qs::network::Network* network READ network NOTIFY networkChanged BINDABLE bindableNetwork
);
/// The maximum speed of the physical device link, in megabits per second.
Q_PROPERTY(quint32 linkSpeed READ default NOTIFY linkSpeedChanged BINDABLE bindableLinkSpeed);
/// True if the wired device has a physical link (cable plugged in).
+12
View File
@@ -126,6 +126,8 @@ void GreetdConnection::setActive(bool active) {
{"type", "cancel_session"},
});
// Greetd requests are answered in order, so drop all responses to requests prior to cancelling.
this->mCancelledResponses = this->mPendingResponses;
this->setInactive();
}
} else {
@@ -172,6 +174,14 @@ void GreetdConnection::onSocketReady() {
qCDebug(logGreetd).noquote() << "Received greetd response:" << text;
if (this->mPendingResponses != 0) this->mPendingResponses--;
if (this->mCancelledResponses != 0) {
qCDebug(logGreetd) << "Discarding response to a cancelled session.";
this->mCancelledResponses--;
continue;
}
if (type == "success") {
switch (this->mState) {
case GreetdState::Authenticating:
@@ -260,6 +270,8 @@ void GreetdConnection::sendRequest(const QJsonObject& json) {
this->socket.write(text);
this->socket.flush();
this->mPendingResponses++;
}
GreetdConnection* GreetdConnection::instance() {
+2
View File
@@ -74,6 +74,8 @@ private:
bool mExitAfterLaunch = false;
QString mMessage;
bool mResponseRequired = false;
quint32 mPendingResponses = 0;
quint32 mCancelledResponses = 0;
QString mUser;
QLocalSocket socket;
StreamReader reader;
+1 -1
View File
@@ -33,7 +33,7 @@ qt_add_qml_module(quickshell-service-mpris
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-service-mpris Quickshell)
qs_add_module_deps_light(quickshell-service-mpris quickshell-core)
install_qml_module(quickshell-service-mpris)
+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
@@ -22,7 +22,7 @@ qt_add_qml_module(quickshell-service-notifications
VERSION 0.1
)
qs_add_module_deps_light(quickshell-service-notifications Quickshell)
qs_add_module_deps_light(quickshell-service-notifications quickshell-core)
install_qml_module(quickshell-service-notifications)
target_link_libraries(quickshell-service-notifications PRIVATE Qt::Quick Qt::DBus)
+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 -1
View File
@@ -20,7 +20,7 @@ qt_add_qml_module(quickshell-service-pipewire
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-service-pipewire Quickshell)
qs_add_module_deps_light(quickshell-service-pipewire quickshell-core)
install_qml_module(quickshell-service-pipewire)
+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;
}
+3
View File
@@ -55,6 +55,8 @@ bool PwCore::start(bool retry) {
return false;
}
pw_loop_enter(this->loop);
this->context = pw_context_new(this->loop, nullptr, 0);
if (this->context == nullptr) {
if (retry) {
@@ -103,6 +105,7 @@ void PwCore::shutdown() {
}
if (this->loop != nullptr) {
pw_loop_leave(this->loop);
pw_loop_destroy(this->loop);
this->loop = nullptr;
}
+2 -2
View File
@@ -41,9 +41,9 @@ signals:
void defaultConfiguredSourceNameChanged();
private slots:
void onMetadataAdded(PwMetadata* metadata);
void onMetadataAdded(qs::service::pipewire::PwMetadata* metadata);
void onMetadataProperty(const char* key, const char* type, const char* value);
void onNodeAdded(PwNode* node);
void onNodeAdded(qs::service::pipewire::PwNode* node);
void onDefaultSinkDestroyed();
void onDefaultSourceDestroyed();
void onDefaultConfiguredSinkDestroyed();
+2 -1
View File
@@ -39,7 +39,8 @@ public:
signals:
void deviceReady();
void routeVolumesChanged(qint32 routeDevice, const PwVolumeProps& volumeProps);
void
routeVolumesChanged(qint32 routeDevice, const qs::service::pipewire::PwVolumeProps& volumeProps);
private slots:
void polled();
+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 {
+2 -1
View File
@@ -209,7 +209,8 @@ signals:
private slots:
void onDeviceReady();
void onDeviceVolumesChanged(qint32 routeDevice, const PwVolumeProps& props);
void
onDeviceVolumesChanged(qint32 routeDevice, const qs::service::pipewire::PwVolumeProps& props);
private:
void updateVolumeProps(const PwVolumeProps& volumeProps);
+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.
///
+5 -5
View File
@@ -158,11 +158,11 @@ signals:
void readyChanged();
private slots:
void onNodeAdded(PwNode* node);
void onNodeAdded(qs::service::pipewire::PwNode* node);
void onNodeRemoved(QObject* object);
void onLinkAdded(PwLink* link);
void onLinkAdded(qs::service::pipewire::PwLink* link);
void onLinkRemoved(QObject* object);
void onLinkGroupAdded(PwLinkGroup* group);
void onLinkGroupAdded(qs::service::pipewire::PwLinkGroup* group);
void onLinkGroupRemoved(QObject* object);
private:
@@ -199,7 +199,7 @@ signals:
private slots:
void onNodeDestroyed();
void onLinkGroupCreated(PwLinkGroup* linkGroup);
void onLinkGroupCreated(qs::service::pipewire::PwLinkGroup* linkGroup);
void onLinkGroupDestroyed(QObject* object);
private:
@@ -361,7 +361,7 @@ class PwLinkIface: public PwObjectIface {
/// The current state of the link.
///
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
Q_PROPERTY(PwLinkState::Enum state READ state NOTIFY stateChanged);
Q_PROPERTY(qs::service::pipewire::PwLinkState::Enum state READ state NOTIFY stateChanged);
QML_NAMED_ELEMENT(PwLink);
QML_UNCREATABLE("PwLinks cannot be created directly");
+5 -5
View File
@@ -49,7 +49,7 @@ public:
signals:
// goes with safeDestroy
void destroying(PwBindableObject* self);
void destroying(qs::service::pipewire::PwBindableObject* self);
protected:
void registryBind(const char* interface, quint32 version);
@@ -132,10 +132,10 @@ public:
[[nodiscard]] PwNode* findNodeByName(QStringView name) const;
signals:
void nodeAdded(PwNode* node);
void linkAdded(PwLink* link);
void linkGroupAdded(PwLinkGroup* group);
void metadataAdded(PwMetadata* metadata);
void nodeAdded(qs::service::pipewire::PwNode* node);
void linkAdded(qs::service::pipewire::PwLink* link);
void linkGroupAdded(qs::service::pipewire::PwLinkGroup* group);
void metadataAdded(qs::service::pipewire::PwMetadata* metadata);
void initialized();
void cleared();
+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
+2 -2
View File
@@ -45,12 +45,12 @@ class AuthFlow
/// Each identity may be a user or a group. You may select any of them to
/// authenticate by setting @@selectedIdentity. By default, the first identity
/// in the list is selected.
Q_PROPERTY(QList<Identity*> identities READ identities CONSTANT);
Q_PROPERTY(QList<qs::service::polkit::Identity*> identities READ identities CONSTANT);
/// The identity that will be used to authenticate.
///
/// Changing this will abort any ongoing authentication conversations and start a new one.
Q_PROPERTY(Identity* selectedIdentity READ default WRITE setSelectedIdentity NOTIFY selectedIdentityChanged BINDABLE selectedIdentity);
Q_PROPERTY(qs::service::polkit::Identity* selectedIdentity READ default WRITE setSelectedIdentity NOTIFY selectedIdentityChanged BINDABLE selectedIdentity);
/// Indicates that a response from the user is required from the user,
/// typically a password.
+1 -1
View File
@@ -47,7 +47,7 @@ class PolkitAgent
/// The current authentication state if an authentication request is active.
///
/// Null when no authentication request is active.
Q_PROPERTY(AuthFlow* flow READ default NOTIFY flowChanged BINDABLE flow);
Q_PROPERTY(qs::service::polkit::AuthFlow* flow READ default NOTIFY flowChanged BINDABLE flow);
public:
explicit PolkitAgent(QObject* parent = nullptr): QObject(parent) {};
+8 -1
View File
@@ -44,7 +44,7 @@ qt_add_qml_module(quickshell-service-statusnotifier
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-service-statusnotifier Quickshell Quickshell.DBusMenu)
qs_add_module_deps_light(quickshell-service-statusnotifier quickshell-core quickshell-dbusmenu)
install_qml_module(quickshell-service-statusnotifier)
@@ -52,3 +52,10 @@ target_link_libraries(quickshell-service-statusnotifier PRIVATE Qt::Quick Qt::DB
target_link_libraries(quickshell PRIVATE quickshell-service-statusnotifierplugin)
qs_module_pch(quickshell-service-statusnotifier SET dbus)
if (WAYLAND)
target_compile_definitions(quickshell-service-statusnotifier PRIVATE QS_WAYLAND)
target_link_libraries(quickshell-service-statusnotifier PRIVATE
quickshell-wayland-activation
)
endif()
+3 -3
View File
@@ -29,9 +29,9 @@ public:
static StatusNotifierHost* instance();
signals:
void itemRegistered(StatusNotifierItem* item);
void itemReady(StatusNotifierItem* item);
void itemUnregistered(StatusNotifierItem* item);
void itemRegistered(qs::service::sni::StatusNotifierItem* item);
void itemReady(qs::service::sni::StatusNotifierItem* item);
void itemUnregistered(qs::service::sni::StatusNotifierItem* item);
private slots:
void onWatcherRegistered();
+48 -3
View File
@@ -12,6 +12,7 @@
#include <qobject.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qpointer.h>
#include <qqmlengine.h>
#include <qrect.h>
#include <qsize.h>
@@ -30,6 +31,10 @@
#include "host.hpp"
#include "watcher.hpp"
#ifdef QS_WAYLAND
#include "../../wayland/activation.hpp"
#endif
using namespace qs::dbus;
using namespace qs::dbus::dbusmenu;
using namespace qs::menu::platform;
@@ -103,7 +108,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,
@@ -214,6 +224,39 @@ QPixmap StatusNotifierItem::createPixmap(const QSize& size) const {
}
void StatusNotifierItem::activate() {
#ifdef QS_WAYLAND
auto callback = [item = QPointer(this)](const QString& token) {
if (item) item->activateWithToken(token);
};
if (qs::wayland::activation::requestActivationToken(callback)) return;
#endif
this->activateWithoutToken();
}
void StatusNotifierItem::activateWithToken(const QString& token) {
if (token.isEmpty()) {
this->activateWithoutToken();
return;
}
auto* call = new QDBusPendingCallWatcher(this->item->ProvideXdgActivationToken(token), this);
QObject::connect(
call,
&QDBusPendingCallWatcher::finished,
this,
[this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError() && reply.error().type() != QDBusError::UnknownMethod) {
qCWarning(logStatusNotifierItem) << "Error providing activation token:" << reply.error();
}
delete call;
this->activateWithoutToken();
}
);
}
void StatusNotifierItem::activateWithoutToken() {
auto pendingCall = this->item->Activate(0, 0);
auto* call = new QDBusPendingCallWatcher(pendingCall, this);
@@ -270,11 +313,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() {
+2
View File
@@ -169,6 +169,8 @@ private slots:
void onGetAllFailed() const;
private:
void activateWithToken(const QString& token);
void activateWithoutToken();
void updateMenuState();
void updatePixmapIndex();
void onMenuPathChanged();
@@ -29,6 +29,9 @@
<arg type="i" direction="in" name="x"/>
<arg type="i" direction="in" name="y"/>
</method>
<method name="ProvideXdgActivationToken">
<arg type="s" direction="in" name="token"/>
</method>
<method name="Activate">
<arg type="i" direction="in" name="x"/>
<arg type="i" direction="in" name="y"/>
+1 -1
View File
@@ -34,7 +34,7 @@ qt_add_qml_module(quickshell-service-upower
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-service-upower Quickshell)
qs_add_module_deps_light(quickshell-service-upower quickshell-core)
install_qml_module(quickshell-service-upower)
+11 -1
View File
@@ -73,6 +73,16 @@ endfunction()
# -----
wl_proto(wlp-xdg-activation xdg-activation-v1 "${WAYLAND_PROTOCOLS}/staging/xdg-activation")
qt_add_library(quickshell-wayland-activation STATIC
activation.cpp
)
target_link_libraries(quickshell-wayland-activation PRIVATE
Qt::Gui Qt::WaylandClient Qt::WaylandClientPrivate wlp-xdg-activation
)
qt_add_library(quickshell-wayland STATIC
wl_proxy_safe_deref.cpp
platformmenu.cpp
@@ -148,7 +158,7 @@ qt_add_qml_module(quickshell-wayland
IMPORTS ${WAYLAND_MODULES}
)
qs_add_module_deps_light(quickshell-wayland Quickshell)
qs_add_module_deps_light(quickshell-wayland quickshell-core)
install_qml_module(quickshell-wayland)
+89
View File
@@ -0,0 +1,89 @@
#include "activation.hpp"
#include <functional>
#include <utility>
#include <private/qwaylanddisplay_p.h>
#include <private/qwaylandinputdevice_p.h>
#include <private/qwaylandintegration_p.h>
#include <private/qwaylandwindow_p.h>
#include <qguiapplication.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qpointer.h>
#include <qstring.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qwayland-xdg-activation-v1.h>
#include <qwaylandclientextension.h>
#include "wayland-xdg-activation-v1-client-protocol.h"
namespace qs::wayland::activation {
namespace {
class ActivationManager
: public QWaylandClientExtensionTemplate<ActivationManager>
, public QtWayland::xdg_activation_v1 {
public:
ActivationManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); }
};
class ActivationToken
: public QObject
, public QtWayland::xdg_activation_token_v1 {
public:
ActivationToken(::xdg_activation_token_v1* token, QtWaylandClient::QWaylandWindow* window)
: QObject(window)
, QtWayland::xdg_activation_token_v1(token)
, window(window) {}
~ActivationToken() override { this->destroy(); }
Q_DISABLE_COPY_MOVE(ActivationToken);
private:
void xdg_activation_token_v1_done(const QString& token) override {
this->deleteLater();
if (this->window) emit this->window->xdgActivationTokenCreated(token);
}
QPointer<QtWaylandClient::QWaylandWindow> window;
};
} // namespace
bool requestActivationToken(std::function<void(const QString&)> callback) {
if (QGuiApplication::platformName() != "wayland") return false;
auto* display = QtWaylandClient::QWaylandIntegration::instance()->display();
auto* window = display->lastInputWindow();
if (!window || !window->surface() || !display->lastInputDevice()) return false;
QObject::connect(
window,
&QtWaylandClient::QWaylandWindow::xdgActivationTokenCreated,
window,
std::move(callback),
Qt::SingleShotConnection
);
window->requestXdgActivationToken(display->lastInputSerial());
return true;
}
void requestXdgActivationToken(QtWaylandClient::QWaylandWindow* window, quint32 serial) {
auto* display = window->display();
auto* seat = display->lastInputDevice();
static auto* manager = new ActivationManager(); // NOLINT
if (!manager->isActive() || !window->surface() || !seat) {
emit window->xdgActivationTokenCreated({});
return;
}
auto* token = new ActivationToken(manager->get_activation_token(), window);
token->set_serial(serial, seat->object());
token->set_surface(window->surface());
token->commit();
}
} // namespace qs::wayland::activation
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <functional>
#include <qstring.h>
#include <qtypes.h>
namespace QtWaylandClient { // NOLINT(readability-identifier-naming)
class QWaylandWindow;
}
namespace qs::wayland::activation {
// Request a token using Qt's most recent input event. Returns false if unable to request a token.
bool requestActivationToken(std::function<void(const QString&)> callback);
// Request an activation token for a window to be delivered via QWaylandWindow::xdgActivationTokenCreated
void requestXdgActivationToken(QtWaylandClient::QWaylandWindow* window, quint32 serial);
} // namespace qs::wayland::activation
@@ -10,6 +10,8 @@ qt_add_qml_module(quickshell-wayland-background-effect
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-wayland-background-effect quickshell-core)
install_qml_module(quickshell-wayland-background-effect)
wl_proto(wlp-background-effect ext-background-effect-v1 "${WAYLAND_PROTOCOLS}/staging/ext-background-effect")
+1 -1
View File
@@ -30,7 +30,7 @@ qt_add_qml_module(quickshell-hyprland
IMPORTS ${HYPRLAND_MODULES}
)
qs_add_module_deps_light(quickshell-io Quickshell)
qs_add_module_deps_light(quickshell-hyprland quickshell-core)
install_qml_module(quickshell-hyprland)
# intentionally no pch as the module is empty
@@ -10,7 +10,7 @@ qt_add_qml_module(quickshell-hyprland-focus-grab
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-hyprland-focus-grab Quickshell)
qs_add_module_deps_light(quickshell-hyprland-focus-grab quickshell-core)
install_qml_module(quickshell-hyprland-focus-grab)
+3 -1
View File
@@ -11,13 +11,15 @@ qt_add_qml_module(quickshell-hyprland-ipc
DEPENDENCIES QtQuick
)
qs_add_module_deps_light(quickshell-hyprland-ipc Quickshell)
qs_add_module_deps_light(quickshell-hyprland-ipc quickshell-core)
install_qml_module(quickshell-hyprland-ipc)
target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick quickshell-core)
if (WAYLAND_TOPLEVEL_MANAGEMENT)
qs_add_module_deps_light(quickshell-hyprland-ipc quickshell-wayland-toplevel-management)
target_sources(quickshell-hyprland-ipc PRIVATE
toplevel_mapping.cpp
hyprland_toplevel.cpp
+80 -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,21 +437,46 @@ 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);
} else if (event->name == "changeworkspaceid") {
auto args = event->parseView(2);
auto oldAddress = QString::fromUtf8(args.at(0));
auto newAddress = QString::fromUtf8(args.at(1));
const auto& mList = this->mWorkspaces.valueList();
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableAddress().value() == oldAddress;
});
if (workspaceIter == mList.end()) return;
auto* workspace = *workspaceIter;
qCDebug(logHyprlandIpc) << "Workspace with address" << oldAddress << "changed id to"
<< newAddress;
// Hyprland names a workspace after its new id unless it was renamed.
auto name = workspace->bindableName().value();
if (name == oldAddress) name = newAddress;
// The list is sorted by id, so the workspace moves to its new place in it.
this->mWorkspaces.removeObject(workspace);
workspace->updateInitial(newAddress, name);
this->mWorkspaces.insertObjectSorted(workspace, &HyprlandIpc::compareWorkspaces);
} else if (event->name == "fullscreen") {
if (auto* workspace = this->bFocusedWorkspace.value()) {
workspace->bindableHasFullscreen().setValue(event->data == "1");
@@ -486,7 +525,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 +617,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 +645,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 +669,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 +687,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 +705,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 +794,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 +862,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;
});
+12 -16
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);
@@ -116,7 +112,7 @@ public:
signals:
void connected();
void rawEvent(HyprlandIpcEvent* event);
void rawEvent(qs::hyprland::ipc::HyprlandIpcEvent* event);
void usingLuaChanged();
void focusedMonitorChanged();
@@ -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;
@@ -28,7 +28,7 @@ class HyprlandToplevel: public QObject {
Q_PROPERTY(QString address READ addressStr NOTIFY addressChanged);
/// The toplevel handle, exposing the Hyprland toplevel.
/// Will be null until the address is reported
Q_PROPERTY(HyprlandToplevel* handle READ hyprlandHandle NOTIFY hyprlandHandleChanged);
Q_PROPERTY(qs::hyprland::ipc::HyprlandToplevel* handle READ hyprlandHandle NOTIFY hyprlandHandleChanged);
/// The wayland toplevel handle. Will be null intil the address is reported
Q_PROPERTY(qs::wayland::toplevel::Toplevel* wayland READ waylandHandle NOTIFY waylandHandleChanged);
/// The title of the toplevel
@@ -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; }
+1 -1
View File
@@ -52,7 +52,7 @@ public:
Q_INVOKABLE static void dispatch(const QString& request);
/// Get the HyprlandMonitor object that corresponds to a quickshell screen.
Q_INVOKABLE static HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen);
Q_INVOKABLE static qs::hyprland::ipc::HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen);
/// Refresh monitor information.
///
+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);
@@ -10,6 +10,8 @@ qt_add_qml_module(quickshell-hyprland-surface-extensions
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-hyprland-surface-extensions quickshell-core)
install_qml_module(quickshell-hyprland-surface-extensions)
wl_proto(wlp-hyprland-surface hyprland-surface-v1 "${CMAKE_CURRENT_SOURCE_DIR}")
@@ -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 {
+1 -1
View File
@@ -11,7 +11,7 @@ qt_add_qml_module(quickshell-wayland-idle-inhibit
install_qml_module(quickshell-wayland-idle-inhibit)
qs_add_module_deps_light(quickshell-wayland-idle-inhibit Quickshell)
qs_add_module_deps_light(quickshell-wayland-idle-inhibit quickshell-core)
wl_proto(wlp-idle-inhibit idle-inhibit-unstable-v1 "${WAYLAND_PROTOCOLS}/unstable/idle-inhibit")
+1 -1
View File
@@ -11,7 +11,7 @@ qt_add_qml_module(quickshell-wayland-idle-notify
install_qml_module(quickshell-wayland-idle-notify)
qs_add_module_deps_light(quickshell-wayland-idle-notify Quickshell)
qs_add_module_deps_light(quickshell-wayland-idle-notify quickshell-core)
wl_proto(wlp-idle-notify ext-idle-notify-v1 "${WAYLAND_PROTOCOLS}/staging/ext-idle-notify")
@@ -220,6 +220,8 @@ IccOutputSourceManager* IccOutputSourceManager::instance() {
}
ScreencopyContext* IccOutputSourceManager::captureOutput(QScreen* screen, bool paintCursors) {
if (screen == nullptr) return nullptr;
auto* waylandScreen = dynamic_cast<QtWaylandClient::QWaylandScreen*>(screen->handle());
if (!waylandScreen) return nullptr;
+2
View File
@@ -25,6 +25,8 @@ namespace qs::wayland::screencopy {
ScreencopyContext* ScreencopyManager::createContext(QObject* object, bool paintCursors) {
if (auto* screen = qobject_cast<QuickshellScreenInfo*>(object)) {
if (screen->screen == nullptr) return nullptr;
#if SCREENCOPY_ICC
{
auto* manager = icc::IccOutputSourceManager::instance();
Loaded 100 of 126 files, more files were not shown because too many files have changed in this diff. Show more