64 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
outfoxxed 1a4716cde7 version: 0.3.1 2026-08-20 19:28:55 -07:00
outfoxxed 069e33a93e core/scriptmodel: structurally compare model entries
Comparisons regressed to being identity based when the values list was
changed from being a `QList<QVariant>` to `QList<QJSValue>`.
2026-08-20 19:08:57 -07:00
outfoxxed 532a0606ca core/scriptmodel: handle reentrancy in ScriptModel::setValues 2026-08-16 00:22:04 -07:00
outfoxxed 78672a8405 x11/panelwindow: avoid using destroyed panel stack after free 2026-08-15 20:53:19 -07:00
outfoxxed 990a8badab core/popupanchor: null check mItem in onItemWindowChanged
Also emit itemChanged in PopupAnchor::setItem().
2026-08-15 20:53:19 -07:00
outfoxxed 7e6c7ae3fb services/pipewire: match node and stream channels in peak sampling
Also fixes potentially requeuing a null buffer
2026-08-15 20:53:19 -07:00
outfoxxed 8f1316d0c2 wayland/screencopy: fix bo double free on alloc fail in createDmabuf
gbm_bo_free is already called by the WlDmaBuffer destructor
2026-08-15 20:53:19 -07:00
outfoxxed 63eb2ba6ef core/colorquant: copy source to operations and cancel on destroy
Fixes UAFing the source url in ColorQuantizerOperation if the
ColorQuantizer is destroyed.
2026-08-15 20:53:18 -07:00
outfoxxed b1fc3e0740 io/fileview: disconnect deleteLater'd file watchers immediately
Without this disconnect, the watcher can send a signal whose handler
references the (now null) watcher pointer before the watcher is deleted.
2026-08-15 16:04:18 -07:00
outfoxxed 21fec77de7 wayland/toplevel: null check window passed to Toplevel::setRectangle
Fixes Toplevel::unsetRectangle, which passes a null.
2026-08-15 15:55:46 -07:00
outfoxxed d9cbc90a8c io/fileview: fix swapped new/old branch in JsonAdapter deserialize 2026-08-15 14:44:52 -07:00
outfoxxed 28771c7c74 ipc: ensure handler deregistration upon destruction
Deregistration when an engine generation lookup fails has been
reported as an issue but couldn't be reproduced. This is a speculative
fix.
2026-08-02 13:53:41 -07:00
outfoxxed 43d4fa9e88 crash: strip crash env and avoid leaking crash info fd on launch 2026-07-26 23:37:18 -07:00
outfoxxed 10b439fc6e launch: prevent double initialization of logger 2026-07-23 02:42:56 -07:00
outfoxxed e649d24749 launch: don't print dead instance help logs from --no-duplicate 2026-07-20 19:22:17 -07:00
outfoxxed 025c709e94 ipc: wait for remote process to exit before returning from kill 2026-07-20 19:21:27 -07:00
outfoxxed 4df562dfb2 service/notifications: emit NotificationClosed after Notify returns 2026-07-10 01:58:38 -07:00
outfoxxed 98b6d0b7e7 io/ipchandler: stop reimplementing QVariant in ipc value slots 2026-07-10 01:26:48 -07:00
outfoxxed 25eb04fb06 tooling: use QList<QJSValue> over QJSValueList
QJSValueList is not currently a defined QML type, breaking qmllint and qmlls.
2026-07-08 01:23:01 -07:00
outfoxxed ead3b00afd networking: fix UAFs in NMWirelessNetwork ap/setting destroy handler 2026-07-07 00:34:06 -07:00
outfoxxed a611932352 core/scriptmodel: represent members as a QJSValueList
Fixes UAFs caused by access to pointers stored either directly or
contained in an object inside a ScriptModel which were freed and then
accessed.

These accesses could be observed by not setting `values` to a binding,
access before updateValuesUnique finished, or randomly before a
binding had been fully updated.
2026-07-06 23:45:42 -07:00
outfoxxed 1661ef100a wm: recompute WindowsetProjection.screens on each usage
Fixes potential UAF from destroyed screens not yet removed by a
wayland commit.
2026-06-30 02:31:19 -07:00
bbedward 68c2c85c33 core/window: render a frame when re-enabling updatesEnabled 2026-06-18 22:43:11 -07:00
luke 897fcdaa99 wayland/lock: avoid creating lock surfaces for uninited wl screens 2026-06-17 22:59:12 -07:00
outfoxxed d99d87d5e5 crash: remove logs from crash template 2026-06-09 18:04:48 -07:00
outfoxxed b66495fcc5 all: add/fix property change signals on various properties
- FileView.atomicWrites notified on blockWrites
- StatusNotifierItem.onlyMenu missing notification
- MarginWrapper.extraMargin missing notification
- MarginWrapper.resizeChild missing notification
- PopupWindow.grabFocus missing notification
2026-06-01 01:48:57 -07:00
outfoxxed eaba6c8b62 services/mpris: fix minRate and maxRate returning rate 2026-06-01 01:37:19 -07:00
outfoxxed 4b4fca3224 docs: document private ABI usage 2026-05-21 23:14:12 -07:00
outfoxxed 7d1c9a9c67 wayland/toplevel: reorganize toplevel management 2026-05-11 01:38:16 -07:00
Aditya Alok d1760ed1f3 wayland/lock: add window null check in surface isVisible 2026-05-07 23:40:27 -07:00
outfoxxed b93d61385f wayland/lock: run scenegraph callbacks for lock surfaces
Fixes ScreencopyView never initializing if only using lock surfaces.
2026-05-07 03:08:57 -07:00
165 changed files with 1929 additions and 1160 deletions

No files matched your search

