mirror of
https://github.com/quickshell-mirror/quickshell.git
synced 2026-10-28 02:55:05 +00:00
Compare commits
107
Commits
wip-ext-workspace
...
0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f508be500 | ||
|
|
11ca60be22 | ||
|
|
8fd0e25844 | ||
|
|
5d5d49873f | ||
|
|
41651d7dcd | ||
|
|
4594680eb4 | ||
|
|
25da30db02 | ||
|
|
fae96f1a5b | ||
|
|
c6a516096d | ||
|
|
86b4275879 | ||
|
|
9b4b206961 | ||
|
|
92cb8909eb | ||
|
|
2d3b3e9c70 | ||
|
|
ec0be5b36f | ||
|
|
fd116897cf | ||
|
|
916a0dd90c | ||
|
|
0f9939ca4f | ||
|
|
052059fb7c | ||
|
|
39e14d3627 | ||
|
|
9f59d0a05d | ||
|
|
afb2c27cd6 | ||
|
|
0fed22a2c4 | ||
|
|
1a4716cde7 | ||
|
|
069e33a93e | ||
|
|
532a0606ca | ||
|
|
78672a8405 | ||
|
|
990a8badab | ||
|
|
7e6c7ae3fb | ||
|
|
8f1316d0c2 | ||
|
|
63eb2ba6ef | ||
|
|
b1fc3e0740 | ||
|
|
21fec77de7 | ||
|
|
d9cbc90a8c | ||
|
|
28771c7c74 | ||
|
|
43d4fa9e88 | ||
|
|
10b439fc6e | ||
|
|
e649d24749 | ||
|
|
025c709e94 | ||
|
|
4df562dfb2 | ||
|
|
98b6d0b7e7 | ||
|
|
25eb04fb06 | ||
|
|
ead3b00afd | ||
|
|
a611932352 | ||
|
|
1661ef100a | ||
|
|
68c2c85c33 | ||
|
|
897fcdaa99 | ||
|
|
d99d87d5e5 | ||
|
|
b66495fcc5 | ||
|
|
eaba6c8b62 | ||
|
|
4b4fca3224 | ||
|
|
7d1c9a9c67 | ||
|
|
d1760ed1f3 | ||
|
|
b93d61385f | ||
|
|
59e9c47b0e | ||
|
|
ffe95f06ac | ||
|
|
8db8ca1fec | ||
|
|
0baa81aa03 | ||
|
|
01834e9c8f | ||
|
|
2ba2ae48b4 | ||
|
|
055e384668 | ||
|
|
11a71d233a | ||
|
|
e162429b6f | ||
|
|
783c953987 | ||
|
|
b850b8a1a9 | ||
|
|
d60498adc0 | ||
|
|
9a54119893 | ||
|
|
18075d1218 | ||
|
|
fb08eced44 | ||
|
|
d4c92973b5 | ||
|
|
7f7ab6bc8a | ||
|
|
7208f68bb7 | ||
|
|
f0d0216b3d | ||
|
|
7c5a6c4bd4 | ||
|
|
5bf6a412b0 | ||
|
|
13fe9b0d98 | ||
|
|
ad5fd9116e | ||
|
|
49d4f46cf1 | ||
|
|
9b98d10178 | ||
|
|
854088c48c | ||
|
|
b4e71cb2c0 | ||
|
|
ceac3c6cfa | ||
|
|
aaff22f4b0 | ||
|
|
50cdf98868 | ||
|
|
4b751ccb0d | ||
|
|
20c691cdf1 | ||
|
|
92b336c80c | ||
|
|
d612227740 | ||
|
|
b83c39c8af | ||
|
|
ee1100eb98 | ||
|
|
9bf752ac33 | ||
|
|
313f4e47f6 | ||
|
|
6ef86dd5aa | ||
|
|
308f1e249b | ||
|
|
08058326f0 | ||
|
|
6a244c3c56 | ||
|
|
d745184823 | ||
|
|
77c04a9447 | ||
|
|
eb6eaf59c7 | ||
|
|
7511545ee2 | ||
|
|
0cb62920a7 | ||
|
|
3520c85d77 | ||
|
|
3cf65af49f | ||
|
|
a51dcd0a01 | ||
|
|
97b2688ad6 | ||
|
|
0a859d51f2 | ||
|
|
1bd5b083cb | ||
|
|
365bf16b1e |
No files matched your search
@@ -1,82 +1,17 @@
|
||||
name: Crash Report (v1)
|
||||
description: Quickshell has crashed
|
||||
labels: ["bug", "crash"]
|
||||
description: Quickshell has crashed (old)
|
||||
labels: ["unactionable"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: crashinfo
|
||||
- type: markdown
|
||||
attributes:
|
||||
label: General crash information
|
||||
description: |
|
||||
Paste the contents of the `info.txt` file in your crash folder here.
|
||||
value: "<details> <summary>General information</summary>
|
||||
|
||||
|
||||
```
|
||||
|
||||
<Paste the contents of the file here inside of the triple backticks>
|
||||
|
||||
```
|
||||
|
||||
|
||||
</details>"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: userinfo
|
||||
value: |
|
||||
Thank you for taking the time to click the report button.
|
||||
At this point most of the worst issues in 0.2.1 and before have been fixed and we are
|
||||
preparing for a new release. Please do not submit this report.
|
||||
- type: checkboxes
|
||||
id: donotcheck
|
||||
attributes:
|
||||
label: What caused the crash
|
||||
description: |
|
||||
Any information likely to help debug the crash. What were you doing when the crash occurred,
|
||||
what changes did you make, can you get it to happen again?
|
||||
- type: textarea
|
||||
id: dump
|
||||
attributes:
|
||||
label: Minidump
|
||||
description: |
|
||||
Attach `minidump.dmp.log` here. If it is too big to upload, compress it.
|
||||
|
||||
You may skip this step if quickshell crashed while processing a password
|
||||
or other sensitive information. If you skipped it write why instead.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log file
|
||||
description: |
|
||||
Attach `log.qslog.log` here. If it is too big to upload, compress it.
|
||||
|
||||
You can preview the log if you'd like using `quickshell read-log <path-to-log>`.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: |
|
||||
Attach your configuration here, preferrably in full (not just one file).
|
||||
Compress it into a zip, tar, etc.
|
||||
|
||||
This will help us reproduce the crash ourselves.
|
||||
- type: textarea
|
||||
id: bt
|
||||
attributes:
|
||||
label: Backtrace
|
||||
description: |
|
||||
If you have gdb installed and use systemd, or otherwise know how to get a backtrace,
|
||||
we would appreciate one. (You may have gdb installed without knowing it)
|
||||
|
||||
1. Run `coredumpctl debug <pid>` where `pid` is the number shown after "Crashed process ID"
|
||||
in the crash reporter.
|
||||
2. Once it loads, type `bt -full` (then enter)
|
||||
3. Copy the output and attach it as a file or in a spoiler.
|
||||
- type: textarea
|
||||
id: exe
|
||||
attributes:
|
||||
label: Executable
|
||||
description: |
|
||||
If the crash folder contains a executable.txt file, upload it here. If not you can ignore this field.
|
||||
If it is too big to upload, compress it.
|
||||
|
||||
Note: executable.txt is the quickshell binary. It has a .txt extension due to github's limitations on
|
||||
filetypes.
|
||||
label: Read the text above. Do not submit the report.
|
||||
options:
|
||||
- label: Yes I want this report to be deleted.
|
||||
required: true
|
||||
@@ -9,21 +9,15 @@ body:
|
||||
description: |
|
||||
Any information likely to help debug the crash. What were you doing when the crash occurred,
|
||||
what changes did you make, can you get it to happen again?
|
||||
- type: textarea
|
||||
- type: upload
|
||||
id: report
|
||||
attributes:
|
||||
label: Report file
|
||||
description: Attach `report.txt` here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
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 `quickshell read-log <path-to-log>`.
|
||||
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,7 +25,7 @@ body:
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: |
|
||||
Attach your configuration here, preferrably in full (not just one file).
|
||||
Attach or link your configuration here, preferrably in full (not just one file).
|
||||
Compress it into a zip, tar, etc.
|
||||
|
||||
This will help us reproduce the crash ourselves.
|
||||
|
||||
@@ -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.
|
||||
-->
|
||||
@@ -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:
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
|
||||
project(quickshell VERSION "0.3.2" LANGUAGES CXX C)
|
||||
|
||||
set(UNRELEASED_FEATURES)
|
||||
|
||||
@@ -87,8 +87,9 @@ include(cmake/util.cmake)
|
||||
|
||||
add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension)
|
||||
|
||||
# pipewire defines this, breaking PCH
|
||||
# pipewire defines these, breaking PCH
|
||||
add_compile_definitions(_REENTRANT)
|
||||
add_compile_options(-fno-strict-overflow)
|
||||
|
||||
if (FRAME_POINTERS)
|
||||
add_compile_options(-fno-omit-frame-pointer)
|
||||
|
||||
@@ -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**.
|
||||
|
||||
@@ -20,17 +20,21 @@ set shell id.
|
||||
- Added the ability to handle move and resize events to FloatingWindow.
|
||||
- Pipewire service now reconnects if pipewire dies or a protocol error occurs.
|
||||
- Added pipewire audio peak detection.
|
||||
- Added initial support for network management.
|
||||
- Added network management support.
|
||||
- Added support for grabbing focus from popup windows.
|
||||
- Added support for IPC signal listeners.
|
||||
- Added Quickshell version checking and version gated preprocessing.
|
||||
- Added a way to detect if an icon is from the system icon theme or not.
|
||||
- Added vulkan support to screencopy.
|
||||
- Added generic WindowManager interface implementing ext-workspace.
|
||||
- Added ext-background-effect window blur support.
|
||||
- Added per-corner radius support to Region.
|
||||
- Added ColorQuantizer region selection.
|
||||
- Added dialog window support to FloatingWindow.
|
||||
- Added lua config support to Hyprland module.
|
||||
|
||||
## Other Changes
|
||||
|
||||
- FreeBSD is now partially supported.
|
||||
- IPC operations filter available instances to the current display connection by default.
|
||||
- PwNodeLinkTracker ignores sound level monitoring programs.
|
||||
- Replaced breakpad with cpptrace.
|
||||
@@ -38,6 +42,10 @@ set shell id.
|
||||
- Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching.
|
||||
- Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling.
|
||||
- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
|
||||
- Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID.
|
||||
- Added `DropExpensiveFonts` pragma and `QS_DROP_EXPENSIVE_FONTS` environment variable which avoids loading fonts which may cause lag and excessive memory usage if many variants are used.
|
||||
- Added `DefaultEnv` pragma which sets an environment variable if not already set.
|
||||
- Unrecognized pragmas are no longer a hard error for future backward compatibility.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
@@ -48,6 +56,7 @@ set shell id.
|
||||
- Fixed volumes not initializing if a pipewire device was already loaded before its node.
|
||||
- Fixed hyprland active toplevel not resetting after window closes.
|
||||
- Fixed hyprland ipc window names and titles being reversed.
|
||||
- Fixed a hyprland ipc crash when refreshing toplevels before workspaces.
|
||||
- Fixed missing signals for system tray item title and description updates.
|
||||
- Fixed asynchronous loaders not working after reload.
|
||||
- Fixed asynchronous loaders not working before window creation.
|
||||
@@ -58,6 +67,18 @@ set shell id.
|
||||
- Fixed ToplevelManager not clearing activeToplevel on deactivation.
|
||||
- Desktop action order is now preserved.
|
||||
- Fixed partial socket reads in greetd and hyprland on slow machines.
|
||||
- Worked around Qt bug causing crashes when plugging and unplugging monitors.
|
||||
- Fixed HyprlandFocusGrab crashing if windows were destroyed after being passed to it.
|
||||
- Fixed ScreencopyView pixelation when scaled.
|
||||
- Fixed JsonAdapter crashing and providing bad data on read when using JsonObject.
|
||||
- Fixed JsonAdapter sending unnecessary property changes for primitive values.
|
||||
- Fixed JsonAdapter serialization for lists.
|
||||
- Fixed pipewire crashes after hotplugging devices and changing default outputs.
|
||||
- Fixed launches failing for `--daemonize` on some systems.
|
||||
- Fixed screencopy crashing when used across GPUs.
|
||||
- Fixed pipewire volumes not working for some pw-pulse clients.
|
||||
- Fixed nulls in Toplevel.screens after unplugging a monitor.
|
||||
- Fixed some DbusMenu updates being dropped from apps.
|
||||
|
||||
## Packaging Changes
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,17 @@
|
||||
## Other Changes
|
||||
|
||||
- Added support for Qt 6.12
|
||||
- Added support for Hyprland's new workspace address format.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Fixed main process crashes on pam subprocess misbehavior.
|
||||
- Fixed crashes when attempting to create or modify session locks reentrantly.
|
||||
- Fixed networking state breaking after restarting NetworkManager.
|
||||
- Fixed accessibility information not being provided for Quickshell windows.
|
||||
- Fixed `WlSessionLockSurface.screen` being null and never updating with a real screen on monitor plug.
|
||||
- Fixed `NotificationAction.text` not accepting text updates.
|
||||
- Fixed PipeWire route volume writes for devices without a reported volume step.
|
||||
- Fixed a crash when quitting or reloading while a `Loader` was still loading asynchronously.
|
||||
- Fixed future `Socket` connection attempts after a failed connection attempt.
|
||||
- Fixed Open wifi networks showing as Unknown security.
|
||||
+1
-1
@@ -56,7 +56,7 @@
|
||||
|
||||
unwrapped = stdenv.mkDerivation {
|
||||
pname = "quickshell${lib.optionalString debug "-debug"}";
|
||||
version = "0.2.1";
|
||||
version = "0.3.2";
|
||||
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
|
||||
|
||||
dontWrapQtApps = true; # see wrappers
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
(define-module (quickshell)
|
||||
#:use-module ((guix licenses) #:prefix license:)
|
||||
#:use-module (gnu packages cpp)
|
||||
#:use-module (gnu packages freedesktop)
|
||||
#:use-module (gnu packages gcc)
|
||||
#:use-module (gnu packages gl)
|
||||
#:use-module (gnu packages jemalloc)
|
||||
#:use-module (gnu packages linux)
|
||||
#:use-module (gnu packages ninja)
|
||||
#:use-module (gnu packages pkg-config)
|
||||
#:use-module (gnu packages qt)
|
||||
#:use-module (gnu packages vulkan)
|
||||
#:use-module (gnu packages xdisorg)
|
||||
#:use-module (gnu packages xorg)
|
||||
#:use-module (guix build-system cmake)
|
||||
#:use-module (guix download)
|
||||
#:use-module (guix gexp)
|
||||
#:use-module (guix git-download)
|
||||
#:use-module (guix packages)
|
||||
#:use-module (guix packages)
|
||||
#:use-module (guix utils))
|
||||
|
||||
(define-public quickshell-git
|
||||
(package
|
||||
(name "quickshell")
|
||||
(version "git")
|
||||
(source (local-file "." "quickshell-checkout"
|
||||
#:recursive? #t
|
||||
#:select? (or (git-predicate (current-source-directory))
|
||||
(const #t))))
|
||||
(build-system cmake-build-system)
|
||||
(propagated-inputs (list qtbase qtdeclarative qtsvg))
|
||||
(native-inputs (list ninja
|
||||
gcc-14
|
||||
pkg-config
|
||||
qtshadertools
|
||||
spirv-tools
|
||||
wayland-protocols
|
||||
cli11))
|
||||
(inputs (list jemalloc
|
||||
libdrm
|
||||
libxcb
|
||||
libxkbcommon
|
||||
linux-pam
|
||||
polkit
|
||||
mesa
|
||||
pipewire
|
||||
qtbase
|
||||
qtdeclarative
|
||||
qtwayland
|
||||
vulkan-headers
|
||||
wayland))
|
||||
(arguments
|
||||
(list #:tests? #f
|
||||
#:configure-flags
|
||||
#~(list "-GNinja"
|
||||
"-DDISTRIBUTOR=\"In-tree Guix channel\""
|
||||
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
|
||||
"-DCRASH_HANDLER=OFF")
|
||||
#:phases
|
||||
#~(modify-phases %standard-phases
|
||||
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
|
||||
(replace 'install (lambda _ (invoke "cmake" "--install" ".")))
|
||||
(add-after 'install 'wrap-program
|
||||
(lambda* (#:key inputs #:allow-other-keys)
|
||||
(wrap-program (string-append #$output "/bin/quickshell")
|
||||
`("QML_IMPORT_PATH" ":"
|
||||
= (,(getenv "QML_IMPORT_PATH")))))))))
|
||||
(home-page "https://quickshell.outfoxxed.me")
|
||||
(synopsis "QtQuick-based desktop shell toolkit")
|
||||
(description
|
||||
"Quickshell is a flexible QtQuick-based toolkit for creating and
|
||||
customizing toolbars, notification centers, and other desktop
|
||||
environment tools in a live programming environment.")
|
||||
(license license:lgpl3)))
|
||||
|
||||
quickshell-git
|
||||
@@ -29,6 +29,10 @@ if (X11)
|
||||
add_subdirectory(x11)
|
||||
endif()
|
||||
|
||||
if (I3)
|
||||
add_subdirectory(x11/i3)
|
||||
endif()
|
||||
|
||||
add_subdirectory(services)
|
||||
|
||||
if (BLUETOOTH)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "colorquantizer.hpp"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <qatomic.h>
|
||||
#include <qcolor.h>
|
||||
@@ -13,6 +14,7 @@
|
||||
#include <qnumeric.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qrect.h>
|
||||
#include <qrgb.h>
|
||||
#include <qthreadpool.h>
|
||||
#include <qtmetamacros.h>
|
||||
@@ -24,19 +26,30 @@ namespace {
|
||||
QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg);
|
||||
}
|
||||
|
||||
ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
|
||||
: source(source)
|
||||
ColorQuantizerOperation::ColorQuantizerOperation(
|
||||
QUrl source,
|
||||
qreal depth,
|
||||
QRect imageRect,
|
||||
qreal rescaleSize
|
||||
)
|
||||
: source(std::move(source))
|
||||
, maxDepth(depth)
|
||||
, imageRect(imageRect)
|
||||
, rescaleSize(rescaleSize) {
|
||||
this->setAutoDelete(false);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize)
|
||||
&& this->rescaleSize > 0)
|
||||
{
|
||||
@@ -49,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;
|
||||
}
|
||||
|
||||
@@ -179,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();
|
||||
@@ -198,16 +213,27 @@ void ColorQuantizer::setDepth(qreal depth) {
|
||||
this->mDepth = depth;
|
||||
emit this->depthChanged();
|
||||
|
||||
if (this->componentCompleted) this->quantizeAsync();
|
||||
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
void ColorQuantizer::setImageRect(QRect imageRect) {
|
||||
if (this->mImageRect != imageRect) {
|
||||
this->mImageRect = imageRect;
|
||||
emit this->imageRectChanged();
|
||||
|
||||
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
void ColorQuantizer::resetImageRect() { this->setImageRect(QRect()); }
|
||||
|
||||
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
||||
if (this->mRescaleSize != rescaleSize) {
|
||||
this->mRescaleSize = rescaleSize;
|
||||
emit this->rescaleSizeChanged();
|
||||
|
||||
if (this->componentCompleted) this->quantizeAsync();
|
||||
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,8 +247,13 @@ void ColorQuantizer::quantizeAsync() {
|
||||
if (this->liveOperation) this->cancelAsync();
|
||||
|
||||
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
||||
this->liveOperation =
|
||||
new ColorQuantizerOperation(&this->mSource, this->mDepth, this->mRescaleSize);
|
||||
|
||||
this->liveOperation = new ColorQuantizerOperation(
|
||||
this->mSource,
|
||||
this->mDepth,
|
||||
this->mImageRect,
|
||||
this->mRescaleSize
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->liveOperation,
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qrect.h>
|
||||
#include <qrunnable.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qurl.h>
|
||||
@@ -16,7 +18,7 @@ class ColorQuantizerOperation
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
|
||||
explicit ColorQuantizerOperation(QUrl source, qreal depth, QRect imageRect, qreal rescaleSize);
|
||||
|
||||
void run() override;
|
||||
void tryCancel();
|
||||
@@ -42,8 +44,9 @@ private:
|
||||
|
||||
QAtomicInteger<bool> shouldCancel = false;
|
||||
QList<QColor> colors;
|
||||
QUrl* source;
|
||||
QUrl source;
|
||||
qreal maxDepth;
|
||||
QRect imageRect;
|
||||
qreal rescaleSize;
|
||||
};
|
||||
|
||||
@@ -78,13 +81,22 @@ class ColorQuantizer
|
||||
/// binary split of the color space
|
||||
Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
|
||||
|
||||
// clang-format off
|
||||
/// Rectangle that the source image is cropped to.
|
||||
///
|
||||
/// Can be set to `undefined` to reset.
|
||||
Q_PROPERTY(QRect imageRect READ imageRect WRITE setImageRect RESET resetImageRect NOTIFY imageRectChanged);
|
||||
// clang-format on
|
||||
|
||||
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
|
||||
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
|
||||
/// > reccommended to rescale, otherwise the quantization process will take much longer.
|
||||
/// > recommended to rescale, otherwise the quantization process will take much longer.
|
||||
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
|
||||
|
||||
public:
|
||||
explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {}
|
||||
~ColorQuantizer() override;
|
||||
Q_DISABLE_COPY_MOVE(ColorQuantizer);
|
||||
|
||||
void componentComplete() override;
|
||||
void classBegin() override {}
|
||||
@@ -97,6 +109,10 @@ public:
|
||||
[[nodiscard]] qreal depth() const { return this->mDepth; }
|
||||
void setDepth(qreal depth);
|
||||
|
||||
[[nodiscard]] QRect imageRect() const { return this->mImageRect; }
|
||||
void setImageRect(QRect imageRect);
|
||||
void resetImageRect();
|
||||
|
||||
[[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; }
|
||||
void setRescaleSize(int rescaleSize);
|
||||
|
||||
@@ -104,6 +120,7 @@ signals:
|
||||
void colorsChanged();
|
||||
void sourceChanged();
|
||||
void depthChanged();
|
||||
void imageRectChanged();
|
||||
void rescaleSizeChanged();
|
||||
|
||||
public slots:
|
||||
@@ -117,6 +134,7 @@ private:
|
||||
ColorQuantizerOperation* liveOperation = nullptr;
|
||||
QUrl mSource;
|
||||
qreal mDepth = 0;
|
||||
QRect mImageRect;
|
||||
qreal mRescaleSize = 0;
|
||||
|
||||
Q_OBJECT_BINDABLE_PROPERTY(
|
||||
|
||||
@@ -129,12 +129,13 @@ QString envInfo() {
|
||||
auto stream = QTextStream(&info);
|
||||
|
||||
for (auto** envp = environ; *envp != nullptr; ++envp) { // NOLINT
|
||||
auto prefixes = std::array<std::string_view, 5> {
|
||||
auto prefixes = std::array<std::string_view, 6> {
|
||||
"QS_",
|
||||
"QT_",
|
||||
"QML_",
|
||||
"QML2_",
|
||||
"QSG_",
|
||||
"XDG_CURRENT_DESKTOP=",
|
||||
};
|
||||
|
||||
for (const auto& prefix: prefixes) {
|
||||
|
||||
+32
-19
@@ -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;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#include "generation.hpp"
|
||||
|
||||
// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
|
||||
// QMenu re-calls pixmap() every time the mouse moves so it's important to cache it.
|
||||
class PixmapCacheIconEngine: public QIconEngine {
|
||||
void paint(
|
||||
QPainter* /*unused*/,
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
#include <qdatastream.h>
|
||||
|
||||
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
|
||||
stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid
|
||||
<< info.display;
|
||||
stream << info.instanceId << info.configPath << info.shellId << info.appId << info.launchTime
|
||||
<< info.pid << info.display;
|
||||
return stream;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
|
||||
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid
|
||||
>> info.display;
|
||||
stream >> info.instanceId >> info.configPath >> info.shellId >> info.appId >> info.launchTime
|
||||
>> info.pid >> info.display;
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ struct InstanceInfo {
|
||||
QString instanceId;
|
||||
QString configPath;
|
||||
QString shellId;
|
||||
QString appId;
|
||||
QDateTime launchTime;
|
||||
pid_t pid = -1;
|
||||
QString display;
|
||||
|
||||
+49
-25
@@ -14,6 +14,7 @@
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qmutex.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
@@ -31,6 +32,9 @@
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
#ifdef __FreeBSD__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "instanceinfo.hpp"
|
||||
#include "logcat.hpp"
|
||||
@@ -67,7 +71,7 @@ bool copyFileData(int sourceFd, int destFd, qint64 size) {
|
||||
return true;
|
||||
#else
|
||||
std::array<char, 64 * 1024> buffer = {};
|
||||
auto remaining = totalTarget;
|
||||
auto remaining = usize;
|
||||
|
||||
while (remaining > 0) {
|
||||
auto chunk = std::min(remaining, buffer.size());
|
||||
@@ -217,6 +221,7 @@ void LogManager::messageHandler(
|
||||
}
|
||||
|
||||
if (display) {
|
||||
auto locker = QMutexLocker(&self->stdoutMutex);
|
||||
LogMessage::formatMessage(
|
||||
self->stdoutStream,
|
||||
message,
|
||||
@@ -252,9 +257,21 @@ void LogManager::filterCategory(QLoggingCategory* category) {
|
||||
filter.warn = filter.info || instance->mDefaultLevel == QtWarningMsg || defaultLevel == QtWarningMsg;
|
||||
filter.critical = filter.warn || instance->mDefaultLevel == QtCriticalMsg || defaultLevel == QtCriticalMsg;
|
||||
// clang-format on
|
||||
} else if (instance->lastCategoryFilter) {
|
||||
instance->lastCategoryFilter(category);
|
||||
filter = CategoryFilter(category);
|
||||
} else {
|
||||
if (instance->lastCategoryFilter) {
|
||||
instance->lastCategoryFilter(category);
|
||||
filter = CategoryFilter(category);
|
||||
}
|
||||
|
||||
// Hides virtual/override property warnings.
|
||||
// Getting rid of this is blocked by https://qt-project.atlassian.net/browse/QTBUG-145977
|
||||
// for internal Quickshell types, and may still be desired for shells that want to maintain
|
||||
// compatibility with Qt versions prior to 6.11.
|
||||
if (categoryName == QLatin1StringView("qt.qml.propertyCache.append")
|
||||
&& !qEnvironmentVariableIsSet("QS_NO_FILTER_QT_LOGS"))
|
||||
{
|
||||
filter.warn = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& rule: *instance->rules) {
|
||||
@@ -287,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;
|
||||
@@ -305,32 +326,42 @@ void LogManager::init(
|
||||
instance->rules->append(parser.rules());
|
||||
}
|
||||
|
||||
qInstallMessageHandler(&LogManager::messageHandler);
|
||||
|
||||
instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
|
||||
|
||||
qCDebug(logLogging) << "Creating offthread logger...";
|
||||
auto* thread = new QThread();
|
||||
instance->threadProxy.moveToThread(thread);
|
||||
thread->start();
|
||||
if (instance->lastCategoryFilter == &LogManager::filterCategory) {
|
||||
qCFatal(logLogging) << "Quickshell's log filter has been installed twice. This is a bug.";
|
||||
instance->lastCategoryFilter = nullptr;
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(
|
||||
&instance->threadProxy,
|
||||
&LoggingThreadProxy::initInThread,
|
||||
Qt::BlockingQueuedConnection
|
||||
);
|
||||
qInstallMessageHandler(&LogManager::messageHandler);
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
@@ -343,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);
|
||||
|
||||
@@ -520,7 +544,7 @@ void ThreadLogging::initFs() {
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
qCDebug(logLogging) << "Switched threaded logger to queued eventloop connection.";
|
||||
qCDebug(logLogging) << "Switched threaded logger to queued event loop connection.";
|
||||
}
|
||||
|
||||
void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
|
||||
|
||||
+4
-15
@@ -10,6 +10,7 @@
|
||||
#include <qlatin1stringview.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qmutex.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
@@ -54,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;
|
||||
}
|
||||
@@ -103,6 +90,7 @@ public:
|
||||
const QString& prefix = ""
|
||||
);
|
||||
|
||||
static void initThreadLogging();
|
||||
static void initFs();
|
||||
static LogManager* instance();
|
||||
|
||||
@@ -135,7 +123,8 @@ private:
|
||||
QHash<QLatin1StringView, CategoryFilter> allFilters;
|
||||
|
||||
QTextStream stdoutStream;
|
||||
LoggingThreadProxy threadProxy;
|
||||
QMutex stdoutMutex;
|
||||
ThreadLogging* threadLogging = nullptr;
|
||||
|
||||
friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel);
|
||||
};
|
||||
|
||||
@@ -114,9 +114,11 @@ public:
|
||||
explicit ThreadLogging(QObject* parent): QObject(parent) {}
|
||||
|
||||
void init();
|
||||
void initFs();
|
||||
void setupFileLogging();
|
||||
|
||||
public slots:
|
||||
void initFs();
|
||||
|
||||
private slots:
|
||||
void onMessage(const LogMessage& msg, bool showInSparse);
|
||||
|
||||
|
||||
@@ -138,6 +138,10 @@ public:
|
||||
qsizetype oi = 0;
|
||||
for (auto* object: newValues) {
|
||||
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
|
||||
// object may already be present further down (reorder case);
|
||||
// drop the old row first so the same pointer is not inserted twice.
|
||||
auto old = this->mValuesList.indexOf(object, oi);
|
||||
if (old != -1) this->removeAt(old);
|
||||
this->insertObject(object, oi);
|
||||
}
|
||||
|
||||
|
||||
+13
-10
@@ -1,6 +1,7 @@
|
||||
#include "paths.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <fcntl.h>
|
||||
@@ -9,7 +10,6 @@
|
||||
#include <qdir.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qpair.h>
|
||||
#include <qstandardpaths.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtversionchecks.h>
|
||||
@@ -64,7 +64,7 @@ QDir* QsPaths::baseRunDir() {
|
||||
if (this->baseRunState == DirState::Unknown) {
|
||||
auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR");
|
||||
if (runtimeDir.isEmpty()) {
|
||||
runtimeDir = QString("/run/user/$1").arg(getuid());
|
||||
runtimeDir = QString("/run/user/%1").arg(getuid());
|
||||
qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir;
|
||||
}
|
||||
|
||||
@@ -412,10 +412,11 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
|
||||
return true;
|
||||
}
|
||||
|
||||
QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||
std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||
QsPaths::collectInstances(const QString& path, const QString& display) {
|
||||
qCDebug(logPaths) << "Collecting instances from" << path;
|
||||
auto liveInstances = QVector<InstanceLockInfo>();
|
||||
auto mismatchedInstances = QVector<InstanceLockInfo>();
|
||||
auto deadInstances = QVector<InstanceLockInfo>();
|
||||
auto dir = QDir(path);
|
||||
|
||||
@@ -427,20 +428,22 @@ QsPaths::collectInstances(const QString& path, const QString& display) {
|
||||
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
|
||||
<< info.pid << ") at " << path;
|
||||
|
||||
if (!display.isEmpty() && info.instance.display != display) {
|
||||
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
|
||||
if (info.pid == -1) {
|
||||
deadInstances.push_back(info);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.pid == -1) {
|
||||
deadInstances.push_back(info);
|
||||
} else {
|
||||
liveInstances.push_back(info);
|
||||
if (!display.isEmpty() && info.instance.display != display) {
|
||||
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
|
||||
mismatchedInstances.push_back(info);
|
||||
continue;
|
||||
}
|
||||
|
||||
liveInstances.push_back(info);
|
||||
} else {
|
||||
qCDebug(logPaths) << "Skipped potential instance at" << path;
|
||||
}
|
||||
}
|
||||
|
||||
return qMakePair(liveInstances, deadInstances);
|
||||
return {liveInstances, mismatchedInstances, deadInstances};
|
||||
}
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
#pragma once
|
||||
#include <tuple>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdatetime.h>
|
||||
#include <qdir.h>
|
||||
#include <qpair.h>
|
||||
@@ -29,7 +32,7 @@ public:
|
||||
static QString ipcPath(const QString& id);
|
||||
static bool
|
||||
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
|
||||
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||
static std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||
collectInstances(const QString& path, const QString& display);
|
||||
|
||||
QDir* baseRunDir();
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "../window/proxywindow.hpp"
|
||||
#include "../window/windowinterface.hpp"
|
||||
#include "iconprovider.hpp"
|
||||
#include "model.hpp"
|
||||
#include "platformmenu_p.hpp"
|
||||
@@ -91,10 +90,8 @@ bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relati
|
||||
} else if (parentWindow == nullptr) {
|
||||
qCritical() << "Cannot display PlatformMenuEntry with null parent window.";
|
||||
return false;
|
||||
} else if (auto* proxy = qobject_cast<ProxyWindowBase*>(parentWindow)) {
|
||||
} else if (auto* proxy = ProxyWindowBase::forObject(parentWindow)) {
|
||||
window = proxy->backingWindow();
|
||||
} else if (auto* interface = qobject_cast<WindowInterface*>(parentWindow)) {
|
||||
window = interface->proxyWindow()->backingWindow();
|
||||
} else {
|
||||
qCritical() << "PlatformMenuEntry.display() must be called with a window.";
|
||||
return false;
|
||||
|
||||
@@ -9,6 +9,18 @@ static QVector<QsEnginePlugin*> plugins; // NOLINT
|
||||
|
||||
void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); }
|
||||
|
||||
void QsEnginePlugin::preinitPluginsOnly() {
|
||||
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
|
||||
|
||||
std::ranges::sort(plugins, [](QsEnginePlugin* a, QsEnginePlugin* b) {
|
||||
return b->dependencies().contains(a->name());
|
||||
});
|
||||
|
||||
for (QsEnginePlugin* plugin: plugins) {
|
||||
plugin->preinit();
|
||||
}
|
||||
}
|
||||
|
||||
void QsEnginePlugin::initPlugins() {
|
||||
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
|
||||
|
||||
@@ -16,6 +28,10 @@ void QsEnginePlugin::initPlugins() {
|
||||
return b->dependencies().contains(a->name());
|
||||
});
|
||||
|
||||
for (QsEnginePlugin* plugin: plugins) {
|
||||
plugin->preinit();
|
||||
}
|
||||
|
||||
for (QsEnginePlugin* plugin: plugins) {
|
||||
plugin->init();
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ public:
|
||||
virtual QString name() { return QString(); }
|
||||
virtual QList<QString> dependencies() { return {}; }
|
||||
virtual bool applies() { return true; }
|
||||
virtual void preinit() {}
|
||||
virtual void init() {}
|
||||
virtual void registerTypes() {}
|
||||
virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT
|
||||
virtual void onReload() {}
|
||||
|
||||
static void registerPlugin(QsEnginePlugin& plugin);
|
||||
static void preinitPluginsOnly();
|
||||
static void initPlugins();
|
||||
static void runConstructGeneration(EngineGeneration& generation);
|
||||
static void runOnReload();
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <qwindow.h>
|
||||
|
||||
#include "../window/proxywindow.hpp"
|
||||
#include "../window/windowinterface.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
bool PopupAnchorState::operator==(const PopupAnchorState& other) const {
|
||||
@@ -40,10 +39,8 @@ void PopupAnchor::setWindowInternal(QObject* window) {
|
||||
}
|
||||
|
||||
if (window) {
|
||||
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
|
||||
if (auto* proxy = ProxyWindowBase::forObject(window)) {
|
||||
this->bProxyWindow = proxy;
|
||||
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
|
||||
this->bProxyWindow = interface->proxyWindow();
|
||||
} else {
|
||||
qWarning() << "Tried to set popup anchor window to" << window
|
||||
<< "which is not a quickshell window.";
|
||||
@@ -96,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() {
|
||||
@@ -112,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);
|
||||
|
||||
+39
-6
@@ -18,14 +18,17 @@
|
||||
#include <qscreen.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtversion.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
#include <qversionnumber.h>
|
||||
#include <qwindowdefs.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../io/processcore.hpp"
|
||||
#include "generation.hpp"
|
||||
#include "iconimageprovider.hpp"
|
||||
#include "instanceinfo.hpp"
|
||||
#include "paths.hpp"
|
||||
#include "qmlscreen.hpp"
|
||||
#include "rootwrapper.hpp"
|
||||
@@ -85,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;
|
||||
@@ -93,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() {
|
||||
@@ -153,6 +164,22 @@ qint32 QuickshellGlobal::processId() const { // NOLINT
|
||||
return getpid();
|
||||
}
|
||||
|
||||
QString QuickshellGlobal::instanceId() const { // NOLINT
|
||||
return InstanceInfo::CURRENT.instanceId;
|
||||
}
|
||||
|
||||
QString QuickshellGlobal::shellId() const { // NOLINT
|
||||
return InstanceInfo::CURRENT.shellId;
|
||||
}
|
||||
|
||||
QString QuickshellGlobal::appId() const { // NOLINT
|
||||
return InstanceInfo::CURRENT.appId;
|
||||
}
|
||||
|
||||
QDateTime QuickshellGlobal::launchTime() const { // NOLINT
|
||||
return InstanceInfo::CURRENT.launchTime;
|
||||
}
|
||||
|
||||
qsizetype QuickshellGlobal::screensCount(QQmlListProperty<QuickshellScreenInfo>* /*unused*/) {
|
||||
return QuickshellTracked::instance()->screens.size();
|
||||
}
|
||||
@@ -326,6 +353,12 @@ bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) {
|
||||
return QuickshellGlobal::hasVersion(major, minor, QStringList());
|
||||
}
|
||||
|
||||
bool QuickshellGlobal::hasQtVersion(int major, int minor) {
|
||||
auto qtVersion = QVersionNumber::fromString(qVersion());
|
||||
auto requiredVersion = QVersionNumber(major, minor);
|
||||
return qtVersion >= requiredVersion;
|
||||
}
|
||||
|
||||
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
|
||||
auto* qsg = new QuickshellGlobal();
|
||||
auto* generation = EngineGeneration::findEngineGeneration(engine);
|
||||
|
||||
+33
-1
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "../io/processcore.hpp"
|
||||
#include "doc.hpp"
|
||||
#include "instanceinfo.hpp"
|
||||
#include "qmlscreen.hpp"
|
||||
|
||||
///! Accessor for some options under the Quickshell type.
|
||||
@@ -69,6 +70,7 @@ public:
|
||||
QVector<QuickshellScreenInfo*> screens;
|
||||
QuickshellScreenInfo* screenInfo(QScreen* screen) const;
|
||||
|
||||
static void init();
|
||||
static QuickshellTracked* instance();
|
||||
|
||||
private slots:
|
||||
@@ -83,6 +85,21 @@ class QuickshellGlobal: public QObject {
|
||||
// clang-format off
|
||||
/// Quickshell's process id.
|
||||
Q_PROPERTY(qint32 processId READ processId CONSTANT);
|
||||
/// A unique identifier for this Quickshell instance
|
||||
Q_PROPERTY(QString instanceId READ instanceId CONSTANT)
|
||||
/// The shell ID, used to differentiate between different shell configurations.
|
||||
///
|
||||
/// Defaults to a stable value derived from the config path.
|
||||
/// Can be overridden with `//@ pragma ShellId <id>` in the root qml file.
|
||||
Q_PROPERTY(QString shellId READ shellId CONSTANT)
|
||||
/// The desktop application ID.
|
||||
///
|
||||
/// Defaults to `org.quickshell`.
|
||||
/// Can be overridden with `//@ pragma AppId <id>` in the root qml file
|
||||
/// or the `QS_APP_ID` environment variable.
|
||||
Q_PROPERTY(QString appId READ appId CONSTANT)
|
||||
/// The time at which this Quickshell instance was launched.
|
||||
Q_PROPERTY(QDateTime launchTime READ launchTime CONSTANT)
|
||||
/// All currently connected screens.
|
||||
///
|
||||
/// This property updates as connected screens change.
|
||||
@@ -149,6 +166,10 @@ class QuickshellGlobal: public QObject {
|
||||
|
||||
public:
|
||||
[[nodiscard]] qint32 processId() const;
|
||||
[[nodiscard]] QString instanceId() const;
|
||||
[[nodiscard]] QString shellId() const;
|
||||
[[nodiscard]] QString appId() const;
|
||||
[[nodiscard]] QDateTime launchTime() const;
|
||||
|
||||
QQmlListProperty<QuickshellScreenInfo> screens();
|
||||
|
||||
@@ -224,7 +245,7 @@ public:
|
||||
/// it is assumed that all unreleased features are present. The unreleased feature list
|
||||
/// may be omitted.
|
||||
///
|
||||
/// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which
|
||||
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
|
||||
/// > has the same function available.
|
||||
/// >
|
||||
/// > ```qml
|
||||
@@ -234,6 +255,17 @@ public:
|
||||
/// > ```
|
||||
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
|
||||
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
|
||||
/// Check if Qt's version is at least `major.minor`.
|
||||
///
|
||||
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
|
||||
/// > has the same function available.
|
||||
/// >
|
||||
/// > ```qml
|
||||
/// > //@ if hasVersion(6, 10)
|
||||
/// > ...
|
||||
/// > //@ endif
|
||||
/// > ```
|
||||
Q_INVOKABLE static bool hasQtVersion(qint32 major, qint32 minor);
|
||||
|
||||
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
|
||||
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
/// Monitor object useful for setting the monitor for a @@QsWindow
|
||||
/// or querying information about the monitor.
|
||||
///
|
||||
/// > [!WARNING] If the monitor is disconnected than any stored copies of its ShellMonitor will
|
||||
/// > [!WARNING] If the monitor is disconnected, then any stored copies of its ShellMonitor will
|
||||
/// > be marked as dangling and all properties will return default values.
|
||||
/// > Reconnecting the monitor will not reconnect it to the ShellMonitor object.
|
||||
///
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+134
-1
@@ -1,4 +1,5 @@
|
||||
#include "region.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <qobject.h>
|
||||
@@ -18,6 +19,11 @@ PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
|
||||
QObject::connect(this, &PendingRegion::yChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::radiusChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::topLeftRadiusChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::topRightRadiusChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::bottomLeftRadiusChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::bottomRightRadiusChanged, this, &PendingRegion::changed);
|
||||
QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed);
|
||||
}
|
||||
|
||||
@@ -45,6 +51,79 @@ void PendingRegion::onItemDestroyed() { this->mItem = nullptr; }
|
||||
|
||||
void PendingRegion::onChildDestroyed() { this->mRegions.removeAll(this->sender()); }
|
||||
|
||||
qint32 PendingRegion::radius() const { return this->mRadius; }
|
||||
|
||||
void PendingRegion::setRadius(qint32 radius) {
|
||||
if (radius == this->mRadius) return;
|
||||
this->mRadius = radius;
|
||||
emit this->radiusChanged();
|
||||
|
||||
if (!(this->mCornerOverrides & TopLeft)) emit this->topLeftRadiusChanged();
|
||||
if (!(this->mCornerOverrides & TopRight)) emit this->topRightRadiusChanged();
|
||||
if (!(this->mCornerOverrides & BottomLeft)) emit this->bottomLeftRadiusChanged();
|
||||
if (!(this->mCornerOverrides & BottomRight)) emit this->bottomRightRadiusChanged();
|
||||
}
|
||||
|
||||
qint32 PendingRegion::topLeftRadius() const {
|
||||
return (this->mCornerOverrides & TopLeft) ? this->mTopLeftRadius : this->mRadius;
|
||||
}
|
||||
|
||||
void PendingRegion::setTopLeftRadius(qint32 radius) {
|
||||
this->mTopLeftRadius = radius;
|
||||
this->mCornerOverrides |= TopLeft;
|
||||
emit this->topLeftRadiusChanged();
|
||||
}
|
||||
|
||||
void PendingRegion::resetTopLeftRadius() {
|
||||
this->mCornerOverrides &= ~TopLeft;
|
||||
emit this->topLeftRadiusChanged();
|
||||
}
|
||||
|
||||
qint32 PendingRegion::topRightRadius() const {
|
||||
return (this->mCornerOverrides & TopRight) ? this->mTopRightRadius : this->mRadius;
|
||||
}
|
||||
|
||||
void PendingRegion::setTopRightRadius(qint32 radius) {
|
||||
this->mTopRightRadius = radius;
|
||||
this->mCornerOverrides |= TopRight;
|
||||
emit this->topRightRadiusChanged();
|
||||
}
|
||||
|
||||
void PendingRegion::resetTopRightRadius() {
|
||||
this->mCornerOverrides &= ~TopRight;
|
||||
emit this->topRightRadiusChanged();
|
||||
}
|
||||
|
||||
qint32 PendingRegion::bottomLeftRadius() const {
|
||||
return (this->mCornerOverrides & BottomLeft) ? this->mBottomLeftRadius : this->mRadius;
|
||||
}
|
||||
|
||||
void PendingRegion::setBottomLeftRadius(qint32 radius) {
|
||||
this->mBottomLeftRadius = radius;
|
||||
this->mCornerOverrides |= BottomLeft;
|
||||
emit this->bottomLeftRadiusChanged();
|
||||
}
|
||||
|
||||
void PendingRegion::resetBottomLeftRadius() {
|
||||
this->mCornerOverrides &= ~BottomLeft;
|
||||
emit this->bottomLeftRadiusChanged();
|
||||
}
|
||||
|
||||
qint32 PendingRegion::bottomRightRadius() const {
|
||||
return (this->mCornerOverrides & BottomRight) ? this->mBottomRightRadius : this->mRadius;
|
||||
}
|
||||
|
||||
void PendingRegion::setBottomRightRadius(qint32 radius) {
|
||||
this->mBottomRightRadius = radius;
|
||||
this->mCornerOverrides |= BottomRight;
|
||||
emit this->bottomRightRadiusChanged();
|
||||
}
|
||||
|
||||
void PendingRegion::resetBottomRightRadius() {
|
||||
this->mCornerOverrides &= ~BottomRight;
|
||||
emit this->bottomRightRadiusChanged();
|
||||
}
|
||||
|
||||
QQmlListProperty<PendingRegion> PendingRegion::regions() {
|
||||
return QQmlListProperty<PendingRegion>(
|
||||
this,
|
||||
@@ -90,6 +169,60 @@ QRegion PendingRegion::build() const {
|
||||
region = QRegion(this->mX, this->mY, this->mWidth, this->mHeight, type);
|
||||
}
|
||||
|
||||
if (this->mShape == RegionShape::Rect && !region.isEmpty()) {
|
||||
auto tl = std::max(this->topLeftRadius(), 0);
|
||||
auto tr = std::max(this->topRightRadius(), 0);
|
||||
auto bl = std::max(this->bottomLeftRadius(), 0);
|
||||
auto br = std::max(this->bottomRightRadius(), 0);
|
||||
|
||||
if (tl > 0 || tr > 0 || bl > 0 || br > 0) {
|
||||
auto rect = region.boundingRect();
|
||||
auto x = rect.x();
|
||||
auto y = rect.y();
|
||||
auto w = rect.width();
|
||||
auto h = rect.height();
|
||||
|
||||
// Normalize so adjacent corners don't exceed their shared edge.
|
||||
// Each corner is scaled by the tightest constraint of its two edges.
|
||||
auto topScale = tl + tr > w ? static_cast<double>(w) / (tl + tr) : 1.0;
|
||||
auto bottomScale = bl + br > w ? static_cast<double>(w) / (bl + br) : 1.0;
|
||||
auto leftScale = tl + bl > h ? static_cast<double>(h) / (tl + bl) : 1.0;
|
||||
auto rightScale = tr + br > h ? static_cast<double>(h) / (tr + br) : 1.0;
|
||||
|
||||
tl = static_cast<qint32>(tl * std::min(topScale, leftScale));
|
||||
tr = static_cast<qint32>(tr * std::min(topScale, rightScale));
|
||||
bl = static_cast<qint32>(bl * std::min(bottomScale, leftScale));
|
||||
br = static_cast<qint32>(br * std::min(bottomScale, rightScale));
|
||||
|
||||
// Unlock each corner: subtract (cornerBox - quarterEllipse) from the
|
||||
// full rect. Each corner only modifies pixels inside its own box,
|
||||
// so no diagonal overlap is possible.
|
||||
if (tl > 0) {
|
||||
auto box = QRegion(x, y, tl, tl);
|
||||
auto ellipse = QRegion(x, y, tl * 2, tl * 2, QRegion::Ellipse);
|
||||
region -= box - (ellipse & box);
|
||||
}
|
||||
|
||||
if (tr > 0) {
|
||||
auto box = QRegion(x + w - tr, y, tr, tr);
|
||||
auto ellipse = QRegion(x + w - tr * 2, y, tr * 2, tr * 2, QRegion::Ellipse);
|
||||
region -= box - (ellipse & box);
|
||||
}
|
||||
|
||||
if (bl > 0) {
|
||||
auto box = QRegion(x, y + h - bl, bl, bl);
|
||||
auto ellipse = QRegion(x, y + h - bl * 2, bl * 2, bl * 2, QRegion::Ellipse);
|
||||
region -= box - (ellipse & box);
|
||||
}
|
||||
|
||||
if (br > 0) {
|
||||
auto box = QRegion(x + w - br, y + h - br, br, br);
|
||||
auto ellipse = QRegion(x + w - br * 2, y + h - br * 2, br * 2, br * 2, QRegion::Ellipse);
|
||||
region -= box - (ellipse & box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& childRegion: this->mRegions) {
|
||||
region = childRegion->applyTo(region);
|
||||
}
|
||||
@@ -109,7 +242,7 @@ QRegion PendingRegion::applyTo(QRegion& region) const {
|
||||
}
|
||||
|
||||
QRegion PendingRegion::applyTo(const QRect& rect) const {
|
||||
// if left as the default, dont combine it with the whole rect area, leave it as is.
|
||||
// if left as the default, don't combine it with the whole rect area, leave it as is.
|
||||
if (this->mIntersection == Intersection::Combine) {
|
||||
return this->build();
|
||||
} else {
|
||||
|
||||
@@ -66,6 +66,29 @@ class PendingRegion: public QObject {
|
||||
Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged);
|
||||
/// Defaults to 0. Does nothing if @@item is set.
|
||||
Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged);
|
||||
// clang-format off
|
||||
/// Corner radius for rounded rectangles. Only applies when @@shape is `Rect`. Defaults to 0.
|
||||
///
|
||||
/// Acts as the default for @@topLeftRadius, @@topRightRadius, @@bottomLeftRadius,
|
||||
/// and @@bottomRightRadius.
|
||||
Q_PROPERTY(qint32 radius READ radius WRITE setRadius NOTIFY radiusChanged);
|
||||
/// Top-left corner radius. Only applies when @@shape is `Rect`.
|
||||
///
|
||||
/// Defaults to @@radius, and may be reset by assigning `undefined`.
|
||||
Q_PROPERTY(qint32 topLeftRadius READ topLeftRadius WRITE setTopLeftRadius RESET resetTopLeftRadius NOTIFY topLeftRadiusChanged);
|
||||
/// Top-right corner radius. Only applies when @@shape is `Rect`.
|
||||
///
|
||||
/// Defaults to @@radius, and may be reset by assigning `undefined`.
|
||||
Q_PROPERTY(qint32 topRightRadius READ topRightRadius WRITE setTopRightRadius RESET resetTopRightRadius NOTIFY topRightRadiusChanged);
|
||||
/// Bottom-left corner radius. Only applies when @@shape is `Rect`.
|
||||
///
|
||||
/// Defaults to @@radius, and may be reset by assigning `undefined`.
|
||||
Q_PROPERTY(qint32 bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius RESET resetBottomLeftRadius NOTIFY bottomLeftRadiusChanged);
|
||||
/// Bottom-right corner radius. Only applies when @@shape is `Rect`.
|
||||
///
|
||||
/// Defaults to @@radius, and may be reset by assigning `undefined`.
|
||||
Q_PROPERTY(qint32 bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius RESET resetBottomRightRadius NOTIFY bottomRightRadiusChanged);
|
||||
// clang-format on
|
||||
|
||||
/// Regions to apply on top of this region.
|
||||
///
|
||||
@@ -91,6 +114,25 @@ public:
|
||||
|
||||
void setItem(QQuickItem* item);
|
||||
|
||||
[[nodiscard]] qint32 radius() const;
|
||||
void setRadius(qint32 radius);
|
||||
|
||||
[[nodiscard]] qint32 topLeftRadius() const;
|
||||
void setTopLeftRadius(qint32 radius);
|
||||
void resetTopLeftRadius();
|
||||
|
||||
[[nodiscard]] qint32 topRightRadius() const;
|
||||
void setTopRightRadius(qint32 radius);
|
||||
void resetTopRightRadius();
|
||||
|
||||
[[nodiscard]] qint32 bottomLeftRadius() const;
|
||||
void setBottomLeftRadius(qint32 radius);
|
||||
void resetBottomLeftRadius();
|
||||
|
||||
[[nodiscard]] qint32 bottomRightRadius() const;
|
||||
void setBottomRightRadius(qint32 radius);
|
||||
void resetBottomRightRadius();
|
||||
|
||||
QQmlListProperty<PendingRegion> regions();
|
||||
|
||||
[[nodiscard]] bool empty() const;
|
||||
@@ -109,6 +151,11 @@ signals:
|
||||
void yChanged();
|
||||
void widthChanged();
|
||||
void heightChanged();
|
||||
void radiusChanged();
|
||||
void topLeftRadiusChanged();
|
||||
void topRightRadiusChanged();
|
||||
void bottomLeftRadiusChanged();
|
||||
void bottomRightRadiusChanged();
|
||||
void childrenChanged();
|
||||
|
||||
/// Triggered when the region's geometry changes.
|
||||
@@ -130,12 +177,25 @@ private:
|
||||
static void
|
||||
regionsReplace(QQmlListProperty<PendingRegion>* prop, qsizetype i, PendingRegion* region);
|
||||
|
||||
enum CornerOverride : quint8 {
|
||||
TopLeft = 0b1,
|
||||
TopRight = 0b10,
|
||||
BottomLeft = 0b100,
|
||||
BottomRight = 0b1000,
|
||||
};
|
||||
|
||||
QQuickItem* mItem = nullptr;
|
||||
|
||||
qint32 mX = 0;
|
||||
qint32 mY = 0;
|
||||
qint32 mWidth = 0;
|
||||
qint32 mHeight = 0;
|
||||
qint32 mRadius = 0;
|
||||
qint32 mTopLeftRadius = 0;
|
||||
qint32 mTopRightRadius = 0;
|
||||
qint32 mBottomLeftRadius = 0;
|
||||
qint32 mBottomRightRadius = 0;
|
||||
quint8 mCornerOverrides = 0;
|
||||
|
||||
QList<PendingRegion*> mRegions;
|
||||
};
|
||||
+1
-1
@@ -116,7 +116,7 @@ QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId
|
||||
auto* reloadable = qobject_cast<Reloadable*>(child);
|
||||
if (reloadable != nullptr) {
|
||||
if (reloadable->mReloadableId == reloadId) return reloadable;
|
||||
// if not then don't check its children as thats a seperate reload scope.
|
||||
// if not then don't check its children as that's a separate reload scope.
|
||||
} else {
|
||||
auto* reloadable = Reloadable::getChildByReloadId(child, reloadId);
|
||||
if (reloadable != nullptr) return reloadable;
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ class Reloadable
|
||||
/// previous state.
|
||||
///
|
||||
/// Simply keeping a stable identifier across config versions (saves) is
|
||||
/// enough to help the reloader figure out which object in the old revision corrosponds to
|
||||
/// enough to help the reloader figure out which object in the old revision corresponds to
|
||||
/// this object in the current revision, and facilitate smoother reloading.
|
||||
///
|
||||
/// Note that identifiers are scoped, and will try to do the right thing in context.
|
||||
|
||||
+14
-7
@@ -89,7 +89,7 @@ void QmlScanner::scanDir(const QDir& dir) {
|
||||
QString qmldir;
|
||||
auto stream = QTextStream(&qmldir);
|
||||
|
||||
// cant derive a module name if not in shell path
|
||||
// can't derive a module name if not in shell path
|
||||
if (path.startsWith(this->rootPath.path())) {
|
||||
auto end = path.sliced(this->rootPath.path().length());
|
||||
|
||||
@@ -145,10 +145,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
||||
QString overrideText;
|
||||
bool isOverridden = false;
|
||||
|
||||
auto pragmaEngine = QJSEngine();
|
||||
pragmaEngine.globalObject().setPrototype(
|
||||
pragmaEngine.newQObject(new qs::scan::env::PreprocEnv())
|
||||
);
|
||||
auto& pragmaEngine = *QmlScanner::preprocEngine();
|
||||
|
||||
auto postError = [&, this](QString error) {
|
||||
this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum});
|
||||
@@ -163,7 +160,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
||||
if (!singleton && line == "pragma Singleton") {
|
||||
singleton = true;
|
||||
} else if (line.startsWith("import")) {
|
||||
// we dont care about "import qs" as we always load the root folder
|
||||
// we don't care about "import qs" as we always load the root folder
|
||||
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
|
||||
importCursor += 4;
|
||||
QString path;
|
||||
@@ -214,7 +211,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
||||
mask = false;
|
||||
}
|
||||
if (!sourceMasked && mask) hideMask = true;
|
||||
mask = sourceMasked || mask; // cant unmask if a nested if passes
|
||||
mask = sourceMasked || mask; // can't unmask if a nested if passes
|
||||
ifScopes.append(mask);
|
||||
if (mask) isOverridden = true;
|
||||
sourceMasked = mask;
|
||||
@@ -370,3 +367,13 @@ QPair<QString, QString> QmlScanner::jsonToQml(const QJsonValue& value, int inden
|
||||
return qMakePair(QStringLiteral("var"), "null");
|
||||
}
|
||||
}
|
||||
|
||||
QJSEngine* QmlScanner::preprocEngine() {
|
||||
static auto* engine = [] {
|
||||
auto* engine = new QJSEngine();
|
||||
engine->globalObject().setPrototype(engine->newQObject(new qs::scan::env::PreprocEnv()));
|
||||
return engine;
|
||||
}();
|
||||
|
||||
return engine;
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdir.h>
|
||||
#include <qhash.h>
|
||||
#include <qjsengine.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qvector.h>
|
||||
|
||||
@@ -42,4 +43,6 @@ private:
|
||||
bool scanQmlFile(const QString& path, bool& singleton, bool& internal);
|
||||
bool scanQmlJson(const QString& path);
|
||||
[[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0);
|
||||
|
||||
static QJSEngine* preprocEngine();
|
||||
};
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtversion.h>
|
||||
#include <qversionnumber.h>
|
||||
|
||||
#include "build.hpp"
|
||||
|
||||
@@ -20,6 +22,12 @@ bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
|
||||
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
|
||||
}
|
||||
|
||||
bool PreprocEnv::hasQtVersion(int major, int minor) {
|
||||
auto qtVersion = QVersionNumber::fromString(qVersion());
|
||||
auto requiredVersion = QVersionNumber(major, minor);
|
||||
return qtVersion >= requiredVersion;
|
||||
}
|
||||
|
||||
QString PreprocEnv::env(const QString& variable) {
|
||||
return qEnvironmentVariable(variable.toStdString().c_str());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ class PreprocEnv: public QObject {
|
||||
public:
|
||||
Q_INVOKABLE static bool
|
||||
hasVersion(int major, int minor, const QStringList& features = QStringList());
|
||||
Q_INVOKABLE static bool hasQtVersion(int major, int minor);
|
||||
|
||||
Q_INVOKABLE static QString env(const QString& variable);
|
||||
Q_INVOKABLE static bool isEnvSet(const QString& variable);
|
||||
|
||||
+118
-28
@@ -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,8 +197,9 @@ 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) {
|
||||
} else if (!newIter->strictlyEquals(*iter)) {
|
||||
auto first = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
|
||||
auto index = first;
|
||||
|
||||
@@ -140,26 +208,42 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
|
||||
++iter;
|
||||
++newIter;
|
||||
++index;
|
||||
} while (iter != this->mValues.end() && newIter != newValues.end() && *newIter != *iter);
|
||||
} while (iter != this->mValues.end() && newIter != newValues.end()
|
||||
&& !newIter->strictlyEquals(*iter));
|
||||
|
||||
this->dataChanged(
|
||||
this->index(first, 0, QModelIndex()),
|
||||
this->index(index - 1, 0, QModelIndex()),
|
||||
{Qt::UserRole}
|
||||
);
|
||||
|
||||
anyChanges = true;
|
||||
} else {
|
||||
++iter;
|
||||
++newIter;
|
||||
}
|
||||
}
|
||||
|
||||
this->hasActiveIterators = false;
|
||||
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 +253,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 +266,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"}}; }
|
||||
@@ -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);
|
||||
};
|
||||
@@ -8,3 +8,4 @@ qs_test(transformwatcher transformwatcher.cpp)
|
||||
qs_test(ringbuffer ringbuf.cpp)
|
||||
qs_test(scriptmodel scriptmodel.cpp)
|
||||
qs_test(stacklist stacklist.cpp)
|
||||
qs_test(objectmodel objectmodel.cpp)
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "objectmodel.hpp"
|
||||
|
||||
#include <qlist.h>
|
||||
#include <qobject.h>
|
||||
#include <qtest.h>
|
||||
#include <qtestcase.h>
|
||||
|
||||
#include "../model.hpp"
|
||||
|
||||
void TestObjectModel::diffUpdateInsertRemove() {
|
||||
QObject a;
|
||||
QObject b;
|
||||
QObject c;
|
||||
QObject d;
|
||||
|
||||
auto model = ObjectModel<QObject>(nullptr);
|
||||
model.insertObject(&a);
|
||||
model.insertObject(&b);
|
||||
model.insertObject(&c);
|
||||
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c}));
|
||||
|
||||
// drop b, append d — relative order of survivors unchanged
|
||||
model.diffUpdate({&a, &c, &d});
|
||||
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &c, &d}));
|
||||
}
|
||||
|
||||
void TestObjectModel::diffUpdateReorder() {
|
||||
QObject a;
|
||||
QObject b;
|
||||
QObject c;
|
||||
QObject d;
|
||||
|
||||
auto model = ObjectModel<QObject>(nullptr);
|
||||
model.insertObject(&d);
|
||||
model.insertObject(&a);
|
||||
model.insertObject(&b);
|
||||
model.insertObject(&c);
|
||||
QCOMPARE(model.valueList(), (QList<QObject*> {&d, &a, &b, &c}));
|
||||
|
||||
// pure permutation: same elements, different order. Previously turned
|
||||
// [d,a,b,c] into [a,b,c,d,a,b,c] because the old row was never removed
|
||||
// before inserting at the new index.
|
||||
model.diffUpdate({&a, &b, &c, &d});
|
||||
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c, &d}));
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestObjectModel);
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class TestObjectModel: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
private slots:
|
||||
static void diffUpdateInsertRemove();
|
||||
static void diffUpdateReorder();
|
||||
};
|
||||
+156
-54
@@ -4,10 +4,11 @@
|
||||
#include <qabstractitemmodeltester.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.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 +16,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 +43,44 @@ 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 +88,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");
|
||||
@@ -90,7 +130,7 @@ void TestScriptModel::unique_data() {
|
||||
QTest::addRow("move_overlapping")
|
||||
<< "ABCDEFG" << "ABDEFCG" << OpList({{ModelOperation::Move, 3, 3, 2}});
|
||||
|
||||
// Ensure iterators arent skipping anything at the end of operations by performing
|
||||
// Ensure iterators aren't skipping anything at the end of operations by performing
|
||||
// multiple back to back.
|
||||
|
||||
QTest::addRow("insert_state_ok") << "ABCDEFG" << "ABXXEFG"
|
||||
@@ -117,65 +157,127 @@ 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);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestScriptModel);
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlist.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
@@ -26,6 +28,29 @@ struct ModelOperation {
|
||||
[[nodiscard]] bool operator==(const ModelOperation& other) const;
|
||||
};
|
||||
|
||||
using OpList = QList<ModelOperation>;
|
||||
|
||||
class ChangeObserver: public QObject {
|
||||
public:
|
||||
explicit ChangeObserver(QAbstractItemModel* model);
|
||||
|
||||
[[nodiscard]] const OpList& operations() const { return this->mOperations; }
|
||||
void clear() { this->mOperations.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
|
||||
);
|
||||
|
||||
OpList mOperations;
|
||||
};
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const ModelOperation& op);
|
||||
|
||||
class TestScriptModel: public QObject {
|
||||
@@ -34,4 +59,6 @@ class TestScriptModel: public QObject {
|
||||
private slots:
|
||||
static void unique_data(); // NOLINT
|
||||
static void unique();
|
||||
static void structuralEquality();
|
||||
static void comparisonModes();
|
||||
};
|
||||
@@ -177,6 +177,8 @@ void QmlToolingSupport::updateToolingFs(
|
||||
auto fileInfo = QFileInfo(path);
|
||||
if (!fileInfo.isFile()) continue;
|
||||
|
||||
if (scanner.fileIntercepts.contains(path)) continue;
|
||||
|
||||
auto spath = linkDir.filePath(name);
|
||||
auto sFileInfo = QFileInfo(spath);
|
||||
|
||||
@@ -205,8 +207,10 @@ void QmlToolingSupport::updateToolingFs(
|
||||
}
|
||||
|
||||
auto spath = linkDir.filePath(name);
|
||||
QFile::remove(spath);
|
||||
|
||||
auto file = QFile(spath);
|
||||
if (!file.open(QFile::ReadWrite | QFile::Text)) {
|
||||
if (!file.open(QFile::ReadWrite | QFile::Text | QFile::NewOnly)) {
|
||||
qCCritical(logTooling) << "Failed to open injected file" << spath;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ void Variants::updateVariants() {
|
||||
|
||||
{
|
||||
if (this->mInstances.contains(variant)) {
|
||||
continue; // we dont need to recreate this one
|
||||
continue; // we don't need to recreate this one
|
||||
}
|
||||
|
||||
auto variantMap = QVariantMap();
|
||||
|
||||
@@ -43,7 +43,7 @@ class Variants: public Reloadable {
|
||||
Q_OBJECT;
|
||||
/// The component to create instances of.
|
||||
///
|
||||
/// The delegate should define a `modelData` property that will be popuplated with a value
|
||||
/// The delegate should define a `modelData` property that will be populated with a value
|
||||
/// from the @@model.
|
||||
Q_PROPERTY(QQmlComponent* delegate MEMBER mDelegate);
|
||||
/// The list of sets of properties to create instances with.
|
||||
|
||||
+26
-6
@@ -5,9 +5,11 @@
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/forward.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
#include <qdatastream.h>
|
||||
#include <qfile.h>
|
||||
#include <qlogging.h>
|
||||
@@ -58,6 +60,12 @@ void signalHandler(
|
||||
siginfo_t* /*info*/, // NOLINT (misc-include-cleaner)
|
||||
void* /*context*/
|
||||
) {
|
||||
// NOLINTBEGIN (misc-include-cleaner)
|
||||
sigset_t set;
|
||||
sigfillset(&set);
|
||||
sigprocmask(SIG_UNBLOCK, &set, nullptr);
|
||||
// NOLINTEND
|
||||
|
||||
if (CrashInfo::INSTANCE.traceFd != -1) {
|
||||
auto traceBuffer = std::array<cpptrace::frame_ptr, 1024>();
|
||||
auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1);
|
||||
@@ -79,13 +87,9 @@ void signalHandler(
|
||||
fail:;
|
||||
}
|
||||
|
||||
// TODO: coredump fork and crash reporter remain as zombies, fix
|
||||
auto coredumpPid = fork();
|
||||
if (coredumpPid == 0) {
|
||||
// NOLINTBEGIN (misc-include-cleaner)
|
||||
sigset_t set;
|
||||
sigfillset(&set);
|
||||
sigprocmask(SIG_UNBLOCK, &set, nullptr);
|
||||
// NOLINTEND
|
||||
raise(sig);
|
||||
_exit(-1);
|
||||
}
|
||||
@@ -131,7 +135,6 @@ void signalHandler(
|
||||
perror("Failed to fork and launch crash reporter.\n");
|
||||
_exit(-1);
|
||||
} else if (pid == 0) {
|
||||
|
||||
// dup to remove CLOEXEC
|
||||
auto dumpFdStr = std::array<char, 48>();
|
||||
auto logFdStr = std::array<char, 48>();
|
||||
@@ -155,6 +158,21 @@ void signalHandler(
|
||||
}
|
||||
}
|
||||
|
||||
void handleCppTerminate() {
|
||||
if (auto ptr = std::current_exception()) {
|
||||
try {
|
||||
std::rethrow_exception(ptr);
|
||||
} catch (std::exception& e) {
|
||||
qFatal().nospace() << "Terminate called with C++ exception ("
|
||||
<< cpptrace::demangle(typeid(e).name()).data() << "): " << e.what();
|
||||
} catch (...) {
|
||||
qFatal() << "Terminate called with non exception object";
|
||||
}
|
||||
}
|
||||
|
||||
qFatal() << "Terminate called without active C++ exception";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CrashHandler::init() {
|
||||
@@ -203,6 +221,8 @@ void CrashHandler::init() {
|
||||
|
||||
// NOLINTEND (misc-include-cleaner)
|
||||
|
||||
std::set_terminate(&handleCppTerminate);
|
||||
|
||||
qCInfo(logCrashHandler) << "Crash handler initialized.";
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -25,6 +25,7 @@
|
||||
#include "../core/logging.hpp"
|
||||
#include "../core/logging_p.hpp"
|
||||
#include "../core/paths.hpp"
|
||||
#include "../core/plugin.hpp"
|
||||
#include "../core/ringbuf.hpp"
|
||||
#include "interface.hpp"
|
||||
|
||||
@@ -230,12 +231,17 @@ void qsCheckCrash(int argc, char** argv) {
|
||||
);
|
||||
|
||||
auto app = QApplication(argc, argv);
|
||||
QApplication::setDesktopFileName("org.quickshell");
|
||||
auto desktopId =
|
||||
info.instance.appId.isEmpty() ? QStringLiteral("org.quickshell") : info.instance.appId;
|
||||
QApplication::setDesktopFileName(desktopId);
|
||||
|
||||
auto crashDir = QsPaths::crashDir(info.instance.instanceId);
|
||||
|
||||
qCInfo(logCrashReporter) << "Starting crash reporter...";
|
||||
|
||||
// Required platform compatibility hooks
|
||||
QsEnginePlugin::preinitPluginsOnly();
|
||||
|
||||
recordCrashInfo(crashDir, info.instance);
|
||||
|
||||
auto gui = CrashReporterGui(crashDir.path(), crashProc);
|
||||
|
||||
@@ -301,6 +301,13 @@ DBusMenu::DBusMenu(const QString& service, const QString& path, QObject* parent)
|
||||
&DBusMenu::onLayoutUpdated
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->interface,
|
||||
&DBusMenuInterface::ItemsPropertiesUpdated,
|
||||
this,
|
||||
&DBusMenu::onItemPropertiesUpdated
|
||||
);
|
||||
|
||||
this->properties.setInterface(this->interface);
|
||||
this->properties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ void SplitParser::parseBytes(QByteArray& incoming, QByteArray& buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we dont miss any delimiters in the buffer if the delimiter changes
|
||||
// make sure we don't miss any delimiters in the buffer if the delimiter changes
|
||||
if (this->mSplitMarkerChanged) {
|
||||
this->mSplitMarkerChanged = false;
|
||||
this->parseBytes(buffer, buffer);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+3
-3
@@ -29,9 +29,9 @@ class FileViewError: public QObject {
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// No error occured.
|
||||
/// No error occurred.
|
||||
Success = 0,
|
||||
/// An unknown error occured. Check the logs for details.
|
||||
/// An unknown error occurred. Check the logs for details.
|
||||
Unknown = 1,
|
||||
/// The file to read does not exist.
|
||||
FileNotFound = 2,
|
||||
@@ -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);
|
||||
|
||||
+67
-154
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
+58
-21
@@ -1,11 +1,13 @@
|
||||
#include "jsonadapter.hpp"
|
||||
|
||||
#include <qassociativeiterable.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qjsonvalue.h>
|
||||
#include <qjsvalue.h>
|
||||
#include <qmetacontainer.h>
|
||||
#include <qmetaobject.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
@@ -14,6 +16,7 @@
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmlinfo.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qsequentialiterable.h>
|
||||
#include <qstringview.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
@@ -131,13 +134,22 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas
|
||||
}
|
||||
|
||||
json.insert(prop.name(), array);
|
||||
} else if (val.canConvert<QJSValue>()) {
|
||||
auto variant = val.value<QJSValue>().toVariant();
|
||||
auto jv = QJsonValue::fromVariant(variant);
|
||||
json.insert(prop.name(), jv);
|
||||
} else {
|
||||
auto jv = QJsonValue::fromVariant(val);
|
||||
json.insert(prop.name(), jv);
|
||||
if (val.canConvert<QJSValue>()) val = val.value<QJSValue>().toVariant();
|
||||
|
||||
auto jsonVal = QJsonValue::fromVariant(val);
|
||||
|
||||
if (jsonVal.isNull() && !val.isNull() && val.isValid()) {
|
||||
if (val.canConvert<QAssociativeIterable>()) {
|
||||
val.convert(QMetaType::fromType<QVariantMap>());
|
||||
} else if (val.canConvert<QSequentialIterable>()) {
|
||||
val.convert(QMetaType::fromType<QVariantList>());
|
||||
}
|
||||
|
||||
jsonVal = QJsonValue::fromVariant(val);
|
||||
}
|
||||
|
||||
json.insert(prop.name(), jsonVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,14 +166,16 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
||||
auto jval = json.value(prop.name());
|
||||
|
||||
if (prop.metaType() == QMetaType::fromType<QVariant>()) {
|
||||
auto variant = jval.toVariant();
|
||||
auto oldValue = prop.read(this).value<QJSValue>();
|
||||
auto newVariant = jval.toVariant();
|
||||
auto oldValue = prop.read(obj);
|
||||
auto oldVariant =
|
||||
oldValue.canConvert<QJSValue>() ? oldValue.value<QJSValue>().toVariant() : oldValue;
|
||||
|
||||
// Calling prop.write with a new QJSValue will cause a property update
|
||||
// even if content is identical.
|
||||
if (jval.toVariant() != oldValue.toVariant()) {
|
||||
auto jsValue = qmlEngine(this)->fromVariant<QJSValue>(jval.toVariant());
|
||||
prop.write(this, QVariant::fromValue(jsValue));
|
||||
if (newVariant != oldVariant) {
|
||||
auto jsValue = qmlEngine(this)->fromVariant<QJSValue>(newVariant);
|
||||
prop.write(obj, QVariant::fromValue(jsValue));
|
||||
}
|
||||
} else if (QMetaType::canView(prop.metaType(), QMetaType::fromType<JsonObject*>())) {
|
||||
// FIXME: This doesn't support creating descendants of JsonObject, as QMetaType.metaObject()
|
||||
@@ -196,7 +210,7 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
||||
QMetaType::fromType<QQmlListProperty<JsonObject>>()
|
||||
))
|
||||
{
|
||||
auto pval = prop.read(this);
|
||||
auto pval = prop.read(obj);
|
||||
|
||||
if (pval.canConvert<QQmlListProperty<JsonObject>>()) {
|
||||
auto lp = pval.value<QQmlListProperty<JsonObject>>();
|
||||
@@ -211,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(
|
||||
@@ -247,12 +261,35 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
||||
}
|
||||
} else {
|
||||
auto variant = jval.toVariant();
|
||||
auto convVariant = variant;
|
||||
|
||||
if (variant.convert(prop.metaType())) {
|
||||
prop.write(obj, variant);
|
||||
if (convVariant.convert(prop.metaType())) {
|
||||
prop.write(obj, convVariant);
|
||||
} else {
|
||||
qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected "
|
||||
<< prop.metaType().name() << " but got " << jval.toVariant().typeName();
|
||||
auto pval = prop.read(obj);
|
||||
if (variant.canConvert<QSequentialIterable>() && pval.canView<QSequentialIterable>()) {
|
||||
auto targetv = QVariant(pval.metaType());
|
||||
auto target = targetv.view<QSequentialIterable>().metaContainer();
|
||||
auto valueType = target.valueMetaType();
|
||||
auto i = 0;
|
||||
|
||||
for (QVariant item: variant.value<QSequentialIterable>()) {
|
||||
if (item.convert(valueType)) {
|
||||
target.addValueAtEnd(targetv.data(), item.constData());
|
||||
} else {
|
||||
qmlWarning(this) << "Failed to deserialize list member " << i << " of property "
|
||||
<< prop.name() << ": expected " << valueType.name() << " but got "
|
||||
<< item.typeName();
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
prop.write(obj, targetv);
|
||||
} else {
|
||||
qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected "
|
||||
<< prop.metaType().name() << " but got "
|
||||
<< jval.toVariant().typeName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
+2
-2
@@ -96,7 +96,7 @@ class SocketServer: public Reloadable {
|
||||
Q_OBJECT;
|
||||
/// If the socket server is currently active. Defaults to false.
|
||||
///
|
||||
/// Setting this to false will destory all active connections and delete
|
||||
/// Setting this to false will destroy all active connections and delete
|
||||
/// the socket file on disk.
|
||||
///
|
||||
/// If path is empty setting this property will have no effect.
|
||||
@@ -105,7 +105,7 @@ class SocketServer: public Reloadable {
|
||||
///
|
||||
/// Setting this property while the server is active will have no effect.
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
|
||||
/// Connection handler component. Must creeate a @@Socket.
|
||||
/// Connection handler component. Must create a @@Socket.
|
||||
///
|
||||
/// The created socket should not set @@connected or @@path or the incoming
|
||||
/// socket connection will be dropped (they will be set by the socket server.)
|
||||
|
||||
+12
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -19,7 +19,7 @@
|
||||
|
||||
template <typename... Types>
|
||||
constexpr void assertSerializable() {
|
||||
// monostate being zero ensures transactional reads wont break
|
||||
// monostate being zero ensures transactional reads won't break
|
||||
static_assert(
|
||||
std::is_same_v<std::variant_alternative_t<0, std::variant<Types...>>, std::monostate>,
|
||||
"Serialization of variants without std::monostate at index 0 is disallowed."
|
||||
@@ -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
|
||||
+88
-44
@@ -2,6 +2,7 @@
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include <qconfig.h>
|
||||
@@ -12,7 +13,6 @@
|
||||
#include <qdebug.h>
|
||||
#include <qdir.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
@@ -120,7 +120,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
qCCritical(logBare) << "Could not open maifest at path" << *cmd.config.manifest;
|
||||
qCCritical(logBare) << "Could not open manifest at path" << *cmd.config.manifest;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
@@ -165,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;
|
||||
|
||||
@@ -174,18 +179,22 @@ 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()) {
|
||||
path = basePath->filePath("by-pid");
|
||||
auto [liveInstances, deadInstances] =
|
||||
auto [liveInstances, mismatchedInstances, deadInstances] =
|
||||
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
||||
|
||||
liveInstances.removeIf([&](const InstanceLockInfo& info) {
|
||||
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||
});
|
||||
|
||||
mismatchedInstances.removeIf([&](const InstanceLockInfo& info) {
|
||||
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||
});
|
||||
|
||||
deadInstances.removeIf([&](const InstanceLockInfo& info) {
|
||||
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||
});
|
||||
@@ -193,27 +202,43 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
||||
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
if (deadFallback) {
|
||||
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
|
||||
} else {
|
||||
qCInfo(logBare) << "No running instances 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;
|
||||
|
||||
if (!deadInstances.isEmpty()) {
|
||||
qCInfo(logBare) << "Some dead instances match:";
|
||||
qCInfo(logBare) << "Some instances on other displays match:";
|
||||
|
||||
for (auto& instance: deadInstances) {
|
||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
|
||||
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 (!deadInstances.isEmpty()) {
|
||||
qCInfo(logBare) << "Some dead instances match:";
|
||||
|
||||
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;
|
||||
@@ -230,7 +255,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
||||
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
|
||||
auto [liveInstances, deadInstances] =
|
||||
auto [liveInstances, mismatchedInstances, deadInstances] =
|
||||
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
||||
|
||||
auto instances = liveInstances;
|
||||
@@ -244,16 +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;
|
||||
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:";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -314,7 +352,7 @@ int listInstances(CommandState& cmd) {
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
}
|
||||
|
||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(
|
||||
auto [liveInstances, mismatchedInstances, deadInstances] = QsPaths::collectInstances(
|
||||
path,
|
||||
cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection()
|
||||
);
|
||||
@@ -428,7 +466,7 @@ int ipcCommand(CommandState& cmd) {
|
||||
});
|
||||
}
|
||||
|
||||
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
|
||||
int launchFromCommand(CommandState& cmd) {
|
||||
QString configPath;
|
||||
|
||||
auto r = locateConfigFile(cmd, configPath);
|
||||
@@ -436,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;
|
||||
}
|
||||
@@ -448,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;
|
||||
|
||||
@@ -518,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
|
||||
@@ -540,23 +581,26 @@ 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() {
|
||||
auto platform = qEnvironmentVariable("QT_QPA_PLATFORM");
|
||||
// Doesn't actually have to match what qt picks, but will 99% of the time.
|
||||
// Running quickshell under wayland in x11 mode doesn't really make any sense.
|
||||
auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY");
|
||||
auto xDisplay = qEnvironmentVariable("DISPLAY");
|
||||
|
||||
if (platform == "wayland" || (platform.isEmpty() && !wlDisplay.isEmpty())) {
|
||||
return "wayland," + wlDisplay;
|
||||
} else if (platform == "xcb" || (platform.isEmpty() && !xDisplay.isEmpty())) {
|
||||
return "x11," + xDisplay;
|
||||
if (!wlDisplay.isEmpty()) {
|
||||
return "wayland/" + wlDisplay;
|
||||
} else if (!xDisplay.isEmpty()) {
|
||||
return "x11/" + xDisplay;
|
||||
} else {
|
||||
return "unk," + QGuiApplication::platformName();
|
||||
return "unk";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+82
-18
@@ -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);
|
||||
|
||||
@@ -76,6 +77,9 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
bool useSystemStyle = false;
|
||||
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
|
||||
QHash<QString, QString> envOverrides;
|
||||
QHash<QString, QString> defaultEnv;
|
||||
QString appId = qEnvironmentVariable("QS_APP_ID");
|
||||
bool dropExpensiveFonts = false;
|
||||
QString dataDir;
|
||||
QString stateDir;
|
||||
QString cacheDir;
|
||||
@@ -87,23 +91,31 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
if (line.startsWith("//@ pragma ")) {
|
||||
auto pragma = line.sliced(11).trimmed();
|
||||
|
||||
if (pragma == "UseQApplication") pragmas.useQApplication = true;
|
||||
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
|
||||
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
|
||||
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
|
||||
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
|
||||
else if (pragma.startsWith("Env ")) {
|
||||
auto envPragma = pragma.sliced(4);
|
||||
auto splitIdx = envPragma.indexOf('=');
|
||||
auto isEnv = pragma.startsWith("Env ");
|
||||
auto isDefaultEnv = pragma.startsWith("DefaultEnv ");
|
||||
|
||||
if (isEnv || isDefaultEnv) {
|
||||
auto content = pragma.sliced(isDefaultEnv ? 11 : 4);
|
||||
auto splitIdx = content.indexOf('=');
|
||||
|
||||
if (splitIdx == -1) {
|
||||
qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'";
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto var = envPragma.sliced(0, splitIdx).trimmed();
|
||||
auto val = envPragma.sliced(splitIdx + 1).trimmed();
|
||||
pragmas.envOverrides.insert(var, val);
|
||||
auto var = content.sliced(0, splitIdx).trimmed();
|
||||
auto val = content.sliced(splitIdx + 1).trimmed();
|
||||
|
||||
if (isDefaultEnv) pragmas.defaultEnv.insert(var, val);
|
||||
else pragmas.envOverrides.insert(var, val);
|
||||
} else if (pragma == "UseQApplication") pragmas.useQApplication = true;
|
||||
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
|
||||
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
|
||||
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
|
||||
else if (pragma == "DropExpensiveFonts") pragmas.dropExpensiveFonts = true;
|
||||
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
|
||||
else if (pragma.startsWith("AppId ")) {
|
||||
pragmas.appId = pragma.sliced(6).trimmed();
|
||||
} else if (pragma.startsWith("ShellId ")) {
|
||||
shellId = pragma.sliced(8).trimmed();
|
||||
} else if (pragma.startsWith("DataDir ")) {
|
||||
@@ -113,8 +125,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
} else if (pragma.startsWith("CacheDir ")) {
|
||||
pragmas.cacheDir = pragma.sliced(9).trimmed();
|
||||
} else {
|
||||
qCritical() << "Unrecognized pragma" << pragma;
|
||||
return -1;
|
||||
qWarning() << "Unrecognized pragma" << pragma;
|
||||
}
|
||||
} else if (line.startsWith("import")) break;
|
||||
}
|
||||
@@ -128,10 +139,13 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
qInfo() << "Shell ID:" << shellId << "Path ID" << pathId;
|
||||
|
||||
auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch();
|
||||
auto appId = pragmas.appId.isEmpty() ? QStringLiteral("org.quickshell") : pragmas.appId;
|
||||
|
||||
InstanceInfo::CURRENT = InstanceInfo {
|
||||
.instanceId = base36Encode(getpid()) + base36Encode(launchTime),
|
||||
.configPath = args.configPath,
|
||||
.shellId = shellId,
|
||||
.appId = appId,
|
||||
.launchTime = qs::Common::LAUNCH_TIME,
|
||||
.pid = getpid(),
|
||||
.display = getDisplayConnection(),
|
||||
@@ -158,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();
|
||||
|
||||
@@ -167,10 +180,56 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion");
|
||||
}
|
||||
|
||||
for (auto [var, val]: pragmas.defaultEnv.asKeyValueRange()) {
|
||||
if (!qEnvironmentVariableIsSet(var.toUtf8())) qputenv(var.toUtf8(), val.toUtf8());
|
||||
}
|
||||
|
||||
for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) {
|
||||
qputenv(var.toUtf8(), val.toUtf8());
|
||||
}
|
||||
|
||||
pragmas.dropExpensiveFonts |= qEnvironmentVariableIntValue("QS_DROP_EXPENSIVE_FONTS") == 1;
|
||||
|
||||
if (pragmas.dropExpensiveFonts) {
|
||||
if (auto* runDir = QsPaths::instance()->instanceRunDir()) {
|
||||
auto baseConfigPath = qEnvironmentVariable("FONTCONFIG_FILE");
|
||||
if (baseConfigPath.isEmpty()) baseConfigPath = "/etc/fonts/fonts.conf";
|
||||
|
||||
auto filterPath = runDir->filePath("fonts-override.conf");
|
||||
auto filterFile = QFile(filterPath);
|
||||
if (filterFile.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) {
|
||||
auto filterTemplate = QStringLiteral(R"(<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
|
||||
<fontconfig>
|
||||
<include ignore_missing="no">%1</include>
|
||||
<selectfont>
|
||||
<rejectfont>
|
||||
<pattern>
|
||||
<patelt name="fontwrapper">
|
||||
<string>woff</string>
|
||||
</patelt>
|
||||
</pattern>
|
||||
<pattern>
|
||||
<patelt name="fontwrapper">
|
||||
<string>woff2</string>
|
||||
</patelt>
|
||||
</pattern>
|
||||
</rejectfont>
|
||||
</selectfont>
|
||||
</fontconfig>
|
||||
)");
|
||||
|
||||
QTextStream(&filterFile) << filterTemplate.arg(baseConfigPath);
|
||||
filterFile.close();
|
||||
qputenv("FONTCONFIG_FILE", filterPath.toUtf8());
|
||||
} else {
|
||||
qCritical() << "Could not write fontconfig filter to" << filterPath;
|
||||
}
|
||||
} else {
|
||||
qCritical() << "Could not create fontconfig filter: instance run directory unavailable";
|
||||
}
|
||||
}
|
||||
|
||||
// The qml engine currently refuses to cache non file (qsintercept) paths.
|
||||
|
||||
// if (auto* cacheDir = QsPaths::instance()->cacheDir()) {
|
||||
@@ -220,8 +279,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
|
||||
QGuiApplication::setDesktopSettingsAware(pragmas.desktopSettingsAware);
|
||||
|
||||
delete coreApplication;
|
||||
|
||||
QGuiApplication* app = nullptr;
|
||||
auto qArgC = 0;
|
||||
|
||||
@@ -231,7 +288,10 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
app = new QGuiApplication(qArgC, argv);
|
||||
}
|
||||
|
||||
QGuiApplication::setDesktopFileName("org.quickshell");
|
||||
LogManager::initThreadLogging();
|
||||
LogManager::initFs();
|
||||
|
||||
QGuiApplication::setDesktopFileName(appId);
|
||||
|
||||
if (args.debugPort != -1) {
|
||||
QQmlDebuggingEnabler::enableDebugging(true);
|
||||
@@ -240,6 +300,10 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
QQmlDebuggingEnabler::startTcpDebugServer(args.debugPort, wait);
|
||||
}
|
||||
|
||||
// This needs to run early to get the first connection to QGuiApplication::screenAdded() in Qs.
|
||||
// If we don't do that, attempts to get a QuickshellScreenInfo from a QScreen will fail in screenAdded bindings.
|
||||
QuickshellTracked::init();
|
||||
|
||||
QsEnginePlugin::initPlugins();
|
||||
|
||||
// Base window transparency appears to be additive.
|
||||
|
||||
@@ -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
|
||||
+39
-27
@@ -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
|
||||
@@ -84,21 +91,29 @@ void exitDaemon(int code) {
|
||||
|
||||
close(DAEMON_PIPE);
|
||||
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
|
||||
if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stdin";
|
||||
auto fd = open("/dev/null", O_RDWR);
|
||||
if (fd == -1) {
|
||||
qCritical().nospace() << "Failed to open /dev/null for daemon stdio" << errno << ": "
|
||||
<< qt_error_string();
|
||||
return;
|
||||
}
|
||||
|
||||
if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stdout";
|
||||
if (dup2(fd, STDIN_FILENO) != STDIN_FILENO) { // NOLINT
|
||||
qCritical().nospace() << "Failed to set daemon stdin to /dev/null" << errno << ": "
|
||||
<< qt_error_string();
|
||||
}
|
||||
|
||||
if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stderr";
|
||||
if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) { // NOLINT
|
||||
qCritical().nospace() << "Failed to set daemon stdout to /dev/null" << errno << ": "
|
||||
<< qt_error_string();
|
||||
}
|
||||
|
||||
if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) { // NOLINT
|
||||
qCritical().nospace() << "Failed to set daemon stderr to /dev/null" << errno << ": "
|
||||
<< qt_error_string();
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -108,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;
|
||||
|
||||
@@ -3,7 +3,10 @@ add_subdirectory(nm)
|
||||
qt_add_library(quickshell-network STATIC
|
||||
network.cpp
|
||||
device.cpp
|
||||
wired.cpp
|
||||
wifi.cpp
|
||||
enums.cpp
|
||||
qml.cpp
|
||||
)
|
||||
|
||||
target_include_directories(quickshell-network PRIVATE
|
||||
|
||||
+13
-43
@@ -8,6 +8,8 @@
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../core/logcat.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "network.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
@@ -15,49 +17,9 @@ namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg);
|
||||
} // namespace
|
||||
|
||||
QString DeviceConnectionState::toString(DeviceConnectionState::Enum state) {
|
||||
switch (state) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case Connecting: return QStringLiteral("Connecting");
|
||||
case Connected: return QStringLiteral("Connected");
|
||||
case Disconnecting: return QStringLiteral("Disconnecting");
|
||||
case Disconnected: return QStringLiteral("Disconnected");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString DeviceType::toString(DeviceType::Enum type) {
|
||||
switch (type) {
|
||||
case None: return QStringLiteral("None");
|
||||
case Wifi: return QStringLiteral("Wifi");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString NMDeviceState::toString(NMDeviceState::Enum state) {
|
||||
switch (state) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case Unmanaged: return QStringLiteral("Not managed by NetworkManager");
|
||||
case Unavailable: return QStringLiteral("Unavailable");
|
||||
case Disconnected: return QStringLiteral("Disconnected");
|
||||
case Prepare: return QStringLiteral("Preparing to connect");
|
||||
case Config: return QStringLiteral("Connecting to a network");
|
||||
case NeedAuth: return QStringLiteral("Waiting for authentication");
|
||||
case IPConfig: return QStringLiteral("Requesting IPv4 and/or IPv6 addresses from the network");
|
||||
case IPCheck:
|
||||
return QStringLiteral("Checking if further action is required for the requested connection");
|
||||
case Secondaries:
|
||||
return QStringLiteral("Waiting for a required secondary connection to activate");
|
||||
case Activated: return QStringLiteral("Connected");
|
||||
case Deactivating: return QStringLiteral("Disconnecting");
|
||||
case Failed: return QStringLiteral("Failed to connect");
|
||||
default: return QStringLiteral("Unknown");
|
||||
};
|
||||
}
|
||||
|
||||
NetworkDevice::NetworkDevice(DeviceType::Enum type, QObject* parent): QObject(parent), mType(type) {
|
||||
this->bindableConnected().setBinding([this]() {
|
||||
return this->bState == DeviceConnectionState::Connected;
|
||||
return this->bState == ConnectionState::Connected;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -66,12 +28,17 @@ void NetworkDevice::setAutoconnect(bool autoconnect) {
|
||||
emit this->requestSetAutoconnect(autoconnect);
|
||||
}
|
||||
|
||||
void NetworkDevice::setNmManaged(bool managed) {
|
||||
if (this->bNmManaged == managed) return;
|
||||
emit this->requestSetNmManaged(managed);
|
||||
}
|
||||
|
||||
void NetworkDevice::disconnect() {
|
||||
if (this->bState == DeviceConnectionState::Disconnected) {
|
||||
if (this->bState == ConnectionState::Disconnected) {
|
||||
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected";
|
||||
return;
|
||||
}
|
||||
if (this->bState == DeviceConnectionState::Disconnecting) {
|
||||
if (this->bState == ConnectionState::Disconnecting) {
|
||||
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting";
|
||||
return;
|
||||
}
|
||||
@@ -79,4 +46,7 @@ void NetworkDevice::disconnect() {
|
||||
this->requestDisconnect();
|
||||
}
|
||||
|
||||
void NetworkDevice::networkAdded(Network* net) { this->mNetworks.insertObject(net); }
|
||||
void NetworkDevice::networkRemoved(Network* net) { this->mNetworks.removeObject(net); }
|
||||
|
||||
} // namespace qs::network
|
||||
+45
-79
@@ -6,88 +6,44 @@
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../core/doc.hpp"
|
||||
#include "../core/model.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "network.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
///! Connection state of a NetworkDevice.
|
||||
class DeviceConnectionState: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Connecting = 1,
|
||||
Connected = 2,
|
||||
Disconnecting = 3,
|
||||
Disconnected = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(DeviceConnectionState::Enum state);
|
||||
};
|
||||
|
||||
///! Type of network device.
|
||||
class DeviceType: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
Wifi = 1,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(DeviceType::Enum type);
|
||||
};
|
||||
|
||||
///! NetworkManager-specific device state.
|
||||
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState.
|
||||
class NMDeviceState: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Unmanaged = 10,
|
||||
Unavailable = 20,
|
||||
Disconnected = 30,
|
||||
Prepare = 40,
|
||||
Config = 50,
|
||||
NeedAuth = 60,
|
||||
IPConfig = 70,
|
||||
IPCheck = 80,
|
||||
Secondaries = 90,
|
||||
Activated = 100,
|
||||
Deactivating = 110,
|
||||
Failed = 120,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(NMDeviceState::Enum state);
|
||||
};
|
||||
|
||||
///! A network device.
|
||||
/// When @@type is `Wifi`, the device is a @@WifiDevice, which can be used to scan for and connect to access points.
|
||||
/// The @@type property may be used to determine if this device is a @@WifiDevice or @@WiredDevice.
|
||||
class NetworkDevice: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("Devices can only be acquired through Network");
|
||||
// clang-format off
|
||||
/// The device type.
|
||||
///
|
||||
/// 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);
|
||||
/// 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.
|
||||
///
|
||||
/// When the device type is 'Wifi', this model will only contain @@WifiNetwork.
|
||||
QSDOC_TYPE_OVERRIDE(ObjectModel<Network>*);
|
||||
Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT);
|
||||
/// The hardware address of the device in the XX:XX:XX:XX:XX:XX format.
|
||||
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
|
||||
/// True if the device is connected.
|
||||
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
|
||||
/// Connection state of the device.
|
||||
Q_PROPERTY(qs::network::DeviceConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
|
||||
/// A more specific device state when the backend is NetworkManager.
|
||||
Q_PROPERTY(qs::network::NMDeviceState::Enum nmState READ default NOTIFY nmStateChanged BINDABLE bindableNmState);
|
||||
/// True if the device is allowed to autoconnect.
|
||||
Q_PROPERTY(qs::network::ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
|
||||
/// True if the device is managed by NetworkManager.
|
||||
///
|
||||
/// > [!WARNING] Only valid for the NetworkManager backend.
|
||||
Q_PROPERTY(bool nmManaged READ nmManaged WRITE setNmManaged NOTIFY nmManagedChanged)
|
||||
/// True if the device is allowed to autoconnect to a network.
|
||||
Q_PROPERTY(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged);
|
||||
// clang-format on
|
||||
|
||||
@@ -97,35 +53,45 @@ public:
|
||||
/// Disconnects the device and prevents it from automatically activating further connections.
|
||||
Q_INVOKABLE void disconnect();
|
||||
|
||||
[[nodiscard]] DeviceType::Enum type() const { return this->mType; };
|
||||
QBindable<QString> bindableName() { return &this->bName; };
|
||||
[[nodiscard]] QString name() const { return this->bName; };
|
||||
QBindable<QString> bindableAddress() { return &this->bAddress; };
|
||||
QBindable<bool> bindableConnected() { return &this->bConnected; };
|
||||
QBindable<DeviceConnectionState::Enum> bindableState() { return &this->bState; };
|
||||
QBindable<NMDeviceState::Enum> bindableNmState() { return &this->bNmState; };
|
||||
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
|
||||
QBindable<bool> bindableAutoconnect() { return &this->bAutoconnect; };
|
||||
virtual void networkAdded(Network* net);
|
||||
virtual void networkRemoved(Network* net);
|
||||
|
||||
[[nodiscard]] ObjectModel<Network>* networks() { return &this->mNetworks; }
|
||||
[[nodiscard]] DeviceType::Enum type() const { return this->mType; }
|
||||
QBindable<QString> bindableName() { return &this->bName; }
|
||||
[[nodiscard]] QString name() const { return this->bName; }
|
||||
QBindable<QString> bindableAddress() { return &this->bAddress; }
|
||||
QBindable<bool> bindableConnected() { return &this->bConnected; }
|
||||
QBindable<ConnectionState::Enum> bindableState() { return &this->bState; }
|
||||
QBindable<bool> bindableNmManaged() { return &this->bNmManaged; }
|
||||
[[nodiscard]] bool nmManaged() { return this->bNmManaged; }
|
||||
void setNmManaged(bool managed);
|
||||
QBindable<bool> bindableAutoconnect() { return &this->bAutoconnect; }
|
||||
[[nodiscard]] bool autoconnect() { return this->bAutoconnect; }
|
||||
void setAutoconnect(bool autoconnect);
|
||||
|
||||
signals:
|
||||
void requestDisconnect();
|
||||
void requestSetAutoconnect(bool autoconnect);
|
||||
QSDOC_HIDE void requestDisconnect();
|
||||
QSDOC_HIDE void requestSetAutoconnect(bool autoconnect);
|
||||
QSDOC_HIDE void requestSetNmManaged(bool managed);
|
||||
void nameChanged();
|
||||
void addressChanged();
|
||||
void connectedChanged();
|
||||
void stateChanged();
|
||||
void nmStateChanged();
|
||||
void nmManagedChanged();
|
||||
void autoconnectChanged();
|
||||
|
||||
protected:
|
||||
ObjectModel<Network> mNetworks {this};
|
||||
|
||||
private:
|
||||
DeviceType::Enum mType;
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, DeviceConnectionState::Enum, bState, &NetworkDevice::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NMDeviceState::Enum, bNmState, &NetworkDevice::nmStateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, ConnectionState::Enum, bState, &NetworkDevice::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bNmManaged, &NetworkDevice::nmManagedChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bAutoconnect, &NetworkDevice::autoconnectChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "enums.hpp"
|
||||
|
||||
#include <qstring.h>
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
QString NetworkConnectivity::toString(NetworkConnectivity::Enum conn) {
|
||||
switch (conn) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case None: return QStringLiteral("Not connected to a network");
|
||||
case Portal: return QStringLiteral("Connection intercepted by a captive portal");
|
||||
case Limited: return QStringLiteral("Partial internet connectivity");
|
||||
case Full: return QStringLiteral("Full internet connectivity");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString NetworkBackendType::toString(NetworkBackendType::Enum type) {
|
||||
switch (type) {
|
||||
case NetworkBackendType::None: return "None";
|
||||
case NetworkBackendType::NetworkManager: return "NetworkManager";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
QString ConnectionState::toString(ConnectionState::Enum state) {
|
||||
switch (state) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case Connecting: return QStringLiteral("Connecting");
|
||||
case Connected: return QStringLiteral("Connected");
|
||||
case Disconnecting: return QStringLiteral("Disconnecting");
|
||||
case Disconnected: return QStringLiteral("Disconnected");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString ConnectionFailReason::toString(ConnectionFailReason::Enum reason) {
|
||||
switch (reason) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case NoSecrets: return QStringLiteral("Secrets were required but not provided");
|
||||
case WifiClientDisconnected: return QStringLiteral("Wi-Fi supplicant disconnected");
|
||||
case WifiClientFailed: return QStringLiteral("Wi-Fi supplicant failed");
|
||||
case WifiAuthTimeout: return QStringLiteral("Wi-Fi connection took too long to authenticate");
|
||||
case WifiNetworkLost: return QStringLiteral("Wi-Fi network could not be found");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString DeviceType::toString(DeviceType::Enum type) {
|
||||
switch (type) {
|
||||
case None: return QStringLiteral("None");
|
||||
case Wifi: return QStringLiteral("Wifi");
|
||||
case Wired: return QStringLiteral("Wired");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString WifiSecurityType::toString(WifiSecurityType::Enum type) {
|
||||
switch (type) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit");
|
||||
case Sae: return QStringLiteral("WPA3");
|
||||
case Wpa2Eap: return QStringLiteral("WPA2 Enterprise");
|
||||
case Wpa2Psk: return QStringLiteral("WPA2");
|
||||
case WpaEap: return QStringLiteral("WPA Enterprise");
|
||||
case WpaPsk: return QStringLiteral("WPA");
|
||||
case StaticWep: return QStringLiteral("WEP");
|
||||
case DynamicWep: return QStringLiteral("Dynamic WEP");
|
||||
case Leap: return QStringLiteral("LEAP");
|
||||
case Owe: return QStringLiteral("OWE");
|
||||
case Open: return QStringLiteral("Open");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) {
|
||||
switch (mode) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case AdHoc: return QStringLiteral("Ad-Hoc");
|
||||
case Station: return QStringLiteral("Station");
|
||||
case AccessPoint: return QStringLiteral("Access Point");
|
||||
case Mesh: return QStringLiteral("Mesh");
|
||||
default: return QStringLiteral("Unknown");
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
///! The degree to which the host can reach the internet.
|
||||
class NetworkConnectivity: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// Network connectivity is unknown. This means the connectivity checks are disabled or have not run yet.
|
||||
Unknown = 0,
|
||||
/// The host is not connected to any network.
|
||||
None = 1,
|
||||
/// The internet connection is hijacked by a captive portal gateway.
|
||||
/// This indicates the shell should open a sandboxed web browser window for the purpose of authenticating to a gateway.
|
||||
Portal = 2,
|
||||
/// The host is connected to a network but does not appear to be able to reach the full internet.
|
||||
Limited = 3,
|
||||
/// The host is connected to a network and appears to be able to reach the full internet.
|
||||
Full = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(NetworkConnectivity::Enum conn);
|
||||
};
|
||||
|
||||
///! The backend supplying the Network service.
|
||||
class NetworkBackendType: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
NetworkManager = 1,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(NetworkBackendType::Enum type);
|
||||
};
|
||||
|
||||
///! The connection state of a device or network.
|
||||
class ConnectionState: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Connecting = 1,
|
||||
Connected = 2,
|
||||
Disconnecting = 3,
|
||||
Disconnected = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(ConnectionState::Enum state);
|
||||
};
|
||||
|
||||
///! The reason a connection failed.
|
||||
class ConnectionFailReason: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// The connection failed for an unknown reason.
|
||||
Unknown = 0,
|
||||
/// Secrets were required, but not provided.
|
||||
NoSecrets = 1,
|
||||
/// The Wi-Fi supplicant disconnected.
|
||||
WifiClientDisconnected = 2,
|
||||
/// The Wi-Fi supplicant failed.
|
||||
WifiClientFailed = 3,
|
||||
/// The Wi-Fi connection took too long to authenticate.
|
||||
WifiAuthTimeout = 4,
|
||||
/// The Wi-Fi network could not be found.
|
||||
WifiNetworkLost = 5,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(ConnectionFailReason::Enum reason);
|
||||
};
|
||||
|
||||
///! Type of a @@NetworkDevice.
|
||||
class DeviceType: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
Wifi = 1,
|
||||
Wired = 2,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(DeviceType::Enum type);
|
||||
};
|
||||
|
||||
///! The security type of a @@WifiNetwork.
|
||||
class WifiSecurityType: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Wpa3SuiteB192 = 0,
|
||||
Sae = 1,
|
||||
Wpa2Eap = 2,
|
||||
Wpa2Psk = 3,
|
||||
WpaEap = 4,
|
||||
WpaPsk = 5,
|
||||
StaticWep = 6,
|
||||
DynamicWep = 7,
|
||||
Leap = 8,
|
||||
Owe = 9,
|
||||
Open = 10,
|
||||
Unknown = 11,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(WifiSecurityType::Enum type);
|
||||
};
|
||||
|
||||
///! The 802.11 mode of a @@WifiDevice.
|
||||
class WifiDeviceMode: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// The device is part of an Ad-Hoc network without a central access point.
|
||||
AdHoc = 0,
|
||||
/// The device is a station that can connect to networks.
|
||||
Station = 1,
|
||||
/// The device is a local hotspot/access point.
|
||||
AccessPoint = 2,
|
||||
/// The device is an 802.11s mesh point.
|
||||
Mesh = 3,
|
||||
/// The device mode is unknown.
|
||||
Unknown = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode);
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -1,9 +1,13 @@
|
||||
name = "Quickshell.Networking"
|
||||
description = "Network API"
|
||||
headers = [
|
||||
"qml.hpp",
|
||||
"network.hpp",
|
||||
"device.hpp",
|
||||
"wifi.hpp",
|
||||
"wired.hpp",
|
||||
"enums.hpp",
|
||||
"nm/settings.hpp",
|
||||
]
|
||||
-----
|
||||
This module exposes Network management APIs provided by a supported network backend.
|
||||
|
||||
+48
-43
@@ -1,15 +1,15 @@
|
||||
#include "network.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../core/logcat.hpp"
|
||||
#include "device.hpp"
|
||||
#include "nm/backend.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "nm/settings.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
@@ -17,49 +17,54 @@ namespace {
|
||||
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
|
||||
} // namespace
|
||||
|
||||
QString NetworkState::toString(NetworkState::Enum state) {
|
||||
switch (state) {
|
||||
case NetworkState::Connecting: return QStringLiteral("Connecting");
|
||||
case NetworkState::Connected: return QStringLiteral("Connected");
|
||||
case NetworkState::Disconnecting: return QStringLiteral("Disconnecting");
|
||||
case NetworkState::Disconnected: return QStringLiteral("Disconnected");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
Networking::Networking(QObject* parent): QObject(parent) {
|
||||
// Try to create the NetworkManager backend and bind to it.
|
||||
auto* nm = new NetworkManager(this);
|
||||
if (nm->isAvailable()) {
|
||||
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
|
||||
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
|
||||
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
|
||||
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
|
||||
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
|
||||
|
||||
this->mBackend = nm;
|
||||
this->mBackendType = NetworkBackendType::NetworkManager;
|
||||
return;
|
||||
} else {
|
||||
delete nm;
|
||||
}
|
||||
|
||||
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
|
||||
}
|
||||
|
||||
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
|
||||
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
|
||||
|
||||
void Networking::setWifiEnabled(bool enabled) {
|
||||
if (this->bWifiEnabled == enabled) return;
|
||||
emit this->requestSetWifiEnabled(enabled);
|
||||
}
|
||||
|
||||
Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) {
|
||||
Network::Network(QString name, NetworkDevice* device, QObject* parent)
|
||||
: QObject(parent)
|
||||
, bName(std::move(name))
|
||||
, mDevice(device) {
|
||||
this->bStateChanging.setBinding([this] {
|
||||
auto state = this->bState.value();
|
||||
return state == NetworkState::Connecting || state == NetworkState::Disconnecting;
|
||||
return state == ConnectionState::Connecting || state == ConnectionState::Disconnecting;
|
||||
});
|
||||
};
|
||||
|
||||
void Network::connect() {
|
||||
if (this->bConnected) {
|
||||
qCCritical(logNetwork) << this << "is already connected.";
|
||||
return;
|
||||
}
|
||||
this->requestConnect();
|
||||
}
|
||||
|
||||
void Network::connectWithSettings(NMSettings* settings) {
|
||||
if (this->bConnected) {
|
||||
qCCritical(logNetwork) << this << "is already connected.";
|
||||
return;
|
||||
}
|
||||
if (this->bNmSettings.value().indexOf(settings) == -1) return;
|
||||
this->requestConnectWithSettings(settings);
|
||||
}
|
||||
|
||||
void Network::disconnect() {
|
||||
if (!this->bConnected) {
|
||||
qCCritical(logNetwork) << this << "is not currently connected";
|
||||
return;
|
||||
}
|
||||
this->requestDisconnect();
|
||||
}
|
||||
|
||||
void Network::forget() { this->requestForget(); }
|
||||
|
||||
void Network::settingsAdded(NMSettings* settings) {
|
||||
auto list = this->bNmSettings.value();
|
||||
if (list.contains(settings)) return;
|
||||
list.append(settings);
|
||||
this->bNmSettings = list;
|
||||
}
|
||||
|
||||
void Network::settingsRemoved(NMSettings* settings) {
|
||||
auto list = this->bNmSettings.value();
|
||||
list.removeOne(settings);
|
||||
this->bNmSettings = list;
|
||||
}
|
||||
|
||||
} // namespace qs::network
|
||||
+66
-101
@@ -6,137 +6,102 @@
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../core/model.hpp"
|
||||
#include "device.hpp"
|
||||
#include "../core/doc.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "nm/settings.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
class NetworkDevice;
|
||||
}
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
///! The connection state of a Network.
|
||||
class NetworkState: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Connecting = 1,
|
||||
Connected = 2,
|
||||
Disconnecting = 3,
|
||||
Disconnected = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(NetworkState::Enum state);
|
||||
};
|
||||
|
||||
///! The backend supplying the Network service.
|
||||
class NetworkBackendType: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
NetworkManager = 1,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
class NetworkBackend: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
[[nodiscard]] virtual bool isAvailable() const = 0;
|
||||
|
||||
protected:
|
||||
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
|
||||
};
|
||||
|
||||
///! The Network service.
|
||||
/// An interface to a network backend (currently only NetworkManager),
|
||||
/// which can be used to view, configure, and connect to various networks.
|
||||
class Networking: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_SINGLETON;
|
||||
QML_ELEMENT;
|
||||
// clang-format off
|
||||
/// A list of all network devices.
|
||||
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::network::NetworkDevice>*);
|
||||
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
|
||||
/// The backend being used to power the Network service.
|
||||
Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT);
|
||||
/// Switch for the rfkill software block of all wireless devices.
|
||||
Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged);
|
||||
/// State of the rfkill hardware block of all wireless devices.
|
||||
Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit Networking(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] ObjectModel<NetworkDevice>* devices() { return &this->mDevices; };
|
||||
[[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; };
|
||||
QBindable<bool> bindableWifiEnabled() { return &this->bWifiEnabled; };
|
||||
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; };
|
||||
void setWifiEnabled(bool enabled);
|
||||
QBindable<bool> bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; };
|
||||
|
||||
signals:
|
||||
void requestSetWifiEnabled(bool enabled);
|
||||
void wifiEnabledChanged();
|
||||
void wifiHardwareEnabledChanged();
|
||||
|
||||
private slots:
|
||||
void deviceAdded(NetworkDevice* dev);
|
||||
void deviceRemoved(NetworkDevice* dev);
|
||||
|
||||
private:
|
||||
ObjectModel<NetworkDevice> mDevices {this};
|
||||
NetworkBackend* mBackend = nullptr;
|
||||
NetworkBackendType::Enum mBackendType = NetworkBackendType::None;
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
///! A network.
|
||||
/// A network. Networks derived from a @@WifiDevice are @@WifiNetwork instances.
|
||||
class Network: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("BaseNetwork can only be aqcuired through network devices");
|
||||
QML_UNCREATABLE("Network can only be aqcuired through networking devices");
|
||||
|
||||
// clang-format off
|
||||
/// The name of the network.
|
||||
Q_PROPERTY(QString name READ name CONSTANT);
|
||||
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
|
||||
/// The device this network belongs to.
|
||||
Q_PROPERTY(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);
|
||||
/// 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(NetworkState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
|
||||
Q_PROPERTY(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
|
||||
|
||||
public:
|
||||
explicit Network(QString name, QObject* parent = nullptr);
|
||||
explicit Network(QString name, NetworkDevice* device, QObject* parent = nullptr);
|
||||
/// Attempt to connect to the network.
|
||||
///
|
||||
/// > [!NOTE] If the network is a @@WifiNetwork and requires secrets, a @@connectionFailed(s)
|
||||
/// > signal will be emitted with `NoSecrets`.
|
||||
/// > @@WifiNetwork.connectWithPsk() can be used to provide secrets.
|
||||
Q_INVOKABLE void connect();
|
||||
/// Attempt to connect to the network with a specific @@nmSettings entry.
|
||||
///
|
||||
/// > [!WARNING] Only valid for the NetworkManager backend.
|
||||
Q_INVOKABLE void connectWithSettings(NMSettings* settings);
|
||||
/// Disconnect from the network.
|
||||
Q_INVOKABLE void disconnect();
|
||||
/// Forget all connection settings for this network.
|
||||
Q_INVOKABLE void forget();
|
||||
|
||||
[[nodiscard]] QString name() const { return this->mName; };
|
||||
void settingsAdded(NMSettings* settings);
|
||||
void settingsRemoved(NMSettings* settings);
|
||||
|
||||
// clang-format off
|
||||
[[nodiscard]] QString name() const { return this->bName; }
|
||||
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
|
||||
[[nodiscard]] NetworkDevice* device() const { return this->mDevice; }
|
||||
[[nodiscard]] const QList<NMSettings*>& nmSettings() const { return this->bNmSettings; }
|
||||
QBindable<QList<NMSettings*>> bindableNmSettings() const { return &this->bNmSettings; }
|
||||
QBindable<bool> bindableConnected() { return &this->bConnected; }
|
||||
QBindable<NetworkState::Enum> bindableState() { return &this->bState; }
|
||||
QBindable<bool> bindableKnown() { return &this->bKnown; }
|
||||
[[nodiscard]] ConnectionState::Enum state() const { return this->bState; }
|
||||
QBindable<ConnectionState::Enum> bindableState() { return &this->bState; }
|
||||
QBindable<bool> bindableStateChanging() { return &this->bStateChanging; }
|
||||
// clang-format on
|
||||
|
||||
signals:
|
||||
/// Signals that a connection to the network has failed because of the given @@ConnectionFailReason.
|
||||
void connectionFailed(ConnectionFailReason::Enum reason);
|
||||
|
||||
void nameChanged();
|
||||
void connectedChanged();
|
||||
void knownChanged();
|
||||
void stateChanged();
|
||||
void stateChangingChanged();
|
||||
void nmSettingsChanged();
|
||||
QSDOC_HIDE void requestConnect();
|
||||
QSDOC_HIDE void requestConnectWithSettings(NMSettings* settings);
|
||||
QSDOC_HIDE void requestDisconnect();
|
||||
QSDOC_HIDE void requestForget();
|
||||
|
||||
protected:
|
||||
QString mName;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, QString, bName, &Network::nameChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, NetworkState::Enum, bState, &Network::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bKnown, &Network::knownChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, ConnectionState::Enum, bState, &Network::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, QList<NMSettings*>, bNmSettings, &Network::nmSettingsChanged);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
NetworkDevice* mDevice;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -29,6 +29,16 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
dbus_nm_wireless
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.Device.Wired.xml PROPERTIES
|
||||
CLASSNAME DBusNMWiredProxy
|
||||
NO_NAMESPACE TRUE
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.Device.Wired.xml
|
||||
dbus_nm_wired
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES
|
||||
CLASSNAME DBusNMAccessPointProxy
|
||||
NO_NAMESPACE TRUE
|
||||
@@ -63,10 +73,14 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
qt_add_library(quickshell-network-nm STATIC
|
||||
backend.cpp
|
||||
device.cpp
|
||||
connection.cpp
|
||||
active_connection.cpp
|
||||
settings.cpp
|
||||
accesspoint.cpp
|
||||
wireless.cpp
|
||||
wired.cpp
|
||||
utils.cpp
|
||||
dbus_types.cpp
|
||||
network.cpp
|
||||
enums.hpp
|
||||
${NM_DBUS_INTERFACES}
|
||||
)
|
||||
@@ -77,3 +91,4 @@ target_include_directories(quickshell-network-nm PUBLIC
|
||||
|
||||
target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus)
|
||||
qs_add_link_dependencies(quickshell-network-nm quickshell-dbus)
|
||||
qs_add_link_dependencies(quickshell-network-nm quickshell-network)
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "../enums.hpp"
|
||||
#include "dbus_nm_accesspoint.h"
|
||||
#include "enums.hpp"
|
||||
|
||||
@@ -48,14 +48,14 @@ public:
|
||||
[[nodiscard]] bool isValid() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] QByteArray ssid() const { return this->bSsid; };
|
||||
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; };
|
||||
[[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; };
|
||||
[[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; };
|
||||
[[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; };
|
||||
[[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; };
|
||||
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; };
|
||||
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
|
||||
[[nodiscard]] QByteArray ssid() const { return this->bSsid; }
|
||||
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
|
||||
[[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; }
|
||||
[[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; }
|
||||
[[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; }
|
||||
[[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; }
|
||||
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; }
|
||||
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
|
||||
|
||||
signals:
|
||||
void loaded();
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "active_connection.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "dbus_nm_active_connection.h"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QObject(parent) {
|
||||
this->proxy = new DBusNMActiveConnectionProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->proxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(&this->activeConnectionProperties, &DBusPropertyGroup::getAllFinished, this, &NMActiveConnection::loaded, Qt::SingleShotConnection);
|
||||
QObject::connect(this->proxy, &DBusNMActiveConnectionProxy::StateChanged, this, &NMActiveConnection::onStateChanged);
|
||||
// clang-format on
|
||||
|
||||
this->activeConnectionProperties.setInterface(this->proxy);
|
||||
this->activeConnectionProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) {
|
||||
auto enumReason = static_cast<NMConnectionStateReason::Enum>(reason);
|
||||
if (this->bStateReason == enumReason) return;
|
||||
this->bStateReason = enumReason;
|
||||
}
|
||||
|
||||
bool NMActiveConnection::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||
QString NMActiveConnection::address() const {
|
||||
return this->proxy ? this->proxy->service() : QString();
|
||||
}
|
||||
QString NMActiveConnection::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NMConnectionState::Enum>
|
||||
DBusDataTransform<qs::network::NMConnectionState::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMConnectionState::Enum>(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
@@ -1,18 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qstringlist.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_nm_active_connection.h"
|
||||
#include "dbus_nm_connection_settings.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
@@ -28,40 +26,6 @@ struct DBusDataTransform<qs::network::NMConnectionState::Enum> {
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
// Proxy of a /org/freedesktop/NetworkManager/Settings/Connection/* object.
|
||||
class NMConnectionSettings: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMConnectionSettings(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
void forget();
|
||||
|
||||
[[nodiscard]] bool isValid() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] ConnectionSettingsMap settings() const { return this->bSettings; };
|
||||
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
|
||||
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; };
|
||||
|
||||
signals:
|
||||
void loaded();
|
||||
void settingsChanged(ConnectionSettingsMap settings);
|
||||
void securityChanged(WifiSecurityType::Enum security);
|
||||
void ssidChanged(QString ssid);
|
||||
|
||||
private:
|
||||
bool mLoaded = false;
|
||||
void updateSettings();
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, ConnectionSettingsMap, bSettings, &NMConnectionSettings::settingsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, WifiSecurityType::Enum, bSecurity, &NMConnectionSettings::securityChanged);
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMConnectionSettings, connectionSettingsProperties);
|
||||
// clang-format on
|
||||
|
||||
DBusNMConnectionSettingsProxy* proxy = nullptr;
|
||||
};
|
||||
|
||||
// Proxy of a /org/freedesktop/NetworkManager/ActiveConnection/* object.
|
||||
class NMActiveConnection: public QObject {
|
||||
Q_OBJECT;
|
||||
@@ -72,31 +36,27 @@ public:
|
||||
[[nodiscard]] bool isValid() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; };
|
||||
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; };
|
||||
[[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->mStateReason; };
|
||||
[[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; }
|
||||
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
|
||||
[[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->bStateReason; }
|
||||
|
||||
signals:
|
||||
void loaded();
|
||||
void connectionChanged(QDBusObjectPath path);
|
||||
void stateChanged(NMConnectionState::Enum state);
|
||||
void stateReasonChanged(NMConnectionStateReason::Enum reason);
|
||||
void uuidChanged(const QString& uuid);
|
||||
|
||||
private slots:
|
||||
void onStateChanged(quint32 state, quint32 reason);
|
||||
|
||||
private:
|
||||
NMConnectionStateReason::Enum mStateReason = NMConnectionStateReason::Unknown;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QDBusObjectPath, bConnection, &NMActiveConnection::connectionChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QString, bUuid, &NMActiveConnection::uuidChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionState::Enum, bState, &NMActiveConnection::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionStateReason::Enum, bStateReason, &NMActiveConnection::stateReasonChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMActiveConnection, activeConnectionProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pConnection, bConnection, activeConnectionProperties, "Connection");
|
||||
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pUuid, bUuid, activeConnectionProperties, "Uuid");
|
||||
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pState, bState, activeConnectionProperties, "State");
|
||||
// clang-format on
|
||||
DBusNMActiveConnectionProxy* proxy = nullptr;
|
||||
+87
-85
@@ -1,10 +1,12 @@
|
||||
#include "backend.hpp"
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbusmetatype.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qdbusservicewatcher.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
@@ -14,14 +16,13 @@
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../device.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "../qml.hpp"
|
||||
#include "dbus_nm_backend.h"
|
||||
#include "dbus_nm_device.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "wired.hpp"
|
||||
#include "wireless.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
@@ -31,7 +32,8 @@ QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWa
|
||||
}
|
||||
|
||||
NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
|
||||
qDBusRegisterMetaType<ConnectionSettingsMap>();
|
||||
qCDebug(logNetworkManager) << "Connecting to NetworkManager";
|
||||
qDBusRegisterMetaType<NMSettingsMap>();
|
||||
|
||||
auto bus = QDBusConnection::systemBus();
|
||||
if (!bus.isConnected()) {
|
||||
@@ -41,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",
|
||||
@@ -48,25 +61,37 @@ 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() {
|
||||
auto pending = this->proxy->CheckConnectivity();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<quint32> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCInfo(logNetworkManager) << "Failed to check connectivity: " << reply.error().message();
|
||||
}
|
||||
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NetworkManager::registerDevices() {
|
||||
@@ -113,27 +138,27 @@ void NetworkManager::registerDevice(const QString& path) {
|
||||
|
||||
switch (type) {
|
||||
case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break;
|
||||
case NMDeviceType::Ethernet: dev = new NMWiredDevice(path); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (dev) {
|
||||
qCDebug(logNetworkManager) << "Device added:" << path;
|
||||
if (!dev->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
||||
delete dev;
|
||||
} else {
|
||||
this->mDevices[path] = dev;
|
||||
// Only register a frontend device while it's managed by NM.
|
||||
auto onManagedChanged = [this, dev, type](bool managed) {
|
||||
managed ? this->registerFrontendDevice(type, dev) : this->removeFrontendDevice(dev);
|
||||
};
|
||||
// clang-format off
|
||||
QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
|
||||
QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection);
|
||||
QObject::connect(dev, &NMDevice::managedChanged, this, onManagedChanged);
|
||||
// clang-format on
|
||||
|
||||
if (dev->managed()) this->registerFrontendDevice(type, dev);
|
||||
QObject::connect(dev, &NMDevice::loaded, this, [this, dev]() {
|
||||
emit this->deviceAdded(dev->frontend());
|
||||
});
|
||||
}
|
||||
} else {
|
||||
qCDebug(logNetworkManager) << "Ignoring registration of unsupported device:" << path;
|
||||
}
|
||||
temp->deleteLater();
|
||||
}
|
||||
@@ -142,66 +167,6 @@ void NetworkManager::registerDevice(const QString& path) {
|
||||
qs::dbus::asyncReadProperty<uint>(*temp, "DeviceType", callback);
|
||||
}
|
||||
|
||||
void NetworkManager::registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev) {
|
||||
NetworkDevice* frontendDev = nullptr;
|
||||
switch (type) {
|
||||
case NMDeviceType::Wifi: {
|
||||
auto* frontendWifiDev = new WifiDevice(dev);
|
||||
auto* wifiDev = qobject_cast<NMWirelessDevice*>(dev);
|
||||
// Bind WifiDevice-specific properties
|
||||
auto translateMode = [wifiDev]() {
|
||||
switch (wifiDev->mode()) {
|
||||
case NM80211Mode::Unknown: return WifiDeviceMode::Unknown;
|
||||
case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc;
|
||||
case NM80211Mode::Infra: return WifiDeviceMode::Station;
|
||||
case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint;
|
||||
case NM80211Mode::Mesh: return WifiDeviceMode::Mesh;
|
||||
}
|
||||
};
|
||||
// clang-format off
|
||||
frontendWifiDev->bindableMode().setBinding(translateMode);
|
||||
wifiDev->bindableScanning().setBinding([frontendWifiDev]() { return frontendWifiDev->scannerEnabled(); });
|
||||
QObject::connect(wifiDev, &NMWirelessDevice::networkAdded, frontendWifiDev, &WifiDevice::networkAdded);
|
||||
QObject::connect(wifiDev, &NMWirelessDevice::networkRemoved, frontendWifiDev, &WifiDevice::networkRemoved);
|
||||
// clang-format on
|
||||
frontendDev = frontendWifiDev;
|
||||
break;
|
||||
}
|
||||
default: return;
|
||||
}
|
||||
|
||||
// Bind generic NetworkDevice properties
|
||||
auto translateState = [dev]() {
|
||||
switch (dev->state()) {
|
||||
case 0 ... 20: return DeviceConnectionState::Unknown;
|
||||
case 30: return DeviceConnectionState::Disconnected;
|
||||
case 40 ... 90: return DeviceConnectionState::Connecting;
|
||||
case 100: return DeviceConnectionState::Connected;
|
||||
case 110 ... 120: return DeviceConnectionState::Disconnecting;
|
||||
}
|
||||
};
|
||||
// clang-format off
|
||||
frontendDev->bindableName().setBinding([dev]() { return dev->interface(); });
|
||||
frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); });
|
||||
frontendDev->bindableNmState().setBinding([dev]() { return dev->state(); });
|
||||
frontendDev->bindableState().setBinding(translateState);
|
||||
frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); });
|
||||
QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect);
|
||||
QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect);
|
||||
// clang-format on
|
||||
|
||||
this->mFrontendDevices.insert(dev->path(), frontendDev);
|
||||
emit this->deviceAdded(frontendDev);
|
||||
}
|
||||
|
||||
void NetworkManager::removeFrontendDevice(NMDevice* dev) {
|
||||
auto* frontendDev = this->mFrontendDevices.take(dev->path());
|
||||
if (frontendDev) {
|
||||
emit this->deviceRemoved(frontendDev);
|
||||
frontendDev->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::onDevicePathAdded(const QDBusObjectPath& path) {
|
||||
this->registerDevice(path.path());
|
||||
}
|
||||
@@ -215,7 +180,8 @@ void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
|
||||
auto* dev = iter.value();
|
||||
this->mDevices.erase(iter);
|
||||
if (dev) {
|
||||
this->removeFrontendDevice(dev);
|
||||
qCDebug(logNetworkManager) << "Device removed:" << path.path();
|
||||
emit this->deviceRemoved(dev->frontend());
|
||||
delete dev;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +206,7 @@ void NetworkManager::activateConnection(
|
||||
}
|
||||
|
||||
void NetworkManager::addAndActivateConnection(
|
||||
const ConnectionSettingsMap& settings,
|
||||
const NMSettingsMap& settings,
|
||||
const QDBusObjectPath& devPath,
|
||||
const QDBusObjectPath& specificObjectPath
|
||||
) {
|
||||
@@ -259,12 +225,48 @@ void NetworkManager::addAndActivateConnection(
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NetworkManager::setConnectivityCheckEnabled(bool enabled) {
|
||||
if (enabled == this->bConnectivityCheckEnabled) return;
|
||||
this->bConnectivityCheckEnabled = enabled;
|
||||
this->pConnectivityCheckEnabled.write();
|
||||
}
|
||||
|
||||
void NetworkManager::setWifiEnabled(bool enabled) {
|
||||
if (enabled == this->bWifiEnabled) return;
|
||||
this->bWifiEnabled = 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
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NMConnectivityState::Enum>
|
||||
DBusDataTransform<qs::network::NMConnectivityState::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMConnectivityState::Enum>(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbusservicewatcher.h>
|
||||
#include <qhash.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
@@ -8,9 +9,22 @@
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "../qml.hpp"
|
||||
#include "dbus_nm_backend.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NMConnectivityState::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NMConnectivityState::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
@@ -21,47 +35,62 @@ public:
|
||||
explicit NetworkManager(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool isAvailable() const override;
|
||||
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; };
|
||||
[[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; };
|
||||
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }
|
||||
[[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; }
|
||||
[[nodiscard]] bool connectivityCheckAvailable() const {
|
||||
return this->bConnectivityCheckAvailable;
|
||||
};
|
||||
[[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; }
|
||||
[[nodiscard]] NMConnectivityState::Enum connectivity() const { return this->bConnectivity; }
|
||||
|
||||
signals:
|
||||
void deviceAdded(NetworkDevice* device);
|
||||
void deviceRemoved(NetworkDevice* device);
|
||||
void wifiEnabledChanged(bool enabled);
|
||||
void wifiHardwareEnabledChanged(bool enabled);
|
||||
void connectivityStateChanged(NMConnectivityState::Enum state);
|
||||
void connectivityCheckAvailableChanged(bool available);
|
||||
void connectivityCheckEnabledChanged(bool enabled);
|
||||
|
||||
public slots:
|
||||
void setWifiEnabled(bool enabled);
|
||||
void setConnectivityCheckEnabled(bool enabled);
|
||||
void checkConnectivity();
|
||||
|
||||
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 ConnectionSettingsMap& settings,
|
||||
const NMSettingsMap& settings,
|
||||
const QDBusObjectPath& devPath,
|
||||
const QDBusObjectPath& specificObjectPath
|
||||
);
|
||||
|
||||
private:
|
||||
void init();
|
||||
void registerDevices();
|
||||
void registerDevice(const QString& path);
|
||||
void registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev);
|
||||
void removeFrontendDevice(NMDevice* dev);
|
||||
|
||||
QHash<QString, NMDevice*> mDevices;
|
||||
QHash<QString, NetworkDevice*> mFrontendDevices;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiHardwareEnabled, &NetworkManager::wifiHardwareEnabledChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, NMConnectivityState::Enum, bConnectivity, &NetworkManager::connectivityStateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bConnectivityCheckAvailable, &NetworkManager::connectivityCheckAvailableChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bConnectivityCheckEnabled, &NetworkManager::connectivityCheckEnabledChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiEnabled, bWifiEnabled, dbusProperties, "WirelessEnabled");
|
||||
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiHardwareEnabled, bWifiHardwareEnabled, dbusProperties, "WirelessHardwareEnabled");
|
||||
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivity, bConnectivity, dbusProperties, "Connectivity");
|
||||
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckAvailable, bConnectivityCheckAvailable, dbusProperties, "ConnectivityCheckAvailable");
|
||||
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckEnabled, bConnectivityCheckEnabled, dbusProperties, "ConnectivityCheckEnabled");
|
||||
// clang-format on
|
||||
DBusNetworkManagerProxy* proxy = nullptr;
|
||||
QDBusServiceWatcher serviceWatcher;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -1,151 +0,0 @@
|
||||
#include "connection.hpp"
|
||||
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusmetatype.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_nm_active_connection.h"
|
||||
#include "dbus_nm_connection_settings.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMConnectionSettings::NMConnectionSettings(const QString& path, QObject* parent): QObject(parent) {
|
||||
qDBusRegisterMetaType<ConnectionSettingsMap>();
|
||||
|
||||
this->proxy = new DBusNMConnectionSettingsProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->proxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
this->proxy,
|
||||
&DBusNMConnectionSettingsProxy::Updated,
|
||||
this,
|
||||
&NMConnectionSettings::updateSettings
|
||||
);
|
||||
this->bSecurity.setBinding([this]() { return securityFromConnectionSettings(this->bSettings); });
|
||||
|
||||
this->connectionSettingsProperties.setInterface(this->proxy);
|
||||
this->connectionSettingsProperties.updateAllViaGetAll();
|
||||
|
||||
this->updateSettings();
|
||||
}
|
||||
|
||||
void NMConnectionSettings::updateSettings() {
|
||||
auto pending = this->proxy->GetSettings();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<ConnectionSettingsMap> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to get" << this->path() << "settings:" << reply.error().message();
|
||||
} else {
|
||||
this->bSettings = reply.value();
|
||||
}
|
||||
|
||||
if (!this->mLoaded) {
|
||||
emit this->loaded();
|
||||
this->mLoaded = true;
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NMConnectionSettings::forget() {
|
||||
auto pending = this->proxy->Delete();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to forget" << this->path() << ":" << reply.error().message();
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
bool NMConnectionSettings::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||
QString NMConnectionSettings::address() const {
|
||||
return this->proxy ? this->proxy->service() : QString();
|
||||
}
|
||||
QString NMConnectionSettings::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||
|
||||
NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QObject(parent) {
|
||||
this->proxy = new DBusNMActiveConnectionProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->proxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(&this->activeConnectionProperties, &DBusPropertyGroup::getAllFinished, this, &NMActiveConnection::loaded, Qt::SingleShotConnection);
|
||||
QObject::connect(this->proxy, &DBusNMActiveConnectionProxy::StateChanged, this, &NMActiveConnection::onStateChanged);
|
||||
// clang-format on
|
||||
|
||||
this->activeConnectionProperties.setInterface(this->proxy);
|
||||
this->activeConnectionProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) {
|
||||
auto enumReason = static_cast<NMConnectionStateReason::Enum>(reason);
|
||||
if (this->mStateReason == enumReason) return;
|
||||
this->mStateReason = enumReason;
|
||||
emit this->stateReasonChanged(enumReason);
|
||||
}
|
||||
|
||||
bool NMActiveConnection::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||
QString NMActiveConnection::address() const {
|
||||
return this->proxy ? this->proxy->service() : QString();
|
||||
}
|
||||
QString NMActiveConnection::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NMConnectionState::Enum>
|
||||
DBusDataTransform<qs::network::NMConnectionState::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMConnectionState::Enum>(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "dbus_types.hpp"
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusargument.h>
|
||||
#include <qmap.h>
|
||||
#include <qmetatype.h>
|
||||
#include <qstring.h>
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
const QDBusArgument& operator>>(const QDBusArgument& argument, NMSettingsMap& map) {
|
||||
argument.beginMap();
|
||||
while (!argument.atEnd()) {
|
||||
argument.beginMapEntry();
|
||||
QString groupName;
|
||||
argument >> groupName;
|
||||
|
||||
QVariantMap group;
|
||||
argument >> group;
|
||||
|
||||
map.insert(groupName, group);
|
||||
argument.endMapEntry();
|
||||
}
|
||||
argument.endMap();
|
||||
return argument;
|
||||
}
|
||||
|
||||
const QDBusArgument& operator<<(QDBusArgument& argument, const NMSettingsMap& map) {
|
||||
argument.beginMap(qMetaTypeId<QString>(), qMetaTypeId<QVariantMap>());
|
||||
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
|
||||
argument.beginMapEntry();
|
||||
argument << it.key();
|
||||
argument << it.value();
|
||||
argument.endMapEntry();
|
||||
}
|
||||
argument.endMap();
|
||||
return argument;
|
||||
}
|
||||
|
||||
const QDBusArgument& operator>>(const QDBusArgument& argument, NMIPv6Address& addr) {
|
||||
argument.beginStructure();
|
||||
argument >> addr.address >> addr.prefix >> addr.gateway;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
const QDBusArgument& operator<<(QDBusArgument& argument, const NMIPv6Address& addr) {
|
||||
argument.beginStructure();
|
||||
argument << addr.address << addr.prefix << addr.gateway;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
const QDBusArgument& operator>>(const QDBusArgument& argument, NMIPv6Route& route) {
|
||||
argument.beginStructure();
|
||||
argument >> route.destination >> route.prefix >> route.nexthop >> route.metric;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
const QDBusArgument& operator<<(QDBusArgument& argument, const NMIPv6Route& route) {
|
||||
argument.beginStructure();
|
||||
argument << route.destination << route.prefix << route.nexthop << route.metric;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -1,9 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qbytearray.h>
|
||||
#include <qdbusargument.h>
|
||||
#include <qmap.h>
|
||||
#include <qstring.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
using ConnectionSettingsMap = QMap<QString, QVariantMap>;
|
||||
Q_DECLARE_METATYPE(ConnectionSettingsMap);
|
||||
namespace qs::network {
|
||||
|
||||
using NMSettingsMap = QMap<QString, QVariantMap>;
|
||||
|
||||
const QDBusArgument& operator>>(const QDBusArgument& argument, NMSettingsMap& map);
|
||||
const QDBusArgument& operator<<(QDBusArgument& argument, const NMSettingsMap& map);
|
||||
|
||||
struct NMIPv6Address {
|
||||
QByteArray address;
|
||||
quint32 prefix = 0;
|
||||
QByteArray gateway;
|
||||
};
|
||||
|
||||
const QDBusArgument& operator>>(const QDBusArgument& argument, qs::network::NMIPv6Address& addr);
|
||||
const QDBusArgument& operator<<(QDBusArgument& argument, const qs::network::NMIPv6Address& addr);
|
||||
|
||||
struct NMIPv6Route {
|
||||
QByteArray destination;
|
||||
quint32 prefix = 0;
|
||||
QByteArray nexthop;
|
||||
quint32 metric = 0;
|
||||
};
|
||||
|
||||
const QDBusArgument& operator>>(const QDBusArgument& argument, qs::network::NMIPv6Route& route);
|
||||
const QDBusArgument& operator<<(QDBusArgument& argument, const qs::network::NMIPv6Route& route);
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
Q_DECLARE_METATYPE(qs::network::NMSettingsMap);
|
||||
Q_DECLARE_METATYPE(qs::network::NMIPv6Address);
|
||||
Q_DECLARE_METATYPE(qs::network::NMIPv6Route);
|
||||
+86
-18
@@ -15,8 +15,13 @@
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../device.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "../enums.hpp"
|
||||
#include "active_connection.hpp"
|
||||
#include "dbus_nm_device.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "network.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
@@ -39,19 +44,68 @@ NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) {
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(this, &NMDevice::availableConnectionPathsChanged, this, &NMDevice::onAvailableConnectionPathsChanged);
|
||||
QObject::connect(this, &NMDevice::availableSettingsPathsChanged, this, &NMDevice::onAvailableSettingsPathsChanged);
|
||||
QObject::connect(this, &NMDevice::activeConnectionPathChanged, this, &NMDevice::onActiveConnectionPathChanged);
|
||||
QObject::connect(this->deviceProxy, &DBusNMDeviceProxy::StateChanged, this, &NMDevice::onStateChanged);
|
||||
// clang-format on
|
||||
|
||||
this->deviceProperties.setInterface(this->deviceProxy);
|
||||
this->deviceProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void NMDevice::bindFrontend(NetworkDevice* frontend) {
|
||||
auto translateState = [this]() {
|
||||
switch (this->state()) {
|
||||
case 0 ... 20: return ConnectionState::Unknown;
|
||||
case 30: return ConnectionState::Disconnected;
|
||||
case 40 ... 90: return ConnectionState::Connecting;
|
||||
case 100: return ConnectionState::Connected;
|
||||
case 110 ... 120: return ConnectionState::Disconnecting;
|
||||
}
|
||||
};
|
||||
// clang-format off
|
||||
frontend->bindableName().setBinding([this]() { return this->interface(); });
|
||||
frontend->bindableAddress().setBinding([this]() { return this->hwAddress(); });
|
||||
frontend->bindableState().setBinding(translateState);
|
||||
frontend->bindableAutoconnect().setBinding([this]() { return this->autoconnect(); });
|
||||
frontend->bindableNmManaged().setBinding([this]() { return this->managed(); });
|
||||
QObject::connect(frontend, &NetworkDevice::requestDisconnect, this, &NMDevice::disconnect);
|
||||
QObject::connect(frontend, &NetworkDevice::requestSetAutoconnect, this, &NMDevice::setAutoconnect);
|
||||
QObject::connect(frontend, &NetworkDevice::requestSetNmManaged, this, &NMDevice::setManaged);
|
||||
QObject::connect(this, &NMDevice::networkAdded, frontend, &NetworkDevice::networkAdded);
|
||||
QObject::connect(this, &NMDevice::networkRemoved, frontend, &NetworkDevice::networkRemoved);
|
||||
}
|
||||
|
||||
void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 reason) {
|
||||
auto enumReason = static_cast<NMDeviceStateReason::Enum>(reason);
|
||||
auto enumNewState = static_cast<NMDeviceState::Enum>(newState);
|
||||
if (enumNewState == NMDeviceState::Failed) this->bLastFailReason = enumReason;
|
||||
if (this->bStateReason == enumReason) return;
|
||||
this->bStateReason = enumReason;
|
||||
}
|
||||
|
||||
void NMDevice::bindNetwork(NMNetwork* net) {
|
||||
net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); });
|
||||
QObject::connect(net, &NMNetwork::requestDisconnect, this, &NMDevice::disconnect);
|
||||
QObject::connect(net, &NMNetwork::requestActivateConnection, this, [this](const QString& settingsPath){
|
||||
emit this->activateConnection(QDBusObjectPath(settingsPath), QDBusObjectPath(this->path()));
|
||||
});
|
||||
QObject::connect(net, &NMNetwork::requestAddAndActivateConnection, this, [this](const NMSettingsMap& settingsMap, const QString& specificObject){
|
||||
emit this->addAndActivateConnection(settingsMap, QDBusObjectPath(this->path()), QDBusObjectPath(specificObject));
|
||||
});
|
||||
QObject::connect(net, &NMNetwork::visibilityChanged, this, [this, net](bool visible) {
|
||||
if (visible) emit this->networkAdded(net->frontend());
|
||||
else emit this->networkRemoved(net->frontend());
|
||||
});
|
||||
if (net->visible()) emit this->networkAdded(net->frontend());
|
||||
}
|
||||
|
||||
void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
|
||||
const QString stringPath = path.path();
|
||||
|
||||
// Remove old active connection
|
||||
if (this->mActiveConnection) {
|
||||
qCDebug(logNetworkManager) << "Active connection removed:" << this->mActiveConnection->path();
|
||||
QObject::disconnect(this->mActiveConnection, nullptr, this, nullptr);
|
||||
delete this->mActiveConnection;
|
||||
this->mActiveConnection = nullptr;
|
||||
@@ -64,6 +118,7 @@ void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << stringPath;
|
||||
delete active;
|
||||
} else {
|
||||
qCDebug(logNetworkManager) << "Active connection added:" << stringPath;
|
||||
this->mActiveConnection = active;
|
||||
QObject::connect(
|
||||
active,
|
||||
@@ -76,42 +131,44 @@ void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
|
||||
}
|
||||
}
|
||||
|
||||
void NMDevice::onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths) {
|
||||
void NMDevice::onAvailableSettingsPathsChanged(const QList<QDBusObjectPath>& paths) {
|
||||
QSet<QString> newPathSet;
|
||||
for (const QDBusObjectPath& path: paths) {
|
||||
newPathSet.insert(path.path());
|
||||
}
|
||||
const auto existingPaths = this->mConnections.keys();
|
||||
const auto existingPaths = this->mSettings.keys();
|
||||
const QSet<QString> existingPathSet(existingPaths.begin(), existingPaths.end());
|
||||
|
||||
const auto addedConnections = newPathSet - existingPathSet;
|
||||
const auto removedConnections = existingPathSet - newPathSet;
|
||||
const auto addedSettings = newPathSet - existingPathSet;
|
||||
const auto removedSettings = existingPathSet - newPathSet;
|
||||
|
||||
for (const QString& path: addedConnections) {
|
||||
this->registerConnection(path);
|
||||
for (const QString& path: addedSettings) {
|
||||
this->registerSettings(path);
|
||||
}
|
||||
for (const QString& path: removedConnections) {
|
||||
auto* connection = this->mConnections.take(path);
|
||||
for (const QString& path: removedSettings) {
|
||||
auto* connection = this->mSettings.take(path);
|
||||
if (!connection) {
|
||||
qCDebug(logNetworkManager) << "Sent removal signal for" << path << "which is not registered.";
|
||||
} else {
|
||||
qCDebug(logNetworkManager) << "Connection settings removed:" << path;
|
||||
delete connection;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void NMDevice::registerConnection(const QString& path) {
|
||||
auto* connection = new NMConnectionSettings(path, this);
|
||||
if (!connection->isValid()) {
|
||||
void NMDevice::registerSettings(const QString& path) {
|
||||
auto* settings = new NMSettings(path, this);
|
||||
if (!settings->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
||||
delete connection;
|
||||
delete settings;
|
||||
} else {
|
||||
this->mConnections.insert(path, connection);
|
||||
qCDebug(logNetworkManager) << "Connection settings added:" << path;
|
||||
this->mSettings.insert(path, settings);
|
||||
QObject::connect(
|
||||
connection,
|
||||
&NMConnectionSettings::loaded,
|
||||
settings,
|
||||
&NMSettings::loaded,
|
||||
this,
|
||||
[this, connection]() { emit this->connectionLoaded(connection); },
|
||||
[this, settings]() { emit this->settingsLoaded(settings); },
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
}
|
||||
@@ -125,6 +182,12 @@ void NMDevice::setAutoconnect(bool autoconnect) {
|
||||
this->pAutoconnect.write();
|
||||
}
|
||||
|
||||
void NMDevice::setManaged(bool managed) {
|
||||
if (managed == this->bManaged) return;
|
||||
this->bManaged = managed;
|
||||
this->pManaged.write();
|
||||
}
|
||||
|
||||
bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); }
|
||||
QString NMDevice::address() const {
|
||||
return this->deviceProxy ? this->deviceProxy->service() : QString();
|
||||
@@ -140,4 +203,9 @@ DBusDataTransform<qs::network::NMDeviceState::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMDeviceState::Enum>(wire));
|
||||
}
|
||||
|
||||
DBusResult<qs::network::NMDeviceInterfaceFlags::Enum>
|
||||
DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMDeviceInterfaceFlags::Enum>(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
+49
-16
@@ -8,8 +8,12 @@
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "../device.hpp"
|
||||
#include "active_connection.hpp"
|
||||
#include "dbus_nm_device.h"
|
||||
#include "enums.hpp"
|
||||
#include "network.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
@@ -20,6 +24,13 @@ struct DBusDataTransform<qs::network::NMDeviceState::Enum> {
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NMDeviceInterfaceFlags::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
namespace qs::network {
|
||||
@@ -36,43 +47,61 @@ public:
|
||||
[[nodiscard]] virtual bool isValid() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] QString interface() const { return this->bInterface; };
|
||||
[[nodiscard]] QString hwAddress() const { return this->bHwAddress; };
|
||||
[[nodiscard]] bool managed() const { return this->bManaged; };
|
||||
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; };
|
||||
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
|
||||
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; };
|
||||
[[nodiscard]] QString interface() const { return this->bInterface; }
|
||||
[[nodiscard]] QString hwAddress() const { return this->bHwAddress; }
|
||||
[[nodiscard]] bool managed() const { return this->bManaged; }
|
||||
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; }
|
||||
[[nodiscard]] NMDeviceStateReason::Enum stateReason() const { return this->bStateReason; }
|
||||
[[nodiscard]] NMDeviceStateReason::Enum lastFailReason() const { return this->bLastFailReason; }
|
||||
[[nodiscard]] NMDeviceInterfaceFlags::Enum interfaceFlags() const {
|
||||
return this->bInterfaceFlags;
|
||||
}
|
||||
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; }
|
||||
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; }
|
||||
[[nodiscard]] virtual NetworkDevice* frontend() = 0;
|
||||
|
||||
signals:
|
||||
void loaded();
|
||||
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
|
||||
void addAndActivateConnection(
|
||||
const ConnectionSettingsMap& settings,
|
||||
const NMSettingsMap& settings,
|
||||
const QDBusObjectPath& devPath,
|
||||
const QDBusObjectPath& apPath
|
||||
const QDBusObjectPath& specificObjectPath
|
||||
);
|
||||
void connectionLoaded(NMConnectionSettings* connection);
|
||||
void connectionRemoved(NMConnectionSettings* connection);
|
||||
void availableConnectionPathsChanged(QList<QDBusObjectPath> paths);
|
||||
void networkAdded(Network* net);
|
||||
void networkRemoved(Network* net);
|
||||
void settingsLoaded(NMSettings* settings);
|
||||
void settingsRemoved(NMSettings* settings);
|
||||
void availableSettingsPathsChanged(QList<QDBusObjectPath> paths);
|
||||
void activeConnectionPathChanged(const QDBusObjectPath& connection);
|
||||
void activeConnectionLoaded(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 autoconnectChanged(bool autoconnect);
|
||||
void interfaceFlagsChanged(NMDeviceInterfaceFlags::Enum flags);
|
||||
|
||||
public slots:
|
||||
void disconnect();
|
||||
void setAutoconnect(bool autoconnect);
|
||||
void setManaged(bool managed);
|
||||
|
||||
protected:
|
||||
void bindFrontend(NetworkDevice* frontend);
|
||||
void bindNetwork(NMNetwork* net);
|
||||
|
||||
private slots:
|
||||
void onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths);
|
||||
void onStateChanged(quint32 newState, quint32 oldState, quint32 reason);
|
||||
void onAvailableSettingsPathsChanged(const QList<QDBusObjectPath>& paths);
|
||||
void onActiveConnectionPathChanged(const QDBusObjectPath& path);
|
||||
|
||||
private:
|
||||
void registerConnection(const QString& path);
|
||||
void registerSettings(const QString& path);
|
||||
|
||||
QHash<QString, NMConnectionSettings*> mConnections;
|
||||
QHash<QString, NMSettings*> mSettings;
|
||||
NMActiveConnection* mActiveConnection = nullptr;
|
||||
|
||||
// clang-format off
|
||||
@@ -80,9 +109,12 @@ private:
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bHwAddress, &NMDevice::hwAddressChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceStateReason::Enum, bStateReason, &NMDevice::stateReasonChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceStateReason::Enum, bLastFailReason, &NMDevice::lastFailReasonChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableConnectionPathsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableSettingsPathsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceInterfaceFlags::Enum, bInterfaceFlags, &NMDevice::interfaceFlagsChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface");
|
||||
@@ -92,6 +124,7 @@ private:
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect");
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections");
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection");
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pInterfaceFlags, bInterfaceFlags, deviceProperties, "InterfaceFlags");
|
||||
// clang-format on
|
||||
|
||||
DBusNMDeviceProxy* deviceProxy = nullptr;
|
||||
|
||||
@@ -7,6 +7,20 @@
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMConnectivityState
|
||||
class NMConnectivityState: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
None = 1,
|
||||
Portal = 2,
|
||||
Limited = 3,
|
||||
Full = 4,
|
||||
};
|
||||
};
|
||||
|
||||
// Indicates the type of hardware represented by a device object.
|
||||
class NMDeviceType: public QObject {
|
||||
Q_OBJECT;
|
||||
@@ -52,6 +66,140 @@ public:
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState.
|
||||
class NMDeviceState: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Unmanaged = 10,
|
||||
Unavailable = 20,
|
||||
Disconnected = 30,
|
||||
Prepare = 40,
|
||||
Config = 50,
|
||||
NeedAuth = 60,
|
||||
IPConfig = 70,
|
||||
IPCheck = 80,
|
||||
Secondaries = 90,
|
||||
Activated = 100,
|
||||
Deactivating = 110,
|
||||
Failed = 120,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// Device state change reason codes.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceStateReason.
|
||||
class NMDeviceStateReason: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
Unknown = 1,
|
||||
NowManaged = 2,
|
||||
NowUnmanaged = 3,
|
||||
ConfigFailed = 4,
|
||||
IpConfigUnavailable = 5,
|
||||
IpConfigExpired = 6,
|
||||
NoSecrets = 7,
|
||||
SupplicantDisconnect = 8,
|
||||
SupplicantConfigFailed = 9,
|
||||
SupplicantFailed = 10,
|
||||
SupplicantTimeout = 11,
|
||||
PppStartFailed = 12,
|
||||
PppDisconnect = 13,
|
||||
PppFailed = 14,
|
||||
DhcpStartFailed = 15,
|
||||
DhcpError = 16,
|
||||
DhcpFailed = 17,
|
||||
SharedStartFailed = 18,
|
||||
SharedFailed = 19,
|
||||
AutoIpStartFailed = 20,
|
||||
AutoIpError = 21,
|
||||
AutoIpFailed = 22,
|
||||
ModemBusy = 23,
|
||||
ModemNoDialTone = 24,
|
||||
ModemNoCarrier = 25,
|
||||
ModemDialTimeout = 26,
|
||||
ModemDialFailed = 27,
|
||||
ModemInitFailed = 28,
|
||||
GsmApnFailed = 29,
|
||||
GsmRegistrationNotSearching = 30,
|
||||
GsmRegistrationDenied = 31,
|
||||
GsmRegistrationTimeout = 32,
|
||||
GsmRegistrationFailed = 33,
|
||||
GsmPinCheckFailed = 34,
|
||||
FirmwareMissing = 35,
|
||||
Removed = 36,
|
||||
Sleeping = 37,
|
||||
ConnectionRemoved = 38,
|
||||
UserRequested = 39,
|
||||
Carrier = 40,
|
||||
ConnectionAssumed = 41,
|
||||
SupplicantAvailable = 42,
|
||||
ModemNotFound = 43,
|
||||
BtFailed = 44,
|
||||
GsmSimNotInserted = 45,
|
||||
GsmSimPinRequired = 46,
|
||||
GsmSimPukRequired = 47,
|
||||
GsmSimWrong = 48,
|
||||
InfinibandMode = 49,
|
||||
DependencyFailed = 50,
|
||||
Br2684Failed = 51,
|
||||
ModemManagerUnavailable = 52,
|
||||
SsidNotFound = 53,
|
||||
SecondaryConnectionFailed = 54,
|
||||
DcbFcoeFailed = 55,
|
||||
TeamdControlFailed = 56,
|
||||
ModemFailed = 57,
|
||||
ModemAvailable = 58,
|
||||
SimPinIncorrect = 59,
|
||||
NewActivation = 60,
|
||||
ParentChanged = 61,
|
||||
ParentManagedChanged = 62,
|
||||
OvsdbFailed = 63,
|
||||
IpAddressDuplicate = 64,
|
||||
IpMethodUnsupported = 65,
|
||||
SriovConfigurationFailed = 66,
|
||||
PeerNotFound = 67,
|
||||
DeviceHandlerFailed = 68,
|
||||
UnmanagedByDefault = 69,
|
||||
UnmanagedExternalDown = 70,
|
||||
UnmanagedLinkNotInit = 71,
|
||||
UnmanagedQuitting = 72,
|
||||
UnmanagedManagerDisabled = 73,
|
||||
UnmanagedUserConf = 74,
|
||||
UnmanagedUserExplicit = 75,
|
||||
UnmanagedUserSettings = 76,
|
||||
UnmanagedUserUdev = 77,
|
||||
NetworkingOff = 78,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// Flags for a network interface.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceInterfaceFlags.
|
||||
class NMDeviceInterfaceFlags: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint32 {
|
||||
None = 0x0,
|
||||
Up = 0x1,
|
||||
LowerUp = 0x2,
|
||||
Promisc = 0x4,
|
||||
Carrier = 0x10000,
|
||||
LldpClientEnabled = 0x20000,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// 802.11 specific device encryption and authentication capabilities.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities.
|
||||
class NMWirelessCapabilities: public QObject {
|
||||
@@ -153,4 +301,31 @@ public:
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionStateReason.
|
||||
class NMConnectionStateReason: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
None = 1,
|
||||
UserDisconnected = 2,
|
||||
DeviceDisconnected = 3,
|
||||
ServiceStopped = 4,
|
||||
IpConfigInvalid = 5,
|
||||
ConnectTimeout = 6,
|
||||
ServiceStartTimeout = 7,
|
||||
ServiceStartFailed = 8,
|
||||
NoSecrets = 9,
|
||||
LoginFailed = 10,
|
||||
ConnectionRemoved = 11,
|
||||
DependencyFailed = 12,
|
||||
DeviceRealizeFailed = 13,
|
||||
DeviceRemoved = 14
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,310 @@
|
||||
#include "network.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qpointer.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../enums.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "accesspoint.hpp"
|
||||
#include "active_connection.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMNetwork::NMNetwork(QObject* parent)
|
||||
: QObject(parent)
|
||||
, bKnown(false)
|
||||
, bReason(NMConnectionStateReason::None)
|
||||
, bState(NMConnectionState::Deactivated) {}
|
||||
|
||||
void NMNetwork::updateReferenceSettings() {
|
||||
// If the network has no connections, the reference is nullptr.
|
||||
if (this->mSettings.isEmpty()) {
|
||||
this->bReferenceSettings = nullptr;
|
||||
return;
|
||||
};
|
||||
|
||||
// If the network has an active connection, use its settings as the reference.
|
||||
if (this->mActiveConnection) {
|
||||
auto* settings = this->mSettings.value(this->mActiveConnection->connection().path());
|
||||
if (settings && settings != this->bReferenceSettings) {
|
||||
this->bReferenceSettings = settings;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, choose the settings responsible for the last successful connection.
|
||||
NMSettings* selectedSettings = nullptr;
|
||||
quint64 selectedTimestamp = 0;
|
||||
for (auto* settings: this->mSettings.values()) {
|
||||
const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong();
|
||||
if (!selectedSettings || timestamp > selectedTimestamp) {
|
||||
selectedSettings = settings;
|
||||
selectedTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->bReferenceSettings != selectedSettings) {
|
||||
this->bReferenceSettings = selectedSettings;
|
||||
}
|
||||
}
|
||||
|
||||
void NMNetwork::addSettings(NMSettings* settings) {
|
||||
if (this->mSettings.contains(settings->path())) return;
|
||||
this->mSettings.insert(settings->path(), settings);
|
||||
|
||||
auto onDestroyed = [this, settings]() {
|
||||
if (this->mSettings.take(settings->path())) {
|
||||
this->updateReferenceSettings();
|
||||
if (this->mSettings.isEmpty()) this->bKnown = false;
|
||||
// Deletes `this`
|
||||
emit this->settingsRemoved(settings);
|
||||
}
|
||||
};
|
||||
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
|
||||
this->bKnown = true;
|
||||
this->updateReferenceSettings();
|
||||
emit this->settingsAdded(settings);
|
||||
};
|
||||
|
||||
void NMNetwork::addActiveConnection(NMActiveConnection* active) {
|
||||
if (this->mActiveConnection) return;
|
||||
this->mActiveConnection = active;
|
||||
|
||||
this->bState.setBinding([active]() { return active->state(); });
|
||||
this->bReason.setBinding([active]() { return active->stateReason(); });
|
||||
auto onDestroyed = [this, active]() {
|
||||
if (this->mActiveConnection && this->mActiveConnection == active) {
|
||||
this->mActiveConnection = nullptr;
|
||||
this->updateReferenceSettings();
|
||||
this->bState = NMConnectionState::Deactivated;
|
||||
this->bReason = NMConnectionStateReason::None;
|
||||
}
|
||||
};
|
||||
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
|
||||
this->updateReferenceSettings();
|
||||
};
|
||||
|
||||
void NMNetwork::forget() {
|
||||
if (this->mSettings.isEmpty()) return;
|
||||
for (auto* conn: this->mSettings.values()) {
|
||||
conn->forget();
|
||||
}
|
||||
}
|
||||
|
||||
void NMNetwork::bindFrontend(Network* frontend) {
|
||||
auto translateState = [this]() { return this->state() == NMConnectionState::Activated; };
|
||||
frontend->bindableConnected().setBinding(translateState);
|
||||
frontend->bindableKnown().setBinding([this]() { return this->known(); });
|
||||
frontend->bindableState().setBinding([this]() {
|
||||
return static_cast<ConnectionState::Enum>(this->state());
|
||||
});
|
||||
frontend->bindableStateChanging().setBinding([this]() {
|
||||
auto s = static_cast<ConnectionState::Enum>(this->state());
|
||||
return s == ConnectionState::Connecting || s == ConnectionState::Disconnecting;
|
||||
});
|
||||
|
||||
QObject::connect(this, &NMNetwork::reasonChanged, this, [this, frontend]() {
|
||||
if (this->reason() == NMConnectionStateReason::DeviceDisconnected) {
|
||||
auto deviceReason = this->deviceFailReason();
|
||||
if (deviceReason == NMDeviceStateReason::NoSecrets)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::NoSecrets);
|
||||
if (deviceReason == NMDeviceStateReason::SupplicantDisconnect)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::WifiClientDisconnected);
|
||||
if (deviceReason == NMDeviceStateReason::SupplicantFailed)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::WifiClientFailed);
|
||||
if (deviceReason == NMDeviceStateReason::SupplicantTimeout)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::WifiAuthTimeout);
|
||||
if (deviceReason == NMDeviceStateReason::SsidNotFound)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::WifiNetworkLost);
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
frontend,
|
||||
&Network::requestConnectWithSettings,
|
||||
this,
|
||||
[this](NMSettings* settings) {
|
||||
if (settings) {
|
||||
emit this->requestActivateConnection(settings->path());
|
||||
return;
|
||||
}
|
||||
qCInfo(
|
||||
logNetworkManager
|
||||
) << "Failed to connectWithSettings: The provided settings no longer exist.";
|
||||
}
|
||||
);
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(frontend, &Network::requestForget, this, &NMNetwork::forget);
|
||||
QObject::connect(frontend, &Network::requestDisconnect, this, &NMNetwork::requestDisconnect);
|
||||
QObject::connect(this, &NMNetwork::settingsAdded, frontend, &Network::settingsAdded);
|
||||
QObject::connect(this, &NMNetwork::settingsRemoved, frontend, &Network::settingsRemoved);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
NMGenericNetwork::NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent)
|
||||
: NMNetwork(parent)
|
||||
, mFrontend(new Network(std::move(name), device, this)) {
|
||||
// Regiter and bind the frontend Network.
|
||||
this->bindFrontend();
|
||||
}
|
||||
|
||||
void NMGenericNetwork::bindFrontend() {
|
||||
auto* frontend = this->mFrontend;
|
||||
this->NMNetwork::bindFrontend(frontend);
|
||||
QObject::connect(frontend, &Network::requestConnect, this, [this]() {
|
||||
if (auto* settingsRef = this->referenceSettings()) {
|
||||
emit this->requestActivateConnection(settingsRef->path());
|
||||
return;
|
||||
}
|
||||
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
NMWirelessNetwork::NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent)
|
||||
: NMNetwork(parent)
|
||||
, mSsid(ssid)
|
||||
, bSecurity(WifiSecurityType::Unknown) {
|
||||
|
||||
auto updateSecurity = [this]() {
|
||||
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;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
auto checkDisappeared = [this]() {
|
||||
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
|
||||
};
|
||||
|
||||
QObject::connect(this, &NMWirelessNetwork::referenceSettingsChanged, this, updateSecurity);
|
||||
QObject::connect(this, &NMWirelessNetwork::referenceApChanged, this, updateSecurity);
|
||||
QObject::connect(this, &NMWirelessNetwork::settingsRemoved, this, checkDisappeared);
|
||||
QObject::connect(this, &NMWirelessNetwork::apRemoved, this, checkDisappeared);
|
||||
|
||||
// Register and bind the frontend WifiNetwork.
|
||||
this->mFrontend = new WifiNetwork(ssid, device, this);
|
||||
this->bindFrontend();
|
||||
}
|
||||
|
||||
void NMWirelessNetwork::updateReferenceAp() {
|
||||
// If the network has no APs, the reference is a nullptr.
|
||||
if (this->mAccessPoints.isEmpty()) {
|
||||
this->bReferenceAp = nullptr;
|
||||
this->bSignalStrength = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, choose the AP with the strongest signal.
|
||||
NMAccessPoint* selectedAp = nullptr;
|
||||
for (auto* ap: this->mAccessPoints.values()) {
|
||||
// Always prefer the active AP.
|
||||
if (ap->path() == this->bActiveApPath) {
|
||||
selectedAp = ap;
|
||||
break;
|
||||
}
|
||||
if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) {
|
||||
selectedAp = ap;
|
||||
}
|
||||
}
|
||||
if (this->bReferenceAp != selectedAp) {
|
||||
this->bReferenceAp = selectedAp;
|
||||
this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); });
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
|
||||
if (this->mAccessPoints.contains(ap->path())) return;
|
||||
this->mAccessPoints.insert(ap->path(), ap);
|
||||
auto onDestroyed = [this, ap]() {
|
||||
if (this->mAccessPoints.take(ap->path())) {
|
||||
this->updateReferenceAp();
|
||||
// Deletes `this`
|
||||
emit this->apRemoved(ap);
|
||||
}
|
||||
};
|
||||
// clang-format off
|
||||
QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp);
|
||||
QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed);
|
||||
// clang-format on
|
||||
this->updateReferenceAp();
|
||||
};
|
||||
|
||||
void NMWirelessNetwork::bindFrontend() {
|
||||
auto* frontend = this->mFrontend;
|
||||
this->NMNetwork::bindFrontend(frontend);
|
||||
|
||||
auto translateSignal = [this]() { return this->signalStrength() / 100.0; };
|
||||
frontend->bindableSignalStrength().setBinding(translateSignal);
|
||||
frontend->bindableSecurity().setBinding([this]() { return this->security(); });
|
||||
|
||||
QObject::connect(frontend, &WifiNetwork::requestConnect, this, [this]() {
|
||||
if (auto* settingsRef = this->referenceSettings()) {
|
||||
emit this->requestActivateConnection(settingsRef->path());
|
||||
return;
|
||||
}
|
||||
if (auto* apRef = this->referenceAp()) {
|
||||
emit this->requestAddAndActivateConnection(NMSettingsMap(), apRef->path());
|
||||
return;
|
||||
}
|
||||
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
|
||||
return;
|
||||
});
|
||||
|
||||
QObject::connect(frontend, &WifiNetwork::requestConnectWithPsk, this, [this](const QString& psk) {
|
||||
NMSettingsMap settings;
|
||||
settings["802-11-wireless-security"]["psk"] = psk;
|
||||
if (const QPointer<NMSettings> ref = this->referenceSettings()) {
|
||||
auto* call = ref->updateSettings(settings);
|
||||
QObject::connect(
|
||||
call,
|
||||
&QDBusPendingCallWatcher::finished,
|
||||
this,
|
||||
[this, ref](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<> reply = *call;
|
||||
if (reply.isError()) {
|
||||
qCInfo(logNetworkManager) << "Failed to write PSK: " << reply.error().message();
|
||||
} else {
|
||||
if (!ref) {
|
||||
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The settings disappeared.";
|
||||
} else {
|
||||
emit this->requestActivateConnection(ref->path());
|
||||
}
|
||||
}
|
||||
delete call;
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (auto* apRef = this->referenceAp()) {
|
||||
emit this->requestAddAndActivateConnection(settings, apRef->path());
|
||||
return;
|
||||
}
|
||||
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The network disappeared.";
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
|
||||
#include "../enums.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "accesspoint.hpp"
|
||||
#include "active_connection.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
// NMNetwork aggregates NMActiveConnections and NMSettings of the same network.
|
||||
class NMNetwork: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMNetwork(QObject* parent = nullptr);
|
||||
|
||||
void addSettings(NMSettings* settings);
|
||||
void addActiveConnection(NMActiveConnection* active);
|
||||
void forget();
|
||||
void connect(const QString& devPath);
|
||||
void connectWithSettings(const QString& devPath, NMSettings* settings);
|
||||
|
||||
// clang-format off
|
||||
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
|
||||
[[nodiscard]] bool known() const { return this->bKnown; }
|
||||
[[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }
|
||||
QBindable<NMDeviceStateReason::Enum> bindableDeviceFailReason() { return &this->bDeviceFailReason; }
|
||||
[[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; }
|
||||
[[nodiscard]] QList<NMSettings*> settings() const { return this->mSettings.values(); }
|
||||
[[nodiscard]] NMSettings* referenceSettings() const { return this->bReferenceSettings; }
|
||||
[[nodiscard]] virtual Network* frontend() = 0;
|
||||
QBindable<bool> bindableVisible() { return &this->bVisible; }
|
||||
[[nodiscard]] bool visible() const { return this->bVisible; }
|
||||
// clang-format on
|
||||
|
||||
signals:
|
||||
void requestDisconnect();
|
||||
void requestActivateConnection(const QString& settingsPath);
|
||||
void
|
||||
requestAddAndActivateConnection(const NMSettingsMap& settingsMap, const QString& specificObject);
|
||||
|
||||
void settingsAdded(NMSettings* settings);
|
||||
void settingsRemoved(NMSettings* settings);
|
||||
void stateChanged(NMConnectionState::Enum state);
|
||||
void knownChanged(bool known);
|
||||
void reasonChanged(NMConnectionStateReason::Enum reason);
|
||||
void deviceFailReasonChanged(NMDeviceStateReason::Enum reason);
|
||||
void referenceSettingsChanged(NMSettings* settings);
|
||||
void visibilityChanged(bool visible);
|
||||
|
||||
protected:
|
||||
void bindFrontend(Network* frontend);
|
||||
QHash<QString, NMSettings*> mSettings;
|
||||
Q_OBJECT_BINDABLE_PROPERTY(
|
||||
NMNetwork,
|
||||
NMSettings*,
|
||||
bReferenceSettings,
|
||||
&NMNetwork::referenceSettingsChanged
|
||||
);
|
||||
|
||||
private:
|
||||
void updateReferenceSettings();
|
||||
|
||||
NMActiveConnection* mActiveConnection = nullptr;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bVisible, &NMNetwork::visibilityChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bKnown, &NMNetwork::knownChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionStateReason::Enum, bReason, &NMNetwork::reasonChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionState::Enum, bState, &NMNetwork::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMNetwork::deviceFailReasonChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
// NMGenericNetwork extends NMNetwork to bind and handle the lifetime of a frontend Network.
|
||||
// This is useful for devices with one network that don't need to extend the base Network class.
|
||||
class NMGenericNetwork: public NMNetwork {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent = nullptr);
|
||||
[[nodiscard]] Network* frontend() override { return this->mFrontend; }
|
||||
|
||||
private:
|
||||
void bindFrontend();
|
||||
Network* mFrontend;
|
||||
};
|
||||
|
||||
// NMWirelessNetwork extends NMNetwork to also aggregate NMAccessPoints of the same network and scanning functionality.
|
||||
class NMWirelessNetwork: public NMNetwork {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent = nullptr);
|
||||
|
||||
void addAccessPoint(NMAccessPoint* ap);
|
||||
|
||||
// clang-format off
|
||||
[[nodiscard]] QString ssid() const { return this->mSsid; }
|
||||
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
|
||||
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
|
||||
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->bReferenceAp; }
|
||||
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); }
|
||||
QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; }
|
||||
[[nodiscard]] WifiNetwork* frontend() override { return this->mFrontend; };
|
||||
// clang-format on
|
||||
|
||||
signals:
|
||||
void disappeared();
|
||||
void signalStrengthChanged(quint8 signal);
|
||||
void securityChanged(WifiSecurityType::Enum security);
|
||||
void activeApPathChanged(QString path);
|
||||
void referenceApChanged(NMAccessPoint* ap);
|
||||
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
|
||||
void apRemoved(NMAccessPoint* ap);
|
||||
|
||||
private:
|
||||
void updateReferenceAp();
|
||||
void bindFrontend();
|
||||
|
||||
WifiNetwork* mFrontend;
|
||||
QString mSsid;
|
||||
QHash<QString, NMAccessPoint*> mAccessPoints;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMAccessPoint*, bReferenceAp, &NMWirelessNetwork::referenceApChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,4 @@
|
||||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Device.Wired">
|
||||
</interface>
|
||||
</node>
|
||||
@@ -1,5 +1,10 @@
|
||||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Device">
|
||||
<method name="Disconnect"/>
|
||||
<signal name="StateChanged">
|
||||
<arg name="new_state" type="u"/>
|
||||
<arg name="old_state" type="u"/>
|
||||
<arg name="reason" type="u"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
@@ -2,8 +2,18 @@
|
||||
<interface name="org.freedesktop.NetworkManager.Settings.Connection">
|
||||
<method name="GetSettings">
|
||||
<arg name="settings" type="a{sa{sv}}" direction="out"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ConnectionSettingsMap"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="qs::network::NMSettingsMap"/>
|
||||
</method>
|
||||
<method name="GetSecrets">
|
||||
<arg name="setting_name" type="s" direction="in"/>
|
||||
<arg name="secrets" type="a{sa{sv}}" direction="out"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="qs::network::NMSettingsMap"/>
|
||||
</method>
|
||||
<method name="Update">
|
||||
<arg name="properties" type="a{sa{sv}}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="qs::network::NMSettingsMap"/>
|
||||
</method>
|
||||
<method name="ClearSecrets"/>
|
||||
<method name="Delete"/>
|
||||
<signal name="Updated"/>
|
||||
<signal name="Removed"/>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager">
|
||||
<method name="CheckConnectivity">
|
||||
<arg direction="out" type="u" name="connectivity"/>
|
||||
</method>
|
||||
<method name="GetAllDevices">
|
||||
<arg direction="out" type="ao" name="devices"/>
|
||||
</method>
|
||||
@@ -11,7 +14,7 @@
|
||||
</method>
|
||||
<method name="AddAndActivateConnection">
|
||||
<arg direction="in" type="a{sa{sv}}" name="connection"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="ConnectionSettingsMap"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="qs::network::NMSettingsMap"/>
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="in" type="o" name="specific_object"/>
|
||||
<arg direction="out" type="o" name="path"/>
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusmetatype.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qmetatype.h>
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "dbus_nm_connection_settings.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network", QtWarningMsg);
|
||||
QS_LOGGING_CATEGORY(logNMSettings, "quickshell.network.nm_settings", QtWarningMsg);
|
||||
} // namespace
|
||||
|
||||
NMSettings::NMSettings(const QString& path, QObject* parent): QObject(parent) {
|
||||
qDBusRegisterMetaType<QList<QVariantMap>>();
|
||||
qDBusRegisterMetaType<QList<quint32>>();
|
||||
qDBusRegisterMetaType<QList<QList<quint32>>>();
|
||||
qDBusRegisterMetaType<QList<QByteArray>>();
|
||||
qDBusRegisterMetaType<NMIPv6Address>();
|
||||
qDBusRegisterMetaType<QList<NMIPv6Address>>();
|
||||
qDBusRegisterMetaType<NMIPv6Route>();
|
||||
qDBusRegisterMetaType<QList<NMIPv6Route>>();
|
||||
qDBusRegisterMetaType<QMap<QString, QString>>();
|
||||
|
||||
this->proxy = new DBusNMConnectionSettingsProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->proxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection settings at"
|
||||
<< path;
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
this->proxy,
|
||||
&DBusNMConnectionSettingsProxy::Updated,
|
||||
this,
|
||||
&NMSettings::getSettings
|
||||
);
|
||||
|
||||
this->bId.setBinding([this]() { return this->bSettings.value()["connection"]["id"].toString(); });
|
||||
this->bUuid.setBinding([this]() {
|
||||
return this->bSettings.value()["connection"]["uuid"].toString();
|
||||
});
|
||||
|
||||
this->settingsProperties.setInterface(this->proxy);
|
||||
this->settingsProperties.updateAllViaGetAll();
|
||||
|
||||
this->getSettings();
|
||||
}
|
||||
|
||||
void NMSettings::getSettings() {
|
||||
auto pending = this->proxy->GetSettings();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<NMSettingsMap> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to get settings for" << this->path() << ":" << reply.error().message();
|
||||
} else {
|
||||
auto settings = reply.value();
|
||||
manualSettingDemarshall(settings);
|
||||
this->bSettings = settings;
|
||||
qCDebug(logNetworkManager) << "Settings map updated for" << this->path();
|
||||
|
||||
if (!this->mLoaded) {
|
||||
emit this->loaded();
|
||||
this->mLoaded = true;
|
||||
}
|
||||
};
|
||||
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
QDBusPendingCallWatcher* NMSettings::updateSettings(
|
||||
const NMSettingsMap& settingsToChange,
|
||||
const NMSettingsMap& settingsToRemove
|
||||
) {
|
||||
auto settings = removeSettingsInMap(this->bSettings, settingsToRemove);
|
||||
settings = mergeSettingsMaps(settings, settingsToChange);
|
||||
auto pending = this->proxy->Update(settings);
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
return call;
|
||||
}
|
||||
|
||||
void NMSettings::clearSecrets() {
|
||||
auto pending = this->proxy->ClearSecrets();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to clear secrets for" << this->path() << ":" << reply.error().message();
|
||||
} else {
|
||||
qCDebug(logNetworkManager) << "Cleared secrets for" << this->path();
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NMSettings::forget() {
|
||||
auto pending = this->proxy->Delete();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to forget" << this->path() << ":" << reply.error().message();
|
||||
} else {
|
||||
qCDebug(logNetworkManager) << "Successfully deletion of" << this->path();
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
QVariantMap NMSettings::read() {
|
||||
QVariantMap result;
|
||||
const auto& settings = this->bSettings.value();
|
||||
for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
|
||||
QVariantMap group;
|
||||
for (auto jt = it.value().constBegin(); jt != it.value().constEnd(); ++jt) {
|
||||
group.insert(jt.key(), settingTypeToQml(jt.value()));
|
||||
}
|
||||
result.insert(it.key(), group);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void NMSettings::write(const QVariantMap& settings) {
|
||||
NMSettingsMap changedSettings;
|
||||
NMSettingsMap removedSettings;
|
||||
QStringList failedSettings;
|
||||
|
||||
for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
|
||||
if (!it.value().canConvert<QVariantMap>()) continue;
|
||||
|
||||
auto group = it.value().toMap();
|
||||
QVariantMap toChange;
|
||||
QVariantMap toRemove;
|
||||
for (auto jt = group.constBegin(); jt != group.constEnd(); ++jt) {
|
||||
if (jt.value().isNull()) {
|
||||
toRemove.insert(jt.key(), QVariant());
|
||||
} else {
|
||||
auto converted = settingTypeFromQml(it.key(), jt.key(), jt.value());
|
||||
if (!converted.isValid()) failedSettings.append(it.key() + "." + jt.key());
|
||||
else toChange.insert(jt.key(), converted);
|
||||
}
|
||||
}
|
||||
if (!toChange.isEmpty()) changedSettings.insert(it.key(), toChange);
|
||||
if (!toRemove.isEmpty()) removedSettings.insert(it.key(), toRemove);
|
||||
}
|
||||
|
||||
if (!failedSettings.isEmpty()) {
|
||||
qCWarning(logNMSettings) << "A write to" << this
|
||||
<< "has received bad types for the following settings:"
|
||||
<< failedSettings.join(", ");
|
||||
}
|
||||
|
||||
auto* call = this->updateSettings(changedSettings, removedSettings);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to update settings for" << this->path() << ":" << reply.error().message();
|
||||
} else {
|
||||
qCDebug(logNMSettings) << "Successful write to" << this;
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
bool NMSettings::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||
QString NMSettings::address() const { return this->proxy ? this->proxy->service() : QString(); }
|
||||
QString NMSettings::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
QDebug operator<<(QDebug debug, const qs::network::NMSettings* settings) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
|
||||
if (settings) {
|
||||
debug.nospace() << "NMSettings(" << static_cast<const void*>(settings)
|
||||
<< ", uuid=" << settings->uuid() << ")";
|
||||
} else {
|
||||
debug << "WifiNetwork(nullptr)";
|
||||
}
|
||||
|
||||
return debug;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qstringlist.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "dbus_nm_connection_settings.h"
|
||||
#include "dbus_types.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
// Proxy of a /org/freedesktop/NetworkManager/Settings/Connection/* object.
|
||||
///! A NetworkManager connection settings profile.
|
||||
class NMSettings: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("");
|
||||
|
||||
/// The human-readable unique identifier for the connection.
|
||||
Q_PROPERTY(QString id READ default NOTIFY idChanged BINDABLE bindableId);
|
||||
/// A universally unique identifier for the connection.
|
||||
Q_PROPERTY(QString uuid READ uuid NOTIFY uuidChanged BINDABLE bindableUuid);
|
||||
|
||||
public:
|
||||
explicit NMSettings(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
/// Clear all of the secrets belonging to the settings.
|
||||
Q_INVOKABLE void clearSecrets();
|
||||
/// Delete the settings.
|
||||
Q_INVOKABLE void forget();
|
||||
/// Update the connection with new settings and save the connection to disk.
|
||||
/// Only changed fields need to be included.
|
||||
/// Writing a setting to `null` will remove the setting or reset it to its default.
|
||||
///
|
||||
/// > [!NOTE] Secrets may be part of the update request,
|
||||
/// > and will be either stored in persistent storage or sent to a Secret Agent for storage,
|
||||
/// > depending on the flags associated with each secret.
|
||||
Q_INVOKABLE void write(const QVariantMap& settings);
|
||||
/// Get the settings map describing this network configuration.
|
||||
///
|
||||
/// > [!NOTE] This will never include any secrets required for connection to the network, as those are often protected.
|
||||
Q_INVOKABLE QVariantMap read();
|
||||
|
||||
[[nodiscard]] bool isValid() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] NMSettingsMap map() { return this->bSettings; }
|
||||
QDBusPendingCallWatcher*
|
||||
updateSettings(const NMSettingsMap& settingsToChange, const NMSettingsMap& settingsToRemove = {});
|
||||
QBindable<QString> bindableId() { return &this->bId; }
|
||||
[[nodiscard]] QString uuid() const { return this->bUuid; }
|
||||
QBindable<QString> bindableUuid() { return &this->bUuid; }
|
||||
|
||||
signals:
|
||||
void loaded();
|
||||
void settingsChanged(NMSettingsMap settings);
|
||||
void idChanged(QString id);
|
||||
void uuidChanged(QString uuid);
|
||||
|
||||
private:
|
||||
bool mLoaded = false;
|
||||
|
||||
void getSettings();
|
||||
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMSettings, NMSettingsMap, bSettings, &NMSettings::settingsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMSettings, QString, bId, &NMSettings::idChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMSettings, QString, bUuid, &NMSettings::uuidChanged);
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMSettings, settingsProperties);
|
||||
DBusNMConnectionSettingsProxy* proxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
QDebug operator<<(QDebug debug, const qs::network::NMSettings* settings);
|
||||
Loaded 100 of 214 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user