+3 -9
View File
@@ -13,17 +13,11 @@ body:
id: report
attributes:
label: Report file
description: Attach `report.txt` here.
validations:
required: true
- type: upload
id: logs
attributes:
label: Log file
description: |
Attach `log.qslog.log` here. If it is too big to upload, compress it.
Attach `report.txt` here.
You can preview the log if you'd like using `qs log <path-to-log> -r '*=true'`.
Please ensure the report does not contain personal information,
as reports may contain detailed logs leading up to the crash.
validations:
required: true
- type: textarea
+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.
-->
+3 -7
View File
@@ -39,6 +39,9 @@ Quickshell has a set of base dependencies you will always need, names vary by di
- `pkg-config` (build-time)
- `cli11` (static library)
Quickshell relies on private Qt APIs and **MUST** be rebuilt against the each Qt release
or crashes via ABI mismatches will occur.
Build time dependencies and static libraries don't have to exist at runtime,
however build time dependencies must be compiled for the architecture of
the builder, while static libraries must be compiled for the architecture
@@ -97,13 +100,6 @@ To disable: `-DSOCKETS=OFF`
### Wayland
This feature enables wayland support. Subfeatures exist for each particular wayland integration.
WARNING: Wayland integration relies on features that are not part of the public Qt API and which
may break in minor releases. Updating quickshell's dependencies without ensuring without ensuring
that the current Qt version is supported WILL result in quickshell failing to build or misbehaving
at runtime.
Currently supported Qt versions: `6.6`, `6.7`.
To disable: `-DWAYLAND=OFF`
Dependencies:
+1 -1
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(quickshell VERSION "0.3.0" 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.
+23
View File
@@ -0,0 +1,23 @@
## Bug Fixes
- Fixed `ScreencopyView` not displaying when only lock surfaces are shown.
- Fixed `WlSessionLockSurface.visible` crashing if accessed before backing surface creation.
- Fixed `MprisPlayer` returning `rate` for `minRate` and `maxRate`.
- Fixed missing/wrong change signals on various properties.
- Fixed session lock crashes on sleep, wake, DPMS, and unlocking.
- `QsWindow.updatesEnabled` makes sure windows are redrawn when set to true.
- Fixed potential crashes from usage of `WindowsetProjection.screens` during monitor unplug.
- Fixed crashes from accessing freed objects laundered through a `ScriptModel`.
- Fixed crashes when a wifi network disappear.
- Fixed unhandled notifications sending `NotificationClosed` out of order.
- Fixed `qs kill` not waiting for the process to exit.
- Fixed IPC calls from children of a crashed and relaunched process crashing.
- Fixed `JsonAdapter` crashing when deserializing new objects into an array.
- Fixed `Toplevel.unsetRectangle` crashing when calle.
- Fixed intermittent `FileView` crashes when updating watched files.
- Fixed `ColorQuantizer` deletion crashing if an operation was live.
- Fixed crashes when failing to create a `ScreencopyView`.
- Fixed `PwNodePeakMonitor` crashing when sampling a stream with mismatched channels from the node.
- Fixed unsetting `PopupAnchor.item` causing a crash.
- Fixed hiding the last `PanelWindow` on screen causing a crash under X11.
- Fixed crashes when `ScriptModel.values` is set while processing previously set values.
+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.0";
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
+9 -6
View File
@@ -1,5 +1,6 @@
#include "colorquantizer.hpp"
#include <algorithm>
#include <utility>
#include <qatomic.h>
#include <qcolor.h>
@@ -26,12 +27,12 @@ QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg
}
ColorQuantizerOperation::ColorQuantizerOperation(
QUrl* source,
QUrl source,
qreal depth,
QRect imageRect,
qreal rescaleSize
)
: source(source)
: source(std::move(source))
, maxDepth(depth)
, imageRect(imageRect)
, rescaleSize(rescaleSize) {
@@ -39,11 +40,11 @@ ColorQuantizerOperation::ColorQuantizerOperation(
}
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
if (shouldCancel.loadAcquire() || this->source->isEmpty()) return;
if (shouldCancel.loadAcquire() || this->source.isEmpty()) return;
this->colors.clear();
auto image = QImage(this->source->toLocalFile());
auto image = QImage(this->source.toLocalFile());
if (this->imageRect.isValid()) {
image = image.copy(this->imageRect);
@@ -61,7 +62,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCa
}
if (image.isNull()) {
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString();
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source.toString();
return;
}
@@ -191,6 +192,8 @@ void ColorQuantizerOperation::run() {
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
ColorQuantizer::~ColorQuantizer() { this->cancelAsync(); }
void ColorQuantizer::componentComplete() {
this->componentCompleted = true;
if (!this->mSource.isEmpty()) this->quantizeAsync();
@@ -246,7 +249,7 @@ void ColorQuantizer::quantizeAsync() {
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
this->liveOperation = new ColorQuantizerOperation(
&this->mSource,
this->mSource,
this->mDepth,
this->mImageRect,
this->mRescaleSize
+5 -2
View File
@@ -7,6 +7,7 @@
#include <qqmlparserstatus.h>
#include <qrect.h>
#include <qrunnable.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qurl.h>
@@ -17,7 +18,7 @@ class ColorQuantizerOperation
Q_OBJECT;
public:
explicit ColorQuantizerOperation(QUrl* source, qreal depth, QRect imageRect, qreal rescaleSize);
explicit ColorQuantizerOperation(QUrl source, qreal depth, QRect imageRect, qreal rescaleSize);
void run() override;
void tryCancel();
@@ -43,7 +44,7 @@ private:
QAtomicInteger<bool> shouldCancel = false;
QList<QColor> colors;
QUrl* source;
QUrl source;
qreal maxDepth;
QRect imageRect;
qreal rescaleSize;
@@ -94,6 +95,8 @@ class ColorQuantizer
public:
explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {}
~ColorQuantizer() override;
Q_DISABLE_COPY_MOVE(ColorQuantizer);
void componentComplete() override;
void classBegin() override {}
+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;
}
+21 -19
View File
@@ -304,6 +304,10 @@ void LogManager::init(
const QString& rules,
const QString& prefix
) {
static bool alreadyInitialized = false;
if (alreadyInitialized) return;
alreadyInitialized = true;
auto* instance = LogManager::instance();
instance->colorLogs = color;
instance->timestampLogs = timestamp;
@@ -331,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
);
}
@@ -365,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;
+3 -1
View File
@@ -93,6 +93,8 @@ void PopupAnchor::setItem(QQuickItem* item) {
QObject::connect(item, &QObject::destroyed, this, &PopupAnchor::onItemDestroyed);
QObject::connect(item, &QQuickItem::windowChanged, this, &PopupAnchor::onItemWindowChanged);
}
emit this->itemChanged();
}
void PopupAnchor::onWindowDestroyed() {
@@ -109,7 +111,7 @@ void PopupAnchor::onItemDestroyed() {
}
void PopupAnchor::onItemWindowChanged() {
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem->window())) {
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem ? this->mItem->window() : nullptr)) {
this->setWindowInternal(window->proxy());
} else {
this->setWindowInternal(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);
}
+135 -42
View File
@@ -4,39 +4,102 @@
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qjsvalue.h>
#include <qjsvalueiterator.h>
#include <qlist.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qtversionchecks.h>
#include <qtypes.h>
#include <qvariant.h>
void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->hasActiveIterators = true;
namespace {
struct RecursionPair {
const QJSValue* a;
const QJSValue* b;
const RecursionPair* previous;
};
bool qjsValueStructuralEq(
const QJSValue& a,
const QJSValue& b,
const RecursionPair* activePair = nullptr
) {
if (a.strictlyEquals(b)) return true;
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;
}
const auto nextActivePair = RecursionPair {.a = &a, .b = &b, .previous = activePair};
auto aIter = QJSValueIterator(a);
auto bIter = QJSValueIterator(b);
while (aIter.hasNext()) {
if (!bIter.hasNext()) return false;
aIter.next();
bIter.next();
const auto aName = aIter.name();
const auto bName = bIter.name();
const auto aValue = aIter.value();
if (aName == bName) {
const auto bValue = bIter.value();
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
} else {
if (!b.hasOwnProperty(aName)) return false;
const auto bValue = b.property(aName);
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
}
}
return !bIter.hasNext();
}
} // namespace
bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
auto anyChanges = false;
this->isModifying = true;
this->mValues.reserve(newValues.size());
auto iter = this->mValues.begin();
auto newIter = newValues.begin();
// TODO: cache this
auto getCmpKey = [this](const QVariant& v) {
if (v.canConvert<QVariantMap>()) {
auto vMap = v.value<QVariantMap>();
if (vMap.contains(this->cmpKey)) {
return vMap.value(this->cmpKey);
}
auto comparisonValue = [this](const QJSValue& value) {
if (value.hasProperty(this->cmpKey)) return value.property(this->cmpKey);
return value;
};
auto valueCmp = [&, this](const QJSValue& a, const QJSValue& b) {
if (a.strictlyEquals(b)) return true;
const auto aValue = this->cmpKey.isEmpty() ? a : comparisonValue(a);
const auto bValue = this->cmpKey.isEmpty() ? b : comparisonValue(b);
if (this->mComparisonMode == ObjectComparison::Identity) {
return aValue.strictlyEquals(bValue);
} else {
return qjsValueStructuralEq(aValue, bValue);
}
return v;
};
auto variantCmp = [&, this](const QVariant& a, const QVariant& b) {
if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b);
else return a == b;
};
auto eqPredicate = [&](const QVariant& b) {
return [&](const QVariant& a) { return variantCmp(a, b); };
auto eqPredicate = [&](const QJSValue& b) {
return [&](const QJSValue& a) { return valueCmp(a, b); };
};
while (true) {
@@ -49,6 +112,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->beginRemoveRows(QModelIndex(), startIndex, endIndex);
this->mValues.erase(iter, this->mValues.end());
this->endRemoveRows();
anyChanges = true;
break;
} else if (iter == this->mValues.end()) {
@@ -59,9 +123,10 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->beginInsertRows(QModelIndex(), startIndex, endIndex);
this->mValues.append(newValues.sliced(startIndex));
this->endInsertRows();
anyChanges = true;
break;
} else if (!variantCmp(*newIter, *iter)) {
} else if (!valueCmp(*newIter, *iter)) {
auto oldIter = std::find_if(iter, this->mValues.end(), eqPredicate(*newIter));
if (oldIter != this->mValues.end()) {
@@ -81,6 +146,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->beginRemoveRows(QModelIndex(), startIndex, index - 1);
iter = this->mValues.erase(startIter, iter);
this->endRemoveRows();
anyChanges = true;
} else {
// Advance iters to capture a whole move sequence as a single operation if possible.
auto oldStartIter = oldIter;
@@ -88,7 +154,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
++oldIter;
++newIter;
} while (oldIter != this->mValues.end() && newIter != newValues.end()
&& variantCmp(*oldIter, *newIter));
&& valueCmp(*oldIter, *newIter));
auto index = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
auto oldStartIndex =
@@ -105,6 +171,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
iter = this->mValues.begin() + (index + len);
this->endMoveRows();
anyChanges = true;
}
} else {
auto startNewIter = newIter;
@@ -130,36 +197,56 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
std::move_backward(iter, this->mValues.end() - len, this->mValues.end());
iter = std::copy(startNewIter, newIter, iter);
this->endInsertRows();
anyChanges = true;
}
} else if (*newIter != *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 != *iter);
this->dataChanged(
this->index(first, 0, QModelIndex()),
this->index(index - 1, 0, QModelIndex()),
{Qt::UserRole}
);
} else {
++iter;
++newIter;
}
}
this->hasActiveIterators = false;
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()) {
auto values = *this->stagedValues;
this->stagedValues.reset();
anyChanges |= this->updateValuesUnique(values);
}
return anyChanges;
}
void ScriptModel::setValues(const QVariantList& newValues) {
if (newValues == this->mValues) return;
this->updateValuesUnique(newValues);
emit this->valuesChanged();
void ScriptModel::setValues(const QList<QJSValue>& newValues) {
// Re-entrant modification waits for the original modification to complete to avoid mishandled duplicates.
if (this->isModifying) {
this->stagedValues = newValues;
return;
}
auto changed = this->updateValuesUnique(newValues);
if (changed) emit this->valuesChanged();
}
void ScriptModel::setObjectProp(const QString& objectProp) {
@@ -169,6 +256,12 @@ void ScriptModel::setObjectProp(const QString& objectProp) {
emit this->objectPropChanged();
}
void ScriptModel::setComparisonMode(ObjectComparison::Enum comparisonMode) {
if (comparisonMode == this->mComparisonMode) return;
this->mComparisonMode = comparisonMode;
emit this->comparisonModeChanged();
}
qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) return 0;
return static_cast<qint32>(this->mValues.length());
@@ -176,7 +269,7 @@ qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
QVariant ScriptModel::data(const QModelIndex& index, qint32 role) const {
if (role != Qt::UserRole) return QVariant();
return this->mValues.at(index.row());
return this->mValues.at(index.row()).toVariant(QJSValue::RetainJSObjects);
}
QHash<int, QByteArray> ScriptModel::roleNames() const { return {{Qt::UserRole, "modelData"}}; }
+42 -7
View File
@@ -1,10 +1,31 @@
#pragma once
#include <optional>
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qjsvalue.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
///! @@ScriptModel value comparison mode.
namespace ObjectComparison { // NOLINT
Q_NAMESPACE;
QML_ELEMENT;
enum Enum : quint8 {
/// Compare values using strict JavaScript equality.
Identity = 0,
/// Compare records and arrays structurally.
Structure = 1,
};
Q_ENUM_NS(Enum);
} // namespace ObjectComparison
///! QML model reflecting a javascript expression
/// ScriptModel is a QML [Data Model] that generates model operations based on changes
@@ -40,6 +61,7 @@
/// [Data Model]: https://doc.qt.io/qt-6/qtquick-modelviewsdata-modelview.html#qml-data-models
class ScriptModel: public QAbstractListModel {
Q_OBJECT;
// clang-format off
/// The list of values to reflect in the model.
/// > [!WARNING] ScriptModel currently only works with lists of *unique* values.
/// > There must not be any duplicates in the given list, or behavior of the model is undefined.
@@ -66,7 +88,8 @@ class ScriptModel: public QAbstractListModel {
/// > values: [...DesktopEntries.applications.values].sort(...)
/// > }
/// > ```
Q_PROPERTY(QVariantList values READ values WRITE setValues NOTIFY valuesChanged);
// TODO: use qjsobject for gc/free checks?
Q_PROPERTY(QList<QJSValue> values READ values WRITE setValues NOTIFY valuesChanged);
/// The property that javascript objects passed into the model will be compared with.
///
/// For example, if `objectProp` is `"myprop"` then `{ myprop: "a", other: "y" }` and
@@ -74,21 +97,30 @@ class ScriptModel: public QAbstractListModel {
///
/// Defaults to `""`, meaning no key.
Q_PROPERTY(QString objectProp READ objectProp WRITE setObjectProp NOTIFY objectPropChanged);
/// How values should be compared. Defaults to `ObjectComparison.Structure`.
///
/// Identity based comparison is faster if usable for a given model, and often
/// achievable with @@objectProp.
Q_PROPERTY(ObjectComparison::Enum comparisonMode READ comparisonMode WRITE setComparisonMode NOTIFY comparisonModeChanged);
// clang-format on
QML_ELEMENT;
public:
[[nodiscard]] QVariantList values() const {
[[nodiscard]] QList<QJSValue> values() const {
auto values = this->mValues;
// If not detached, the QML engine will invalidate iterators in updateValuesUnique.
if (this->hasActiveIterators) values.detach();
if (this->isModifying) values.detach();
return values;
}
void setValues(const QVariantList& newValues);
void setValues(const QList<QJSValue>& newValues);
[[nodiscard]] QString objectProp() const { return this->cmpKey; }
void setObjectProp(const QString& objectProp);
[[nodiscard]] ObjectComparison::Enum comparisonMode() const { return this->mComparisonMode; }
void setComparisonMode(ObjectComparison::Enum comparisonMode);
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override;
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
@@ -96,11 +128,14 @@ public:
signals:
void valuesChanged();
void objectPropChanged();
void comparisonModeChanged();
private:
QVariantList mValues;
QList<QJSValue> mValues;
QString cmpKey;
bool hasActiveIterators = false;
ObjectComparison::Enum mComparisonMode = ObjectComparison::Structure;
bool isModifying = false;
std::optional<QList<QJSValue>> stagedValues;
void updateValuesUnique(const QVariantList& newValues);
bool updateValuesUnique(const QList<QJSValue>& newValues);
};
+258 -53
View File
@@ -4,10 +4,12 @@
#include <qabstractitemmodeltester.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qhash.h>
#include <qjsengine.h>
#include <qjsvalue.h>
#include <qlist.h>
#include <qlogging.h>
#include <qobject.h>
#include <qsignalspy.h>
#include <qstring.h>
#include <qtest.h>
#include <qtestcase.h>
@@ -15,8 +17,6 @@
#include "../scriptmodel.hpp"
using OpList = QList<ModelOperation>;
bool ModelOperation::operator==(const ModelOperation& other) const {
return other.operation == this->operation && other.index == this->index
&& other.length == this->length && other.destIndex == this->destIndex;
@@ -44,15 +44,49 @@ QDebug& operator<<(QDebug& debug, const ModelOperation& op) {
return debug;
}
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) {
QCOMPARE(parent, QModelIndex());
this->mOperations.emplaceBack(ModelOperation::Insert, first, last - first + 1);
}
void ChangeObserver::onRemove(const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
this->mOperations.emplaceBack(ModelOperation::Remove, first, last - first + 1);
}
void ChangeObserver::onMove(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
) {
QCOMPARE(sourceParent, QModelIndex());
QCOMPARE(destParent, QModelIndex());
this->mOperations
.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 QVariantList& list) {
auto str = QString();
QDebug& operator<<(QDebug& debug, const QJSValueList& list) {
QString str;
for (const auto& var: list) {
if (var.canConvert<QChar>()) {
str += var.value<QChar>();
if (var.isString()) {
str += var.toString();
} else {
qFatal() << "QVariantList debug overridden in test";
qFatal() << "QJSValueList debug overridden in test";
}
}
@@ -60,6 +94,18 @@ QDebug& operator<<(QDebug& debug, const QVariantList& list) {
return debug;
}
namespace {
bool qjsValueListsEqual(const QJSValueList& a, const QJSValueList& b) {
if (a.length() != b.length()) return false;
for (auto i = 0; i != a.length(); i++) {
if (!a.at(i).strictlyEquals(b.at(i))) return false;
}
return true;
}
} // namespace
void TestScriptModel::unique_data() {
QTest::addColumn<QString>("oldstr");
QTest::addColumn<QString>("newstr");
@@ -117,65 +163,224 @@ void TestScriptModel::unique() {
QFETCH(const QString, newstr);
QFETCH(const OpList, operations);
auto strToVariantList = [](const QString& str) -> QVariantList {
QVariantList list;
auto strToJsValueList = [](const QString& str) -> QJSValueList {
QJSValueList list;
for (auto c: str) {
list.emplace_back(c);
list.emplace_back(QString(c));
}
return list;
};
auto oldlist = strToVariantList(oldstr);
auto newlist = strToVariantList(newstr);
auto oldList = strToJsValueList(oldstr);
auto newList = strToJsValueList(newstr);
auto model = ScriptModel();
ScriptModel model;
auto modelTester = QAbstractItemModelTester(&model);
auto observer = ChangeObserver(&model);
OpList actualOperations;
auto onInsert = [&](const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
actualOperations << ModelOperation(ModelOperation::Insert, first, last - first + 1);
};
auto onRemove = [&](const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
actualOperations << ModelOperation(ModelOperation::Remove, first, last - first + 1);
};
auto onMove = [&](const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart) {
QCOMPARE(sourceParent, QModelIndex());
QCOMPARE(destParent, QModelIndex());
actualOperations << ModelOperation(
ModelOperation::Move,
sourceStart,
sourceEnd - sourceStart + 1,
destStart
);
};
QObject::connect(&model, &QAbstractItemModel::rowsInserted, &model, onInsert);
QObject::connect(&model, &QAbstractItemModel::rowsRemoved, &model, onRemove);
QObject::connect(&model, &QAbstractItemModel::rowsMoved, &model, onMove);
model.setValues(oldlist);
QCOMPARE_EQ(model.values(), oldlist);
model.setValues(oldList);
QVERIFY(qjsValueListsEqual(model.values(), oldList));
QCOMPARE_EQ(
actualOperations,
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldlist.length())}})
observer.operations(),
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
);
actualOperations.clear();
observer.clear();
model.setValues(newlist);
QCOMPARE_EQ(model.values(), newlist);
QCOMPARE_EQ(actualOperations, operations);
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), operations);
QCOMPARE_EQ(observer.changes(), ChangeList());
}
void TestScriptModel::structuralEquality() {
ScriptModel model;
QJSEngine engine;
auto object = engine.newObject();
object.setProperty("foo", "bar");
object.setProperty("bar", "baz");
object.setProperty("subobject", engine.newObject());
auto replacement = engine.newObject();
replacement.setProperty("subobject", engine.newObject());
replacement.setProperty("bar", "baz");
replacement.setProperty("foo", "bar");
auto oldList = QList<QJSValue> {object};
auto newList = QList<QJSValue> {replacement};
auto modelTester = QAbstractItemModelTester(&model);
auto observer = ChangeObserver(&model);
model.setValues(oldList);
QVERIFY(qjsValueListsEqual(model.values(), oldList));
QCOMPARE_EQ(
observer.operations(),
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
);
observer.clear();
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), OpList());
auto structurallyEquals = [](const QJSValue& a, const QJSValue& b) {
ScriptModel model;
model.setValues({a});
auto observer = ChangeObserver(&model);
model.setValues({b});
return observer.operations().isEmpty();
};
auto different = engine.newObject();
different.setProperty("foo", "bar");
different.setProperty("bar", "baz");
auto differentSubobject = engine.newObject();
differentSubobject.setProperty("value", 1);
different.setProperty("subobject", differentSubobject);
QVERIFY(!structurallyEquals(object, different));
auto cycleA = engine.newObject();
cycleA.setProperty("self", cycleA);
auto cycleB = engine.newObject();
cycleB.setProperty("self", cycleB);
QVERIFY(!structurallyEquals(cycleA, cycleB));
const auto arrayA = engine.evaluate("[1, { value: 'x' }]");
const auto arrayB = engine.evaluate("[1, { value: 'x' }]");
QVERIFY(structurallyEquals(arrayA, arrayB));
auto sparseArrayA = engine.newArray(1);
auto sparseArrayB = engine.newArray(2);
QVERIFY(!structurallyEquals(sparseArrayA, sparseArrayB));
}
void TestScriptModel::comparisonModes() {
QJSEngine engine;
auto makeValue = [&engine]() {
auto value = engine.newObject();
value.setProperty("nested", engine.newObject());
return value;
};
ScriptModel model;
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Structure);
model.setValues({makeValue()});
model.setComparisonMode(ObjectComparison::Identity);
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Identity);
auto observer = ChangeObserver(&model);
model.setValues({makeValue()});
QVERIFY(!observer.operations().isEmpty());
model.setComparisonMode(ObjectComparison::Structure);
observer.clear();
model.setValues({makeValue()});
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);
+38
View File
@@ -1,7 +1,10 @@
#pragma once
#include <qabstractitemmodel.h>
#include <qdebug.h>
#include <qlist.h>
#include <qobject.h>
#include <qpair.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -26,6 +29,37 @@ struct ModelOperation {
[[nodiscard]] bool operator==(const ModelOperation& other) const;
};
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; }
[[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);
void onRemove(const QModelIndex& parent, int first, int last);
void onMove(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
);
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
OpList mOperations;
ChangeList mChanges;
};
QDebug& operator<<(QDebug& debug, const ModelOperation& op);
class TestScriptModel: public QObject {
@@ -34,4 +68,8 @@ class TestScriptModel: public QObject {
private slots:
static void unique_data(); // NOLINT
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
View File
@@ -495,6 +495,7 @@ void FileView::updateWatchedFiles() {
// If inotify events are sent to the watcher after deletion and deleteLater
// isn't used, a use after free in the QML engine will occur.
if (this->watcher) {
QObject::disconnect(this->watcher, nullptr, this, nullptr);
this->watcher->deleteLater();
this->watcher = nullptr;
}
+2 -2
View File
@@ -204,7 +204,7 @@ class FileView: public QObject {
///
/// > [!NOTE] This works by creating another file with the desired content, and renaming
/// > it over the existing file if successful.
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY blockWritesChanged BINDABLE bindableAtomicWrites);
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY atomicWritesChanged BINDABLE bindableAtomicWrites);
/// If true (default), read or write errors will be printed to the quickshell logs.
/// If false, all known errors will not be printed.
QSDOC_PROPERTY_OVERRIDE(bool printErrors READ default WRITE default NOTIFY printErrorsChanged);
@@ -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);
+67 -154
View File
@@ -1,181 +1,94 @@
#include "ipc.hpp"
#include <cstring>
#include <utility>
#include <qcolor.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
#include <qtypes.h>
#include <qvariant.h>
namespace qs::io::ipc {
const VoidIpcType VoidIpcType::INSTANCE {};
const StringIpcType StringIpcType::INSTANCE {};
const IntIpcType IntIpcType::INSTANCE {};
const BoolIpcType BoolIpcType::INSTANCE {};
const DoubleIpcType DoubleIpcType::INSTANCE {};
const ColorIpcType ColorIpcType::INSTANCE {};
bool IpcValueSlot::isSupported(const QMetaType& type) { return IpcValueSlot::typeName(type); }
void* IpcType::copyStorage(const void* data) const {
auto* storage = this->createStorage();
memcpy(storage, data, this->size());
return storage;
}
const IpcType* IpcType::ipcType(const QMetaType& metaType) {
if (metaType.id() == QMetaType::Void) return &VoidIpcType::INSTANCE;
if (metaType.id() == QMetaType::QString) return &StringIpcType::INSTANCE;
if (metaType.id() == QMetaType::Int) return &IntIpcType::INSTANCE;
if (metaType.id() == QMetaType::Bool) return &BoolIpcType::INSTANCE;
if (metaType.id() == QMetaType::Double) return &DoubleIpcType::INSTANCE;
if (metaType.id() == QMetaType::QColor) return &ColorIpcType::INSTANCE;
return nullptr;
}
IpcTypeSlot::IpcTypeSlot(IpcTypeSlot&& other) noexcept { *this = std::move(other); }
IpcTypeSlot& IpcTypeSlot::operator=(IpcTypeSlot&& other) noexcept {
this->mType = other.mType;
this->storage = other.storage;
other.mType = nullptr;
other.storage = nullptr;
return *this;
}
IpcTypeSlot::~IpcTypeSlot() { this->replace(nullptr); }
const IpcType* IpcTypeSlot::type() const { return this->mType; }
void* IpcTypeSlot::get() {
if (this->storage == nullptr) {
this->storage = this->mType->createStorage();
}
return this->storage;
}
QGenericArgument IpcTypeSlot::asGenericArgument() {
if (this->mType) {
return QGenericArgument(this->mType->genericArgumentName(), this->get());
} else {
return QGenericArgument();
const char* IpcValueSlot::typeName(const QMetaType& type) {
switch (type.id()) {
case QMetaType::Void: return "void";
case QMetaType::QString: return "string";
case QMetaType::Int: return "int";
case QMetaType::Bool: return "bool";
case QMetaType::Double: return "real";
case QMetaType::QColor: return "color";
default: return nullptr;
}
}
QGenericReturnArgument IpcTypeSlot::asGenericReturnArgument() {
if (this->mType) {
return QGenericReturnArgument(this->mType->genericArgumentName(), this->get());
} else {
return QGenericReturnArgument();
const char* IpcValueSlot::name() const {
return this->mValue.isValid() ? IpcValueSlot::typeName(this->mValue.metaType()) : "void";
}
bool IpcValueSlot::isVoid() const { return !this->mValue.isValid(); }
const QVariant& IpcValueSlot::value() const { return this->mValue; }
bool IpcValueSlot::setString(const QString& string) {
switch (this->mValue.typeId()) {
case QMetaType::QString: this->mValue = QVariant::fromValue(string); return true;
case QMetaType::Int: {
auto ok = false;
auto value = string.toInt(&ok);
if (ok) this->mValue = QVariant::fromValue(value);
return ok;
}
case QMetaType::Bool: {
if (string == "true" || string == "false") {
this->mValue = QVariant::fromValue(string == "true");
return true;
}
auto ok = false;
auto value = string.toInt(&ok);
if (ok) this->mValue = QVariant::fromValue(value != 0);
return ok;
}
case QMetaType::Double: {
auto ok = false;
auto value = string.toDouble(&ok);
if (ok) this->mValue = QVariant::fromValue(value);
return ok;
}
case QMetaType::QColor: {
auto value = QColor::fromString(string);
if (!value.isValid() && !string.startsWith('#')) value = QColor::fromString('#' % string);
if (value.isValid()) this->mValue = QVariant::fromValue(value);
return value.isValid();
}
default: return false;
}
}
void IpcTypeSlot::replace(void* value) {
if (this->storage != nullptr) {
this->mType->destroyStorage(this->storage);
void IpcValueSlot::setValue(QVariant value) { this->mValue = std::move(value); }
QString IpcValueSlot::toString() const {
switch (this->mValue.typeId()) {
case QMetaType::UnknownType: return "";
case QMetaType::QString: return this->mValue.value<QString>();
case QMetaType::Int: return QString::number(this->mValue.value<int>());
case QMetaType::Bool: return this->mValue.value<bool>() ? "true" : "false";
case QMetaType::Double: return QString::number(this->mValue.value<double>());
case QMetaType::QColor: return this->mValue.value<QColor>().name(QColor::HexArgb);
default: return "";
}
this->storage = value;
}
void IpcTypeSlot::replace(const QVariant& value) {
this->replace(this->mType->copyStorage(value.constData()));
QGenericArgument IpcValueSlot::asGenericArgument() const {
return QGenericArgument(this->mValue.metaType().name(), this->mValue.constData());
}
const char* VoidIpcType::name() const { return "void"; }
const char* VoidIpcType::genericArgumentName() const { return "void"; }
qsizetype VoidIpcType::size() const { return 0; }
// string
const char* StringIpcType::name() const { return "string"; }
const char* StringIpcType::genericArgumentName() const { return "QString"; }
qsizetype StringIpcType::size() const { return sizeof(QString); }
void* StringIpcType::fromString(const QString& string) const { return new QString(string); }
QString StringIpcType::toString(void* slot) const { return *static_cast<QString*>(slot); }
void* StringIpcType::createStorage() const { return new QString(); }
void StringIpcType::destroyStorage(void* slot) const { delete static_cast<QString*>(slot); }
// int
const char* IntIpcType::name() const { return "int"; }
const char* IntIpcType::genericArgumentName() const { return "int"; }
qsizetype IntIpcType::size() const { return sizeof(int); }
void* IntIpcType::fromString(const QString& string) const {
auto ok = false;
auto v = string.toInt(&ok);
return ok ? new int(v) : nullptr;
QGenericReturnArgument IpcValueSlot::asGenericReturnArgument() {
if (!this->mValue.isValid()) return QGenericReturnArgument();
return QGenericReturnArgument(this->mValue.metaType().name(), this->mValue.data());
}
QString IntIpcType::toString(void* slot) const { return QString::number(*static_cast<int*>(slot)); }
void* IntIpcType::createStorage() const { return new int(); }
void IntIpcType::destroyStorage(void* slot) const { delete static_cast<int*>(slot); }
// bool
const char* BoolIpcType::name() const { return "bool"; }
const char* BoolIpcType::genericArgumentName() const { return "bool"; }
qsizetype BoolIpcType::size() const { return sizeof(bool); }
void* BoolIpcType::fromString(const QString& string) const {
if (string == "true") return new bool(true);
if (string == "false") return new bool(false);
auto isInt = false;
auto iv = string.toInt(&isInt);
return isInt ? new bool(iv != 0) : nullptr;
}
QString BoolIpcType::toString(void* slot) const {
return *static_cast<bool*>(slot) ? "true" : "false";
}
void* BoolIpcType::createStorage() const { return new bool(); }
void BoolIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
// double
const char* DoubleIpcType::name() const { return "real"; }
const char* DoubleIpcType::genericArgumentName() const { return "double"; }
qsizetype DoubleIpcType::size() const { return sizeof(double); }
void* DoubleIpcType::fromString(const QString& string) const {
auto ok = false;
auto v = string.toDouble(&ok);
return ok ? new double(v) : nullptr;
}
QString DoubleIpcType::toString(void* slot) const {
return QString::number(*static_cast<double*>(slot));
}
void* DoubleIpcType::createStorage() const { return new double(); }
void DoubleIpcType::destroyStorage(void* slot) const { delete static_cast<double*>(slot); }
// color
const char* ColorIpcType::name() const { return "color"; }
const char* ColorIpcType::genericArgumentName() const { return "QColor"; }
qsizetype ColorIpcType::size() const { return sizeof(QColor); }
void* ColorIpcType::fromString(const QString& string) const {
auto color = QColor::fromString(string);
if (!color.isValid() && !string.startsWith('#')) {
color = QColor::fromString('#' % string);
}
return color.isValid() ? new QColor(color) : nullptr;
}
QString ColorIpcType::toString(void* slot) const {
return static_cast<QColor*>(slot)->name(QColor::HexArgb);
}
void* ColorIpcType::createStorage() const { return new bool(); }
void ColorIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
QString WireFunctionDefinition::toString() const {
QString paramString;
for (const auto& [name, type]: this->arguments) {
+15 -110
View File
@@ -1,130 +1,35 @@
#pragma once
#include <qcontainerfwd.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
#include <qtclasshelpermacros.h>
#include <qtypes.h>
#include <qvariant.h>
#include "../ipc/ipc.hpp"
namespace qs::io::ipc {
class IpcTypeSlot;
class IpcType {
class IpcValueSlot {
public:
IpcType() = default;
virtual ~IpcType() = default;
IpcType(const IpcType&) = default;
IpcType(IpcType&&) = default;
IpcType& operator=(const IpcType&) = default;
IpcType& operator=(IpcType&&) = default;
IpcValueSlot() = default;
explicit IpcValueSlot(QMetaType type): mValue(type) {}
[[nodiscard]] virtual const char* name() const = 0;
[[nodiscard]] virtual const char* genericArgumentName() const = 0;
[[nodiscard]] virtual qsizetype size() const = 0;
[[nodiscard]] virtual void* fromString(const QString& /*string*/) const { return nullptr; }
[[nodiscard]] virtual QString toString(void* /*slot*/) const { return ""; }
[[nodiscard]] virtual void* createStorage() const { return nullptr; }
virtual void destroyStorage(void* /*slot*/) const {}
void* copyStorage(const void* data) const;
[[nodiscard]] static bool isSupported(const QMetaType& type);
[[nodiscard]] static const char* typeName(const QMetaType& type);
[[nodiscard]] const char* name() const;
[[nodiscard]] bool isVoid() const;
[[nodiscard]] const QVariant& value() const;
static const IpcType* ipcType(const QMetaType& metaType);
};
bool setString(const QString& string);
void setValue(QVariant value);
[[nodiscard]] QString toString() const;
class IpcTypeSlot {
public:
explicit IpcTypeSlot(const IpcType* type = nullptr): mType(type) {}
~IpcTypeSlot();
Q_DISABLE_COPY(IpcTypeSlot);
IpcTypeSlot(IpcTypeSlot&& other) noexcept;
IpcTypeSlot& operator=(IpcTypeSlot&& other) noexcept;
[[nodiscard]] const IpcType* type() const;
[[nodiscard]] void* get();
[[nodiscard]] QGenericArgument asGenericArgument();
[[nodiscard]] QGenericArgument asGenericArgument() const;
[[nodiscard]] QGenericReturnArgument asGenericReturnArgument();
void replace(void* value);
void replace(const QVariant& value);
private:
const IpcType* mType = nullptr;
void* storage = nullptr;
};
class VoidIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
static const VoidIpcType INSTANCE;
};
class StringIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const StringIpcType INSTANCE;
};
class IntIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const IntIpcType INSTANCE;
};
class BoolIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const BoolIpcType INSTANCE;
};
class DoubleIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const DoubleIpcType INSTANCE;
};
class ColorIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const ColorIpcType INSTANCE;
QVariant mValue;
};
struct WireFunctionDefinition {
+3 -3
View File
@@ -173,7 +173,7 @@ void StringCallCommand::exec(qs::ipc::IpcServerConnection* conn) const {
func->invoke(handler, storage);
resp << Completed {
.isVoid = func->returnType == &VoidIpcType::INSTANCE,
.isVoid = func->returnType.isVoid(),
.returnValue = storage.getReturnStr(),
};
} else {
@@ -269,11 +269,11 @@ void StringPropReadCommand::exec(qs::ipc::IpcServerConnection* conn) const {
return;
}
auto slot = IpcTypeSlot(prop->type);
auto slot = IpcValueSlot(prop->type);
prop->read(handler, slot);
resp << PropertyValue {
.value = slot.type()->toString(slot.get()),
.value = slot.toString(),
};
} else {
conn->respond(StringCallResponse(NoCurrentGeneration()));
+36 -39
View File
@@ -34,9 +34,8 @@ bool IpcFunction::resolve(QString& error) {
for (auto i = 0; i < this->method.parameterCount(); i++) {
const auto& metaType = this->method.parameterMetaType(i);
const auto* type = IpcType::ipcType(metaType);
if (type == nullptr) {
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Type of argument %1 (%2: %3) cannot be used across IPC.")
.arg(i + 1)
.arg(this->method.parameterNames().value(i))
@@ -45,23 +44,20 @@ bool IpcFunction::resolve(QString& error) {
return false;
}
this->argumentTypes.append(type);
this->argumentTypes.emplaceBack(metaType);
}
const auto& metaType = this->method.returnMetaType();
const auto* type = IpcType::ipcType(metaType);
auto metaType = this->method.returnMetaType();
if (type == nullptr) {
// void and var get mixed by qml engine in return types
if (metaType.id() == QMetaType::QVariant) type = &VoidIpcType::INSTANCE;
// void and var get mixed by qml engine in return types
if (metaType.id() == QMetaType::QVariant) metaType = QMetaType::fromType<void>();
if (type == nullptr) {
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
return false;
}
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
return false;
}
this->returnType = type;
this->returnType = IpcValueSlot(metaType);
return true;
}
@@ -92,52 +88,53 @@ QString IpcFunction::toString() const {
QString paramString;
auto paramNames = this->method.parameterNames();
for (auto i = 0; i < this->argumentTypes.length(); i++) {
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i)->name();
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i).name();
if (i + 1 != this->argumentTypes.length()) {
paramString += ", ";
}
}
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType->name();
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType.name();
}
WireFunctionDefinition IpcFunction::wireDef() const {
WireFunctionDefinition wire;
wire.name = this->method.name();
wire.returnType = this->returnType->name();
wire.returnType = this->returnType.name();
auto paramNames = this->method.parameterNames();
for (auto i = 0; i < this->argumentTypes.length(); i++) {
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i)->name());
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i).name());
}
return wire;
}
bool IpcProperty::resolve(QString& error) {
this->type = IpcType::ipcType(this->property.metaType());
const auto& metaType = this->property.metaType();
if (!this->type) {
error = QString("Type %1 cannot be used across IPC.").arg(this->property.metaType().name());
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Type %1 cannot be used across IPC.").arg(metaType.name());
return false;
}
this->type = metaType;
return true;
}
void IpcProperty::read(QObject* target, IpcTypeSlot& slot) const {
slot.replace(this->property.read(target));
void IpcProperty::read(QObject* target, IpcValueSlot& slot) const {
slot.setValue(this->property.read(target));
}
QString IpcProperty::toString() const {
return QString("property ") % this->property.name() % ": " % this->type->name();
return QString("property ") % this->property.name() % ": " % IpcValueSlot::typeName(this->type);
}
WirePropertyDefinition IpcProperty::wireDef() const {
WirePropertyDefinition wire;
wire.name = this->property.name();
wire.type = this->type->name();
wire.type = IpcValueSlot::typeName(this->type);
return wire;
}
@@ -217,21 +214,15 @@ IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function
}
bool IpcCallStorage::setArgumentStr(size_t i, const QString& value) {
auto& slot = this->argumentSlots.at(i);
auto* data = slot.type()->fromString(value);
slot.replace(data);
return data != nullptr;
return this->argumentSlots.at(i).setString(value);
}
QString IpcCallStorage::getReturnStr() {
return this->returnSlot.type()->toString(this->returnSlot.get());
}
QString IpcCallStorage::getReturnStr() { return this->returnSlot.toString(); }
IpcHandler::~IpcHandler() {
if (this->registeredState.enabled) {
this->targetState.enabled = false;
this->updateRegistration(true);
this->updateRegistration();
}
}
@@ -310,28 +301,34 @@ void IpcHandler::onSignalTriggered(const QString& signal, const QString& value)
-> triggered(this->registeredState.target, signal, value);
}
void IpcHandler::updateRegistration(bool destroying) {
void IpcHandler::updateRegistration() {
if (!this->complete) return;
auto* generation = EngineGeneration::findObjectGeneration(this);
IpcHandlerRegistry* registry = nullptr;
if (this->registeredState.enabled) {
registry = this->registry.get();
} else {
auto* generation = EngineGeneration::findObjectGeneration(this);
if (!generation) {
if (!destroying) {
if (!generation) {
qmlWarning(this) << "Unable to identify engine generation, cannot register.";
return;
}
return;
registry = IpcHandlerRegistry::forGeneration(generation);
}
auto* registry = IpcHandlerRegistry::forGeneration(generation);
if (!registry) return;
if (this->registeredState.enabled) {
registry->deregisterHandler(this);
this->registry = nullptr;
qCDebug(logIpcHandler) << "Deregistered" << this << "from registry" << registry;
}
if (this->targetState.enabled && !this->targetState.target.isEmpty()) {
registry->registerHandler(this);
this->registry = registry;
qCDebug(logIpcHandler) << "Registered" << this << "to registry" << registry;
}
}
+16 -9
View File
@@ -11,6 +11,7 @@
#include <qmetaobject.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qpointer.h>
#include <qqmlintegration.h>
#include <qqmlparserstatus.h>
#include <qtclasshelpermacros.h>
@@ -37,8 +38,8 @@ public:
[[nodiscard]] WireFunctionDefinition wireDef() const;
QMetaMethod method;
QVector<const IpcType*> argumentTypes;
const IpcType* returnType = nullptr;
QVector<IpcValueSlot> argumentTypes;
IpcValueSlot returnType;
};
class IpcCallStorage {
@@ -49,8 +50,8 @@ public:
[[nodiscard]] QString getReturnStr();
private:
std::vector<IpcTypeSlot> argumentSlots;
IpcTypeSlot returnSlot;
std::vector<IpcValueSlot> argumentSlots;
IpcValueSlot returnSlot;
friend class IpcFunction;
};
@@ -60,13 +61,13 @@ public:
explicit IpcProperty(QMetaProperty property): property(property) {}
bool resolve(QString& error);
void read(QObject* target, IpcTypeSlot& slot) const;
void read(QObject* target, IpcValueSlot& slot) const;
[[nodiscard]] QString toString() const;
[[nodiscard]] WirePropertyDefinition wireDef() const;
QMetaProperty property;
const IpcType* type = nullptr;
QMetaType type;
};
class IpcSignalListener: public QObject {
@@ -185,7 +186,7 @@ class IpcHandlerRegistry;
///
/// function getRadius(): int { return rect.radius; }
///
/// signal radiusChanged(newRadius: int);
/// signal radiusChanged(newRadius: int);
/// }
/// }
/// ```
@@ -254,7 +255,7 @@ public slots:
void onSignalTriggered(const QString& signal, const QString& value) const;
private:
void updateRegistration(bool destroying = false);
void updateRegistration();
struct RegistrationState {
explicit RegistrationState(bool enabled = false): enabled(enabled) {}
@@ -271,10 +272,16 @@ private:
QHash<QString, IpcProperty> propertyMap;
QHash<QString, IpcSignal> signalMap;
QPointer<IpcHandlerRegistry> registry;
friend class IpcHandlerRegistry;
};
class IpcHandlerRegistry: public EngineGenerationExt {
class IpcHandlerRegistry
: public QObject
, public EngineGenerationExt {
Q_OBJECT;
public:
static IpcHandlerRegistry* forGeneration(EngineGeneration* generation);
+5 -5
View File
@@ -225,15 +225,15 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
const auto& jsonValue = array.at(i);
if (jsonValue.isObject()) {
if (isNew) {
currentValue = lp.at(&lp, i);
if (this->oldCreatedObjects.removeOne(currentValue)) {
this->createdObjects.push_back(currentValue);
}
} else {
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
currentValue = static_cast<JsonObject*>(QMetaType::fromType<JsonObject>().create());
currentValue->setParent(this);
this->createdObjects.push_back(currentValue);
} else {
currentValue = lp.at(&lp, i);
if (this->oldCreatedObjects.removeOne(currentValue)) {
this->createdObjects.push_back(currentValue);
}
}
this->deserializeRec(
+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;
+12 -3
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();
}
@@ -105,11 +106,19 @@ IpcClient::IpcClient(const QString& path) {
bool IpcClient::isConnected() const { return this->socket.isValid(); }
void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
void IpcClient::waitForDisconnected() {
this->waitingForDisconnect = true;
this->socket.waitForDisconnected();
}
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
void IpcClient::kill() {
this->sendMessage(IpcCommand(IpcKillCommand()));
this->waitForDisconnected();
}
void IpcClient::onError(QLocalSocket::LocalSocketError error) const {
if (this->waitingForDisconnect && error == QLocalSocket::PeerClosedError) return;
qCCritical(logIpc) << "Socket Error" << error;
}
+4 -1
View File
@@ -216,7 +216,10 @@ signals:
void disconnected();
private slots:
static void onError(QLocalSocket::LocalSocketError error);
void onError(QLocalSocket::LocalSocketError error) const;
private:
bool waitingForDisconnect = false;
};
} // namespace qs::ipc
+67 -49
View File
@@ -2,6 +2,7 @@
#include <array>
#include <cerrno>
#include <cstring>
#include <functional>
#include <utility>
#include <qconfig.h>
@@ -164,7 +165,12 @@ void sortInstances(QVector<InstanceLockInfo>& list, bool newestFirst) {
});
};
int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallback = false) {
int selectInstance(
CommandState& cmd,
InstanceLockInfo* instance,
bool deadFallback = false,
bool logHelp = true
) {
auto* basePath = QsPaths::instance()->baseRunDir();
if (!basePath) return -1;
@@ -173,7 +179,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
if (cmd.instance.pid != -1) {
path = QDir(basePath->filePath("by-pid")).filePath(QString::number(cmd.instance.pid));
if (!QsPaths::checkLock(path, instance, deadFallback)) {
qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
if (logHelp) qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
return -1;
}
} else if (!cmd.instance.id->isEmpty()) {
@@ -196,39 +202,43 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
if (instances.isEmpty()) {
if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances on the current display" << getDisplayConnection()
<< "start with" << *cmd.instance.id;
if (logHelp) {
if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances on the current display" << getDisplayConnection()
<< "start with" << *cmd.instance.id;
qCInfo(logBare) << "Some instances on other displays match:";
qCInfo(logBare) << "Some instances on other displays match:";
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace()
<< " - " << instance.instance.instanceId << " (" << instance.instance.display << ')';
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace() << " - " << instance.instance.instanceId << " ("
<< instance.instance.display << ')';
}
}
}
if (deadFallback) {
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
} else {
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
if (deadFallback) {
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
} else {
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
if (!deadInstances.isEmpty()) {
qCInfo(logBare) << "Some dead instances match:";
if (!deadInstances.isEmpty()) {
qCInfo(logBare) << "Some dead instances match:";
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
}
}
}
return -1;
} else if (instances.length() != 1) {
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
if (logHelp) {
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
for (auto& instance: instances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId
<< (instance.pid == -1 ? "(dead)" : "");
for (auto& instance: instances) {
qCInfo(logBare).noquote()
<< " -" << instance.instance.instanceId << (instance.pid == -1 ? "(dead)" : "");
}
}
return -1;
@@ -259,26 +269,29 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
);
if (instances.isEmpty()) {
if (liveInstances.isEmpty() && deadInstances.length() > 1) {
qCInfo(logBare) << "No running instances for" << configFilePath;
qCInfo(logBare) << "Dead instances:";
sortInstances(deadInstances, cmd.config.newest);
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
} else if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances for" << configFilePath
<< " present on the current display" << getDisplayConnection();
if (logHelp) {
if (liveInstances.isEmpty() && deadInstances.length() > 1) {
qCInfo(logBare) << "No running instances for" << configFilePath;
qCInfo(logBare) << "Dead instances:";
sortInstances(deadInstances, cmd.config.newest);
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
} else if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances for" << configFilePath
<< " present on the current display" << getDisplayConnection();
qCInfo(logBare) << "Some instances on other displays match:";
qCInfo(logBare) << "Some instances on other displays match:";
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace()
<< " - " << instance.instance.instanceId << " (" << instance.instance.display << ')';
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace() << " - " << instance.instance.instanceId << " ("
<< instance.instance.display << ')';
}
} else {
qCInfo(logBare) << "No running instances for" << configFilePath;
}
} else {
qCInfo(logBare) << "No running instances for" << configFilePath;
}
return -1;
}
@@ -453,7 +466,7 @@ int ipcCommand(CommandState& cmd) {
});
}
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
int launchFromCommand(CommandState& cmd) {
QString configPath;
auto r = locateConfigFile(cmd, configPath);
@@ -461,7 +474,7 @@ int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
{
InstanceLockInfo info;
if (cmd.misc.noDuplicate && selectInstance(cmd, &info) == 0) {
if (cmd.misc.noDuplicate && selectInstance(cmd, &info, false, false) == 0) {
qCInfo(logBare) << "An instance of this configuration is already running.";
return 0;
}
@@ -473,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;
@@ -543,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
@@ -565,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
+21 -17
View File
@@ -24,23 +24,31 @@ 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();
if (!lastInfoFdStr.isEmpty()) {
auto lastInfoFd = lastInfoFdStr.toInt();
QFile file;
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qFatal() << "Failed to open crash info fd. Cannot restart.";
RelaunchInfo info;
{
QFile file;
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qFatal() << "Failed to open crash info fd. Cannot restart.";
}
file.seek(0);
auto ds = QDataStream(&file);
ds >> info;
}
file.seek(0);
auto ds = QDataStream(&file);
RelaunchInfo info;
ds >> info;
qunsetenv("__QUICKSHELL_CRASH_INFO_FD");
qunsetenv("__QUICKSHELL_CRASH_DUMP_PID");
qunsetenv("__QUICKSHELL_CRASH_SIGNAL");
LogManager::init(
!info.noColor,
@@ -50,8 +58,7 @@ void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
info.logRules
);
qCritical().nospace() << "Quickshell has crashed under pid "
<< qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt()
qCritical().nospace() << "Quickshell has crashed under pid " << dumpPid
<< " (Coredumps will be available under that pid.)";
qCritical() << "Further crash information is stored under"
@@ -64,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
@@ -116,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();
+7 -5
View File
@@ -72,9 +72,10 @@ void NMNetwork::addSettings(NMSettings* settings) {
auto onDestroyed = [this, settings]() {
if (this->mSettings.take(settings->path())) {
emit this->settingsRemoved(settings);
this->updateReferenceSettings();
if (this->mSettings.isEmpty()) this->bKnown = false;
// Deletes `this`
emit this->settingsRemoved(settings);
}
};
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
@@ -185,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;
}
@@ -240,8 +241,9 @@ void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
this->mAccessPoints.insert(ap->path(), ap);
auto onDestroyed = [this, ap]() {
if (this->mAccessPoints.take(ap->path())) {
emit this->apRemoved(ap);
this->updateReferenceAp();
// Deletes `this`
emit this->apRemoved(ap);
}
};
// clang-format off
+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)
+2 -2
View File
@@ -325,8 +325,8 @@ public:
void setLoopState(MprisLoopState::Enum loopState);
[[nodiscard]] qreal rate() const { return this->bRate; }
[[nodiscard]] QBindable<qreal> bindableMinRate() const { return &this->bRate; }
[[nodiscard]] QBindable<qreal> bindableMaxRate() const { return &this->bRate; }
[[nodiscard]] QBindable<qreal> bindableMinRate() const { return &this->bMinRate; }
[[nodiscard]] QBindable<qreal> bindableMaxRate() const { return &this->bMaxRate; }
void setRate(qreal rate);
[[nodiscard]] bool shuffle() const { return this->bShuffle; }
+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();
+15 -3
View File
@@ -8,6 +8,8 @@
#include <qdbusservicewatcher.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobjectdefs.h>
#include <qqmlengine.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -192,19 +194,29 @@ uint NotificationServer::Notify(
notification->updateProperties(appName, appIcon, summary, body, actions, hints, expireTimeout);
auto id = notification->id();
if (!old) {
emit this->notification(notification);
if (!notification->isTracked()) {
emit this->NotificationClosed(notification->id(), notification->closeReason());
auto closeReason = notification->closeReason();
// Emit NotificationClosed after Notify returns
QMetaObject::invokeMethod(
this,
[this, id, closeReason]() { emit this->NotificationClosed(id, closeReason); },
Qt::QueuedConnection
);
delete notification;
} else {
this->idMap.insert(notification->id(), notification);
this->idMap.insert(id, notification);
this->mNotifications.insertObject(notification);
}
}
return notification->id();
return id;
}
} // namespace qs::service::notifications
+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);
+23 -2
View File
@@ -245,13 +245,14 @@ void PwPeakStream::handleProcess() {
if (!this->formatReady || this->stream == nullptr) return;
auto* buffer = pw_stream_dequeue_buffer(this->stream);
auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); });
if (buffer == nullptr) {
qCWarning(logPeak) << "Peak monitor ran out of buffers.";
return;
}
auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); });
auto* spaBuffer = buffer->buffer;
if (spaBuffer == nullptr || spaBuffer->n_datas < 1) {
return;
@@ -277,7 +278,27 @@ void PwPeakStream::handleProcess() {
QVector<float> volumes;
if (auto* audioData = dynamic_cast<PwNodeBoundAudio*>(this->node->boundData)) {
if (!this->node->shouldUseDevice()) volumes = audioData->volumes();
// Device volumes don't require inverse scaling
if (!this->node->shouldUseDevice()) {
const auto& nchannels = audioData->channels();
const auto& nvolumes = audioData->volumes();
for (const auto channel: this->monitor->mChannels) {
for (auto i = 0; i != nchannels.length(); i++) {
if (nchannels[i] == channel) {
volumes.push_back(nvolumes[i]);
break;
}
}
}
if (volumes.length() != channelCount) {
qCCritical(logPeak) << this->node
<< "is missing channels present in capture stream. Node channels:"
<< nchannels << "Stream channels:" << this->monitor->mChannels;
return;
}
}
}
this->channelPeaks.fill(0.0f, channelCount);
+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() {
+3 -1
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();
@@ -195,7 +197,7 @@ private:
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, DBusSniIconPixmapList, bAttentionIconPixmaps);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QString, bAttentionMovieName);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, DBusSniTooltip, bTooltip);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bIsMenu);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bIsMenu, &StatusNotifierItem::onlyMenuChanged);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QDBusObjectPath, bMenuPath);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bHasMenu, &StatusNotifierItem::hasMenuChanged);
@@ -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)
+12 -2
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
@@ -105,7 +115,7 @@ if (WAYLAND_SESSION_LOCK)
endif()
if (WAYLAND_TOPLEVEL_MANAGEMENT)
add_subdirectory(toplevel_management)
add_subdirectory(toplevel)
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ToplevelManagement)
endif()
@@ -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")
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff. Show more