mirror of
https://github.com/quickshell-mirror/quickshell.git
synced 2026-10-28 02:55:05 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da4ea5ff95 | ||
|
|
36517a2c10 | ||
|
|
c3c3e2ca25 | ||
|
|
2cf57f43d5 | ||
|
|
a99519c3ad | ||
|
|
158db16b93 | ||
|
|
e7cd1e9982 | ||
|
|
afbc5ffd4e | ||
|
|
dacfa9de82 | ||
|
|
4429c03837 | ||
|
|
395a1301a8 | ||
|
|
1e4d804e7f | ||
|
|
191085a882 | ||
|
|
8fd0de4580 | ||
|
|
7a427ce197 | ||
|
|
5eb6f51f4a | ||
|
|
d03c59768c | ||
|
|
783b97152a | ||
|
|
dca652366a | ||
|
|
de1bfe028d | ||
|
|
db37dc580a | ||
|
|
bcc3d4265e | ||
|
|
eecc2f88b3 | ||
|
|
11d6d67961 | ||
|
|
5d8354a88b | ||
|
|
8d19beb69e | ||
|
|
6742148cf4 | ||
|
|
341a07d05b | ||
|
|
41828c4180 | ||
|
|
3918290c1b | ||
|
|
26531fc46e | ||
|
|
667bd38489 | ||
|
|
9cdda21974 | ||
|
|
d24e8e9736 | ||
|
|
e9bad67619 | ||
|
|
ed036d514b | ||
|
|
1ddb355121 | ||
|
|
ab494dd982 | ||
|
|
fdbb86a06a | ||
|
|
0a7dcf30ea | ||
|
|
1552aca3df | ||
|
|
0a36e3ed40 | ||
|
|
a00ff03944 | ||
|
|
fc704e6b5d | ||
|
|
db1777c20b | ||
|
|
1b147a2c78 | ||
|
|
3e2ce40b18 | ||
|
|
00858812f2 | ||
|
|
1e8cc2e78d | ||
|
|
ea79eaceb0 | ||
|
|
c9d3ffb604 | ||
|
|
f12f0e7c7d | ||
|
|
3e32ae595f | ||
|
|
2eacb713b9 | ||
|
|
c5c438f1cd | ||
|
|
9bb2c043ae | ||
|
|
3bcc1993f4 | ||
|
|
9662234759 | ||
|
|
475856b767 | ||
|
|
482744cfa9 | ||
|
|
6092b37c56 | ||
|
|
1d94144976 | ||
|
|
f78078dfaf | ||
|
|
eeb8181cb1 | ||
|
|
a922694a7d | ||
|
|
afada1eb6c | ||
|
|
b9905ef824 | ||
|
|
2119eb2205 | ||
|
|
e9a574d919 | ||
|
|
59f5744f30 | ||
|
|
49646e4407 | ||
|
|
6eb12551ba | ||
|
|
b8fa424f85 | ||
|
|
2c2983462c | ||
|
|
f592793873 | ||
|
|
f7597cdae2 | ||
|
|
42420ea26d | ||
|
|
b8625aa098 |
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
||||
AlignArrayOfStructures: None
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
|
||||
@@ -20,6 +20,7 @@ Checks: >
|
||||
-cppcoreguidelines-avoid-do-while,
|
||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-cppcoreguidelines-use-enum-class,
|
||||
google-global-names-in-headers,
|
||||
google-readability-casting,
|
||||
google-runtime-int,
|
||||
@@ -63,6 +64,8 @@ CheckOptions:
|
||||
readability-identifier-naming.ParameterCase: camelBack
|
||||
readability-identifier-naming.VariableCase: camelBack
|
||||
|
||||
misc-const-correctness.WarnPointersAsPointers: false
|
||||
|
||||
# does not appear to work
|
||||
readability-operators-representation.BinaryOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
|
||||
readability-operators-representation.OverloadedOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Crash Report
|
||||
name: Crash Report (v1)
|
||||
description: Quickshell has crashed
|
||||
labels: ["bug", "crash"]
|
||||
body:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Crash Report (v2)
|
||||
description: Quickshell has crashed
|
||||
labels: ["bug", "crash"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: userinfo
|
||||
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: 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.
|
||||
|
||||
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: |
|
||||
GDB usually produces better stacktraces than quickshell can. Consider attaching a gdb backtrace
|
||||
following the instructions below.
|
||||
|
||||
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.
|
||||
@@ -6,7 +6,7 @@ jobs:
|
||||
name: Nix
|
||||
strategy:
|
||||
matrix:
|
||||
qtver: [qt6.9.2, qt6.9.1, qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
|
||||
qtver: [qt6.10.1, qt6.10.0, qt6.9.2, qt6.9.1, qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
|
||||
compiler: [clang, gcc]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -50,13 +50,16 @@ jobs:
|
||||
wayland-protocols \
|
||||
wayland \
|
||||
libdrm \
|
||||
vulkan-headers \
|
||||
libxcb \
|
||||
libpipewire \
|
||||
cli11 \
|
||||
jemalloc
|
||||
polkit \
|
||||
jemalloc \
|
||||
libunwind \
|
||||
git # for cpptrace clone
|
||||
|
||||
- name: Build
|
||||
# breakpad is annoying to build in ci due to makepkg not running as root
|
||||
run: |
|
||||
cmake -GNinja -B build -DCRASH_REPORTER=OFF
|
||||
cmake -GNinja -B build -DVENDOR_CPPTRACE=ON
|
||||
cmake --build build
|
||||
@@ -64,14 +64,17 @@ At least Qt 6.6 is required.
|
||||
|
||||
All features are enabled by default and some have their own dependencies.
|
||||
|
||||
### Crash Reporter
|
||||
The crash reporter catches crashes, restarts quickshell when it crashes,
|
||||
### Crash Handler
|
||||
The crash reporter catches crashes, restarts Quickshell when it crashes,
|
||||
and collects useful crash information in one place. Leaving this enabled will
|
||||
enable us to fix bugs far more easily.
|
||||
|
||||
To disable: `-DCRASH_REPORTER=OFF`
|
||||
To disable: `-DCRASH_HANDLER=OFF`
|
||||
|
||||
Dependencies: `google-breakpad` (static library)
|
||||
Dependencies: `cpptrace`
|
||||
|
||||
Note: `-DVENDOR_CPPTRACE=ON` can be set to vendor cpptrace using FetchContent.
|
||||
This still requires `libunwind` to build.
|
||||
|
||||
### Jemalloc
|
||||
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
||||
@@ -146,6 +149,7 @@ To disable: `-DSCREENCOPY=OFF`
|
||||
Dependencies:
|
||||
- `libdrm`
|
||||
- `libgbm`
|
||||
- `vulkan-headers` (build-time)
|
||||
|
||||
Specific protocols can also be disabled:
|
||||
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
|
||||
@@ -192,6 +196,13 @@ To disable: `-DSERVICE_PAM=OFF`
|
||||
|
||||
Dependencies: `pam`
|
||||
|
||||
### Polkit
|
||||
This feature enables creating Polkit agents that can prompt user for authentication.
|
||||
|
||||
To disable: `-DSERVICE_POLKIT=OFF`
|
||||
|
||||
Dependencies: `polkit`, `glib`
|
||||
|
||||
### Hyprland
|
||||
This feature enables hyprland specific integrations. It requires wayland support
|
||||
but has no extra dependencies.
|
||||
|
||||
+11
-3
@@ -1,6 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
|
||||
|
||||
set(UNRELEASED_FEATURES)
|
||||
|
||||
set(QT_MIN_VERSION "6.6.0")
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
@@ -44,8 +46,12 @@ boption(BUILD_TESTING "Build tests (dev)" OFF)
|
||||
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
|
||||
boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN})
|
||||
|
||||
boption(CRASH_REPORTER "Crash Handling" ON)
|
||||
boption(USE_JEMALLOC "Use jemalloc" ON)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||
boption(USE_JEMALLOC "Use jemalloc" OFF)
|
||||
else()
|
||||
boption(USE_JEMALLOC "Use jemalloc" ON)
|
||||
endif()
|
||||
boption(CRASH_HANDLER "Crash Handling" ON)
|
||||
boption(SOCKETS "Unix Sockets" ON)
|
||||
boption(WAYLAND "Wayland" ON)
|
||||
boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND)
|
||||
@@ -67,10 +73,12 @@ boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
|
||||
boption(SERVICE_PIPEWIRE "PipeWire" ON)
|
||||
boption(SERVICE_MPRIS "Mpris" ON)
|
||||
boption(SERVICE_PAM "Pam" ON)
|
||||
boption(SERVICE_POLKIT "Polkit" ON)
|
||||
boption(SERVICE_GREETD "Greetd" ON)
|
||||
boption(SERVICE_UPOWER "UPower" ON)
|
||||
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
|
||||
boption(BLUETOOTH "Bluetooth" ON)
|
||||
boption(NETWORK "Network" ON)
|
||||
|
||||
include(cmake/install-qml-module.cmake)
|
||||
include(cmake/util.cmake)
|
||||
@@ -119,7 +127,7 @@ if (WAYLAND)
|
||||
list(APPEND QT_PRIVDEPS WaylandClientPrivate)
|
||||
endif()
|
||||
|
||||
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH)
|
||||
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH OR NETWORK)
|
||||
set(DBUS ON)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
## Breaking Changes
|
||||
|
||||
### Config paths are no longer canonicalized
|
||||
|
||||
This fixes nix configs changing shell-ids on rebuild as the shell id is now derived from
|
||||
the symlink path. Configs with a symlink in their path will have a different shell id.
|
||||
|
||||
Shell ids are used to derive the default config / state / cache folders, so those files
|
||||
will need to be manually moved if using a config behind a symlinked path without an explicitly
|
||||
set shell id.
|
||||
|
||||
## New Features
|
||||
|
||||
- Added support for creating Polkit agents.
|
||||
- Added support for creating wayland idle inhibitors.
|
||||
- Added support for wayland idle timeouts.
|
||||
- Added support for inhibiting wayland compositor shortcuts for focused windows.
|
||||
- Added the ability to override Quickshell.cacheDir with a custom path.
|
||||
- Added minimized, maximized, and fullscreen properties to FloatingWindow.
|
||||
- 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 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Fixed volume control breaking with pipewire pro audio mode.
|
||||
- Fixed volume control breaking with bluez streams and potentially others.
|
||||
- Fixed volume control breaking for devices without route definitions.
|
||||
- Fixed escape sequence handling in desktop entries.
|
||||
- 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 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.
|
||||
- Fixed memory leak in IPC handlers.
|
||||
- Fixed ClippingRectangle related crashes.
|
||||
- Fixed crashes when monitors are unplugged.
|
||||
- Fixed crashes when default pipewire devices are lost.
|
||||
|
||||
## Packaging Changes
|
||||
|
||||
- `glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
|
||||
- `vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).
|
||||
- `breakpad` has been replaced by `cpptrace`, which is far easier to package, and the `CRASH_REPORTER` cmake variable has been replaced with `CRASH_HANDLER` to stop this from being easy to ignore.
|
||||
+11
-1
@@ -8,7 +8,17 @@ let
|
||||
inherit sha256;
|
||||
}) {};
|
||||
in rec {
|
||||
latest = qt6_9_0;
|
||||
latest = qt6_10_0;
|
||||
|
||||
qt6_10_1 = byCommit {
|
||||
commit = "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38";
|
||||
sha256 = "0fvbizl7j5rv2rf8j76yw0xb3d9l06hahkjys2a7k1yraznvnafm";
|
||||
};
|
||||
|
||||
qt6_10_0 = byCommit {
|
||||
commit = "c5ae371f1a6a7fd27823bc500d9390b38c05fa55";
|
||||
sha256 = "18g0f8cb9m8mxnz9cf48sks0hib79b282iajl2nysyszph993yp0";
|
||||
};
|
||||
|
||||
qt6_9_2 = byCommit {
|
||||
commit = "e9f00bd893984bc8ce46c895c3bf7cac95331127";
|
||||
|
||||
+27
-6
@@ -10,7 +10,9 @@
|
||||
ninja,
|
||||
spirv-tools,
|
||||
qt6,
|
||||
breakpad,
|
||||
cpptrace ? null,
|
||||
libunwind,
|
||||
libdwarf,
|
||||
jemalloc,
|
||||
cli11,
|
||||
wayland,
|
||||
@@ -19,8 +21,11 @@
|
||||
xorg,
|
||||
libdrm,
|
||||
libgbm ? null,
|
||||
vulkan-headers,
|
||||
pipewire,
|
||||
pam,
|
||||
polkit,
|
||||
glib,
|
||||
|
||||
gitRev ? (let
|
||||
headExists = builtins.pathExists ./.git/HEAD;
|
||||
@@ -43,10 +48,14 @@
|
||||
withPam ? true,
|
||||
withHyprland ? true,
|
||||
withI3 ? true,
|
||||
withPolkit ? true,
|
||||
withNetworkManager ? true,
|
||||
}: let
|
||||
withCrashHandler = withCrashReporter && cpptrace != null && lib.strings.compareVersions cpptrace.version "0.7.2" >= 0;
|
||||
|
||||
unwrapped = stdenv.mkDerivation {
|
||||
pname = "quickshell${lib.optionalString debug "-debug"}";
|
||||
version = "0.2.0";
|
||||
version = "0.2.1";
|
||||
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
|
||||
|
||||
dontWrapQtApps = true; # see wrappers
|
||||
@@ -69,14 +78,24 @@
|
||||
cli11
|
||||
]
|
||||
++ lib.optional withQtSvg qt6.qtsvg
|
||||
++ lib.optional withCrashReporter breakpad
|
||||
++ lib.optionals withCrashHandler [
|
||||
(cpptrace.overrideAttrs (prev: {
|
||||
cmakeFlags = prev.cmakeFlags ++ [
|
||||
"-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
|
||||
];
|
||||
buildInputs = prev.buildInputs ++ [ libunwind ];
|
||||
}))
|
||||
libunwind
|
||||
libdwarf
|
||||
]
|
||||
++ lib.optional withJemalloc jemalloc
|
||||
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
||||
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
||||
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
|
||||
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
|
||||
++ lib.optional withX11 xorg.libxcb
|
||||
++ lib.optional withPam pam
|
||||
++ lib.optional withPipewire pipewire;
|
||||
++ lib.optional withPipewire pipewire
|
||||
++ lib.optionals withPolkit [ polkit glib ];
|
||||
|
||||
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
|
||||
|
||||
@@ -85,12 +104,14 @@
|
||||
(lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
|
||||
(lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
|
||||
(lib.cmakeFeature "GIT_REVISION" gitRev)
|
||||
(lib.cmakeBool "CRASH_REPORTER" withCrashReporter)
|
||||
(lib.cmakeBool "CRASH_HANDLER" withCrashHandler)
|
||||
(lib.cmakeBool "USE_JEMALLOC" withJemalloc)
|
||||
(lib.cmakeBool "WAYLAND" withWayland)
|
||||
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
|
||||
(lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
|
||||
(lib.cmakeBool "SERVICE_PAM" withPam)
|
||||
(lib.cmakeBool "SERVICE_NETWORKMANAGER" withNetworkManager)
|
||||
(lib.cmakeBool "SERVICE_POLKIT" withPolkit)
|
||||
(lib.cmakeBool "HYPRLAND" withHyprland)
|
||||
(lib.cmakeBool "I3" withI3)
|
||||
];
|
||||
|
||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1758690382,
|
||||
"narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
|
||||
"lastModified": 1768127708,
|
||||
"narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e643668fd71b949c53f8626614b21ff71a07379d",
|
||||
"rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
+2
-2
@@ -42,6 +42,7 @@
|
||||
libxcb
|
||||
libxkbcommon
|
||||
linux-pam
|
||||
polkit
|
||||
mesa
|
||||
pipewire
|
||||
qtbase
|
||||
@@ -55,8 +56,7 @@
|
||||
#~(list "-GNinja"
|
||||
"-DDISTRIBUTOR=\"In-tree Guix channel\""
|
||||
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
|
||||
;; Breakpad is not currently packaged for Guix.
|
||||
"-DCRASH_REPORTER=OFF")
|
||||
"-DCRASH_HANDLER=OFF")
|
||||
#:phases
|
||||
#~(modify-phases %standard-phases
|
||||
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
|
||||
|
||||
+5
-1
@@ -12,7 +12,7 @@ add_subdirectory(io)
|
||||
add_subdirectory(widgets)
|
||||
add_subdirectory(ui)
|
||||
|
||||
if (CRASH_REPORTER)
|
||||
if (CRASH_HANDLER)
|
||||
add_subdirectory(crash)
|
||||
endif()
|
||||
|
||||
@@ -33,3 +33,7 @@ add_subdirectory(services)
|
||||
if (BLUETOOTH)
|
||||
add_subdirectory(bluetooth)
|
||||
endif()
|
||||
|
||||
if (NETWORK)
|
||||
add_subdirectory(network)
|
||||
endif()
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qstring.h>
|
||||
#include <qstringliteral.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../core/logcat.hpp"
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qstring.h>
|
||||
#include <qstringliteral.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ if (NOT DEFINED GIT_REVISION)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (CRASH_REPORTER)
|
||||
set(CRASH_REPORTER_DEF 1)
|
||||
if (CRASH_HANDLER)
|
||||
set(CRASH_HANDLER_DEF 1)
|
||||
else()
|
||||
set(CRASH_REPORTER_DEF 0)
|
||||
set(CRASH_HANDLER_DEF 0)
|
||||
endif()
|
||||
|
||||
if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
// NOLINTBEGIN
|
||||
#define QS_VERSION "@quickshell_VERSION@"
|
||||
#define QS_VERSION_MAJOR @quickshell_VERSION_MAJOR@
|
||||
#define QS_VERSION_MINOR @quickshell_VERSION_MINOR@
|
||||
#define QS_VERSION_PATCH @quickshell_VERSION_PATCH@
|
||||
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
|
||||
#define GIT_REVISION "@GIT_REVISION@"
|
||||
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
||||
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
|
||||
#define CRASH_REPORTER @CRASH_REPORTER_DEF@
|
||||
#define CRASH_HANDLER @CRASH_HANDLER_DEF@
|
||||
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
|
||||
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
|
||||
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
|
||||
|
||||
@@ -12,6 +12,7 @@ qt_add_library(quickshell-core STATIC
|
||||
singleton.cpp
|
||||
generation.cpp
|
||||
scan.cpp
|
||||
scanenv.cpp
|
||||
qsintercept.cpp
|
||||
incubator.cpp
|
||||
lazyloader.cpp
|
||||
@@ -24,7 +25,6 @@ qt_add_library(quickshell-core STATIC
|
||||
elapsedtimer.cpp
|
||||
desktopentry.cpp
|
||||
desktopentrymonitor.cpp
|
||||
objectrepeater.cpp
|
||||
platformmenu.cpp
|
||||
qsmenu.cpp
|
||||
retainable.cpp
|
||||
@@ -52,7 +52,7 @@ qt_add_qml_module(quickshell-core
|
||||
|
||||
install_qml_module(quickshell-core)
|
||||
|
||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets)
|
||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build)
|
||||
|
||||
qs_module_pch(quickshell-core SET large)
|
||||
|
||||
|
||||
@@ -269,16 +269,22 @@ QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
||||
currentArgument += '\\';
|
||||
escape = 0;
|
||||
}
|
||||
} else if (escape == 2) {
|
||||
currentArgument += c;
|
||||
escape = 0;
|
||||
} else if (escape != 0) {
|
||||
if (escape != 2) {
|
||||
// Technically this is an illegal state, but the spec has a terrible double escape
|
||||
// rule in strings for no discernable reason. Assuming someone might understandably
|
||||
// misunderstand it, treat it as a normal escape and log it.
|
||||
switch (c.unicode()) {
|
||||
case 's': currentArgument += u' '; break;
|
||||
case 'n': currentArgument += u'\n'; break;
|
||||
case 't': currentArgument += u'\t'; break;
|
||||
case 'r': currentArgument += u'\r'; break;
|
||||
case '\\': currentArgument += u'\\'; break;
|
||||
default:
|
||||
qCWarning(logDesktopEntry).noquote()
|
||||
<< "Illegal escape sequence in desktop entry exec string:" << execString;
|
||||
currentArgument += c;
|
||||
break;
|
||||
}
|
||||
|
||||
currentArgument += c;
|
||||
escape = 0;
|
||||
} else if (c == u'"' || c == u'\'') {
|
||||
parsingString = false;
|
||||
|
||||
+9
-19
@@ -49,7 +49,8 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
|
||||
this->engine->addImportPath("qs:@/");
|
||||
|
||||
this->engine->setNetworkAccessManagerFactory(&this->interceptNetFactory);
|
||||
this->engine->setIncubationController(&this->delayedIncubationController);
|
||||
this->incubationController.initLoop();
|
||||
this->engine->setIncubationController(&this->incubationController);
|
||||
|
||||
this->engine->addImageProvider("icon", new IconImageProvider());
|
||||
this->engine->addImageProvider("qsimage", new QsImageProvider());
|
||||
@@ -134,7 +135,7 @@ void EngineGeneration::onReload(EngineGeneration* old) {
|
||||
// new generation acquires it then incubators will hang intermittently
|
||||
qCDebug(logIncubator) << "Locking incubation controllers of old generation" << old;
|
||||
old->incubationControllersLocked = true;
|
||||
old->assignIncubationController();
|
||||
old->updateIncubationMode();
|
||||
}
|
||||
|
||||
QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit);
|
||||
@@ -288,29 +289,18 @@ void EngineGeneration::trackWindowIncubationController(QQuickWindow* window) {
|
||||
|
||||
QObject::connect(window, &QObject::destroyed, this, &EngineGeneration::onTrackedWindowDestroyed);
|
||||
this->trackedWindows.append(window);
|
||||
this->assignIncubationController();
|
||||
this->updateIncubationMode();
|
||||
}
|
||||
|
||||
void EngineGeneration::onTrackedWindowDestroyed(QObject* object) {
|
||||
this->trackedWindows.removeAll(static_cast<QQuickWindow*>(object)); // NOLINT
|
||||
this->assignIncubationController();
|
||||
this->updateIncubationMode();
|
||||
}
|
||||
|
||||
void EngineGeneration::assignIncubationController() {
|
||||
QQmlIncubationController* controller = &this->delayedIncubationController;
|
||||
|
||||
for (auto* window: this->trackedWindows) {
|
||||
if (auto* wctl = window->incubationController()) {
|
||||
controller = wctl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
|
||||
<< this
|
||||
<< "fallback:" << (controller == &this->delayedIncubationController);
|
||||
|
||||
this->engine->setIncubationController(controller);
|
||||
void EngineGeneration::updateIncubationMode() {
|
||||
// If we're in a situation with only hidden but tracked windows this might be wrong,
|
||||
// but it seems to at least work.
|
||||
this->incubationController.setIncubationMode(!this->trackedWindows.empty());
|
||||
}
|
||||
|
||||
EngineGeneration* EngineGeneration::currentGeneration() {
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
QFileSystemWatcher* watcher = nullptr;
|
||||
QVector<QString> deletedWatchedFiles;
|
||||
QVector<QString> extraWatchedFiles;
|
||||
DelayedQmlIncubationController delayedIncubationController;
|
||||
QsIncubationController incubationController;
|
||||
bool reloadComplete = false;
|
||||
QuickshellGlobal* qsgInstance = nullptr;
|
||||
|
||||
@@ -89,7 +89,7 @@ private slots:
|
||||
|
||||
private:
|
||||
void postReload();
|
||||
void assignIncubationController();
|
||||
void updateIncubationMode();
|
||||
QVector<QQuickWindow*> trackedWindows;
|
||||
bool incubationControllersLocked = false;
|
||||
QHash<const void*, EngineGenerationExt*> extensions;
|
||||
|
||||
@@ -19,8 +19,7 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
|
||||
if (splitIdx != -1) {
|
||||
iconName = id.sliced(0, splitIdx);
|
||||
path = id.sliced(splitIdx + 6);
|
||||
qWarning() << "Searching custom icon paths is not yet supported. Icon path will be ignored for"
|
||||
<< id;
|
||||
path = QString("/%1/%2").arg(path, iconName.sliced(iconName.lastIndexOf('/') + 1));
|
||||
} else {
|
||||
splitIdx = id.indexOf("?fallback=");
|
||||
if (splitIdx != -1) {
|
||||
@@ -32,7 +31,8 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
|
||||
}
|
||||
|
||||
auto icon = QIcon::fromTheme(iconName);
|
||||
if (icon.isNull()) icon = QIcon::fromTheme(fallbackName);
|
||||
if (icon.isNull() && !fallbackName.isEmpty()) icon = QIcon::fromTheme(fallbackName);
|
||||
if (icon.isNull() && !path.isEmpty()) icon = QPixmap(path);
|
||||
|
||||
auto targetSize = requestedSize.isValid() ? requestedSize : QSize(100, 100);
|
||||
if (targetSize.width() == 0 || targetSize.height() == 0) targetSize = QSize(2, 2);
|
||||
|
||||
@@ -22,8 +22,8 @@ class PixmapCacheIconEngine: public QIconEngine {
|
||||
QIcon::Mode /*unused*/,
|
||||
QIcon::State /*unused*/
|
||||
) override {
|
||||
qFatal(
|
||||
) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
|
||||
qFatal()
|
||||
<< "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
|
||||
}
|
||||
|
||||
QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override {
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
#include "incubator.hpp"
|
||||
|
||||
#include <private/qsgrenderloop_p.h>
|
||||
#include <qabstractanimation.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qminmax.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qqmlincubator.h>
|
||||
#include <qscreen.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "logcat.hpp"
|
||||
@@ -15,3 +24,112 @@ void QsQmlIncubator::statusChanged(QQmlIncubator::Status status) {
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void QsIncubationController::initLoop() {
|
||||
auto* app = static_cast<QGuiApplication*>(QGuiApplication::instance()); // NOLINT
|
||||
this->renderLoop = QSGRenderLoop::instance();
|
||||
|
||||
QObject::connect(
|
||||
app,
|
||||
&QGuiApplication::screenAdded,
|
||||
this,
|
||||
&QsIncubationController::updateIncubationTime
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
app,
|
||||
&QGuiApplication::screenRemoved,
|
||||
this,
|
||||
&QsIncubationController::updateIncubationTime
|
||||
);
|
||||
|
||||
this->updateIncubationTime();
|
||||
|
||||
QObject::connect(
|
||||
this->renderLoop,
|
||||
&QSGRenderLoop::timeToIncubate,
|
||||
this,
|
||||
&QsIncubationController::incubate
|
||||
);
|
||||
|
||||
QAnimationDriver* animationDriver = this->renderLoop->animationDriver();
|
||||
if (animationDriver) {
|
||||
QObject::connect(
|
||||
animationDriver,
|
||||
&QAnimationDriver::stopped,
|
||||
this,
|
||||
&QsIncubationController::animationStopped
|
||||
);
|
||||
} else {
|
||||
qCInfo(logIncubator) << "Render loop does not have animation driver, animationStopped cannot "
|
||||
"be used to trigger incubation.";
|
||||
}
|
||||
}
|
||||
|
||||
void QsIncubationController::setIncubationMode(bool render) {
|
||||
if (render == this->followRenderloop) return;
|
||||
this->followRenderloop = render;
|
||||
|
||||
if (render) {
|
||||
qCDebug(logIncubator) << "Incubation mode changed: render loop driven";
|
||||
} else {
|
||||
qCDebug(logIncubator) << "Incubation mode changed: event loop driven";
|
||||
}
|
||||
|
||||
if (!render && this->incubatingObjectCount()) this->incubateLater();
|
||||
}
|
||||
|
||||
void QsIncubationController::timerEvent(QTimerEvent* /*event*/) {
|
||||
this->killTimer(this->timerId);
|
||||
this->timerId = 0;
|
||||
this->incubate();
|
||||
}
|
||||
|
||||
void QsIncubationController::incubateLater() {
|
||||
if (this->followRenderloop) {
|
||||
if (this->timerId != 0) {
|
||||
this->killTimer(this->timerId);
|
||||
this->timerId = 0;
|
||||
}
|
||||
|
||||
// Incubate again at the end of the event processing queue
|
||||
QMetaObject::invokeMethod(this, &QsIncubationController::incubate, Qt::QueuedConnection);
|
||||
} else if (this->timerId == 0) {
|
||||
// Wait for a while before processing the next batch. Using a
|
||||
// timer to avoid starvation of system events.
|
||||
this->timerId = this->startTimer(this->incubationTime);
|
||||
}
|
||||
}
|
||||
|
||||
void QsIncubationController::incubate() {
|
||||
if ((!this->followRenderloop || this->renderLoop) && this->incubatingObjectCount()) {
|
||||
if (!this->followRenderloop) {
|
||||
this->incubateFor(10);
|
||||
if (this->incubatingObjectCount()) this->incubateLater();
|
||||
} else if (this->renderLoop->interleaveIncubation()) {
|
||||
this->incubateFor(this->incubationTime);
|
||||
} else {
|
||||
this->incubateFor(this->incubationTime * 2);
|
||||
if (this->incubatingObjectCount()) this->incubateLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QsIncubationController::animationStopped() { this->incubate(); }
|
||||
|
||||
void QsIncubationController::incubatingObjectCountChanged(int count) {
|
||||
if (count
|
||||
&& (!this->followRenderloop
|
||||
|| (this->renderLoop && !this->renderLoop->interleaveIncubation())))
|
||||
{
|
||||
this->incubateLater();
|
||||
}
|
||||
}
|
||||
|
||||
void QsIncubationController::updateIncubationTime() {
|
||||
auto* screen = QGuiApplication::primaryScreen();
|
||||
if (!screen) return;
|
||||
|
||||
// 1/3 frame on primary screen
|
||||
this->incubationTime = qMax(1, static_cast<int>(1000 / screen->refreshRate() / 3));
|
||||
}
|
||||
+34
-3
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qpointer.h>
|
||||
#include <qqmlincubator.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
@@ -25,7 +26,37 @@ signals:
|
||||
void failed();
|
||||
};
|
||||
|
||||
class DelayedQmlIncubationController: public QQmlIncubationController {
|
||||
// Do nothing.
|
||||
// This ensures lazy loaders don't start blocking before onReload creates windows.
|
||||
class QSGRenderLoop;
|
||||
|
||||
class QsIncubationController
|
||||
: public QObject
|
||||
, public QQmlIncubationController {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
void initLoop();
|
||||
void setIncubationMode(bool render);
|
||||
void incubateLater();
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent* event) override;
|
||||
|
||||
public slots:
|
||||
void incubate();
|
||||
void animationStopped();
|
||||
void updateIncubationTime();
|
||||
|
||||
protected:
|
||||
void incubatingObjectCountChanged(int count) override;
|
||||
|
||||
private:
|
||||
// QPointer did not work with forward declarations prior to 6.7
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
QPointer<QSGRenderLoop> renderLoop = nullptr;
|
||||
#else
|
||||
QSGRenderLoop* renderLoop = nullptr;
|
||||
#endif
|
||||
int incubationTime = 0;
|
||||
int timerId = 0;
|
||||
bool followRenderloop = false;
|
||||
};
|
||||
@@ -3,12 +3,14 @@
|
||||
#include <qdatastream.h>
|
||||
|
||||
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
|
||||
stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid;
|
||||
stream << info.instanceId << info.configPath << info.shellId << 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;
|
||||
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid
|
||||
>> info.display;
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ struct InstanceInfo {
|
||||
QString shellId;
|
||||
QDateTime launchTime;
|
||||
pid_t pid = -1;
|
||||
QString display;
|
||||
|
||||
static InstanceInfo CURRENT; // NOLINT
|
||||
};
|
||||
@@ -34,6 +35,8 @@ namespace qs::crash {
|
||||
|
||||
struct CrashInfo {
|
||||
int logFd = -1;
|
||||
int traceFd = -1;
|
||||
int infoFd = -1;
|
||||
|
||||
static CrashInfo INSTANCE; // NOLINT
|
||||
};
|
||||
|
||||
@@ -82,9 +82,6 @@
|
||||
/// > Notably, @@Variants does not corrently support asynchronous
|
||||
/// > loading, meaning using it inside a LazyLoader will block similarly to not
|
||||
/// > having a loader to start with.
|
||||
///
|
||||
/// > [!WARNING] LazyLoaders do not start loading before the first window is created,
|
||||
/// > meaning if you create all windows inside of lazy loaders, none of them will ever load.
|
||||
class LazyLoader: public Reloadable {
|
||||
Q_OBJECT;
|
||||
/// The fully loaded item if the loader is @@loading or @@active, or `null`
|
||||
|
||||
+73
-9
@@ -27,7 +27,10 @@
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <sys/mman.h>
|
||||
#ifdef __linux__
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#include "instanceinfo.hpp"
|
||||
#include "logcat.hpp"
|
||||
@@ -43,6 +46,57 @@ using namespace qt_logging_registry;
|
||||
|
||||
QS_LOGGING_CATEGORY(logLogging, "quickshell.logging", QtWarningMsg);
|
||||
|
||||
namespace {
|
||||
bool copyFileData(int sourceFd, int destFd, qint64 size) {
|
||||
auto usize = static_cast<size_t>(size);
|
||||
|
||||
#ifdef __linux__
|
||||
off_t offset = 0;
|
||||
auto remaining = usize;
|
||||
|
||||
while (remaining > 0) {
|
||||
auto r = sendfile(destFd, sourceFd, &offset, remaining);
|
||||
if (r == -1) {
|
||||
if (errno == EINTR) continue;
|
||||
return false;
|
||||
}
|
||||
if (r == 0) break;
|
||||
remaining -= static_cast<size_t>(r);
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
std::array<char, 64 * 1024> buffer = {};
|
||||
auto remaining = totalTarget;
|
||||
|
||||
while (remaining > 0) {
|
||||
auto chunk = std::min(remaining, buffer.size());
|
||||
auto r = ::read(sourceFd, buffer.data(), chunk);
|
||||
if (r == -1) {
|
||||
if (errno == EINTR) continue;
|
||||
return false;
|
||||
}
|
||||
if (r == 0) break;
|
||||
|
||||
auto readBytes = static_cast<size_t>(r);
|
||||
size_t written = 0;
|
||||
while (written < readBytes) {
|
||||
auto w = ::write(destFd, buffer.data() + written, readBytes - written);
|
||||
if (w == -1) {
|
||||
if (errno == EINTR) continue;
|
||||
return false;
|
||||
}
|
||||
written += static_cast<size_t>(w);
|
||||
}
|
||||
|
||||
remaining -= readBytes;
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool LogMessage::operator==(const LogMessage& other) const {
|
||||
// note: not including time
|
||||
return this->type == other.type && this->category == other.category && this->body == other.body;
|
||||
@@ -361,7 +415,8 @@ void ThreadLogging::initFs() {
|
||||
auto* runDir = QsPaths::instance()->instanceRunDir();
|
||||
|
||||
if (!runDir) {
|
||||
qCCritical(logLogging
|
||||
qCCritical(
|
||||
logLogging
|
||||
) << "Could not start filesystem logging as the runtime directory could not be created.";
|
||||
return;
|
||||
}
|
||||
@@ -372,7 +427,8 @@ void ThreadLogging::initFs() {
|
||||
auto* detailedFile = new QFile(detailedPath);
|
||||
|
||||
if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
|
||||
qCCritical(logLogging
|
||||
qCCritical(
|
||||
logLogging
|
||||
) << "Could not start filesystem logger as the log file could not be created:"
|
||||
<< path;
|
||||
delete file;
|
||||
@@ -383,13 +439,14 @@ void ThreadLogging::initFs() {
|
||||
|
||||
// buffered by WriteBuffer
|
||||
if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) {
|
||||
qCCritical(logLogging
|
||||
qCCritical(
|
||||
logLogging
|
||||
) << "Could not start detailed filesystem logger as the log file could not be created:"
|
||||
<< detailedPath;
|
||||
delete detailedFile;
|
||||
detailedFile = nullptr;
|
||||
} else {
|
||||
auto lock = flock {
|
||||
struct flock lock = {
|
||||
.l_type = F_WRLCK,
|
||||
.l_whence = SEEK_SET,
|
||||
.l_start = 0,
|
||||
@@ -411,7 +468,11 @@ void ThreadLogging::initFs() {
|
||||
auto* oldFile = this->file;
|
||||
if (oldFile) {
|
||||
oldFile->seek(0);
|
||||
sendfile(file->handle(), oldFile->handle(), nullptr, oldFile->size());
|
||||
|
||||
if (!copyFileData(oldFile->handle(), file->handle(), oldFile->size())) {
|
||||
qCritical(logLogging) << "Failed to copy log from memfd with error code " << errno
|
||||
<< qt_error_string(errno);
|
||||
}
|
||||
}
|
||||
|
||||
this->file = file;
|
||||
@@ -423,7 +484,10 @@ void ThreadLogging::initFs() {
|
||||
auto* oldFile = this->detailedFile;
|
||||
if (oldFile) {
|
||||
oldFile->seek(0);
|
||||
sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size());
|
||||
if (!copyFileData(oldFile->handle(), detailedFile->handle(), oldFile->size())) {
|
||||
qCritical(logLogging) << "Failed to copy detailed log from memfd with error code " << errno
|
||||
<< qt_error_string(errno);
|
||||
}
|
||||
}
|
||||
|
||||
crash::CrashInfo::INSTANCE.logFd = detailedFile->handle();
|
||||
@@ -746,11 +810,11 @@ bool EncodedLogReader::readVarInt(quint32* slot) {
|
||||
if (!this->reader.skip(1)) return false;
|
||||
*slot = qFromLittleEndian(n);
|
||||
} else if ((bytes[1] != 0xff || bytes[2] != 0xff) && readLength >= 3) {
|
||||
auto n = *reinterpret_cast<quint16*>(bytes.data() + 1);
|
||||
auto n = *reinterpret_cast<quint16*>(bytes.data() + 1); // NOLINT
|
||||
if (!this->reader.skip(3)) return false;
|
||||
*slot = qFromLittleEndian(n);
|
||||
} else if (readLength == 7) {
|
||||
auto n = *reinterpret_cast<quint32*>(bytes.data() + 3);
|
||||
auto n = *reinterpret_cast<quint32*>(bytes.data() + 3); // NOLINT
|
||||
if (!this->reader.skip(7)) return false;
|
||||
*slot = qFromLittleEndian(n);
|
||||
} else return false;
|
||||
@@ -886,7 +950,7 @@ bool LogReader::continueReading() {
|
||||
}
|
||||
|
||||
void LogFollower::FcntlWaitThread::run() {
|
||||
auto lock = flock {
|
||||
struct flock lock = {
|
||||
.l_type = F_RDLCK, // won't block other read locks when we take it
|
||||
.l_whence = SEEK_SET,
|
||||
.l_start = 0,
|
||||
|
||||
+2
-69
@@ -1,81 +1,14 @@
|
||||
#include "model.hpp"
|
||||
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <qbytearray.h>
|
||||
#include <qhash.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
qint32 UntypedObjectModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent != QModelIndex()) return 0;
|
||||
return static_cast<qint32>(this->valuesList.length());
|
||||
}
|
||||
|
||||
QVariant UntypedObjectModel::data(const QModelIndex& index, qint32 role) const {
|
||||
if (role != Qt::UserRole) return QVariant();
|
||||
return QVariant::fromValue(this->valuesList.at(index.row()));
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> UntypedObjectModel::roleNames() const {
|
||||
return {{Qt::UserRole, "modelData"}};
|
||||
}
|
||||
|
||||
void UntypedObjectModel::insertObject(QObject* object, qsizetype index) {
|
||||
auto iindex = index == -1 ? this->valuesList.length() : index;
|
||||
emit this->objectInsertedPre(object, iindex);
|
||||
|
||||
auto intIndex = static_cast<qint32>(iindex);
|
||||
this->beginInsertRows(QModelIndex(), intIndex, intIndex);
|
||||
this->valuesList.insert(iindex, object);
|
||||
this->endInsertRows();
|
||||
|
||||
emit this->valuesChanged();
|
||||
emit this->objectInsertedPost(object, iindex);
|
||||
}
|
||||
|
||||
void UntypedObjectModel::removeAt(qsizetype index) {
|
||||
auto* object = this->valuesList.at(index);
|
||||
emit this->objectRemovedPre(object, index);
|
||||
|
||||
auto intIndex = static_cast<qint32>(index);
|
||||
this->beginRemoveRows(QModelIndex(), intIndex, intIndex);
|
||||
this->valuesList.removeAt(index);
|
||||
this->endRemoveRows();
|
||||
|
||||
emit this->valuesChanged();
|
||||
emit this->objectRemovedPost(object, index);
|
||||
}
|
||||
|
||||
bool UntypedObjectModel::removeObject(const QObject* object) {
|
||||
auto index = this->valuesList.indexOf(object);
|
||||
if (index == -1) return false;
|
||||
|
||||
this->removeAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UntypedObjectModel::diffUpdate(const QVector<QObject*>& newValues) {
|
||||
for (qsizetype i = 0; i < this->valuesList.length();) {
|
||||
if (newValues.contains(this->valuesList.at(i))) i++;
|
||||
else this->removeAt(i);
|
||||
}
|
||||
|
||||
qsizetype oi = 0;
|
||||
for (auto* object: newValues) {
|
||||
if (this->valuesList.length() == oi || this->valuesList.at(oi) != object) {
|
||||
this->insertObject(object, oi);
|
||||
}
|
||||
|
||||
oi++;
|
||||
}
|
||||
}
|
||||
|
||||
qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); }
|
||||
|
||||
UntypedObjectModel* UntypedObjectModel::emptyInstance() {
|
||||
static auto* instance = new UntypedObjectModel(nullptr); // NOLINT
|
||||
static auto* instance = new ObjectModel<void>(nullptr);
|
||||
return instance;
|
||||
}
|
||||
+73
-25
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <bit>
|
||||
#include <QtCore/qtmetamacros.h>
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qobject.h>
|
||||
@@ -49,14 +49,11 @@ class UntypedObjectModel: public QAbstractListModel {
|
||||
public:
|
||||
explicit UntypedObjectModel(QObject* parent): QAbstractListModel(parent) {}
|
||||
|
||||
[[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;
|
||||
|
||||
[[nodiscard]] QList<QObject*> values() const { return this->valuesList; }
|
||||
void removeAt(qsizetype index);
|
||||
[[nodiscard]] virtual QList<QObject*> values() = 0;
|
||||
|
||||
Q_INVOKABLE qsizetype indexOf(QObject* object);
|
||||
Q_INVOKABLE virtual qsizetype indexOf(QObject* object) const = 0;
|
||||
|
||||
static UntypedObjectModel* emptyInstance();
|
||||
|
||||
@@ -71,15 +68,6 @@ signals:
|
||||
/// Sent immediately after an object is removed from the list.
|
||||
void objectRemovedPost(QObject* object, qsizetype index);
|
||||
|
||||
protected:
|
||||
void insertObject(QObject* object, qsizetype index = -1);
|
||||
bool removeObject(const QObject* object);
|
||||
|
||||
// Assumes only one instance of a specific value
|
||||
void diffUpdate(const QVector<QObject*>& newValues);
|
||||
|
||||
QVector<QObject*> valuesList;
|
||||
|
||||
private:
|
||||
static qsizetype valuesCount(QQmlListProperty<QObject>* property);
|
||||
static QObject* valueAt(QQmlListProperty<QObject>* property, qsizetype index);
|
||||
@@ -90,14 +78,20 @@ class ObjectModel: public UntypedObjectModel {
|
||||
public:
|
||||
explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
|
||||
|
||||
[[nodiscard]] QVector<T*>& valueList() { return *std::bit_cast<QVector<T*>*>(&this->valuesList); }
|
||||
|
||||
[[nodiscard]] const QVector<T*>& valueList() const {
|
||||
return *std::bit_cast<const QVector<T*>*>(&this->valuesList);
|
||||
}
|
||||
[[nodiscard]] const QList<T*>& valueList() const { return this->mValuesList; }
|
||||
[[nodiscard]] QList<T*>& valueList() { return this->mValuesList; }
|
||||
|
||||
void insertObject(T* object, qsizetype index = -1) {
|
||||
this->UntypedObjectModel::insertObject(object, index);
|
||||
auto iindex = index == -1 ? this->mValuesList.length() : index;
|
||||
emit this->objectInsertedPre(object, iindex);
|
||||
|
||||
auto intIndex = static_cast<qint32>(iindex);
|
||||
this->beginInsertRows(QModelIndex(), intIndex, intIndex);
|
||||
this->mValuesList.insert(iindex, object);
|
||||
this->endInsertRows();
|
||||
|
||||
emit this->valuesChanged();
|
||||
emit this->objectInsertedPost(object, iindex);
|
||||
}
|
||||
|
||||
void insertObjectSorted(T* object, const std::function<bool(T*, T*)>& compare) {
|
||||
@@ -110,17 +104,71 @@ public:
|
||||
}
|
||||
|
||||
auto idx = iter - list.begin();
|
||||
this->UntypedObjectModel::insertObject(object, idx);
|
||||
this->insertObject(object, idx);
|
||||
}
|
||||
|
||||
void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); }
|
||||
bool removeObject(const T* object) {
|
||||
auto index = this->mValuesList.indexOf(object);
|
||||
if (index == -1) return false;
|
||||
|
||||
this->removeAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
void removeAt(qsizetype index) {
|
||||
auto* object = this->mValuesList.at(index);
|
||||
emit this->objectRemovedPre(object, index);
|
||||
|
||||
auto intIndex = static_cast<qint32>(index);
|
||||
this->beginRemoveRows(QModelIndex(), intIndex, intIndex);
|
||||
this->mValuesList.removeAt(index);
|
||||
this->endRemoveRows();
|
||||
|
||||
emit this->valuesChanged();
|
||||
emit this->objectRemovedPost(object, index);
|
||||
}
|
||||
|
||||
// Assumes only one instance of a specific value
|
||||
void diffUpdate(const QVector<T*>& newValues) {
|
||||
this->UntypedObjectModel::diffUpdate(*std::bit_cast<const QVector<QObject*>*>(&newValues));
|
||||
void diffUpdate(const QList<T*>& newValues) {
|
||||
for (qsizetype i = 0; i < this->mValuesList.length();) {
|
||||
if (newValues.contains(this->mValuesList.at(i))) i++;
|
||||
else this->removeAt(i);
|
||||
}
|
||||
|
||||
qsizetype oi = 0;
|
||||
for (auto* object: newValues) {
|
||||
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
|
||||
this->insertObject(object, oi);
|
||||
}
|
||||
|
||||
oi++;
|
||||
}
|
||||
}
|
||||
|
||||
static ObjectModel<T>* emptyInstance() {
|
||||
return static_cast<ObjectModel<T>*>(UntypedObjectModel::emptyInstance());
|
||||
}
|
||||
|
||||
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override {
|
||||
if (parent != QModelIndex()) return 0;
|
||||
return static_cast<qint32>(this->mValuesList.length());
|
||||
}
|
||||
|
||||
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override {
|
||||
if (role != Qt::UserRole) return QVariant();
|
||||
// Values must be QObject derived, but we can't assert that here without breaking forward decls,
|
||||
// so no static_cast.
|
||||
return QVariant::fromValue(reinterpret_cast<QObject*>(this->mValuesList.at(index.row())));
|
||||
}
|
||||
|
||||
qsizetype indexOf(QObject* object) const override {
|
||||
return this->mValuesList.indexOf(reinterpret_cast<T*>(object));
|
||||
}
|
||||
|
||||
[[nodiscard]] QList<QObject*> values() override {
|
||||
return *reinterpret_cast<QList<QObject*>*>(&this->mValuesList);
|
||||
}
|
||||
|
||||
private:
|
||||
QList<T*> mValuesList;
|
||||
};
|
||||
@@ -21,7 +21,6 @@ headers = [
|
||||
"model.hpp",
|
||||
"elapsedtimer.hpp",
|
||||
"desktopentry.hpp",
|
||||
"objectrepeater.hpp",
|
||||
"qsmenu.hpp",
|
||||
"retainable.hpp",
|
||||
"popupanchor.hpp",
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
#include "objectrepeater.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qhash.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlcontext.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
QVariant ObjectRepeater::model() const { return this->mModel; }
|
||||
|
||||
void ObjectRepeater::setModel(QVariant model) {
|
||||
if (model == this->mModel) return;
|
||||
|
||||
if (this->itemModel != nullptr) {
|
||||
QObject::disconnect(this->itemModel, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->mModel = std::move(model);
|
||||
emit this->modelChanged();
|
||||
this->reloadElements();
|
||||
}
|
||||
|
||||
void ObjectRepeater::onModelDestroyed() {
|
||||
this->mModel.clear();
|
||||
this->itemModel = nullptr;
|
||||
emit this->modelChanged();
|
||||
this->reloadElements();
|
||||
}
|
||||
|
||||
QQmlComponent* ObjectRepeater::delegate() const { return this->mDelegate; }
|
||||
|
||||
void ObjectRepeater::setDelegate(QQmlComponent* delegate) {
|
||||
if (delegate == this->mDelegate) return;
|
||||
|
||||
if (this->mDelegate != nullptr) {
|
||||
QObject::disconnect(this->mDelegate, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->mDelegate = delegate;
|
||||
|
||||
if (delegate != nullptr) {
|
||||
QObject::connect(
|
||||
this->mDelegate,
|
||||
&QObject::destroyed,
|
||||
this,
|
||||
&ObjectRepeater::onDelegateDestroyed
|
||||
);
|
||||
}
|
||||
|
||||
emit this->delegateChanged();
|
||||
this->reloadElements();
|
||||
}
|
||||
|
||||
void ObjectRepeater::onDelegateDestroyed() {
|
||||
this->mDelegate = nullptr;
|
||||
emit this->delegateChanged();
|
||||
this->reloadElements();
|
||||
}
|
||||
|
||||
void ObjectRepeater::reloadElements() {
|
||||
for (auto i = this->valuesList.length() - 1; i >= 0; i--) {
|
||||
this->removeComponent(i);
|
||||
}
|
||||
|
||||
if (this->mDelegate == nullptr || !this->mModel.isValid()) return;
|
||||
|
||||
if (this->mModel.canConvert<QAbstractItemModel*>()) {
|
||||
auto* model = this->mModel.value<QAbstractItemModel*>();
|
||||
this->itemModel = model;
|
||||
|
||||
this->insertModelElements(model, 0, model->rowCount() - 1); // -1 is fine
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(model, &QObject::destroyed, this, &ObjectRepeater::onModelDestroyed);
|
||||
QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ObjectRepeater::onModelRowsInserted);
|
||||
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ObjectRepeater::onModelRowsRemoved);
|
||||
QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ObjectRepeater::onModelRowsMoved);
|
||||
QObject::connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &ObjectRepeater::onModelAboutToBeReset);
|
||||
// clang-format on
|
||||
} else if (this->mModel.canConvert<QQmlListReference>()) {
|
||||
auto values = this->mModel.value<QQmlListReference>();
|
||||
auto len = values.count();
|
||||
|
||||
for (auto i = 0; i != len; i++) {
|
||||
this->insertComponent(i, {{"modelData", QVariant::fromValue(values.at(i))}});
|
||||
}
|
||||
} else if (this->mModel.canConvert<QVector<QVariant>>()) {
|
||||
auto values = this->mModel.value<QVector<QVariant>>();
|
||||
|
||||
for (auto& value: values) {
|
||||
this->insertComponent(this->valuesList.length(), {{"modelData", value}});
|
||||
}
|
||||
} else {
|
||||
qCritical() << this
|
||||
<< "Cannot create components as the model is not compatible:" << this->mModel;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectRepeater::insertModelElements(QAbstractItemModel* model, int first, int last) {
|
||||
auto roles = model->roleNames();
|
||||
auto roleDataVec = QVector<QModelRoleData>();
|
||||
for (auto id: roles.keys()) {
|
||||
roleDataVec.push_back(QModelRoleData(id));
|
||||
}
|
||||
|
||||
auto values = QModelRoleDataSpan(roleDataVec);
|
||||
auto props = QVariantMap();
|
||||
|
||||
for (auto i = first; i != last + 1; i++) {
|
||||
auto index = model->index(i, 0);
|
||||
model->multiData(index, values);
|
||||
|
||||
for (auto [id, name]: roles.asKeyValueRange()) {
|
||||
props.insert(name, *values.dataForRole(id));
|
||||
}
|
||||
|
||||
this->insertComponent(i, props);
|
||||
|
||||
props.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectRepeater::onModelRowsInserted(const QModelIndex& parent, int first, int last) {
|
||||
if (parent != QModelIndex()) return;
|
||||
|
||||
this->insertModelElements(this->itemModel, first, last);
|
||||
}
|
||||
|
||||
void ObjectRepeater::onModelRowsRemoved(const QModelIndex& parent, int first, int last) {
|
||||
if (parent != QModelIndex()) return;
|
||||
|
||||
for (auto i = last; i != first - 1; i--) {
|
||||
this->removeComponent(i);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectRepeater::onModelRowsMoved(
|
||||
const QModelIndex& sourceParent,
|
||||
int sourceStart,
|
||||
int sourceEnd,
|
||||
const QModelIndex& destParent,
|
||||
int destStart
|
||||
) {
|
||||
auto hasSource = sourceParent != QModelIndex();
|
||||
auto hasDest = destParent != QModelIndex();
|
||||
|
||||
if (!hasSource && !hasDest) return;
|
||||
|
||||
if (hasSource) {
|
||||
this->onModelRowsRemoved(sourceParent, sourceStart, sourceEnd);
|
||||
}
|
||||
|
||||
if (hasDest) {
|
||||
this->onModelRowsInserted(destParent, destStart, destStart + (sourceEnd - sourceStart));
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectRepeater::onModelAboutToBeReset() {
|
||||
auto last = static_cast<int>(this->valuesList.length() - 1);
|
||||
this->onModelRowsRemoved(QModelIndex(), 0, last); // -1 is fine
|
||||
}
|
||||
|
||||
void ObjectRepeater::insertComponent(qsizetype index, const QVariantMap& properties) {
|
||||
auto* context = QQmlEngine::contextForObject(this);
|
||||
auto* instance = this->mDelegate->createWithInitialProperties(properties, context);
|
||||
|
||||
if (instance == nullptr) {
|
||||
qWarning().noquote() << this->mDelegate->errorString();
|
||||
qWarning() << this << "failed to create object for model data" << properties;
|
||||
} else {
|
||||
QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership);
|
||||
instance->setParent(this);
|
||||
}
|
||||
|
||||
this->insertObject(instance, index);
|
||||
}
|
||||
|
||||
void ObjectRepeater::removeComponent(qsizetype index) {
|
||||
auto* instance = this->valuesList.at(index);
|
||||
this->removeAt(index);
|
||||
delete instance;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
#include "model.hpp"
|
||||
|
||||
///! A Repeater / for loop / map for non Item derived objects.
|
||||
/// > [!ERROR] Removed in favor of @@QtQml.Models.Instantiator
|
||||
///
|
||||
/// The ObjectRepeater creates instances of the provided delegate for every entry in the
|
||||
/// given model, similarly to a @@QtQuick.Repeater but for non visual types.
|
||||
class ObjectRepeater: public ObjectModel<QObject> {
|
||||
Q_OBJECT;
|
||||
/// The model providing data to the ObjectRepeater.
|
||||
///
|
||||
/// Currently accepted model types are `list<T>` lists, javascript arrays,
|
||||
/// and [QAbstractListModel] derived models, though only one column will be repeated
|
||||
/// from the latter.
|
||||
///
|
||||
/// Note: @@ObjectModel is a [QAbstractListModel] with a single column.
|
||||
///
|
||||
/// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
|
||||
Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged);
|
||||
/// The delegate component to repeat.
|
||||
///
|
||||
/// The delegate is given the same properties as in a Repeater, except `index` which
|
||||
/// is not currently implemented.
|
||||
///
|
||||
/// If the model is a `list<T>` or javascript array, a `modelData` property will be
|
||||
/// exposed containing the entry from the model. If the model is a [QAbstractListModel],
|
||||
/// the roles from the model will be exposed.
|
||||
///
|
||||
/// Note: @@ObjectModel has a single role named `modelData` for compatibility with normal lists.
|
||||
///
|
||||
/// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
|
||||
Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged);
|
||||
Q_CLASSINFO("DefaultProperty", "delegate");
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("ObjectRepeater has been removed in favor of QtQml.Models.Instantiator.");
|
||||
|
||||
public:
|
||||
explicit ObjectRepeater(QObject* parent = nullptr): ObjectModel(parent) {}
|
||||
|
||||
[[nodiscard]] QVariant model() const;
|
||||
void setModel(QVariant model);
|
||||
|
||||
[[nodiscard]] QQmlComponent* delegate() const;
|
||||
void setDelegate(QQmlComponent* delegate);
|
||||
|
||||
signals:
|
||||
void modelChanged();
|
||||
void delegateChanged();
|
||||
|
||||
private slots:
|
||||
void onDelegateDestroyed();
|
||||
void onModelDestroyed();
|
||||
void onModelRowsInserted(const QModelIndex& parent, int first, int last);
|
||||
void onModelRowsRemoved(const QModelIndex& parent, int first, int last);
|
||||
|
||||
void onModelRowsMoved(
|
||||
const QModelIndex& sourceParent,
|
||||
int sourceStart,
|
||||
int sourceEnd,
|
||||
const QModelIndex& destParent,
|
||||
int destStart
|
||||
);
|
||||
|
||||
void onModelAboutToBeReset();
|
||||
|
||||
private:
|
||||
void reloadElements();
|
||||
void insertModelElements(QAbstractItemModel* model, int first, int last);
|
||||
void insertComponent(qsizetype index, const QVariantMap& properties);
|
||||
void removeComponent(qsizetype index);
|
||||
|
||||
QVariant mModel;
|
||||
QAbstractItemModel* itemModel = nullptr;
|
||||
QQmlComponent* mDelegate = nullptr;
|
||||
};
|
||||
+30
-9
@@ -27,12 +27,19 @@ QsPaths* QsPaths::instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
void QsPaths::init(QString shellId, QString pathId, QString dataOverride, QString stateOverride) {
|
||||
void QsPaths::init(
|
||||
QString shellId,
|
||||
QString pathId,
|
||||
QString dataOverride,
|
||||
QString stateOverride,
|
||||
QString cacheOverride
|
||||
) {
|
||||
auto* instance = QsPaths::instance();
|
||||
instance->shellId = std::move(shellId);
|
||||
instance->pathId = std::move(pathId);
|
||||
instance->shellDataOverride = std::move(dataOverride);
|
||||
instance->shellStateOverride = std::move(stateOverride);
|
||||
instance->shellCacheOverride = std::move(cacheOverride);
|
||||
}
|
||||
|
||||
QDir QsPaths::crashDir(const QString& id) {
|
||||
@@ -168,7 +175,8 @@ void QsPaths::linkRunDir() {
|
||||
auto* shellDir = this->shellRunDir();
|
||||
|
||||
if (!shellDir) {
|
||||
qCCritical(logPaths
|
||||
qCCritical(
|
||||
logPaths
|
||||
) << "Could not create by-id symlink as the shell runtime path could not be created.";
|
||||
} else {
|
||||
auto shellPath = shellDir->filePath(runDir->dirName());
|
||||
@@ -316,9 +324,16 @@ QDir QsPaths::shellStateDir() {
|
||||
|
||||
QDir QsPaths::shellCacheDir() {
|
||||
if (this->shellCacheState == DirState::Unknown) {
|
||||
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
dir = QDir(dir.filePath("by-shell"));
|
||||
dir = QDir(dir.filePath(this->shellId));
|
||||
QDir dir;
|
||||
if (this->shellCacheOverride.isEmpty()) {
|
||||
dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
dir = QDir(dir.filePath("by-shell"));
|
||||
dir = QDir(dir.filePath(this->shellId));
|
||||
} else {
|
||||
auto basedir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
|
||||
dir = QDir(this->shellCacheOverride.replace("$BASE", basedir));
|
||||
}
|
||||
|
||||
this->mShellCacheDir = dir;
|
||||
|
||||
qCDebug(logPaths) << "Initialized cache path:" << dir.path();
|
||||
@@ -346,7 +361,7 @@ void QsPaths::createLock() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = flock {
|
||||
struct flock lock = {
|
||||
.l_type = F_WRLCK,
|
||||
.l_whence = SEEK_SET,
|
||||
.l_start = 0,
|
||||
@@ -364,7 +379,8 @@ void QsPaths::createLock() {
|
||||
qCDebug(logPaths) << "Created instance lock at" << path;
|
||||
}
|
||||
} else {
|
||||
qCCritical(logPaths
|
||||
qCCritical(
|
||||
logPaths
|
||||
) << "Could not create instance lock, as the instance runtime directory could not be created.";
|
||||
}
|
||||
}
|
||||
@@ -373,7 +389,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
|
||||
auto file = QFile(QDir(path).filePath("instance.lock"));
|
||||
if (!file.open(QFile::ReadOnly)) return false;
|
||||
|
||||
auto lock = flock {
|
||||
struct flock lock = {
|
||||
.l_type = F_WRLCK,
|
||||
.l_whence = SEEK_SET,
|
||||
.l_start = 0,
|
||||
@@ -397,7 +413,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
|
||||
}
|
||||
|
||||
QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||
QsPaths::collectInstances(const QString& path) {
|
||||
QsPaths::collectInstances(const QString& path, const QString& display) {
|
||||
qCDebug(logPaths) << "Collecting instances from" << path;
|
||||
auto liveInstances = QVector<InstanceLockInfo>();
|
||||
auto deadInstances = QVector<InstanceLockInfo>();
|
||||
@@ -411,6 +427,11 @@ QsPaths::collectInstances(const QString& path) {
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.pid == -1) {
|
||||
deadInstances.push_back(info);
|
||||
} else {
|
||||
|
||||
+9
-2
@@ -17,14 +17,20 @@ QDataStream& operator>>(QDataStream& stream, InstanceLockInfo& info);
|
||||
class QsPaths {
|
||||
public:
|
||||
static QsPaths* instance();
|
||||
static void init(QString shellId, QString pathId, QString dataOverride, QString stateOverride);
|
||||
static void init(
|
||||
QString shellId,
|
||||
QString pathId,
|
||||
QString dataOverride,
|
||||
QString stateOverride,
|
||||
QString cacheOverride
|
||||
);
|
||||
static QDir crashDir(const QString& id);
|
||||
static QString basePath(const QString& id);
|
||||
static QString ipcPath(const QString& id);
|
||||
static bool
|
||||
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
|
||||
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||
collectInstances(const QString& path);
|
||||
collectInstances(const QString& path, const QString& display);
|
||||
|
||||
QDir* baseRunDir();
|
||||
QDir* shellRunDir();
|
||||
@@ -65,4 +71,5 @@ private:
|
||||
|
||||
QString shellDataOverride;
|
||||
QString shellStateOverride;
|
||||
QString shellCacheOverride;
|
||||
};
|
||||
@@ -28,7 +28,7 @@ void PopupAnchor::markClean() { this->lastState = this->state; }
|
||||
void PopupAnchor::markDirty() { this->lastState.reset(); }
|
||||
|
||||
QWindow* PopupAnchor::backingWindow() const {
|
||||
return this->mProxyWindow ? this->mProxyWindow->backingWindow() : nullptr;
|
||||
return this->bProxyWindow ? this->bProxyWindow->backingWindow() : nullptr;
|
||||
}
|
||||
|
||||
void PopupAnchor::setWindowInternal(QObject* window) {
|
||||
@@ -36,14 +36,14 @@ void PopupAnchor::setWindowInternal(QObject* window) {
|
||||
|
||||
if (this->mWindow) {
|
||||
QObject::disconnect(this->mWindow, nullptr, this, nullptr);
|
||||
QObject::disconnect(this->mProxyWindow, nullptr, this, nullptr);
|
||||
QObject::disconnect(this->bProxyWindow, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
if (window) {
|
||||
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
|
||||
this->mProxyWindow = proxy;
|
||||
this->bProxyWindow = proxy;
|
||||
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
|
||||
this->mProxyWindow = interface->proxyWindow();
|
||||
this->bProxyWindow = interface->proxyWindow();
|
||||
} else {
|
||||
qWarning() << "Tried to set popup anchor window to" << window
|
||||
<< "which is not a quickshell window.";
|
||||
@@ -55,7 +55,7 @@ void PopupAnchor::setWindowInternal(QObject* window) {
|
||||
QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed);
|
||||
|
||||
QObject::connect(
|
||||
this->mProxyWindow,
|
||||
this->bProxyWindow,
|
||||
&ProxyWindowBase::backerVisibilityChanged,
|
||||
this,
|
||||
&PopupAnchor::backingWindowVisibilityChanged
|
||||
@@ -70,7 +70,7 @@ void PopupAnchor::setWindowInternal(QObject* window) {
|
||||
setnull:
|
||||
if (this->mWindow) {
|
||||
this->mWindow = nullptr;
|
||||
this->mProxyWindow = nullptr;
|
||||
this->bProxyWindow = nullptr;
|
||||
|
||||
emit this->windowChanged();
|
||||
emit this->backingWindowVisibilityChanged();
|
||||
@@ -100,7 +100,7 @@ void PopupAnchor::setItem(QQuickItem* item) {
|
||||
|
||||
void PopupAnchor::onWindowDestroyed() {
|
||||
this->mWindow = nullptr;
|
||||
this->mProxyWindow = nullptr;
|
||||
this->bProxyWindow = nullptr;
|
||||
emit this->windowChanged();
|
||||
emit this->backingWindowVisibilityChanged();
|
||||
}
|
||||
@@ -186,11 +186,11 @@ void PopupAnchor::updatePlacement(const QPoint& anchorpoint, const QSize& size)
|
||||
}
|
||||
|
||||
void PopupAnchor::updateAnchor() {
|
||||
if (this->mItem && this->mProxyWindow) {
|
||||
if (this->mItem && this->bProxyWindow) {
|
||||
auto baseRect =
|
||||
this->mUserRect.isEmpty() ? this->mItem->boundingRect() : this->mUserRect.qrect();
|
||||
|
||||
auto rect = this->mProxyWindow->contentItem()->mapFromItem(
|
||||
auto rect = this->bProxyWindow->contentItem()->mapFromItem(
|
||||
this->mItem,
|
||||
baseRect.marginsRemoved(this->mMargins.qmargins())
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qpoint.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qsize.h>
|
||||
@@ -139,7 +140,9 @@ public:
|
||||
void markDirty();
|
||||
|
||||
[[nodiscard]] QObject* window() const { return this->mWindow; }
|
||||
[[nodiscard]] ProxyWindowBase* proxyWindow() const { return this->mProxyWindow; }
|
||||
[[nodiscard]] QBindable<ProxyWindowBase*> bindableProxyWindow() const {
|
||||
return &this->bProxyWindow;
|
||||
}
|
||||
[[nodiscard]] QWindow* backingWindow() const;
|
||||
void setWindowInternal(QObject* window);
|
||||
void setWindow(QObject* window);
|
||||
@@ -193,11 +196,12 @@ private slots:
|
||||
private:
|
||||
QObject* mWindow = nullptr;
|
||||
QQuickItem* mItem = nullptr;
|
||||
ProxyWindowBase* mProxyWindow = nullptr;
|
||||
PopupAnchorState state;
|
||||
Box mUserRect;
|
||||
Margins mMargins;
|
||||
std::optional<PopupAnchorState> lastState;
|
||||
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PopupAnchor, ProxyWindowBase*, bProxyWindow);
|
||||
};
|
||||
|
||||
class PopupPositioner {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "paths.hpp"
|
||||
#include "qmlscreen.hpp"
|
||||
#include "rootwrapper.hpp"
|
||||
#include "scanenv.hpp"
|
||||
|
||||
QuickshellSettings::QuickshellSettings() {
|
||||
QObject::connect(
|
||||
@@ -313,6 +314,16 @@ QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback)
|
||||
return IconImageProvider::requestString(icon, "", fallback);
|
||||
}
|
||||
|
||||
bool QuickshellGlobal::hasThemeIcon(const QString& icon) { return QIcon::hasThemeIcon(icon); }
|
||||
|
||||
bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor, const QStringList& features) {
|
||||
return qs::scan::env::PreprocEnv::hasVersion(major, minor, features);
|
||||
}
|
||||
|
||||
bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) {
|
||||
return QuickshellGlobal::hasVersion(major, minor, QStringList());
|
||||
}
|
||||
|
||||
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
|
||||
auto* qsg = new QuickshellGlobal();
|
||||
auto* generation = EngineGeneration::findEngineGeneration(engine);
|
||||
|
||||
+22
-2
@@ -127,18 +127,21 @@ class QuickshellGlobal: public QObject {
|
||||
/// Usually `~/.local/share/quickshell/by-shell/<shell-id>`
|
||||
///
|
||||
/// Can be overridden using `//@ pragma DataDir $BASE/path` in the root qml file, where `$BASE`
|
||||
/// corrosponds to `$XDG_DATA_HOME` (usually `~/.local/share`).
|
||||
/// corresponds to `$XDG_DATA_HOME` (usually `~/.local/share`).
|
||||
Q_PROPERTY(QString dataDir READ dataDir CONSTANT);
|
||||
/// The per-shell state directory.
|
||||
///
|
||||
/// Usually `~/.local/state/quickshell/by-shell/<shell-id>`
|
||||
///
|
||||
/// Can be overridden using `//@ pragma StateDir $BASE/path` in the root qml file, where `$BASE`
|
||||
/// corrosponds to `$XDG_STATE_HOME` (usually `~/.local/state`).
|
||||
/// corresponds to `$XDG_STATE_HOME` (usually `~/.local/state`).
|
||||
Q_PROPERTY(QString stateDir READ stateDir CONSTANT);
|
||||
/// The per-shell cache directory.
|
||||
///
|
||||
/// Usually `~/.cache/quickshell/by-shell/<shell-id>`
|
||||
///
|
||||
/// Can be overridden using `//@ pragma CacheDir $BASE/path` in the root qml file, where `$BASE`
|
||||
/// corresponds to `$XDG_CACHE_HOME` (usually `~/.cache`).
|
||||
Q_PROPERTY(QString cacheDir READ cacheDir CONSTANT);
|
||||
// clang-format on
|
||||
QML_SINGLETON;
|
||||
@@ -199,6 +202,8 @@ public:
|
||||
/// Setting the `fallback` parameter of `iconPath` will attempt to load the fallback
|
||||
/// icon if the requested one could not be loaded.
|
||||
Q_INVOKABLE static QString iconPath(const QString& icon, const QString& fallback);
|
||||
/// Check if specified icon has an available icon in your icon theme
|
||||
Q_INVOKABLE static bool hasThemeIcon(const QString& icon);
|
||||
/// Equivalent to `${Quickshell.configDir}/${path}`
|
||||
Q_INVOKABLE [[nodiscard]] QString shellPath(const QString& path) const;
|
||||
/// > [!WARNING] Deprecated: Renamed to @@shellPath() for clarity.
|
||||
@@ -214,6 +219,21 @@ public:
|
||||
///
|
||||
/// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`.
|
||||
Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; }
|
||||
/// Check if Quickshell's version is at least `major.minor` and the listed
|
||||
/// unreleased features are available. If Quickshell is newer than the given version
|
||||
/// 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
|
||||
/// > has the same function available.
|
||||
/// >
|
||||
/// > ```qml
|
||||
/// > //@ if hasVersion(0, 3, ["feature"])
|
||||
/// > ...
|
||||
/// > //@ endif
|
||||
/// > ```
|
||||
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
|
||||
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
|
||||
|
||||
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
|
||||
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
|
||||
|
||||
@@ -63,9 +63,6 @@ void RootWrapper::reloadGraph(bool hard) {
|
||||
qs::core::QmlToolingSupport::updateTooling(rootPath, scanner);
|
||||
this->configDirWatcher.addPath(rootPath.path());
|
||||
|
||||
auto* generation = new EngineGeneration(rootPath, std::move(scanner));
|
||||
generation->wrapper = this;
|
||||
|
||||
// todo: move into EngineGeneration
|
||||
if (this->generation != nullptr) {
|
||||
qInfo() << "Reloading configuration...";
|
||||
@@ -74,6 +71,33 @@ void RootWrapper::reloadGraph(bool hard) {
|
||||
|
||||
QDir::setCurrent(this->originalWorkingDirectory);
|
||||
|
||||
if (!scanner.scanErrors.isEmpty()) {
|
||||
qCritical() << "Failed to load configuration";
|
||||
QString errorString = "Failed to load configuration";
|
||||
for (auto& error: scanner.scanErrors) {
|
||||
const auto& file = error.file;
|
||||
QString rel;
|
||||
if (file.startsWith(rootPath.path() % '/')) {
|
||||
rel = '@' % file.sliced(rootPath.path().length() + 1);
|
||||
} else {
|
||||
rel = file;
|
||||
}
|
||||
|
||||
auto msg = " error in " % rel % '[' % QString::number(error.line) % ":0]: " % error.message;
|
||||
errorString += '\n' % msg;
|
||||
qCritical().noquote() << msg;
|
||||
}
|
||||
|
||||
if (this->generation != nullptr && this->generation->qsgInstance != nullptr) {
|
||||
emit this->generation->qsgInstance->reloadFailed(errorString);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto* generation = new EngineGeneration(rootPath, std::move(scanner));
|
||||
generation->wrapper = this;
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("qs");
|
||||
url.setPath("@/qs/" % rootFile.fileName());
|
||||
|
||||
+110
-44
@@ -1,9 +1,11 @@
|
||||
#include "scan.hpp"
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdir.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qjsengine.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
@@ -12,19 +14,20 @@
|
||||
#include <qloggingcategory.h>
|
||||
#include <qpair.h>
|
||||
#include <qstring.h>
|
||||
#include <qstringliteral.h>
|
||||
#include <qtextstream.h>
|
||||
|
||||
#include "logcat.hpp"
|
||||
#include "scanenv.hpp"
|
||||
|
||||
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
||||
|
||||
void QmlScanner::scanDir(const QString& path) {
|
||||
if (this->scannedDirs.contains(path)) return;
|
||||
this->scannedDirs.push_back(path);
|
||||
void QmlScanner::scanDir(const QDir& dir) {
|
||||
if (this->scannedDirs.contains(dir)) return;
|
||||
this->scannedDirs.push_back(dir);
|
||||
|
||||
const auto& path = dir.path();
|
||||
|
||||
qCDebug(logQmlScanner) << "Scanning directory" << path;
|
||||
auto dir = QDir(path);
|
||||
|
||||
struct Entry {
|
||||
QString name;
|
||||
@@ -37,7 +40,8 @@ void QmlScanner::scanDir(const QString& path) {
|
||||
|
||||
for (auto& name: dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) {
|
||||
if (name == "qmldir") {
|
||||
qCDebug(logQmlScanner
|
||||
qCDebug(
|
||||
logQmlScanner
|
||||
) << "Found qmldir file, qmldir synthesization will be disabled for directory"
|
||||
<< path;
|
||||
seenQmldir = true;
|
||||
@@ -114,59 +118,121 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
||||
auto stream = QTextStream(&file);
|
||||
auto imports = QVector<QString>();
|
||||
|
||||
while (!stream.atEnd()) {
|
||||
auto line = stream.readLine().trimmed();
|
||||
if (!singleton && line == "pragma Singleton") {
|
||||
singleton = true;
|
||||
} else if (!internal && line == "//@ pragma Internal") {
|
||||
internal = true;
|
||||
} else if (line.startsWith("import")) {
|
||||
// we dont care about "import qs" as we always load the root folder
|
||||
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
|
||||
importCursor += 4;
|
||||
QString path;
|
||||
bool inHeader = true;
|
||||
auto ifScopes = QVector<bool>();
|
||||
bool sourceMasked = false;
|
||||
int lineNum = 0;
|
||||
QString overrideText;
|
||||
bool isOverridden = false;
|
||||
|
||||
while (importCursor != line.length()) {
|
||||
auto c = line.at(importCursor);
|
||||
if (c == '.') c = '/';
|
||||
else if (c == ' ') break;
|
||||
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|
||||
|| c == '_')
|
||||
{
|
||||
} else {
|
||||
qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line;
|
||||
goto next;
|
||||
auto pragmaEngine = QJSEngine();
|
||||
pragmaEngine.globalObject().setPrototype(
|
||||
pragmaEngine.newQObject(new qs::scan::env::PreprocEnv())
|
||||
);
|
||||
|
||||
auto postError = [&, this](QString error) {
|
||||
this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum});
|
||||
};
|
||||
|
||||
while (!stream.atEnd()) {
|
||||
++lineNum;
|
||||
bool hideMask = false;
|
||||
auto rawLine = stream.readLine();
|
||||
auto line = rawLine.trimmed();
|
||||
if (!sourceMasked && inHeader) {
|
||||
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
|
||||
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
|
||||
importCursor += 4;
|
||||
QString path;
|
||||
|
||||
while (importCursor != line.length()) {
|
||||
auto c = line.at(importCursor);
|
||||
if (c == '.') c = '/';
|
||||
else if (c == ' ') break;
|
||||
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|
||||
|| c == '_')
|
||||
{
|
||||
} else {
|
||||
qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line;
|
||||
goto next;
|
||||
}
|
||||
|
||||
path.append(c);
|
||||
importCursor += 1;
|
||||
}
|
||||
|
||||
path.append(c);
|
||||
importCursor += 1;
|
||||
imports.append(this->rootPath.filePath(path));
|
||||
} else if (auto startQuot = line.indexOf('"');
|
||||
startQuot != -1 && line.length() >= startQuot + 3)
|
||||
{
|
||||
auto endQuot = line.indexOf('"', startQuot + 1);
|
||||
if (endQuot == -1) continue;
|
||||
|
||||
auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1);
|
||||
imports.push_back(name);
|
||||
}
|
||||
|
||||
imports.append(this->rootPath.filePath(path));
|
||||
} else if (auto startQuot = line.indexOf('"');
|
||||
startQuot != -1 && line.length() >= startQuot + 3)
|
||||
{
|
||||
auto endQuot = line.indexOf('"', startQuot + 1);
|
||||
if (endQuot == -1) continue;
|
||||
|
||||
auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1);
|
||||
imports.push_back(name);
|
||||
} else if (!internal && line == "//@ pragma Internal") {
|
||||
internal = true;
|
||||
} else if (line.contains('{')) {
|
||||
inHeader = false;
|
||||
}
|
||||
} else if (line.contains('{')) break;
|
||||
}
|
||||
|
||||
if (line.startsWith("//@ if ")) {
|
||||
auto code = line.sliced(7);
|
||||
auto value = pragmaEngine.evaluate(code, path, 1234);
|
||||
bool mask = true;
|
||||
|
||||
if (value.isError()) {
|
||||
postError(QString("Evaluating if: %0").arg(value.toString()));
|
||||
} else if (!value.isBool()) {
|
||||
postError(QString("If expression \"%0\" is not a boolean").arg(value.toString()));
|
||||
} else if (value.toBool()) {
|
||||
mask = false;
|
||||
}
|
||||
if (!sourceMasked && mask) hideMask = true;
|
||||
mask = sourceMasked || mask; // cant unmask if a nested if passes
|
||||
ifScopes.append(mask);
|
||||
if (mask) isOverridden = true;
|
||||
sourceMasked = mask;
|
||||
} else if (line.startsWith("//@ endif")) {
|
||||
if (ifScopes.isEmpty()) {
|
||||
postError("endif without matching if");
|
||||
} else {
|
||||
ifScopes.pop_back();
|
||||
|
||||
if (ifScopes.isEmpty()) sourceMasked = false;
|
||||
else sourceMasked = ifScopes.last();
|
||||
}
|
||||
}
|
||||
|
||||
if (!hideMask && sourceMasked) overrideText.append("// MASKED: " % rawLine % '\n');
|
||||
else overrideText.append(rawLine % '\n');
|
||||
|
||||
next:;
|
||||
}
|
||||
|
||||
if (!ifScopes.isEmpty()) {
|
||||
postError("unclosed preprocessor if block");
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (isOverridden) {
|
||||
this->fileIntercepts.insert(path, overrideText);
|
||||
}
|
||||
|
||||
if (logQmlScanner().isDebugEnabled() && !imports.isEmpty()) {
|
||||
qCDebug(logQmlScanner) << "Found imports" << imports;
|
||||
}
|
||||
|
||||
auto currentdir = QDir(QFileInfo(path).canonicalPath());
|
||||
auto currentdir = QDir(QFileInfo(path).absolutePath());
|
||||
|
||||
// the root can never be a singleton so it dosent matter if we skip it
|
||||
this->scanDir(currentdir.path());
|
||||
this->scanDir(currentdir);
|
||||
|
||||
for (auto& import: imports) {
|
||||
QString ipath;
|
||||
@@ -179,9 +245,9 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
||||
}
|
||||
|
||||
auto pathInfo = QFileInfo(ipath);
|
||||
auto cpath = pathInfo.canonicalFilePath();
|
||||
auto cpath = pathInfo.absoluteFilePath();
|
||||
|
||||
if (cpath.isEmpty()) {
|
||||
if (!pathInfo.exists()) {
|
||||
qCWarning(logQmlScanner) << "Ignoring unresolvable import" << ipath << "from" << path;
|
||||
continue;
|
||||
}
|
||||
|
||||
+10
-4
@@ -16,15 +16,21 @@ public:
|
||||
QmlScanner() = default;
|
||||
QmlScanner(const QDir& rootPath): rootPath(rootPath) {}
|
||||
|
||||
// path must be canonical
|
||||
void scanDir(const QString& path);
|
||||
|
||||
void scanDir(const QDir& dir);
|
||||
void scanQmlRoot(const QString& path);
|
||||
|
||||
QVector<QString> scannedDirs;
|
||||
QVector<QDir> scannedDirs;
|
||||
QVector<QString> scannedFiles;
|
||||
QHash<QString, QString> fileIntercepts;
|
||||
|
||||
struct ScanError {
|
||||
QString file;
|
||||
QString message;
|
||||
int line;
|
||||
};
|
||||
|
||||
QVector<ScanError> scanErrors;
|
||||
|
||||
private:
|
||||
QDir rootPath;
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "scanenv.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
|
||||
#include "build.hpp"
|
||||
|
||||
namespace qs::scan::env {
|
||||
|
||||
bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
|
||||
if (QS_VERSION_MAJOR > major) return true;
|
||||
if (QS_VERSION_MAJOR == major && QS_VERSION_MINOR > minor) return true;
|
||||
|
||||
auto availFeatures = QString(QS_UNRELEASED_FEATURES).split(';');
|
||||
|
||||
for (const auto& feature: features) {
|
||||
if (!availFeatures.contains(feature)) return false;
|
||||
}
|
||||
|
||||
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
|
||||
}
|
||||
|
||||
QString PreprocEnv::env(const QString& variable) {
|
||||
return qEnvironmentVariable(variable.toStdString().c_str());
|
||||
}
|
||||
|
||||
bool PreprocEnv::isEnvSet(const QString& variable) {
|
||||
return qEnvironmentVariableIsSet(variable.toStdString().c_str());
|
||||
}
|
||||
|
||||
} // namespace qs::scan::env
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
namespace qs::scan::env {
|
||||
|
||||
class PreprocEnv: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
Q_INVOKABLE static bool
|
||||
hasVersion(int major, int minor, const QStringList& features = QStringList());
|
||||
|
||||
Q_INVOKABLE static QString env(const QString& variable);
|
||||
Q_INVOKABLE static bool isEnvSet(const QString& variable);
|
||||
};
|
||||
|
||||
} // namespace qs::scan::env
|
||||
@@ -72,8 +72,8 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
|
||||
do {
|
||||
++iter;
|
||||
} while (iter != this->mValues.end()
|
||||
&& std::find_if(newIter, newValues.end(), eqPredicate(*iter)) == newValues.end()
|
||||
);
|
||||
&& std::find_if(newIter, newValues.end(), eqPredicate(*iter))
|
||||
== newValues.end());
|
||||
|
||||
auto index = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
|
||||
auto startIndex = static_cast<qint32>(std::distance(this->mValues.begin(), startIter));
|
||||
|
||||
@@ -54,7 +54,7 @@ bool QmlToolingSupport::lockTooling() {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lock = flock {
|
||||
struct flock lock = {
|
||||
.l_type = F_WRLCK,
|
||||
.l_whence = SEEK_SET, // NOLINT (fcntl.h??)
|
||||
.l_start = 0,
|
||||
|
||||
+1
-32
@@ -29,7 +29,7 @@ struct StringLiteral16 {
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const QChar* qCharPtr() const noexcept {
|
||||
return std::bit_cast<const QChar*>(&this->value);
|
||||
return std::bit_cast<const QChar*>(&this->value); // NOLINT
|
||||
}
|
||||
|
||||
[[nodiscard]] Q_ALWAYS_INLINE operator QString() const noexcept {
|
||||
@@ -251,37 +251,6 @@ public:
|
||||
GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); }
|
||||
};
|
||||
|
||||
template <auto member, auto destroyedSlot, auto changedSignal>
|
||||
class SimpleObjectHandleOps {
|
||||
using Traits = MemberPointerTraits<decltype(member)>;
|
||||
|
||||
public:
|
||||
static bool setObject(Traits::Class* parent, Traits::Type value) {
|
||||
if (value == parent->*member) return false;
|
||||
|
||||
if (parent->*member != nullptr) {
|
||||
QObject::disconnect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
|
||||
}
|
||||
|
||||
parent->*member = value;
|
||||
|
||||
if (value != nullptr) {
|
||||
QObject::connect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
|
||||
}
|
||||
|
||||
if constexpr (changedSignal != nullptr) {
|
||||
emit(parent->*changedSignal)();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <auto member, auto destroyedSlot, auto changedSignal = nullptr>
|
||||
bool setSimpleObjectHandle(auto* parent, auto* value) {
|
||||
return SimpleObjectHandleOps<member, destroyedSlot, changedSignal>::setObject(parent, value);
|
||||
}
|
||||
|
||||
template <auto methodPtr>
|
||||
class MethodFunctor {
|
||||
using PtrMeta = MemberPointerTraits<decltype(methodPtr)>;
|
||||
|
||||
@@ -6,12 +6,51 @@ qt_add_library(quickshell-crash STATIC
|
||||
|
||||
qs_pch(quickshell-crash SET large)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(breakpad REQUIRED IMPORTED_TARGET breakpad)
|
||||
# only need client?? take only includes from pkg config todo
|
||||
target_link_libraries(quickshell-crash PRIVATE PkgConfig::breakpad -lbreakpad_client)
|
||||
if (VENDOR_CPPTRACE)
|
||||
message(STATUS "Vendoring cpptrace...")
|
||||
include(FetchContent)
|
||||
|
||||
# For use without internet access see: https://cmake.org/cmake/help/latest/module/FetchContent.html#variable:FETCHCONTENT_SOURCE_DIR_%3CuppercaseName%3E
|
||||
FetchContent_Declare(
|
||||
cpptrace
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||
GIT_TAG v1.0.4
|
||||
)
|
||||
|
||||
set(CPPTRACE_UNWIND_WITH_LIBUNWIND TRUE)
|
||||
FetchContent_MakeAvailable(cpptrace)
|
||||
else ()
|
||||
find_package(cpptrace REQUIRED)
|
||||
|
||||
# useful for cross after you have already checked cpptrace is built correctly
|
||||
if (NOT DO_NOT_CHECK_CPPTRACE_USABILITY)
|
||||
try_run(CPPTRACE_SIGNAL_SAFE_UNWIND CPPTRACE_SIGNAL_SAFE_UNWIND_COMP
|
||||
SOURCE_FROM_CONTENT check.cxx "
|
||||
#include <cpptrace/basic.hpp>
|
||||
int main() {
|
||||
return cpptrace::can_signal_safe_unwind() ? 0 : 1;
|
||||
}
|
||||
"
|
||||
LOG_DESCRIPTION "Checking ${CPPTRACE_SIGNAL_SAFE_UNWIND}"
|
||||
LINK_LIBRARIES cpptrace::cpptrace
|
||||
COMPILE_OUTPUT_VARIABLE CPPTRACE_SIGNAL_SAFE_UNWIND_LOG
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
|
||||
if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND_COMP)
|
||||
message(STATUS "${CPPTRACE_SIGNAL_SAFE_UNWIND_LOG}")
|
||||
message(FATAL_ERROR "Failed to compile cpptrace signal safe unwind tester.")
|
||||
endif()
|
||||
|
||||
if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND EQUAL 0)
|
||||
message(STATUS "Cpptrace signal safe unwind test exited with: ${CPPTRACE_SIGNAL_SAFE_UNWIND}")
|
||||
message(FATAL_ERROR "Cpptrace was built without CPPTRACE_UNWIND_WITH_LIBUNWIND set to true. Enable libunwind support in the package or set VENDOR_CPPTRACE to true when building Quickshell.")
|
||||
endif()
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# quick linked for pch compat
|
||||
target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets)
|
||||
target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets cpptrace::cpptrace)
|
||||
|
||||
target_link_libraries(quickshell PRIVATE quickshell-crash)
|
||||
+130
-100
@@ -1,12 +1,12 @@
|
||||
#include "handler.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <bits/types/sigset_t.h>
|
||||
#include <breakpad/client/linux/handler/exception_handler.h>
|
||||
#include <breakpad/client/linux/handler/minidump_descriptor.h>
|
||||
#include <breakpad/common/linux/linux_libc_support.h>
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/forward.hpp>
|
||||
#include <qdatastream.h>
|
||||
#include <qfile.h>
|
||||
#include <qlogging.h>
|
||||
@@ -19,95 +19,60 @@
|
||||
|
||||
extern char** environ; // NOLINT
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace qs::crash {
|
||||
|
||||
namespace {
|
||||
|
||||
QS_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
|
||||
}
|
||||
|
||||
struct CrashHandlerPrivate {
|
||||
ExceptionHandler* exceptionHandler = nullptr;
|
||||
int minidumpFd = -1;
|
||||
int infoFd = -1;
|
||||
void writeEnvInt(char* buf, const char* name, int value) {
|
||||
// NOLINTBEGIN (cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
while (*name != '\0') *buf++ = *name++;
|
||||
*buf++ = '=';
|
||||
|
||||
static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded);
|
||||
};
|
||||
|
||||
CrashHandler::CrashHandler(): d(new CrashHandlerPrivate()) {}
|
||||
|
||||
void CrashHandler::init() {
|
||||
// MinidumpDescriptor has no move constructor and the copy constructor breaks fds.
|
||||
auto createHandler = [this](const MinidumpDescriptor& desc) {
|
||||
this->d->exceptionHandler = new ExceptionHandler(
|
||||
desc,
|
||||
nullptr,
|
||||
&CrashHandlerPrivate::minidumpCallback,
|
||||
this->d,
|
||||
true,
|
||||
-1
|
||||
);
|
||||
};
|
||||
|
||||
qCDebug(logCrashHandler) << "Starting crash handler...";
|
||||
|
||||
this->d->minidumpFd = memfd_create("quickshell:minidump", MFD_CLOEXEC);
|
||||
|
||||
if (this->d->minidumpFd == -1) {
|
||||
qCCritical(logCrashHandler
|
||||
) << "Failed to allocate minidump memfd, minidumps will be saved in the working directory.";
|
||||
createHandler(MinidumpDescriptor("."));
|
||||
} else {
|
||||
qCDebug(logCrashHandler) << "Created memfd" << this->d->minidumpFd
|
||||
<< "for holding possible minidumps.";
|
||||
createHandler(MinidumpDescriptor(this->d->minidumpFd));
|
||||
if (value < 0) {
|
||||
*buf++ = '-';
|
||||
value = -value;
|
||||
}
|
||||
|
||||
qCInfo(logCrashHandler) << "Crash handler initialized.";
|
||||
}
|
||||
|
||||
void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) {
|
||||
this->d->infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
|
||||
|
||||
if (this->d->infoFd == -1) {
|
||||
qCCritical(logCrashHandler
|
||||
) << "Failed to allocate instance info memfd, crash recovery will not work.";
|
||||
if (value == 0) {
|
||||
*buf++ = '0';
|
||||
*buf = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file;
|
||||
|
||||
if (!file.open(this->d->infoFd, QFile::ReadWrite)) {
|
||||
qCCritical(logCrashHandler
|
||||
) << "Failed to open instance info memfd, crash recovery will not work.";
|
||||
auto* start = buf;
|
||||
while (value > 0) {
|
||||
*buf++ = static_cast<char>('0' + (value % 10));
|
||||
value /= 10;
|
||||
}
|
||||
|
||||
QDataStream ds(&file);
|
||||
ds << info;
|
||||
file.flush();
|
||||
|
||||
qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd;
|
||||
*buf = '\0';
|
||||
std::reverse(start, buf);
|
||||
// NOLINTEND
|
||||
}
|
||||
|
||||
CrashHandler::~CrashHandler() {
|
||||
delete this->d->exceptionHandler;
|
||||
delete this->d;
|
||||
}
|
||||
|
||||
bool CrashHandlerPrivate::minidumpCallback(
|
||||
const MinidumpDescriptor& /*descriptor*/,
|
||||
void* context,
|
||||
bool /*success*/
|
||||
void signalHandler(
|
||||
int sig,
|
||||
siginfo_t* /*info*/, // NOLINT (misc-include-cleaner)
|
||||
void* /*context*/
|
||||
) {
|
||||
// A fork that just dies to ensure the coredump is caught by the system.
|
||||
auto coredumpPid = fork();
|
||||
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);
|
||||
|
||||
if (coredumpPid == 0) {
|
||||
return false;
|
||||
for (size_t i = 0; i < static_cast<size_t>(frameCount); i++) {
|
||||
auto frame = cpptrace::safe_object_frame();
|
||||
cpptrace::get_safe_object_frame(traceBuffer[i], &frame);
|
||||
write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame));
|
||||
}
|
||||
}
|
||||
|
||||
auto* self = static_cast<CrashHandlerPrivate*>(context);
|
||||
auto coredumpPid = fork();
|
||||
if (coredumpPid == 0) {
|
||||
raise(sig);
|
||||
_exit(-1);
|
||||
}
|
||||
|
||||
auto exe = std::array<char, 4096>();
|
||||
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
|
||||
@@ -120,17 +85,19 @@ bool CrashHandlerPrivate::minidumpCallback(
|
||||
auto env = std::array<char*, 4096>();
|
||||
auto envi = 0;
|
||||
|
||||
auto infoFd = dup(self->infoFd);
|
||||
auto infoFdStr = std::array<char, 38>();
|
||||
memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30);
|
||||
if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10);
|
||||
// dup to remove CLOEXEC
|
||||
auto infoFdStr = std::array<char, 48>();
|
||||
writeEnvInt(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD", dup(CrashInfo::INSTANCE.infoFd));
|
||||
env[envi++] = infoFdStr.data();
|
||||
|
||||
auto corePidStr = std::array<char, 39>();
|
||||
memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31);
|
||||
if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10);
|
||||
auto corePidStr = std::array<char, 48>();
|
||||
writeEnvInt(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID", coredumpPid);
|
||||
env[envi++] = corePidStr.data();
|
||||
|
||||
auto sigStr = std::array<char, 48>();
|
||||
writeEnvInt(sigStr.data(), "__QUICKSHELL_CRASH_SIGNAL", sig);
|
||||
env[envi++] = sigStr.data();
|
||||
|
||||
auto populateEnv = [&]() {
|
||||
auto senvi = 0;
|
||||
while (envi != 4095) {
|
||||
@@ -142,30 +109,18 @@ bool CrashHandlerPrivate::minidumpCallback(
|
||||
env[envi] = nullptr;
|
||||
};
|
||||
|
||||
sigset_t sigset;
|
||||
sigemptyset(&sigset); // NOLINT (include)
|
||||
sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT
|
||||
|
||||
auto pid = fork();
|
||||
|
||||
if (pid == -1) {
|
||||
perror("Failed to fork and launch crash reporter.\n");
|
||||
return false;
|
||||
_exit(-1);
|
||||
} else if (pid == 0) {
|
||||
|
||||
// dup to remove CLOEXEC
|
||||
// if already -1 will return -1
|
||||
auto dumpFd = dup(self->minidumpFd);
|
||||
auto logFd = dup(CrashInfo::INSTANCE.logFd);
|
||||
|
||||
// allow up to 10 digits, which should never happen
|
||||
auto dumpFdStr = std::array<char, 38>();
|
||||
auto logFdStr = std::array<char, 37>();
|
||||
|
||||
memcpy(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD=-1" /*\0*/, 30);
|
||||
memcpy(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD=-1" /*\0*/, 29);
|
||||
|
||||
if (dumpFd != -1) my_uitos(&dumpFdStr[27], dumpFd, 10);
|
||||
if (logFd != -1) my_uitos(&logFdStr[26], logFd, 10);
|
||||
auto dumpFdStr = std::array<char, 48>();
|
||||
auto logFdStr = std::array<char, 48>();
|
||||
writeEnvInt(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD", dup(CrashInfo::INSTANCE.traceFd));
|
||||
writeEnvInt(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD", dup(CrashInfo::INSTANCE.logFd));
|
||||
|
||||
env[envi++] = dumpFdStr.data();
|
||||
env[envi++] = logFdStr.data();
|
||||
@@ -182,8 +137,83 @@ bool CrashHandlerPrivate::minidumpCallback(
|
||||
perror("Failed to relaunch quickshell.\n");
|
||||
_exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
return false; // should make sure it hits the system coredump handler
|
||||
} // namespace
|
||||
|
||||
void CrashHandler::init() {
|
||||
qCDebug(logCrashHandler) << "Starting crash handler...";
|
||||
|
||||
CrashInfo::INSTANCE.traceFd = memfd_create("quickshell:trace", MFD_CLOEXEC);
|
||||
|
||||
if (CrashInfo::INSTANCE.traceFd == -1) {
|
||||
qCCritical(logCrashHandler) << "Failed to allocate trace memfd, stack traces will not be "
|
||||
"available in crash reports.";
|
||||
} else {
|
||||
qCDebug(logCrashHandler) << "Created memfd" << CrashInfo::INSTANCE.traceFd
|
||||
<< "for holding possible stack traces.";
|
||||
}
|
||||
|
||||
{
|
||||
// Preload anything dynamically linked to avoid malloc etc in the dynamic loader.
|
||||
// See cpptrace documentation for more information.
|
||||
auto buffer = std::array<cpptrace::frame_ptr, 10>();
|
||||
cpptrace::safe_generate_raw_trace(buffer.data(), buffer.size());
|
||||
auto frame = cpptrace::safe_object_frame();
|
||||
cpptrace::get_safe_object_frame(buffer[0], &frame);
|
||||
}
|
||||
|
||||
// NOLINTBEGIN (misc-include-cleaner)
|
||||
|
||||
// Set up alternate signal stack for stack overflow handling
|
||||
auto ss = stack_t();
|
||||
ss.ss_sp = new char[SIGSTKSZ];
|
||||
;
|
||||
ss.ss_size = SIGSTKSZ;
|
||||
ss.ss_flags = 0;
|
||||
sigaltstack(&ss, nullptr);
|
||||
|
||||
// Install signal handlers
|
||||
struct sigaction sa {};
|
||||
sa.sa_sigaction = &signalHandler;
|
||||
sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESETHAND;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
sigaction(SIGSEGV, &sa, nullptr);
|
||||
sigaction(SIGABRT, &sa, nullptr);
|
||||
sigaction(SIGFPE, &sa, nullptr);
|
||||
sigaction(SIGILL, &sa, nullptr);
|
||||
sigaction(SIGBUS, &sa, nullptr);
|
||||
sigaction(SIGTRAP, &sa, nullptr);
|
||||
|
||||
// NOLINTEND (misc-include-cleaner)
|
||||
|
||||
qCInfo(logCrashHandler) << "Crash handler initialized.";
|
||||
}
|
||||
|
||||
void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) {
|
||||
CrashInfo::INSTANCE.infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
|
||||
|
||||
if (CrashInfo::INSTANCE.infoFd == -1) {
|
||||
qCCritical(
|
||||
logCrashHandler
|
||||
) << "Failed to allocate instance info memfd, crash recovery will not work.";
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file;
|
||||
|
||||
if (!file.open(CrashInfo::INSTANCE.infoFd, QFile::ReadWrite)) {
|
||||
qCCritical(
|
||||
logCrashHandler
|
||||
) << "Failed to open instance info memfd, crash recovery will not work.";
|
||||
}
|
||||
|
||||
QDataStream ds(&file);
|
||||
ds << info;
|
||||
file.flush();
|
||||
|
||||
qCDebug(logCrashHandler) << "Stored instance info in memfd" << CrashInfo::INSTANCE.infoFd;
|
||||
}
|
||||
|
||||
} // namespace qs::crash
|
||||
+2
-11
@@ -5,19 +5,10 @@
|
||||
#include "../core/instanceinfo.hpp"
|
||||
namespace qs::crash {
|
||||
|
||||
struct CrashHandlerPrivate;
|
||||
|
||||
class CrashHandler {
|
||||
public:
|
||||
explicit CrashHandler();
|
||||
~CrashHandler();
|
||||
Q_DISABLE_COPY_MOVE(CrashHandler);
|
||||
|
||||
void init();
|
||||
void setRelaunchInfo(const RelaunchInfo& info);
|
||||
|
||||
private:
|
||||
CrashHandlerPrivate* d;
|
||||
static void init();
|
||||
static void setRelaunchInfo(const RelaunchInfo& info);
|
||||
};
|
||||
|
||||
} // namespace qs::crash
|
||||
@@ -66,7 +66,8 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
|
||||
mainLayout->addSpacing(textHeight);
|
||||
|
||||
if (qtVersionMatches) {
|
||||
mainLayout->addWidget(new QLabel("Please open a bug report for this issue via github or email.")
|
||||
mainLayout->addWidget(
|
||||
new QLabel("Please open a bug report for this issue via github or email.")
|
||||
);
|
||||
} else {
|
||||
mainLayout->addWidget(new QLabel(
|
||||
@@ -77,7 +78,7 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
|
||||
|
||||
mainLayout->addWidget(new ReportLabel(
|
||||
"Github:",
|
||||
"https://github.com/quickshell-mirror/quickshell/issues/new?template=crash.yml",
|
||||
"https://github.com/quickshell-mirror/quickshell/issues/new?template=crash2.yml",
|
||||
this
|
||||
));
|
||||
|
||||
@@ -113,7 +114,7 @@ void CrashReporterGui::openFolder() {
|
||||
|
||||
void CrashReporterGui::openReportUrl() {
|
||||
QDesktopServices::openUrl(
|
||||
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml")
|
||||
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+100
-22
@@ -1,7 +1,10 @@
|
||||
#include "main.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
#include <qapplication.h>
|
||||
#include <qconfig.h>
|
||||
#include <qcoreapplication.h>
|
||||
@@ -13,13 +16,17 @@
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtextstream.h>
|
||||
#include <qtversion.h>
|
||||
#include <qtypes.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../core/instanceinfo.hpp"
|
||||
#include "../core/logcat.hpp"
|
||||
#include "../core/logging.hpp"
|
||||
#include "../core/logging_p.hpp"
|
||||
#include "../core/paths.hpp"
|
||||
#include "../core/ringbuf.hpp"
|
||||
#include "build.hpp"
|
||||
#include "interface.hpp"
|
||||
|
||||
@@ -61,6 +68,76 @@ int tryDup(int fd, const QString& path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString readRecentLogs(int logFd, int maxLines, qint64 maxAgeSecs) {
|
||||
QFile file;
|
||||
if (!file.open(logFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||
return QStringLiteral("(failed to open log fd)\n");
|
||||
}
|
||||
|
||||
file.seek(0);
|
||||
|
||||
qs::log::EncodedLogReader reader;
|
||||
reader.setDevice(&file);
|
||||
|
||||
bool readable = false;
|
||||
quint8 logVersion = 0;
|
||||
quint8 readerVersion = 0;
|
||||
if (!reader.readHeader(&readable, &logVersion, &readerVersion) || !readable) {
|
||||
return QStringLiteral("(failed to read log header)\n");
|
||||
}
|
||||
|
||||
// Read all messages, keeping last maxLines in a ring buffer
|
||||
auto tail = RingBuffer<qs::log::LogMessage>(maxLines);
|
||||
qs::log::LogMessage message;
|
||||
while (reader.read(&message)) {
|
||||
tail.emplace(message);
|
||||
}
|
||||
|
||||
if (tail.size() == 0) {
|
||||
return QStringLiteral("(no logs)\n");
|
||||
}
|
||||
|
||||
// Filter to only messages within maxAgeSecs of the newest message
|
||||
auto cutoff = tail.at(0).time.addSecs(-maxAgeSecs);
|
||||
|
||||
QString result;
|
||||
auto stream = QTextStream(&result);
|
||||
for (auto i = tail.size() - 1; i != -1; i--) {
|
||||
if (tail.at(i).time < cutoff) continue;
|
||||
qs::log::LogMessage::formatMessage(stream, tail.at(i), false, true);
|
||||
stream << '\n';
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return QStringLiteral("(no recent logs)\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
cpptrace::stacktrace resolveStacktrace(int dumpFd) {
|
||||
QFile sourceFile;
|
||||
if (!sourceFile.open(dumpFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||
qCCritical(logCrashReporter) << "Failed to open trace memfd.";
|
||||
return {};
|
||||
}
|
||||
|
||||
sourceFile.seek(0);
|
||||
auto data = sourceFile.readAll();
|
||||
|
||||
auto frameCount = static_cast<size_t>(data.size()) / sizeof(cpptrace::safe_object_frame);
|
||||
if (frameCount == 0) return {};
|
||||
|
||||
const auto* frames = reinterpret_cast<const cpptrace::safe_object_frame*>(data.constData());
|
||||
|
||||
cpptrace::object_trace objectTrace;
|
||||
for (size_t i = 0; i < frameCount; i++) {
|
||||
objectTrace.frames.push_back(frames[i].resolve()); // NOLINT
|
||||
}
|
||||
|
||||
return objectTrace.resolve();
|
||||
}
|
||||
|
||||
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||
qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path();
|
||||
|
||||
@@ -71,32 +148,25 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||
}
|
||||
|
||||
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
|
||||
auto crashSignal = qEnvironmentVariable("__QUICKSHELL_CRASH_SIGNAL").toInt();
|
||||
auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt();
|
||||
auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt();
|
||||
|
||||
qCDebug(logCrashReporter) << "Saving minidump from fd" << dumpFd;
|
||||
auto dumpDupStatus = tryDup(dumpFd, crashDir.filePath("minidump.dmp.log"));
|
||||
if (dumpDupStatus != 0) {
|
||||
qCCritical(logCrashReporter) << "Failed to write minidump:" << dumpDupStatus;
|
||||
}
|
||||
qCDebug(logCrashReporter) << "Resolving stacktrace from fd" << dumpFd;
|
||||
auto stacktrace = resolveStacktrace(dumpFd);
|
||||
|
||||
qCDebug(logCrashReporter) << "Saving log from fd" << logFd;
|
||||
auto logDupStatus = tryDup(logFd, crashDir.filePath("log.qslog.log"));
|
||||
qCDebug(logCrashReporter) << "Reading recent log lines from fd" << logFd;
|
||||
auto logDupFd = dup(logFd);
|
||||
auto recentLogs = readRecentLogs(logFd, 100, 10);
|
||||
|
||||
qCDebug(logCrashReporter) << "Saving log from fd" << logDupFd;
|
||||
auto logDupStatus = tryDup(logDupFd, crashDir.filePath("log.qslog.log"));
|
||||
if (logDupStatus != 0) {
|
||||
qCCritical(logCrashReporter) << "Failed to save log:" << logDupStatus;
|
||||
}
|
||||
|
||||
auto copyBinStatus = 0;
|
||||
if (!DISTRIBUTOR_DEBUGINFO_AVAILABLE) {
|
||||
qCDebug(logCrashReporter) << "Copying binary to crash folder";
|
||||
if (!QFile(QCoreApplication::applicationFilePath()).copy(crashDir.filePath("executable.txt"))) {
|
||||
copyBinStatus = 1;
|
||||
qCCritical(logCrashReporter) << "Failed to copy binary.";
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto extraInfoFile = QFile(crashDir.filePath("info.txt"));
|
||||
auto extraInfoFile = QFile(crashDir.filePath("report.txt"));
|
||||
if (!extraInfoFile.open(QFile::WriteOnly)) {
|
||||
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
|
||||
} else {
|
||||
@@ -111,16 +181,12 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||
|
||||
stream << "\n===== Runtime Information =====\n";
|
||||
stream << "Runtime Qt Version: " << qVersion() << '\n';
|
||||
stream << "Signal: " << strsignal(crashSignal) << " (" << crashSignal << ")\n"; // NOLINT
|
||||
stream << "Crashed process ID: " << crashProc << '\n';
|
||||
stream << "Run ID: " << instance.instanceId << '\n';
|
||||
stream << "Shell ID: " << instance.shellId << '\n';
|
||||
stream << "Config Path: " << instance.configPath << '\n';
|
||||
|
||||
stream << "\n===== Report Integrity =====\n";
|
||||
stream << "Minidump save status: " << dumpDupStatus << '\n';
|
||||
stream << "Log save status: " << logDupStatus << '\n';
|
||||
stream << "Binary copy status: " << copyBinStatus << '\n';
|
||||
|
||||
stream << "\n===== System Information =====\n\n";
|
||||
stream << "/etc/os-release:";
|
||||
auto osReleaseFile = QFile("/etc/os-release");
|
||||
@@ -140,6 +206,18 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||
stream << "FAILED TO OPEN\n";
|
||||
}
|
||||
|
||||
stream << "\n===== Stacktrace =====\n";
|
||||
if (stacktrace.empty()) {
|
||||
stream << "(no trace available)\n";
|
||||
} else {
|
||||
auto formatter = cpptrace::formatter().header(std::string());
|
||||
auto traceStr = formatter.format(stacktrace);
|
||||
stream << QString::fromStdString(traceStr) << '\n';
|
||||
}
|
||||
|
||||
stream << "\n===== Log Tail =====\n";
|
||||
stream << recentLogs;
|
||||
|
||||
extraInfoFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +214,10 @@ void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties, bool co
|
||||
}
|
||||
}
|
||||
|
||||
void DBusPropertyGroup::tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant)
|
||||
const {
|
||||
void DBusPropertyGroup::tryUpdateProperty(
|
||||
DBusPropertyCore* property,
|
||||
const QVariant& variant
|
||||
) const {
|
||||
property->mExists = true;
|
||||
|
||||
auto error = property->store(variant);
|
||||
|
||||
@@ -217,7 +217,7 @@ protected:
|
||||
|
||||
private:
|
||||
[[nodiscard]] constexpr Owner* owner() const {
|
||||
auto* self = std::bit_cast<char*>(this);
|
||||
auto* self = std::bit_cast<char*>(this); // NOLINT
|
||||
return std::bit_cast<Owner*>(self - offset()); // NOLINT
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
#include <qqmlinfo.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qstringliteral.h>
|
||||
#include <qstring.h>
|
||||
|
||||
#include "../core/logcat.hpp"
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ qt_add_qml_module(quickshell-io
|
||||
qs_add_module_deps_light(quickshell-io Quickshell)
|
||||
install_qml_module(quickshell-io)
|
||||
|
||||
target_link_libraries(quickshell-io PRIVATE Qt::Quick)
|
||||
target_link_libraries(quickshell-io PRIVATE Qt::Quick quickshell-ipc)
|
||||
target_link_libraries(quickshell PRIVATE quickshell-ioplugin)
|
||||
|
||||
qs_module_pch(quickshell-io)
|
||||
|
||||
@@ -190,6 +190,14 @@ QString WirePropertyDefinition::toString() const {
|
||||
return "property " % this->name % ": " % this->type;
|
||||
}
|
||||
|
||||
QString WireSignalDefinition::toString() const {
|
||||
if (this->rettype.isEmpty()) {
|
||||
return "signal " % this->name % "()";
|
||||
} else {
|
||||
return "signal " % this->name % "(" % this->retname % ": " % this->rettype % ')';
|
||||
}
|
||||
}
|
||||
|
||||
QString WireTargetDefinition::toString() const {
|
||||
QString accum = "target " % this->name;
|
||||
|
||||
@@ -201,6 +209,10 @@ QString WireTargetDefinition::toString() const {
|
||||
accum += "\n " % prop.toString();
|
||||
}
|
||||
|
||||
for (const auto& sig: this->signalFunctions) {
|
||||
accum += "\n " % sig.toString();
|
||||
}
|
||||
|
||||
return accum;
|
||||
}
|
||||
|
||||
|
||||
+21
-4
@@ -146,14 +146,31 @@ struct WirePropertyDefinition {
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type);
|
||||
|
||||
struct WireTargetDefinition {
|
||||
struct WireSignalDefinition {
|
||||
QString name;
|
||||
QVector<WireFunctionDefinition> functions;
|
||||
QVector<WirePropertyDefinition> properties;
|
||||
QString retname;
|
||||
QString rettype;
|
||||
|
||||
[[nodiscard]] QString toString() const;
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions, data.properties);
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(WireSignalDefinition, data.name, data.retname, data.rettype);
|
||||
|
||||
struct WireTargetDefinition {
|
||||
QString name;
|
||||
QVector<WireFunctionDefinition> functions;
|
||||
QVector<WirePropertyDefinition> properties;
|
||||
QVector<WireSignalDefinition> signalFunctions;
|
||||
|
||||
[[nodiscard]] QString toString() const;
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(
|
||||
WireTargetDefinition,
|
||||
data.name,
|
||||
data.functions,
|
||||
data.properties,
|
||||
data.signalFunctions
|
||||
);
|
||||
|
||||
} // namespace qs::io::ipc
|
||||
+104
-5
@@ -1,10 +1,11 @@
|
||||
#include "ipccomm.hpp"
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qtextstream.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
@@ -19,10 +20,6 @@ using namespace qs::ipc;
|
||||
|
||||
namespace qs::io::ipc::comm {
|
||||
|
||||
struct NoCurrentGeneration: std::monostate {};
|
||||
struct TargetNotFound: std::monostate {};
|
||||
struct EntryNotFound: std::monostate {};
|
||||
|
||||
using QueryResponse = std::variant<
|
||||
std::monostate,
|
||||
NoCurrentGeneration,
|
||||
@@ -314,4 +311,106 @@ int getProperty(IpcClient* client, const QString& target, const QString& propert
|
||||
return -1;
|
||||
}
|
||||
|
||||
int listenToSignal(IpcClient* client, const QString& target, const QString& signal, bool once) {
|
||||
if (target.isEmpty()) {
|
||||
qCCritical(logBare) << "Target required to listen for signals.";
|
||||
return -1;
|
||||
} else if (signal.isEmpty()) {
|
||||
qCCritical(logBare) << "Signal required to listen.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
client->sendMessage(IpcCommand(SignalListenCommand {.target = target, .signal = signal}));
|
||||
|
||||
while (true) {
|
||||
SignalListenResponse slot;
|
||||
if (!client->waitForResponse(slot)) return -1;
|
||||
|
||||
if (std::holds_alternative<SignalResponse>(slot)) {
|
||||
auto& result = std::get<SignalResponse>(slot);
|
||||
QTextStream(stdout) << result.response << Qt::endl;
|
||||
if (once) return 0;
|
||||
else continue;
|
||||
} else if (std::holds_alternative<TargetNotFound>(slot)) {
|
||||
qCCritical(logBare) << "Target not found.";
|
||||
} else if (std::holds_alternative<EntryNotFound>(slot)) {
|
||||
qCCritical(logBare) << "Signal not found.";
|
||||
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
|
||||
qCCritical(logBare) << "Not ready to accept queries yet.";
|
||||
} else {
|
||||
qCCritical(logIpc) << "Received invalid IPC response from" << client;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void SignalListenCommand::exec(qs::ipc::IpcServerConnection* conn) {
|
||||
auto resp = conn->responseStream<SignalListenResponse>();
|
||||
|
||||
if (auto* generation = EngineGeneration::currentGeneration()) {
|
||||
auto* registry = IpcHandlerRegistry::forGeneration(generation);
|
||||
|
||||
auto* handler = registry->findHandler(this->target);
|
||||
if (!handler) {
|
||||
resp << TargetNotFound();
|
||||
return;
|
||||
}
|
||||
|
||||
auto* signal = handler->findSignal(this->signal);
|
||||
if (!signal) {
|
||||
resp << EntryNotFound();
|
||||
return;
|
||||
}
|
||||
|
||||
new RemoteSignalListener(conn, *this);
|
||||
} else {
|
||||
conn->respond(SignalListenResponse(NoCurrentGeneration()));
|
||||
}
|
||||
}
|
||||
|
||||
RemoteSignalListener::RemoteSignalListener(
|
||||
qs::ipc::IpcServerConnection* conn,
|
||||
SignalListenCommand command
|
||||
)
|
||||
: conn(conn)
|
||||
, command(std::move(command)) {
|
||||
conn->setParent(this);
|
||||
|
||||
QObject::connect(
|
||||
IpcSignalRemoteListener::instance(),
|
||||
&IpcSignalRemoteListener::triggered,
|
||||
this,
|
||||
&RemoteSignalListener::onSignal
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
conn,
|
||||
&qs::ipc::IpcServerConnection::destroyed,
|
||||
this,
|
||||
&RemoteSignalListener::onConnDestroyed
|
||||
);
|
||||
|
||||
qCDebug(logIpc) << "Remote listener created for" << this->command.target << this->command.signal
|
||||
<< ":" << this;
|
||||
}
|
||||
|
||||
RemoteSignalListener::~RemoteSignalListener() {
|
||||
qCDebug(logIpc) << "Destroying remote listener" << this;
|
||||
}
|
||||
|
||||
void RemoteSignalListener::onSignal(
|
||||
const QString& target,
|
||||
const QString& signal,
|
||||
const QString& value
|
||||
) {
|
||||
if (target != this->command.target || signal != this->command.signal) return;
|
||||
qCDebug(logIpc) << "Remote signal" << signal << "triggered on" << target << "with value" << value;
|
||||
|
||||
this->conn->respond(SignalListenResponse(SignalResponse {.response = value}));
|
||||
}
|
||||
|
||||
void RemoteSignalListener::onConnDestroyed() { this->deleteLater(); }
|
||||
|
||||
} // namespace qs::io::ipc::comm
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qflags.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../ipc/ipc.hpp"
|
||||
@@ -48,4 +50,52 @@ DEFINE_SIMPLE_DATASTREAM_OPS(StringPropReadCommand, data.target, data.property);
|
||||
|
||||
int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property);
|
||||
|
||||
struct SignalListenCommand {
|
||||
QString target;
|
||||
QString signal;
|
||||
|
||||
void exec(qs::ipc::IpcServerConnection* conn);
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(SignalListenCommand, data.target, data.signal);
|
||||
|
||||
int listenToSignal(
|
||||
qs::ipc::IpcClient* client,
|
||||
const QString& target,
|
||||
const QString& signal,
|
||||
bool once
|
||||
);
|
||||
|
||||
struct NoCurrentGeneration: std::monostate {};
|
||||
struct TargetNotFound: std::monostate {};
|
||||
struct EntryNotFound: std::monostate {};
|
||||
|
||||
struct SignalResponse {
|
||||
QString response;
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(SignalResponse, data.response);
|
||||
|
||||
using SignalListenResponse = std::
|
||||
variant<std::monostate, NoCurrentGeneration, TargetNotFound, EntryNotFound, SignalResponse>;
|
||||
|
||||
class RemoteSignalListener: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit RemoteSignalListener(qs::ipc::IpcServerConnection* conn, SignalListenCommand command);
|
||||
|
||||
~RemoteSignalListener() override;
|
||||
|
||||
Q_DISABLE_COPY_MOVE(RemoteSignalListener);
|
||||
|
||||
private slots:
|
||||
void onSignal(const QString& target, const QString& signal, const QString& value);
|
||||
void onConnDestroyed();
|
||||
|
||||
private:
|
||||
qs::ipc::IpcServerConnection* conn;
|
||||
SignalListenCommand command;
|
||||
};
|
||||
|
||||
} // namespace qs::io::ipc::comm
|
||||
+112
-8
@@ -1,5 +1,7 @@
|
||||
#include "ipchandler.hpp"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.h>
|
||||
@@ -139,6 +141,75 @@ WirePropertyDefinition IpcProperty::wireDef() const {
|
||||
return wire;
|
||||
}
|
||||
|
||||
WireSignalDefinition IpcSignal::wireDef() const {
|
||||
WireSignalDefinition wire;
|
||||
wire.name = this->signal.name();
|
||||
if (this->targetSlot != IpcSignalListener::SLOT_VOID) {
|
||||
wire.retname = this->signal.parameterNames().value(0);
|
||||
if (this->targetSlot == IpcSignalListener::SLOT_STRING) wire.rettype = "string";
|
||||
else if (this->targetSlot == IpcSignalListener::SLOT_INT) wire.rettype = "int";
|
||||
else if (this->targetSlot == IpcSignalListener::SLOT_BOOL) wire.rettype = "bool";
|
||||
else if (this->targetSlot == IpcSignalListener::SLOT_REAL) wire.rettype = "real";
|
||||
else if (this->targetSlot == IpcSignalListener::SLOT_COLOR) wire.rettype = "color";
|
||||
}
|
||||
return wire;
|
||||
}
|
||||
|
||||
// NOLINTBEGIN (cppcoreguidelines-interfaces-global-init)
|
||||
// clang-format off
|
||||
const int IpcSignalListener::SLOT_VOID = IpcSignalListener::staticMetaObject.indexOfSlot("invokeVoid()");
|
||||
const int IpcSignalListener::SLOT_STRING = IpcSignalListener::staticMetaObject.indexOfSlot("invokeString(QString)");
|
||||
const int IpcSignalListener::SLOT_INT = IpcSignalListener::staticMetaObject.indexOfSlot("invokeInt(int)");
|
||||
const int IpcSignalListener::SLOT_BOOL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeBool(bool)");
|
||||
const int IpcSignalListener::SLOT_REAL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeReal(double)");
|
||||
const int IpcSignalListener::SLOT_COLOR = IpcSignalListener::staticMetaObject.indexOfSlot("invokeColor(QColor)");
|
||||
// clang-format on
|
||||
// NOLINTEND
|
||||
|
||||
bool IpcSignal::resolve(QString& error) {
|
||||
if (this->signal.parameterCount() > 1) {
|
||||
error = "Due to technical limitations, IPC signals can have at most one argument.";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto slot = IpcSignalListener::SLOT_VOID;
|
||||
|
||||
if (this->signal.parameterCount() == 1) {
|
||||
auto paramType = this->signal.parameterType(0);
|
||||
if (paramType == QMetaType::QString) slot = IpcSignalListener::SLOT_STRING;
|
||||
else if (paramType == QMetaType::Int) slot = IpcSignalListener::SLOT_INT;
|
||||
else if (paramType == QMetaType::Bool) slot = IpcSignalListener::SLOT_BOOL;
|
||||
else if (paramType == QMetaType::Double) slot = IpcSignalListener::SLOT_REAL;
|
||||
else if (paramType == QMetaType::QColor) slot = IpcSignalListener::SLOT_COLOR;
|
||||
else {
|
||||
error = QString("Type of argument (%2: %3) cannot be used across IPC.")
|
||||
.arg(this->signal.parameterNames().value(0))
|
||||
.arg(QMetaType(paramType).name());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->targetSlot = slot;
|
||||
return true;
|
||||
}
|
||||
|
||||
void IpcSignal::connectListener(IpcHandler* handler) {
|
||||
if (this->targetSlot == -1) {
|
||||
qFatal() << "Tried to connect unresolved IPC signal";
|
||||
}
|
||||
|
||||
this->listener = std::make_shared<IpcSignalListener>(this->signal.name());
|
||||
QMetaObject::connect(handler, this->signal.methodIndex(), this->listener.get(), this->targetSlot);
|
||||
|
||||
QObject::connect(
|
||||
this->listener.get(),
|
||||
&IpcSignalListener::triggered,
|
||||
handler,
|
||||
&IpcHandler::onSignalTriggered
|
||||
);
|
||||
}
|
||||
|
||||
IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) {
|
||||
for (const auto& arg: function.argumentTypes) {
|
||||
this->argumentSlots.emplace_back(arg);
|
||||
@@ -172,16 +243,28 @@ void IpcHandler::onPostReload() {
|
||||
// which should handle inheritance on the qml side.
|
||||
for (auto i = smeta.methodCount(); i != meta->methodCount(); i++) {
|
||||
const auto& method = meta->method(i);
|
||||
if (method.methodType() != QMetaMethod::Slot) continue;
|
||||
if (method.methodType() == QMetaMethod::Slot) {
|
||||
auto ipcFunc = IpcFunction(method);
|
||||
QString error;
|
||||
|
||||
auto ipcFunc = IpcFunction(method);
|
||||
QString error;
|
||||
if (!ipcFunc.resolve(error)) {
|
||||
qmlWarning(this).nospace().noquote()
|
||||
<< "Error parsing function \"" << method.name() << "\": " << error;
|
||||
} else {
|
||||
this->functionMap.insert(method.name(), ipcFunc);
|
||||
}
|
||||
} else if (method.methodType() == QMetaMethod::Signal) {
|
||||
qmlDebug(this) << "Signal detected: " << method.name();
|
||||
auto ipcSig = IpcSignal(method);
|
||||
QString error;
|
||||
|
||||
if (!ipcFunc.resolve(error)) {
|
||||
qmlWarning(this).nospace().noquote()
|
||||
<< "Error parsing function \"" << method.name() << "\": " << error;
|
||||
} else {
|
||||
this->functionMap.insert(method.name(), ipcFunc);
|
||||
if (!ipcSig.resolve(error)) {
|
||||
qmlWarning(this).nospace().noquote()
|
||||
<< "Error parsing signal \"" << method.name() << "\": " << error;
|
||||
} else {
|
||||
ipcSig.connectListener(this);
|
||||
this->signalMap.emplace(method.name(), std::move(ipcSig));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +305,11 @@ IpcHandlerRegistry* IpcHandlerRegistry::forGeneration(EngineGeneration* generati
|
||||
return dynamic_cast<IpcHandlerRegistry*>(ext);
|
||||
}
|
||||
|
||||
void IpcHandler::onSignalTriggered(const QString& signal, const QString& value) const {
|
||||
emit IpcSignalRemoteListener::instance()
|
||||
-> triggered(this->registeredState.target, signal, value);
|
||||
}
|
||||
|
||||
void IpcHandler::updateRegistration(bool destroying) {
|
||||
if (!this->complete) return;
|
||||
|
||||
@@ -324,6 +412,10 @@ WireTargetDefinition IpcHandler::wireDef() const {
|
||||
wire.properties += prop.wireDef();
|
||||
}
|
||||
|
||||
for (const auto& sig: this->signalMap.values()) {
|
||||
wire.signalFunctions += sig.wireDef();
|
||||
}
|
||||
|
||||
return wire;
|
||||
}
|
||||
|
||||
@@ -368,6 +460,13 @@ IpcProperty* IpcHandler::findProperty(const QString& name) {
|
||||
else return &*itr;
|
||||
}
|
||||
|
||||
IpcSignal* IpcHandler::findSignal(const QString& name) {
|
||||
auto itr = this->signalMap.find(name);
|
||||
|
||||
if (itr == this->signalMap.end()) return nullptr;
|
||||
else return &*itr;
|
||||
}
|
||||
|
||||
IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) {
|
||||
return this->handlers.value(target);
|
||||
}
|
||||
@@ -382,4 +481,9 @@ QVector<WireTargetDefinition> IpcHandlerRegistry::wireTargets() const {
|
||||
return wire;
|
||||
}
|
||||
|
||||
IpcSignalRemoteListener* IpcSignalRemoteListener::instance() {
|
||||
static auto* instance = new IpcSignalRemoteListener();
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace qs::io::ipc
|
||||
+79
-3
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.h>
|
||||
#include <qhash.h>
|
||||
@@ -67,6 +69,54 @@ public:
|
||||
const IpcType* type = nullptr;
|
||||
};
|
||||
|
||||
class IpcSignalListener: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
IpcSignalListener(QString signal): signal(std::move(signal)) {}
|
||||
|
||||
static const int SLOT_VOID;
|
||||
static const int SLOT_STRING;
|
||||
static const int SLOT_INT;
|
||||
static const int SLOT_BOOL;
|
||||
static const int SLOT_REAL;
|
||||
static const int SLOT_COLOR;
|
||||
|
||||
signals:
|
||||
void triggered(const QString& signal, const QString& value);
|
||||
|
||||
private slots:
|
||||
void invokeVoid() { this->triggered(this->signal, "void"); }
|
||||
void invokeString(const QString& value) { this->triggered(this->signal, value); }
|
||||
void invokeInt(int value) { this->triggered(this->signal, QString::number(value)); }
|
||||
void invokeBool(bool value) { this->triggered(this->signal, value ? "true" : "false"); }
|
||||
void invokeReal(double value) { this->triggered(this->signal, QString::number(value)); }
|
||||
void invokeColor(QColor value) { this->triggered(this->signal, value.name(QColor::HexArgb)); }
|
||||
|
||||
private:
|
||||
QString signal;
|
||||
};
|
||||
|
||||
class IpcHandler;
|
||||
|
||||
class IpcSignal {
|
||||
public:
|
||||
explicit IpcSignal(QMetaMethod signal): signal(signal) {}
|
||||
|
||||
bool resolve(QString& error);
|
||||
|
||||
[[nodiscard]] WireSignalDefinition wireDef() const;
|
||||
|
||||
QMetaMethod signal;
|
||||
int targetSlot = -1;
|
||||
|
||||
void connectListener(IpcHandler* handler);
|
||||
|
||||
private:
|
||||
void connectListener(QObject* handler, IpcSignalListener* listener) const;
|
||||
std::shared_ptr<IpcSignalListener> listener;
|
||||
};
|
||||
|
||||
class IpcHandlerRegistry;
|
||||
|
||||
///! Handler for IPC message calls.
|
||||
@@ -100,6 +150,11 @@ class IpcHandlerRegistry;
|
||||
/// - `real` will be converted to a string and returned.
|
||||
/// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned.
|
||||
///
|
||||
/// #### Signals
|
||||
/// IPC handler signals can be observed remotely using `qs ipc wait` (one call)
|
||||
/// and `qs ipc listen` (many calls). IPC signals may have zero or one argument, where
|
||||
/// the argument is one of the types listed above, or no arguments for void.
|
||||
///
|
||||
/// #### Example
|
||||
/// The following example creates ipc functions to control and retrieve the appearance
|
||||
/// of a Rectangle.
|
||||
@@ -119,10 +174,18 @@ class IpcHandlerRegistry;
|
||||
///
|
||||
/// function setColor(color: color): void { rect.color = color; }
|
||||
/// function getColor(): color { return rect.color; }
|
||||
///
|
||||
/// function setAngle(angle: real): void { rect.rotation = angle; }
|
||||
/// function getAngle(): real { return rect.rotation; }
|
||||
/// function setRadius(radius: int): void { rect.radius = radius; }
|
||||
///
|
||||
/// function setRadius(radius: int): void {
|
||||
/// rect.radius = radius;
|
||||
/// this.radiusChanged(radius);
|
||||
/// }
|
||||
///
|
||||
/// function getRadius(): int { return rect.radius; }
|
||||
///
|
||||
/// signal radiusChanged(newRadius: int);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
@@ -136,6 +199,7 @@ class IpcHandlerRegistry;
|
||||
/// function getAngle(): real
|
||||
/// function setRadius(radius: int): void
|
||||
/// function getRadius(): int
|
||||
/// signal radiusChanged(newRadius: int)
|
||||
/// ```
|
||||
///
|
||||
/// and then invoked using `qs ipc call`.
|
||||
@@ -179,14 +243,15 @@ public:
|
||||
QString listMembers(qsizetype indent);
|
||||
[[nodiscard]] IpcFunction* findFunction(const QString& name);
|
||||
[[nodiscard]] IpcProperty* findProperty(const QString& name);
|
||||
[[nodiscard]] IpcSignal* findSignal(const QString& name);
|
||||
[[nodiscard]] WireTargetDefinition wireDef() const;
|
||||
|
||||
signals:
|
||||
void enabledChanged();
|
||||
void targetChanged();
|
||||
|
||||
private slots:
|
||||
//void handleIpcPropertyChange();
|
||||
public slots:
|
||||
void onSignalTriggered(const QString& signal, const QString& value) const;
|
||||
|
||||
private:
|
||||
void updateRegistration(bool destroying = false);
|
||||
@@ -204,6 +269,7 @@ private:
|
||||
|
||||
QHash<QString, IpcFunction> functionMap;
|
||||
QHash<QString, IpcProperty> propertyMap;
|
||||
QHash<QString, IpcSignal> signalMap;
|
||||
|
||||
friend class IpcHandlerRegistry;
|
||||
};
|
||||
@@ -227,4 +293,14 @@ private:
|
||||
QHash<QString, QVector<IpcHandler*>> knownHandlers;
|
||||
};
|
||||
|
||||
class IpcSignalRemoteListener: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
static IpcSignalRemoteListener* instance();
|
||||
|
||||
signals:
|
||||
void triggered(const QString& target, const QString& signal, const QString& value);
|
||||
};
|
||||
|
||||
} // namespace qs::io::ipc
|
||||
+1
-1
@@ -102,7 +102,7 @@ class Process: public PostReloadHook {
|
||||
/// If the process is already running changing this property will affect the next
|
||||
/// started process. If the property has been changed after starting a process it will
|
||||
/// return the new value, not the one for the currently running process.
|
||||
Q_PROPERTY(QHash<QString, QVariant> environment READ environment WRITE setEnvironment NOTIFY environmentChanged);
|
||||
Q_PROPERTY(QVariantHash environment READ environment WRITE setEnvironment NOTIFY environmentChanged);
|
||||
/// If the process's environment should be cleared prior to applying @@environment.
|
||||
/// Defaults to false.
|
||||
///
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace qs::io::process {
|
||||
|
||||
class ProcessContext {
|
||||
Q_PROPERTY(QList<QString> command MEMBER command WRITE setCommand);
|
||||
Q_PROPERTY(QHash<QString, QVariant> environment MEMBER environment WRITE setEnvironment);
|
||||
Q_PROPERTY(QVariantHash environment MEMBER environment WRITE setEnvironment);
|
||||
Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment WRITE setClearEnvironment);
|
||||
Q_PROPERTY(QString workingDirectory MEMBER workingDirectory WRITE setWorkingDirectory);
|
||||
Q_PROPERTY(bool unbindStdout MEMBER unbindStdout WRITE setUnbindStdout);
|
||||
|
||||
+8
-1
@@ -36,7 +36,8 @@ void IpcServer::start() {
|
||||
auto path = run->filePath("ipc.sock");
|
||||
new IpcServer(path);
|
||||
} else {
|
||||
qCCritical(logIpc
|
||||
qCCritical(
|
||||
logIpc
|
||||
) << "Could not start IPC server as the instance runtime path could not be created.";
|
||||
}
|
||||
}
|
||||
@@ -60,6 +61,7 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server
|
||||
|
||||
void IpcServerConnection::onDisconnected() {
|
||||
qCInfo(logIpc) << "IPC connection disconnected" << this;
|
||||
this->deleteLater();
|
||||
}
|
||||
|
||||
void IpcServerConnection::onReadyRead() {
|
||||
@@ -83,6 +85,11 @@ void IpcServerConnection::onReadyRead() {
|
||||
);
|
||||
|
||||
if (!this->stream.commitTransaction()) return;
|
||||
|
||||
// async connections reparent
|
||||
if (dynamic_cast<IpcServer*>(this->parent()) != nullptr) {
|
||||
this->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
IpcClient::IpcClient(const QString& path) {
|
||||
|
||||
@@ -16,6 +16,7 @@ using IpcCommand = std::variant<
|
||||
IpcKillCommand,
|
||||
qs::io::ipc::comm::QueryMetadataCommand,
|
||||
qs::io::ipc::comm::StringCallCommand,
|
||||
qs::io::ipc::comm::SignalListenCommand,
|
||||
qs::io::ipc::comm::StringPropReadCommand>;
|
||||
|
||||
} // namespace qs::ipc
|
||||
+40
-15
@@ -1,7 +1,6 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
@@ -13,6 +12,7 @@
|
||||
#include <qdebug.h>
|
||||
#include <qdir.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
@@ -89,9 +89,9 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||
}
|
||||
|
||||
if (!manifestPath.isEmpty()) {
|
||||
qWarning(
|
||||
) << "Config manifests (manifest.conf) are deprecated and will be removed in a future "
|
||||
"release.";
|
||||
qWarning()
|
||||
<< "Config manifests (manifest.conf) are deprecated and will be removed in a future "
|
||||
"release.";
|
||||
qWarning() << "Consider using symlinks to a subfolder of quickshell's XDG config dirs.";
|
||||
|
||||
auto file = QFile(manifestPath);
|
||||
@@ -109,7 +109,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||
}
|
||||
|
||||
if (split[0].trimmed() == *cmd.config.name) {
|
||||
path = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed());
|
||||
path = QDir(QFileInfo(file).absolutePath()).filePath(split[1].trimmed());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,8 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||
|
||||
if (path.isEmpty()) {
|
||||
if (name == "default") {
|
||||
qCCritical(logBare
|
||||
qCCritical(
|
||||
logBare
|
||||
) << "Could not find \"default\" config directory or shell.qml in any valid config path.";
|
||||
} else {
|
||||
qCCritical(logBare) << "Could not find" << name
|
||||
@@ -139,8 +140,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
path = QFileInfo(path).canonicalFilePath();
|
||||
return 0;
|
||||
goto rpath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,8 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
path = QFileInfo(path).canonicalFilePath();
|
||||
rpath:
|
||||
path = QFileInfo(path).absoluteFilePath();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -178,7 +179,8 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
||||
}
|
||||
} else if (!cmd.instance.id->isEmpty()) {
|
||||
path = basePath->filePath("by-pid");
|
||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
|
||||
auto [liveInstances, deadInstances] =
|
||||
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
||||
|
||||
liveInstances.removeIf([&](const InstanceLockInfo& info) {
|
||||
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||
@@ -228,7 +230,8 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
||||
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
|
||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
|
||||
auto [liveInstances, deadInstances] =
|
||||
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
||||
|
||||
auto instances = liveInstances;
|
||||
if (instances.isEmpty() && deadFallback) {
|
||||
@@ -311,7 +314,10 @@ int listInstances(CommandState& cmd) {
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
}
|
||||
|
||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
|
||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(
|
||||
path,
|
||||
cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection()
|
||||
);
|
||||
|
||||
sortInstances(liveInstances, cmd.config.newest);
|
||||
|
||||
@@ -373,6 +379,7 @@ int listInstances(CommandState& cmd) {
|
||||
<< " Process ID: " << instance.instance.pid << '\n'
|
||||
<< " Shell ID: " << instance.instance.shellId << '\n'
|
||||
<< " Config path: " << instance.instance.configPath << '\n'
|
||||
<< " Display connection: " << instance.instance.display << '\n'
|
||||
<< " Launch time: " << launchTimeStr
|
||||
<< (isDead ? "" : " (running for " + runtimeStr + ")") << '\n'
|
||||
<< (gray ? "\033[0m" : "");
|
||||
@@ -404,6 +411,10 @@ int ipcCommand(CommandState& cmd) {
|
||||
return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name);
|
||||
} else if (*cmd.ipc.getprop) {
|
||||
return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name);
|
||||
} else if (*cmd.ipc.wait) {
|
||||
return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, true);
|
||||
} else if (*cmd.ipc.listen) {
|
||||
return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, false);
|
||||
} else {
|
||||
QVector<QString> arguments;
|
||||
for (auto& arg: cmd.ipc.arguments) {
|
||||
@@ -453,7 +464,7 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||
QTextStream(stdout) << "\033[31mCOMPATIBILITY WARNING: Quickshell was built against Qt "
|
||||
<< QT_VERSION_STR << " but the system has updated to Qt " << qVersion()
|
||||
<< " without rebuilding the package. This is likely to cause crashes, so "
|
||||
"you must rebuild the quickshell package.\n";
|
||||
"you must rebuild the quickshell package.\n\033[0m";
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -508,8 +519,8 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||
}
|
||||
|
||||
if (state.misc.printVersion) {
|
||||
qCInfo(logBare).noquote().nospace()
|
||||
<< "quickshell 0.2.1, revision " << GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
||||
qCInfo(logBare).noquote().nospace() << "quickshell " << QS_VERSION << ", revision "
|
||||
<< GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
||||
|
||||
if (state.log.verbosity > 1) {
|
||||
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
||||
@@ -545,4 +556,18 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString getDisplayConnection() {
|
||||
auto platform = qEnvironmentVariable("QT_QPA_PLATFORM");
|
||||
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;
|
||||
} else {
|
||||
return "unk," + QGuiApplication::platformName();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace qs::launch
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "build.hpp"
|
||||
#include "launch_p.hpp"
|
||||
|
||||
#if CRASH_REPORTER
|
||||
#if CRASH_HANDLER
|
||||
#include "../crash/handler.hpp"
|
||||
#endif
|
||||
|
||||
@@ -78,6 +78,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
QHash<QString, QString> envOverrides;
|
||||
QString dataDir;
|
||||
QString stateDir;
|
||||
QString cacheDir;
|
||||
} pragmas;
|
||||
|
||||
auto stream = QTextStream(&file);
|
||||
@@ -109,6 +110,8 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
pragmas.dataDir = pragma.sliced(8).trimmed();
|
||||
} else if (pragma.startsWith("StateDir ")) {
|
||||
pragmas.stateDir = pragma.sliced(9).trimmed();
|
||||
} else if (pragma.startsWith("CacheDir ")) {
|
||||
pragmas.cacheDir = pragma.sliced(9).trimmed();
|
||||
} else {
|
||||
qCritical() << "Unrecognized pragma" << pragma;
|
||||
return -1;
|
||||
@@ -131,15 +134,15 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
.shellId = shellId,
|
||||
.launchTime = qs::Common::LAUNCH_TIME,
|
||||
.pid = getpid(),
|
||||
.display = getDisplayConnection(),
|
||||
};
|
||||
|
||||
#if CRASH_REPORTER
|
||||
auto crashHandler = crash::CrashHandler();
|
||||
crashHandler.init();
|
||||
#if CRASH_HANDLER
|
||||
crash::CrashHandler::init();
|
||||
|
||||
{
|
||||
auto* log = LogManager::instance();
|
||||
crashHandler.setRelaunchInfo({
|
||||
crash::CrashHandler::setRelaunchInfo({
|
||||
.instance = InstanceInfo::CURRENT,
|
||||
.noColor = !log->colorLogs,
|
||||
.timestamp = log->timestampLogs,
|
||||
@@ -150,7 +153,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
}
|
||||
#endif
|
||||
|
||||
QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir);
|
||||
QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir, pragmas.cacheDir);
|
||||
QsPaths::instance()->linkRunDir();
|
||||
QsPaths::instance()->linkPathDir();
|
||||
LogManager::initFs();
|
||||
|
||||
@@ -50,6 +50,7 @@ struct CommandState {
|
||||
QStringOption manifest;
|
||||
QStringOption name;
|
||||
bool newest = false;
|
||||
bool anyDisplay = false;
|
||||
} config;
|
||||
|
||||
struct {
|
||||
@@ -73,6 +74,8 @@ struct CommandState {
|
||||
CLI::App* show = nullptr;
|
||||
CLI::App* call = nullptr;
|
||||
CLI::App* getprop = nullptr;
|
||||
CLI::App* wait = nullptr;
|
||||
CLI::App* listen = nullptr;
|
||||
bool showOld = false;
|
||||
QStringOption target;
|
||||
QStringOption name;
|
||||
@@ -106,6 +109,8 @@ void exitDaemon(int code);
|
||||
int parseCommand(int argc, char** argv, CommandState& state);
|
||||
int runCommand(int argc, char** argv, QCoreApplication* coreApplication);
|
||||
|
||||
QString getDisplayConnection();
|
||||
|
||||
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication);
|
||||
|
||||
} // namespace qs::launch
|
||||
+3
-3
@@ -16,7 +16,7 @@
|
||||
#include "build.hpp"
|
||||
#include "launch_p.hpp"
|
||||
|
||||
#if CRASH_REPORTER
|
||||
#if CRASH_HANDLER
|
||||
#include "../crash/main.hpp"
|
||||
#endif
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace qs::launch {
|
||||
namespace {
|
||||
|
||||
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
|
||||
#if CRASH_REPORTER
|
||||
#if CRASH_HANDLER
|
||||
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
|
||||
|
||||
if (!lastInfoFdStr.isEmpty()) {
|
||||
@@ -104,7 +104,7 @@ void exitDaemon(int code) {
|
||||
int main(int argc, char** argv) {
|
||||
QCoreApplication::setApplicationName("quickshell");
|
||||
|
||||
#if CRASH_REPORTER
|
||||
#if CRASH_HANDLER
|
||||
qsCheckCrash(argc, argv);
|
||||
#endif
|
||||
|
||||
|
||||
+48
-19
@@ -1,6 +1,7 @@
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <CLI/App.hpp>
|
||||
#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes
|
||||
@@ -16,7 +17,7 @@ int parseCommand(int argc, char** argv, CommandState& state) {
|
||||
.argv = argv,
|
||||
};
|
||||
|
||||
auto addConfigSelection = [&](CLI::App* cmd, bool withNewestOption = false) {
|
||||
auto addConfigSelection = [&](CLI::App* cmd, bool filtering = false) {
|
||||
auto* group =
|
||||
cmd->add_option_group("Config Selection")
|
||||
->description(
|
||||
@@ -43,15 +44,23 @@ int parseCommand(int argc, char** argv, CommandState& state) {
|
||||
->excludes(path);
|
||||
|
||||
group->add_option("-m,--manifest", state.config.manifest)
|
||||
->description("[DEPRECATED] Path to a quickshell manifest.\n"
|
||||
"If a manifest is specified, configs named by -c will point to its entries.\n"
|
||||
"Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf")
|
||||
->description(
|
||||
"[DEPRECATED] Path to a quickshell manifest.\n"
|
||||
"If a manifest is specified, configs named by -c will point to its entries.\n"
|
||||
"Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf"
|
||||
)
|
||||
->envname("QS_MANIFEST")
|
||||
->excludes(path);
|
||||
|
||||
if (withNewestOption) {
|
||||
if (filtering) {
|
||||
group->add_flag("-n,--newest", state.config.newest)
|
||||
->description("Operate on the most recently launched instance instead of the oldest");
|
||||
|
||||
group->add_flag("--any-display", state.config.anyDisplay)
|
||||
->description(
|
||||
"If passed, instances will not be filtered by the display connection they "
|
||||
"were launched on."
|
||||
);
|
||||
}
|
||||
|
||||
return group;
|
||||
@@ -75,9 +84,11 @@ int parseCommand(int argc, char** argv, CommandState& state) {
|
||||
auto* group = noGroup ? cmd : cmd->add_option_group(noDisplay ? "" : "Logging");
|
||||
|
||||
group->add_flag("--no-color", state.log.noColor)
|
||||
->description("Disables colored logging.\n"
|
||||
"Colored logging can also be disabled by specifying a non empty value "
|
||||
"for the NO_COLOR environment variable.");
|
||||
->description(
|
||||
"Disables colored logging.\n"
|
||||
"Colored logging can also be disabled by specifying a non empty value "
|
||||
"for the NO_COLOR environment variable."
|
||||
);
|
||||
|
||||
group->add_flag("--log-times", state.log.timestamp)
|
||||
->description("Log timestamps with each message.");
|
||||
@@ -86,9 +97,11 @@ int parseCommand(int argc, char** argv, CommandState& state) {
|
||||
->description("Log rules to apply, in the format of QT_LOGGING_RULES.");
|
||||
|
||||
group->add_flag("-v,--verbose", [&](size_t count) { state.log.verbosity = count; })
|
||||
->description("Increases log verbosity.\n"
|
||||
"-v will show INFO level internal logs.\n"
|
||||
"-vv will show DEBUG level internal logs.");
|
||||
->description(
|
||||
"Increases log verbosity.\n"
|
||||
"-v will show INFO level internal logs.\n"
|
||||
"-vv will show DEBUG level internal logs."
|
||||
);
|
||||
|
||||
auto* hgroup = cmd->add_option_group("");
|
||||
hgroup->add_flag("--no-detailed-logs", state.log.sparse);
|
||||
@@ -98,9 +111,11 @@ int parseCommand(int argc, char** argv, CommandState& state) {
|
||||
auto* group = cmd->add_option_group("Instance Selection");
|
||||
|
||||
group->add_option("-i,--id", state.instance.id)
|
||||
->description("The instance id to operate on.\n"
|
||||
"You may also use a substring the id as long as it is unique, "
|
||||
"for example \"abc\" will select \"abcdefg\".");
|
||||
->description(
|
||||
"The instance id to operate on.\n"
|
||||
"You may also use a substring the id as long as it is unique, "
|
||||
"for example \"abc\" will select \"abcdefg\"."
|
||||
);
|
||||
|
||||
group->add_option("--pid", state.instance.pid)
|
||||
->description("The process id of the instance to operate on.");
|
||||
@@ -157,9 +172,11 @@ int parseCommand(int argc, char** argv, CommandState& state) {
|
||||
auto* sub = cli->add_subcommand("list", "List running quickshell instances.");
|
||||
|
||||
auto* all = sub->add_flag("-a,--all", state.instance.all)
|
||||
->description("List all instances.\n"
|
||||
"If unspecified, only instances of"
|
||||
"the selected config will be listed.");
|
||||
->description(
|
||||
"List all instances.\n"
|
||||
"If unspecified, only instances of"
|
||||
"the selected config will be listed."
|
||||
);
|
||||
|
||||
sub->add_flag("-j,--json", state.output.json, "Output the list as a json.");
|
||||
|
||||
@@ -210,6 +227,16 @@ int parseCommand(int argc, char** argv, CommandState& state) {
|
||||
->allow_extra_args();
|
||||
}
|
||||
|
||||
auto signalCmd = [&](std::string cmd, std::string desc) {
|
||||
auto* scmd = sub->add_subcommand(std::move(cmd), std::move(desc));
|
||||
scmd->add_option("target", state.ipc.target, "The target to listen on.");
|
||||
scmd->add_option("signal", state.ipc.name, "The signal to listen for.");
|
||||
return scmd;
|
||||
};
|
||||
|
||||
state.ipc.wait = signalCmd("wait", "Wait for one IpcHandler signal.");
|
||||
state.ipc.listen = signalCmd("listen", "Listen for IpcHandler signals.");
|
||||
|
||||
{
|
||||
auto* prop =
|
||||
sub->add_subcommand("prop", "Manipulate IpcHandler properties.")->require_subcommand();
|
||||
@@ -235,8 +262,10 @@ int parseCommand(int argc, char** argv, CommandState& state) {
|
||||
->allow_extra_args();
|
||||
|
||||
sub->add_flag("-s,--show", state.ipc.showOld)
|
||||
->description("Print information about a function or target if given, or all available "
|
||||
"targets if not.");
|
||||
->description(
|
||||
"Print information about a function or target if given, or all available "
|
||||
"targets if not."
|
||||
);
|
||||
|
||||
auto* instance = addInstanceSelection(sub);
|
||||
addConfigSelection(sub, true)->excludes(instance);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
add_subdirectory(nm)
|
||||
|
||||
qt_add_library(quickshell-network STATIC
|
||||
network.cpp
|
||||
device.cpp
|
||||
wifi.cpp
|
||||
)
|
||||
|
||||
target_include_directories(quickshell-network PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell-network
|
||||
URI Quickshell.Networking
|
||||
VERSION 0.1
|
||||
DEPENDENCIES QtQml
|
||||
)
|
||||
|
||||
qs_add_module_deps_light(quickshell-network Quickshell)
|
||||
install_qml_module(quickshell-network)
|
||||
target_link_libraries(quickshell-network PRIVATE quickshell-network-nm Qt::Qml Qt::DBus)
|
||||
qs_add_link_dependencies(quickshell-network quickshell-dbus)
|
||||
target_link_libraries(quickshell PRIVATE quickshell-networkplugin)
|
||||
qs_module_pch(quickshell-network SET dbus)
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "device.hpp"
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../core/logcat.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
void NetworkDevice::setAutoconnect(bool autoconnect) {
|
||||
if (this->bAutoconnect == autoconnect) return;
|
||||
emit this->requestSetAutoconnect(autoconnect);
|
||||
}
|
||||
|
||||
void NetworkDevice::disconnect() {
|
||||
if (this->bState == DeviceConnectionState::Disconnected) {
|
||||
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected";
|
||||
return;
|
||||
}
|
||||
if (this->bState == DeviceConnectionState::Disconnecting) {
|
||||
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting";
|
||||
return;
|
||||
}
|
||||
qCDebug(logNetworkDevice) << "Disconnecting from device" << this;
|
||||
this->requestDisconnect();
|
||||
}
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
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.
|
||||
class NetworkDevice: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("Devices can only be acquired through Network");
|
||||
// clang-format off
|
||||
/// The device type.
|
||||
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);
|
||||
/// 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(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit NetworkDevice(DeviceType::Enum type, QObject* parent = nullptr);
|
||||
|
||||
/// 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; };
|
||||
void setAutoconnect(bool autoconnect);
|
||||
|
||||
signals:
|
||||
void requestDisconnect();
|
||||
void requestSetAutoconnect(bool autoconnect);
|
||||
void nameChanged();
|
||||
void addressChanged();
|
||||
void connectedChanged();
|
||||
void stateChanged();
|
||||
void nmStateChanged();
|
||||
void autoconnectChanged();
|
||||
|
||||
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, bool, bAutoconnect, &NetworkDevice::autoconnectChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,13 @@
|
||||
name = "Quickshell.Networking"
|
||||
description = "Network API"
|
||||
headers = [
|
||||
"network.hpp",
|
||||
"device.hpp",
|
||||
"wifi.hpp",
|
||||
]
|
||||
-----
|
||||
This module exposes Network management APIs provided by a supported network backend.
|
||||
For now, the only backend available is the NetworkManager DBus interface.
|
||||
Both DBus and NetworkManager must be running to use it.
|
||||
|
||||
See the @@Quickshell.Networking.Networking singleton.
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "network.hpp"
|
||||
#include <utility>
|
||||
|
||||
#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"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
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)) {
|
||||
this->bStateChanging.setBinding([this] {
|
||||
auto state = this->bState.value();
|
||||
return state == NetworkState::Connecting || state == NetworkState::Disconnecting;
|
||||
});
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../core/model.hpp"
|
||||
#include "device.hpp"
|
||||
|
||||
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.
|
||||
class Network: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("BaseNetwork can only be aqcuired through network devices");
|
||||
|
||||
// clang-format off
|
||||
/// The name of the network.
|
||||
Q_PROPERTY(QString name READ name CONSTANT);
|
||||
/// True if the network is connected.
|
||||
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
|
||||
/// The connectivity state of the network.
|
||||
Q_PROPERTY(NetworkState::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);
|
||||
|
||||
[[nodiscard]] QString name() const { return this->mName; };
|
||||
QBindable<bool> bindableConnected() { return &this->bConnected; }
|
||||
QBindable<NetworkState::Enum> bindableState() { return &this->bState; }
|
||||
QBindable<bool> bindableStateChanging() { return &this->bStateChanging; }
|
||||
|
||||
signals:
|
||||
void connectedChanged();
|
||||
void stateChanged();
|
||||
void stateChangingChanged();
|
||||
|
||||
protected:
|
||||
QString mName;
|
||||
|
||||
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, bStateChanging, &Network::stateChangingChanged);
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,79 @@
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES
|
||||
CLASSNAME DBusNetworkManagerProxy
|
||||
NO_NAMESPACE TRUE
|
||||
INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.xml
|
||||
dbus_nm_backend
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES
|
||||
CLASSNAME DBusNMDeviceProxy
|
||||
NO_NAMESPACE TRUE
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.Device.xml
|
||||
dbus_nm_device
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES
|
||||
CLASSNAME DBusNMWirelessProxy
|
||||
NO_NAMESPACE TRUE
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.Device.Wireless.xml
|
||||
dbus_nm_wireless
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES
|
||||
CLASSNAME DBusNMAccessPointProxy
|
||||
NO_NAMESPACE TRUE
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.AccessPoint.xml
|
||||
dbus_nm_accesspoint
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.Settings.Connection.xml PROPERTIES
|
||||
CLASSNAME DBusNMConnectionSettingsProxy
|
||||
NO_NAMESPACE TRUE
|
||||
INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.Settings.Connection.xml
|
||||
dbus_nm_connection_settings
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.Connection.Active.xml PROPERTIES
|
||||
CLASSNAME DBusNMActiveConnectionProxy
|
||||
NO_NAMESPACE TRUE
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.Connection.Active.xml
|
||||
dbus_nm_active_connection
|
||||
)
|
||||
|
||||
qt_add_library(quickshell-network-nm STATIC
|
||||
backend.cpp
|
||||
device.cpp
|
||||
connection.cpp
|
||||
accesspoint.cpp
|
||||
wireless.cpp
|
||||
utils.cpp
|
||||
enums.hpp
|
||||
${NM_DBUS_INTERFACES}
|
||||
)
|
||||
|
||||
target_include_directories(quickshell-network-nm PUBLIC
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus)
|
||||
qs_add_link_dependencies(quickshell-network-nm quickshell-dbus)
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "accesspoint.hpp"
|
||||
|
||||
#include <qdbusconnection.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_accesspoint.h"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMAccessPoint::NMAccessPoint(const QString& path, QObject* parent): QObject(parent) {
|
||||
this->proxy = new DBusNMAccessPointProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->proxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for access point at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
&this->accessPointProperties,
|
||||
&DBusPropertyGroup::getAllFinished,
|
||||
this,
|
||||
&NMAccessPoint::loaded,
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
|
||||
this->accessPointProperties.setInterface(this->proxy);
|
||||
this->accessPointProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
bool NMAccessPoint::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||
QString NMAccessPoint::address() const { return this->proxy ? this->proxy->service() : QString(); }
|
||||
QString NMAccessPoint::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NM80211ApFlags::Enum>
|
||||
DBusDataTransform<qs::network::NM80211ApFlags::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NM80211ApFlags::Enum>(wire));
|
||||
}
|
||||
|
||||
DBusResult<qs::network::NM80211ApSecurityFlags::Enum>
|
||||
DBusDataTransform<qs::network::NM80211ApSecurityFlags::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NM80211ApSecurityFlags::Enum>(wire));
|
||||
}
|
||||
|
||||
DBusResult<qs::network::NM80211Mode::Enum>
|
||||
DBusDataTransform<qs::network::NM80211Mode::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NM80211Mode::Enum>(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_nm_accesspoint.h"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NM80211ApFlags::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NM80211ApFlags::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NM80211ApSecurityFlags::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NM80211ApSecurityFlags::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NM80211Mode::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NM80211Mode::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
/// Proxy of a /org/freedesktop/NetworkManager/AccessPoint/* object.
|
||||
class NMAccessPoint: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMAccessPoint(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
[[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; };
|
||||
|
||||
signals:
|
||||
void loaded();
|
||||
void ssidChanged(const QByteArray& ssid);
|
||||
void signalStrengthChanged(quint8 signal);
|
||||
void flagsChanged(NM80211ApFlags::Enum flags);
|
||||
void wpaFlagsChanged(NM80211ApSecurityFlags::Enum wpaFlags);
|
||||
void rsnFlagsChanged(NM80211ApSecurityFlags::Enum rsnFlags);
|
||||
void modeChanged(NM80211Mode::Enum mode);
|
||||
void securityChanged(WifiSecurityType::Enum security);
|
||||
|
||||
private:
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, QByteArray, bSsid, &NMAccessPoint::ssidChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, quint8, bSignalStrength, &NMAccessPoint::signalStrengthChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApFlags::Enum, bFlags, &NMAccessPoint::flagsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bWpaFlags, &NMAccessPoint::wpaFlagsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bRsnFlags, &NMAccessPoint::rsnFlagsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211Mode::Enum, bMode, &NMAccessPoint::modeChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, WifiSecurityType::Enum, bSecurity, &NMAccessPoint::securityChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSsid, bSsid, accessPointProperties, "Ssid");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSignalStrength, bSignalStrength, accessPointProperties, "Strength");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pFlags, bFlags, accessPointProperties, "Flags");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pWpaFlags, bWpaFlags, accessPointProperties, "WpaFlags");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pRsnFlags, bRsnFlags, accessPointProperties, "RsnFlags");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pMode, bMode, accessPointProperties, "Mode");
|
||||
// clang-format on
|
||||
|
||||
DBusNMAccessPointProxy* proxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "backend.hpp"
|
||||
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbusmetatype.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../device.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_nm_backend.h"
|
||||
#include "dbus_nm_device.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "wireless.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
|
||||
qDBusRegisterMetaType<ConnectionSettingsMap>();
|
||||
|
||||
auto bus = QDBusConnection::systemBus();
|
||||
if (!bus.isConnected()) {
|
||||
qCWarning(
|
||||
logNetworkManager
|
||||
) << "Could not connect to DBus. NetworkManager backend will not work.";
|
||||
return;
|
||||
}
|
||||
|
||||
this->proxy = new DBusNetworkManagerProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
"/org/freedesktop/NetworkManager",
|
||||
bus,
|
||||
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();
|
||||
}
|
||||
|
||||
void NetworkManager::registerDevices() {
|
||||
auto pending = this->proxy->GetAllDevices();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<QList<QDBusObjectPath>> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager) << "Failed to get devices: " << reply.error().message();
|
||||
} else {
|
||||
for (const QDBusObjectPath& devicePath: reply.value()) {
|
||||
this->registerDevice(devicePath.path());
|
||||
}
|
||||
}
|
||||
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NetworkManager::registerDevice(const QString& path) {
|
||||
if (this->mDevices.contains(path)) {
|
||||
qCDebug(logNetworkManager) << "Skipping duplicate registration of device" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* temp = new DBusNMDeviceProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
auto callback = [this, path, temp](uint value, const QDBusError& error) {
|
||||
if (error.isValid()) {
|
||||
qCWarning(logNetworkManager) << "Failed to get device type:" << error;
|
||||
} else {
|
||||
auto type = static_cast<qs::network::NMDeviceType::Enum>(value);
|
||||
NMDevice* dev = nullptr;
|
||||
this->mDevices.insert(path, nullptr);
|
||||
|
||||
switch (type) {
|
||||
case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (dev) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
temp->deleteLater();
|
||||
}
|
||||
};
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
|
||||
auto iter = this->mDevices.find(path.path());
|
||||
if (iter == this->mDevices.end()) {
|
||||
qCWarning(logNetworkManager) << "Sent removal signal for" << path.path()
|
||||
<< "which is not registered.";
|
||||
} else {
|
||||
auto* dev = iter.value();
|
||||
this->mDevices.erase(iter);
|
||||
if (dev) {
|
||||
this->removeFrontendDevice(dev);
|
||||
delete dev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::activateConnection(
|
||||
const QDBusObjectPath& connPath,
|
||||
const QDBusObjectPath& devPath
|
||||
) {
|
||||
auto pending = this->proxy->ActivateConnection(connPath, devPath, QDBusObjectPath("/"));
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<QDBusObjectPath> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager) << "Failed to activate connection:" << reply.error().message();
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NetworkManager::addAndActivateConnection(
|
||||
const ConnectionSettingsMap& settings,
|
||||
const QDBusObjectPath& devPath,
|
||||
const QDBusObjectPath& specificObjectPath
|
||||
) {
|
||||
auto pending = this->proxy->AddAndActivateConnection(settings, devPath, specificObjectPath);
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<QDBusObjectPath, QDBusObjectPath> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to add and activate connection:" << reply.error().message();
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NetworkManager::setWifiEnabled(bool enabled) {
|
||||
if (enabled == this->bWifiEnabled) return;
|
||||
this->bWifiEnabled = enabled;
|
||||
this->pWifiEnabled.write();
|
||||
}
|
||||
|
||||
bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); };
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qhash.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "dbus_nm_backend.h"
|
||||
#include "device.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
class NetworkManager: public NetworkBackend {
|
||||
Q_OBJECT;
|
||||
|
||||
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; };
|
||||
|
||||
signals:
|
||||
void deviceAdded(NetworkDevice* device);
|
||||
void deviceRemoved(NetworkDevice* device);
|
||||
void wifiEnabledChanged(bool enabled);
|
||||
void wifiHardwareEnabledChanged(bool enabled);
|
||||
|
||||
public slots:
|
||||
void setWifiEnabled(bool enabled);
|
||||
|
||||
private slots:
|
||||
void onDevicePathAdded(const QDBusObjectPath& path);
|
||||
void onDevicePathRemoved(const QDBusObjectPath& path);
|
||||
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
|
||||
void addAndActivateConnection(
|
||||
const ConnectionSettingsMap& 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);
|
||||
|
||||
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");
|
||||
// clang-format on
|
||||
DBusNetworkManagerProxy* proxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,151 @@
|
||||
#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,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.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 {
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NMConnectionState::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NMConnectionState::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
explicit NMActiveConnection(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
[[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; };
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qmap.h>
|
||||
#include <qstring.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
using ConnectionSettingsMap = QMap<QString, QVariantMap>;
|
||||
Q_DECLARE_METATYPE(ConnectionSettingsMap);
|
||||
@@ -0,0 +1,143 @@
|
||||
#include "device.hpp"
|
||||
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qset.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../device.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "dbus_nm_device.h"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) {
|
||||
this->deviceProxy = new DBusNMDeviceProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->deviceProxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for device at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(this, &NMDevice::availableConnectionPathsChanged, this, &NMDevice::onAvailableConnectionPathsChanged);
|
||||
QObject::connect(this, &NMDevice::activeConnectionPathChanged, this, &NMDevice::onActiveConnectionPathChanged);
|
||||
// clang-format on
|
||||
|
||||
this->deviceProperties.setInterface(this->deviceProxy);
|
||||
this->deviceProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
|
||||
const QString stringPath = path.path();
|
||||
|
||||
// Remove old active connection
|
||||
if (this->mActiveConnection) {
|
||||
QObject::disconnect(this->mActiveConnection, nullptr, this, nullptr);
|
||||
delete this->mActiveConnection;
|
||||
this->mActiveConnection = nullptr;
|
||||
}
|
||||
|
||||
// Create new active connection
|
||||
if (stringPath != "/") {
|
||||
auto* active = new NMActiveConnection(stringPath, this);
|
||||
if (!active->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << stringPath;
|
||||
delete active;
|
||||
} else {
|
||||
this->mActiveConnection = active;
|
||||
QObject::connect(
|
||||
active,
|
||||
&NMActiveConnection::loaded,
|
||||
this,
|
||||
[this, active]() { emit this->activeConnectionLoaded(active); },
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NMDevice::onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths) {
|
||||
QSet<QString> newPathSet;
|
||||
for (const QDBusObjectPath& path: paths) {
|
||||
newPathSet.insert(path.path());
|
||||
}
|
||||
const auto existingPaths = this->mConnections.keys();
|
||||
const QSet<QString> existingPathSet(existingPaths.begin(), existingPaths.end());
|
||||
|
||||
const auto addedConnections = newPathSet - existingPathSet;
|
||||
const auto removedConnections = existingPathSet - newPathSet;
|
||||
|
||||
for (const QString& path: addedConnections) {
|
||||
this->registerConnection(path);
|
||||
}
|
||||
for (const QString& path: removedConnections) {
|
||||
auto* connection = this->mConnections.take(path);
|
||||
if (!connection) {
|
||||
qCDebug(logNetworkManager) << "Sent removal signal for" << path << "which is not registered.";
|
||||
} else {
|
||||
delete connection;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void NMDevice::registerConnection(const QString& path) {
|
||||
auto* connection = new NMConnectionSettings(path, this);
|
||||
if (!connection->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
||||
delete connection;
|
||||
} else {
|
||||
this->mConnections.insert(path, connection);
|
||||
QObject::connect(
|
||||
connection,
|
||||
&NMConnectionSettings::loaded,
|
||||
this,
|
||||
[this, connection]() { emit this->connectionLoaded(connection); },
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void NMDevice::disconnect() { this->deviceProxy->Disconnect(); }
|
||||
|
||||
void NMDevice::setAutoconnect(bool autoconnect) {
|
||||
if (autoconnect == this->bAutoconnect) return;
|
||||
this->bAutoconnect = autoconnect;
|
||||
this->pAutoconnect.write();
|
||||
}
|
||||
|
||||
bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); }
|
||||
QString NMDevice::address() const {
|
||||
return this->deviceProxy ? this->deviceProxy->service() : QString();
|
||||
}
|
||||
QString NMDevice::path() const { return this->deviceProxy ? this->deviceProxy->path() : QString(); }
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NMDeviceState::Enum>
|
||||
DBusDataTransform<qs::network::NMDeviceState::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMDeviceState::Enum>(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qhash.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "dbus_nm_device.h"
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NMDeviceState::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NMDeviceState::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
|
||||
// Only the members from the org.freedesktop.NetworkManager.Device interface.
|
||||
// Owns the lifetime of NMActiveConnection(s) and NMConnectionSetting(s).
|
||||
class NMDevice: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMDevice(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
[[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; };
|
||||
|
||||
signals:
|
||||
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
|
||||
void addAndActivateConnection(
|
||||
const ConnectionSettingsMap& settings,
|
||||
const QDBusObjectPath& devPath,
|
||||
const QDBusObjectPath& apPath
|
||||
);
|
||||
void connectionLoaded(NMConnectionSettings* connection);
|
||||
void connectionRemoved(NMConnectionSettings* connection);
|
||||
void availableConnectionPathsChanged(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 autoconnectChanged(bool autoconnect);
|
||||
|
||||
public slots:
|
||||
void disconnect();
|
||||
void setAutoconnect(bool autoconnect);
|
||||
|
||||
private slots:
|
||||
void onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths);
|
||||
void onActiveConnectionPathChanged(const QDBusObjectPath& path);
|
||||
|
||||
private:
|
||||
void registerConnection(const QString& path);
|
||||
|
||||
QHash<QString, NMConnectionSettings*> mConnections;
|
||||
NMActiveConnection* mActiveConnection = nullptr;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bInterface, &NMDevice::interfaceChanged);
|
||||
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, bool, bAutoconnect, &NMDevice::autoconnectChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableConnectionPathsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface");
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pAddress, bHwAddress, deviceProperties, "HwAddress");
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pManaged, bManaged, deviceProperties, "Managed");
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pState, bState, deviceProperties, "State");
|
||||
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");
|
||||
// clang-format on
|
||||
|
||||
DBusNMDeviceProxy* deviceProxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
// Indicates the type of hardware represented by a device object.
|
||||
class NMDeviceType: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Ethernet = 1,
|
||||
Wifi = 2,
|
||||
Unused1 = 3,
|
||||
Unused2 = 4,
|
||||
Bluetooth = 5,
|
||||
OlpcMesh = 6,
|
||||
Wimax = 7,
|
||||
Modem = 8,
|
||||
InfiniBand = 9,
|
||||
Bond = 10,
|
||||
Vlan = 11,
|
||||
Adsl = 12,
|
||||
Bridge = 13,
|
||||
Generic = 14,
|
||||
Team = 15,
|
||||
Tun = 16,
|
||||
IpTunnel = 17,
|
||||
MacVlan = 18,
|
||||
VxLan = 19,
|
||||
Veth = 20,
|
||||
MacSec = 21,
|
||||
Dummy = 22,
|
||||
Ppp = 23,
|
||||
OvsInterface = 24,
|
||||
OvsPort = 25,
|
||||
OvsBridge = 26,
|
||||
Wpan = 27,
|
||||
Lowpan = 28,
|
||||
Wireguard = 29,
|
||||
WifiP2P = 30,
|
||||
Vrf = 31,
|
||||
Loopback = 32,
|
||||
Hsr = 33,
|
||||
IpVlan = 34,
|
||||
};
|
||||
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 {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint16 {
|
||||
None = 0,
|
||||
CipherWep40 = 1,
|
||||
CipherWep104 = 2,
|
||||
CipherTkip = 4,
|
||||
CipherCcmp = 8,
|
||||
Wpa = 16,
|
||||
Rsn = 32,
|
||||
Ap = 64,
|
||||
Adhoc = 128,
|
||||
FreqValid = 256,
|
||||
Freq2Ghz = 512,
|
||||
Freq5Ghz = 1024,
|
||||
Freq6Ghz = 2048,
|
||||
Mesh = 4096,
|
||||
IbssRsn = 8192,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// Indicates the 802.11 mode an access point is currently in.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211Mode.
|
||||
class NM80211Mode: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Adhoc = 1,
|
||||
Infra = 2,
|
||||
Ap = 3,
|
||||
Mesh = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// 802.11 access point flags.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags.
|
||||
class NM80211ApFlags: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
Privacy = 1,
|
||||
Wps = 2,
|
||||
WpsPbc = 4,
|
||||
WpsPin = 8,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// 802.11 access point security and authentication flags.
|
||||
// These flags describe the current system requirements of an access point as determined from the access point's beacon.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags.
|
||||
class NM80211ApSecurityFlags: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint16 {
|
||||
None = 0,
|
||||
PairWep40 = 1,
|
||||
PairWep104 = 2,
|
||||
PairTkip = 4,
|
||||
PairCcmp = 8,
|
||||
GroupWep40 = 16,
|
||||
GroupWep104 = 32,
|
||||
GroupTkip = 64,
|
||||
GroupCcmp = 128,
|
||||
KeyMgmtPsk = 256,
|
||||
KeyMgmt8021x = 512,
|
||||
KeyMgmtSae = 1024,
|
||||
KeyMgmtOwe = 2048,
|
||||
KeyMgmtOweTm = 4096,
|
||||
KeyMgmtEapSuiteB192 = 8192,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// Indicates the state of a connection to a specific network while it is starting, connected, or disconnected from that network.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionState.
|
||||
class NMConnectionState: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Activating = 1,
|
||||
Activated = 2,
|
||||
Deactivating = 3,
|
||||
Deactivated = 4
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,4 @@
|
||||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.AccessPoint">
|
||||
</interface>
|
||||
</node>
|
||||
@@ -0,0 +1,8 @@
|
||||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Connection.Active">
|
||||
<signal name="StateChanged">
|
||||
<arg name="state" type="u"/>
|
||||
<arg name="reason" type="u"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
@@ -0,0 +1,15 @@
|
||||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Device.Wireless">
|
||||
<method name="GetAllAccessPoints" type="ao"/>
|
||||
<method name="RequestScan">
|
||||
<arg name="options" type="a{sv}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
|
||||
</method>
|
||||
<signal name="AccessPointAdded">
|
||||
<arg name="access_point" type="o"/>
|
||||
</signal>
|
||||
<signal name="AccessPointRemoved">
|
||||
<arg name="access_point" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
@@ -0,0 +1,5 @@
|
||||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Device">
|
||||
<method name="Disconnect"/>
|
||||
</interface>
|
||||
</node>
|
||||
@@ -0,0 +1,11 @@
|
||||
<node>
|
||||
<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"/>
|
||||
</method>
|
||||
<method name="Delete"/>
|
||||
<signal name="Updated"/>
|
||||
<signal name="Removed"/>
|
||||
</interface>
|
||||
</node>
|
||||
@@ -0,0 +1,27 @@
|
||||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager">
|
||||
<method name="GetAllDevices">
|
||||
<arg direction="out" type="ao" name="devices"/>
|
||||
</method>
|
||||
<method name="ActivateConnection">
|
||||
<arg direction="in" type="o" name="connection"/>
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="in" type="o" name="specific_object"/>
|
||||
<arg direction="out" type="o" name="active_connection"/>
|
||||
</method>
|
||||
<method name="AddAndActivateConnection">
|
||||
<arg direction="in" type="a{sa{sv}}" name="connection"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="ConnectionSettingsMap"/>
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="in" type="o" name="specific_object"/>
|
||||
<arg direction="out" type="o" name="path"/>
|
||||
<arg direction="out" type="o" name="active_connection"/>
|
||||
</method>
|
||||
<signal name="DeviceAdded">
|
||||
<arg type="o" name="device_path"/>
|
||||
</signal>
|
||||
<signal name="DeviceRemoved">
|
||||
<arg type="o" name="device_path"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
@@ -0,0 +1,248 @@
|
||||
#include "utils.hpp"
|
||||
|
||||
// We depend on non-std Linux extensions that ctime doesn't put in the global namespace
|
||||
// NOLINTNEXTLINE(modernize-deprecated-headers)
|
||||
#include <time.h>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdatetime.h>
|
||||
#include <qdbusservicewatcher.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_types.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings) {
|
||||
const QVariantMap& security = settings.value("802-11-wireless-security");
|
||||
if (security.isEmpty()) {
|
||||
return WifiSecurityType::Open;
|
||||
};
|
||||
|
||||
const QString keyMgmt = security["key-mgmt"].toString();
|
||||
const QString authAlg = security["auth-alg"].toString();
|
||||
const QList<QVariant> proto = security["proto"].toList();
|
||||
|
||||
if (keyMgmt == "none") {
|
||||
return WifiSecurityType::StaticWep;
|
||||
} else if (keyMgmt == "ieee8021x") {
|
||||
if (authAlg == "leap") {
|
||||
return WifiSecurityType::Leap;
|
||||
} else {
|
||||
return WifiSecurityType::DynamicWep;
|
||||
}
|
||||
} else if (keyMgmt == "wpa-psk") {
|
||||
if (proto.contains("wpa") && proto.contains("rsn")) return WifiSecurityType::WpaPsk;
|
||||
return WifiSecurityType::Wpa2Psk;
|
||||
} else if (keyMgmt == "wpa-eap") {
|
||||
if (proto.contains("wpa") && proto.contains("rsn")) return WifiSecurityType::WpaEap;
|
||||
return WifiSecurityType::Wpa2Eap;
|
||||
} else if (keyMgmt == "sae") {
|
||||
return WifiSecurityType::Sae;
|
||||
} else if (keyMgmt == "wpa-eap-suite-b-192") {
|
||||
return WifiSecurityType::Wpa3SuiteB192;
|
||||
}
|
||||
return WifiSecurityType::Open;
|
||||
}
|
||||
|
||||
bool deviceSupportsApCiphers(
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
NM80211ApSecurityFlags::Enum apFlags,
|
||||
WifiSecurityType::Enum type
|
||||
) {
|
||||
bool havePair = false;
|
||||
bool haveGroup = false;
|
||||
// Device needs to support at least one pairwise and one group cipher
|
||||
|
||||
if (type == WifiSecurityType::StaticWep) {
|
||||
// Static WEP only uses group ciphers
|
||||
havePair = true;
|
||||
} else {
|
||||
if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::PairWep40) {
|
||||
havePair = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::PairWep104)
|
||||
{
|
||||
havePair = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::PairTkip) {
|
||||
havePair = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::PairCcmp) {
|
||||
havePair = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::GroupWep40) {
|
||||
haveGroup = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::GroupWep104)
|
||||
{
|
||||
haveGroup = true;
|
||||
}
|
||||
if (type != WifiSecurityType::StaticWep) {
|
||||
if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::GroupTkip) {
|
||||
haveGroup = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::GroupCcmp) {
|
||||
haveGroup = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (havePair && haveGroup);
|
||||
}
|
||||
|
||||
bool securityIsValid(
|
||||
WifiSecurityType::Enum type,
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
bool adhoc,
|
||||
NM80211ApFlags::Enum apFlags,
|
||||
NM80211ApSecurityFlags::Enum apWpa,
|
||||
NM80211ApSecurityFlags::Enum apRsn
|
||||
) {
|
||||
switch (type) {
|
||||
case WifiSecurityType::Open:
|
||||
if (apFlags & NM80211ApFlags::Privacy) return false;
|
||||
if (apWpa || apRsn) return false;
|
||||
break;
|
||||
case WifiSecurityType::Leap:
|
||||
if (adhoc) return false;
|
||||
case WifiSecurityType::StaticWep:
|
||||
if (!(apFlags & NM80211ApFlags::Privacy)) return false;
|
||||
if (apWpa || apRsn) {
|
||||
if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::StaticWep)) {
|
||||
if (!deviceSupportsApCiphers(caps, apRsn, WifiSecurityType::StaticWep)) return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WifiSecurityType::DynamicWep:
|
||||
if (adhoc) return false;
|
||||
if (apRsn || !(apFlags & NM80211ApFlags::Privacy)) return false;
|
||||
if (apWpa) {
|
||||
if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) return false;
|
||||
if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::DynamicWep)) return false;
|
||||
}
|
||||
break;
|
||||
case WifiSecurityType::WpaPsk:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Wpa)) return false;
|
||||
if (apWpa & NM80211ApSecurityFlags::KeyMgmtPsk) {
|
||||
if (apWpa & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) {
|
||||
return true;
|
||||
}
|
||||
if (apWpa & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case WifiSecurityType::Wpa2Psk:
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (adhoc) {
|
||||
if (!(caps & NMWirelessCapabilities::IbssRsn)) return false;
|
||||
if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (apRsn & NM80211ApSecurityFlags::KeyMgmtPsk) {
|
||||
if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) {
|
||||
return true;
|
||||
}
|
||||
if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case WifiSecurityType::WpaEap:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Wpa)) return false;
|
||||
if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) return false;
|
||||
if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::WpaEap)) return false;
|
||||
break;
|
||||
case WifiSecurityType::Wpa2Eap:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (!(apRsn & NM80211ApSecurityFlags::KeyMgmt8021x)) return false;
|
||||
if (!deviceSupportsApCiphers(caps, apRsn, WifiSecurityType::Wpa2Eap)) return false;
|
||||
break;
|
||||
case WifiSecurityType::Sae:
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (adhoc) {
|
||||
if (!(caps & NMWirelessCapabilities::IbssRsn)) return false;
|
||||
if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (apRsn & NM80211ApSecurityFlags::KeyMgmtSae) {
|
||||
if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) {
|
||||
return true;
|
||||
}
|
||||
if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case WifiSecurityType::Owe:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (!(apRsn & NM80211ApSecurityFlags::KeyMgmtOwe)
|
||||
&& !(apRsn & NM80211ApSecurityFlags::KeyMgmtOweTm))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case WifiSecurityType::Wpa3SuiteB192:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (!(apRsn & NM80211ApSecurityFlags::KeyMgmtEapSuiteB192)) return false;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
WifiSecurityType::Enum findBestWirelessSecurity(
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
bool adHoc,
|
||||
NM80211ApFlags::Enum apFlags,
|
||||
NM80211ApSecurityFlags::Enum apWpa,
|
||||
NM80211ApSecurityFlags::Enum apRsn
|
||||
) {
|
||||
// Loop through security types from most to least secure since the enum
|
||||
// values are sequential and in priority order (0-10, excluding Unknown=11)
|
||||
for (int i = WifiSecurityType::Wpa3SuiteB192; i <= WifiSecurityType::Open; ++i) {
|
||||
auto type = static_cast<WifiSecurityType::Enum>(i);
|
||||
if (securityIsValid(type, caps, adHoc, apFlags, apWpa, apRsn)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return WifiSecurityType::Unknown;
|
||||
}
|
||||
|
||||
// NOLINTBEGIN
|
||||
QDateTime clockBootTimeToDateTime(qint64 clockBootTime) {
|
||||
clockid_t clkId = CLOCK_BOOTTIME;
|
||||
struct timespec tp {};
|
||||
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
int r = clock_gettime(clkId, &tp);
|
||||
if (r == -1 && errno == EINVAL) {
|
||||
clkId = CLOCK_MONOTONIC;
|
||||
r = clock_gettime(clkId, &tp);
|
||||
}
|
||||
|
||||
// Convert to milliseconds
|
||||
const qint64 nowInMs = tp.tv_sec * 1000 + tp.tv_nsec / 1000000;
|
||||
|
||||
// Return a QDateTime of the millisecond diff
|
||||
const qint64 offset = clockBootTime - nowInMs;
|
||||
return QDateTime::fromMSecsSinceEpoch(now.toMSecsSinceEpoch() + offset);
|
||||
}
|
||||
// NOLINTEND
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusservicewatcher.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_types.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings);
|
||||
|
||||
bool deviceSupportsApCiphers(
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
NM80211ApSecurityFlags::Enum apFlags,
|
||||
WifiSecurityType::Enum type
|
||||
);
|
||||
|
||||
// In sync with NetworkManager/libnm-core/nm-utils.c:nm_utils_security_valid()
|
||||
// Given a set of device capabilities, and a desired security type to check
|
||||
// against, determines whether the combination of device, desired security type,
|
||||
// and AP capabilities intersect.
|
||||
bool securityIsValid(
|
||||
WifiSecurityType::Enum type,
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
bool adhoc,
|
||||
NM80211ApFlags::Enum apFlags,
|
||||
NM80211ApSecurityFlags::Enum apWpa,
|
||||
NM80211ApSecurityFlags::Enum apRsn
|
||||
);
|
||||
|
||||
WifiSecurityType::Enum findBestWirelessSecurity(
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
bool adHoc,
|
||||
NM80211ApFlags::Enum apFlags,
|
||||
NM80211ApSecurityFlags::Enum apWpa,
|
||||
NM80211ApSecurityFlags::Enum apRsn
|
||||
);
|
||||
|
||||
QDateTime clockBootTimeToDateTime(qint64 clockBootTime);
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,457 @@
|
||||
#include "wireless.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qdatetime.h>
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qlist.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 "../network.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "accesspoint.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "dbus_nm_wireless.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMWirelessNetwork::NMWirelessNetwork(QString ssid, QObject* parent)
|
||||
: QObject(parent)
|
||||
, mSsid(std::move(ssid))
|
||||
, bKnown(false)
|
||||
, bSecurity(WifiSecurityType::Unknown)
|
||||
, bReason(NMConnectionStateReason::None)
|
||||
, bState(NMConnectionState::Deactivated) {}
|
||||
|
||||
void NMWirelessNetwork::updateReferenceConnection() {
|
||||
// If the network has no connections, the reference is nullptr.
|
||||
if (this->mConnections.isEmpty()) {
|
||||
this->mReferenceConn = nullptr;
|
||||
this->bSecurity = WifiSecurityType::Unknown;
|
||||
// Set security back to reference AP.
|
||||
if (this->mReferenceAp) {
|
||||
this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); });
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// If the network has an active connection, use it as the reference.
|
||||
if (this->mActiveConnection) {
|
||||
auto* conn = this->mConnections.value(this->mActiveConnection->connection().path());
|
||||
if (conn && conn != this->mReferenceConn) {
|
||||
this->mReferenceConn = conn;
|
||||
this->bSecurity.setBinding([conn]() { return conn->security(); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, choose the connection with the strongest security settings.
|
||||
NMConnectionSettings* selectedConn = nullptr;
|
||||
for (auto* conn: this->mConnections.values()) {
|
||||
if (!selectedConn || conn->security() > selectedConn->security()) {
|
||||
selectedConn = conn;
|
||||
}
|
||||
}
|
||||
if (this->mReferenceConn != selectedConn) {
|
||||
this->mReferenceConn = selectedConn;
|
||||
this->bSecurity.setBinding([selectedConn]() { return selectedConn->security(); });
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessNetwork::updateReferenceAp() {
|
||||
// If the network has no APs, the reference is a nullptr.
|
||||
if (this->mAccessPoints.isEmpty()) {
|
||||
this->mReferenceAp = 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->mReferenceAp != selectedAp) {
|
||||
this->mReferenceAp = selectedAp;
|
||||
this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); });
|
||||
// Reference AP is used for security when there's no connection settings.
|
||||
if (!this->mReferenceConn) {
|
||||
this->bSecurity.setBinding([selectedAp]() { return selectedAp->security(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if (this->mAccessPoints.isEmpty() && this->mConnections.isEmpty()) emit this->disappeared();
|
||||
}
|
||||
};
|
||||
// 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::addConnection(NMConnectionSettings* conn) {
|
||||
if (this->mConnections.contains(conn->path())) return;
|
||||
this->mConnections.insert(conn->path(), conn);
|
||||
auto onDestroyed = [this, conn]() {
|
||||
if (this->mConnections.take(conn->path())) {
|
||||
this->updateReferenceConnection();
|
||||
if (this->mConnections.isEmpty()) this->bKnown = false;
|
||||
if (this->mAccessPoints.isEmpty() && this->mConnections.isEmpty()) emit this->disappeared();
|
||||
}
|
||||
};
|
||||
// clang-format off
|
||||
QObject::connect(conn, &NMConnectionSettings::securityChanged, this, &NMWirelessNetwork::updateReferenceConnection);
|
||||
QObject::connect(conn, &NMConnectionSettings::destroyed, this, onDestroyed);
|
||||
// clang-format on
|
||||
this->bKnown = true;
|
||||
this->updateReferenceConnection();
|
||||
};
|
||||
|
||||
void NMWirelessNetwork::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->updateReferenceConnection();
|
||||
this->bState = NMConnectionState::Deactivated;
|
||||
this->bReason = NMConnectionStateReason::None;
|
||||
}
|
||||
};
|
||||
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
|
||||
this->updateReferenceConnection();
|
||||
};
|
||||
|
||||
void NMWirelessNetwork::forget() {
|
||||
if (this->mConnections.isEmpty()) return;
|
||||
for (auto* conn: this->mConnections.values()) {
|
||||
conn->forget();
|
||||
}
|
||||
}
|
||||
|
||||
NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
|
||||
: NMDevice(path, parent)
|
||||
, mScanTimer(this) {
|
||||
this->wirelessProxy = new DBusNMWirelessProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->wirelessProxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for wireless device at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
&this->wirelessProperties,
|
||||
&DBusPropertyGroup::getAllFinished,
|
||||
this,
|
||||
&NMWirelessDevice::initWireless,
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
|
||||
QObject::connect(&this->mScanTimer, &QTimer::timeout, this, &NMWirelessDevice::onScanTimeout);
|
||||
this->mScanTimer.setSingleShot(true);
|
||||
|
||||
this->wirelessProperties.setInterface(this->wirelessProxy);
|
||||
this->wirelessProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void NMWirelessDevice::initWireless() {
|
||||
// clang-format off
|
||||
QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointAdded, this, &NMWirelessDevice::onAccessPointAdded);
|
||||
QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointRemoved, this, &NMWirelessDevice::onAccessPointRemoved);
|
||||
QObject::connect(this, &NMWirelessDevice::accessPointLoaded, this, &NMWirelessDevice::onAccessPointLoaded);
|
||||
QObject::connect(this, &NMWirelessDevice::connectionLoaded, this, &NMWirelessDevice::onConnectionLoaded);
|
||||
QObject::connect(this, &NMWirelessDevice::activeConnectionLoaded, this, &NMWirelessDevice::onActiveConnectionLoaded);
|
||||
QObject::connect(this, &NMWirelessDevice::scanningChanged, this, &NMWirelessDevice::onScanningChanged);
|
||||
// clang-format on
|
||||
this->registerAccessPoints();
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onAccessPointAdded(const QDBusObjectPath& path) {
|
||||
this->registerAccessPoint(path.path());
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onAccessPointRemoved(const QDBusObjectPath& path) {
|
||||
auto* ap = this->mAccessPoints.take(path.path());
|
||||
if (!ap) {
|
||||
qCDebug(logNetworkManager) << "Sent removal signal for" << path.path()
|
||||
<< "which is not registered.";
|
||||
return;
|
||||
}
|
||||
delete ap;
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onAccessPointLoaded(NMAccessPoint* ap) {
|
||||
const QString ssid = ap->ssid();
|
||||
if (!ssid.isEmpty()) {
|
||||
auto mode = ap->mode();
|
||||
if (mode == NM80211Mode::Infra) {
|
||||
auto* net = this->mNetworks.value(ssid);
|
||||
if (!net) net = this->registerNetwork(ssid);
|
||||
net->addAccessPoint(ap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onConnectionLoaded(NMConnectionSettings* conn) {
|
||||
const ConnectionSettingsMap& settings = conn->settings();
|
||||
// Filter connections that aren't wireless or have missing settings
|
||||
if (settings["connection"]["id"].toString().isEmpty()
|
||||
|| settings["connection"]["uuid"].toString().isEmpty()
|
||||
|| !settings.contains("802-11-wireless")
|
||||
|| settings["802-11-wireless"]["ssid"].toString().isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ssid = settings["802-11-wireless"]["ssid"].toString();
|
||||
const auto mode = settings["802-11-wireless"]["mode"].toString();
|
||||
|
||||
if (mode == "infrastructure") {
|
||||
auto* net = this->mNetworks.value(ssid);
|
||||
if (!net) net = this->registerNetwork(ssid);
|
||||
net->addConnection(conn);
|
||||
|
||||
// Check for active connections that loaded before their respective connection settings
|
||||
auto* active = this->activeConnection();
|
||||
if (active && conn->path() == active->connection().path()) {
|
||||
net->addActiveConnection(active);
|
||||
}
|
||||
}
|
||||
// TODO: Create hotspots when mode == "ap"
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onActiveConnectionLoaded(NMActiveConnection* active) {
|
||||
// Find an exisiting network with connection settings that matches the active
|
||||
const QString activeConnPath = active->connection().path();
|
||||
for (const auto& net: this->mNetworks.values()) {
|
||||
for (auto* conn: net->connections()) {
|
||||
if (activeConnPath == conn->path()) {
|
||||
net->addActiveConnection(active);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onScanTimeout() {
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
const QDateTime lastScan = this->bLastScan;
|
||||
const QDateTime lastScanRequest = this->mLastScanRequest;
|
||||
|
||||
if (lastScan.isValid() && lastScan.msecsTo(now) < this->mScanIntervalMs) {
|
||||
// Rate limit if backend last scan property updated within the interval
|
||||
auto diff = static_cast<int>(this->mScanIntervalMs - lastScan.msecsTo(now));
|
||||
this->mScanTimer.start(diff);
|
||||
} else if (lastScanRequest.isValid() && lastScanRequest.msecsTo(now) < this->mScanIntervalMs) {
|
||||
// Rate limit if frontend changes scanner state within the interval
|
||||
auto diff = static_cast<int>(this->mScanIntervalMs - lastScanRequest.msecsTo(now));
|
||||
this->mScanTimer.start(diff);
|
||||
} else {
|
||||
this->wirelessProxy->RequestScan({});
|
||||
this->mLastScanRequest = now;
|
||||
this->mScanTimer.start(this->mScanIntervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onScanningChanged(bool scanning) {
|
||||
scanning ? this->onScanTimeout() : this->mScanTimer.stop();
|
||||
}
|
||||
|
||||
void NMWirelessDevice::registerAccessPoints() {
|
||||
auto pending = this->wirelessProxy->GetAllAccessPoints();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<QList<QDBusObjectPath>> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to get all access points: " << reply.error().message();
|
||||
} else {
|
||||
for (const QDBusObjectPath& devicePath: reply.value()) {
|
||||
this->registerAccessPoint(devicePath.path());
|
||||
}
|
||||
}
|
||||
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NMWirelessDevice::registerAccessPoint(const QString& path) {
|
||||
if (this->mAccessPoints.contains(path)) {
|
||||
qCDebug(logNetworkManager) << "Skipping duplicate registration of access point" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* ap = new NMAccessPoint(path, this);
|
||||
|
||||
if (!ap->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
||||
delete ap;
|
||||
return;
|
||||
}
|
||||
|
||||
this->mAccessPoints.insert(path, ap);
|
||||
QObject::connect(
|
||||
ap,
|
||||
&NMAccessPoint::loaded,
|
||||
this,
|
||||
[this, ap]() { emit this->accessPointLoaded(ap); },
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
ap->bindableSecurity().setBinding([this, ap]() {
|
||||
return findBestWirelessSecurity(
|
||||
this->bCapabilities,
|
||||
ap->mode() == NM80211Mode::Adhoc,
|
||||
ap->flags(),
|
||||
ap->wpaFlags(),
|
||||
ap->rsnFlags()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) {
|
||||
auto* net = new NMWirelessNetwork(ssid, this);
|
||||
|
||||
// To avoid exposing outdated state to the frontend, filter the backend networks to only show
|
||||
// the known or currently connected networks when the scanner is off.
|
||||
auto visible = [this, net]() {
|
||||
return this->bScanning || net->state() == NMConnectionState::Activated || net->known();
|
||||
};
|
||||
auto onVisibilityChanged = [this, net](bool visible) {
|
||||
visible ? this->registerFrontendNetwork(net) : this->removeFrontendNetwork(net);
|
||||
};
|
||||
|
||||
net->bindableVisible().setBinding(visible);
|
||||
net->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); });
|
||||
QObject::connect(net, &NMWirelessNetwork::disappeared, this, &NMWirelessDevice::removeNetwork);
|
||||
QObject::connect(net, &NMWirelessNetwork::visibilityChanged, this, onVisibilityChanged);
|
||||
|
||||
this->mNetworks.insert(ssid, net);
|
||||
if (net->visible()) this->registerFrontendNetwork(net);
|
||||
return net;
|
||||
}
|
||||
|
||||
void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) {
|
||||
auto ssid = net->ssid();
|
||||
auto* frontendNet = new WifiNetwork(ssid, net);
|
||||
|
||||
// Bind WifiNetwork to NMWirelessNetwork
|
||||
auto translateSignal = [net]() { return net->signalStrength() / 100.0; };
|
||||
auto translateState = [net]() { return net->state() == NMConnectionState::Activated; };
|
||||
frontendNet->bindableSignalStrength().setBinding(translateSignal);
|
||||
frontendNet->bindableConnected().setBinding(translateState);
|
||||
frontendNet->bindableKnown().setBinding([net]() { return net->known(); });
|
||||
frontendNet->bindableNmReason().setBinding([net]() { return net->reason(); });
|
||||
frontendNet->bindableSecurity().setBinding([net]() { return net->security(); });
|
||||
frontendNet->bindableState().setBinding([net]() {
|
||||
return static_cast<NetworkState::Enum>(net->state());
|
||||
});
|
||||
|
||||
QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() {
|
||||
if (net->referenceConnection()) {
|
||||
emit this->activateConnection(
|
||||
QDBusObjectPath(net->referenceConnection()->path()),
|
||||
QDBusObjectPath(this->path())
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (net->referenceAp()) {
|
||||
emit this->addAndActivateConnection(
|
||||
ConnectionSettingsMap(),
|
||||
QDBusObjectPath(this->path()),
|
||||
QDBusObjectPath(net->referenceAp()->path())
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
frontendNet,
|
||||
&WifiNetwork::requestDisconnect,
|
||||
this,
|
||||
&NMWirelessDevice::disconnect
|
||||
);
|
||||
|
||||
QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget);
|
||||
|
||||
this->mFrontendNetworks.insert(ssid, frontendNet);
|
||||
emit this->networkAdded(frontendNet);
|
||||
}
|
||||
|
||||
void NMWirelessDevice::removeFrontendNetwork(NMWirelessNetwork* net) {
|
||||
auto* frontendNet = this->mFrontendNetworks.take(net->ssid());
|
||||
if (frontendNet) {
|
||||
emit this->networkRemoved(frontendNet);
|
||||
frontendNet->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessDevice::removeNetwork() {
|
||||
auto* net = qobject_cast<NMWirelessNetwork*>(this->sender());
|
||||
if (this->mNetworks.take(net->ssid())) {
|
||||
this->removeFrontendNetwork(net);
|
||||
delete net;
|
||||
};
|
||||
}
|
||||
|
||||
bool NMWirelessDevice::isValid() const {
|
||||
return this->NMDevice::isValid() && (this->wirelessProxy && this->wirelessProxy->isValid());
|
||||
}
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NMWirelessCapabilities::Enum>
|
||||
DBusDataTransform<qs::network::NMWirelessCapabilities::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMWirelessCapabilities::Enum>(wire));
|
||||
}
|
||||
|
||||
DBusResult<QDateTime> DBusDataTransform<QDateTime>::fromWire(qint64 wire) {
|
||||
return DBusResult(qs::network::clockBootTimeToDateTime(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qhash.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../wifi.hpp"
|
||||
#include "accesspoint.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "dbus_nm_wireless.h"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NMWirelessCapabilities::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NMWirelessCapabilities::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<QDateTime> {
|
||||
using Wire = qint64;
|
||||
using Data = QDateTime;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
namespace qs::network {
|
||||
|
||||
// NMWirelessNetwork aggregates all related NMActiveConnection, NMAccessPoint, and NMConnectionSetting objects.
|
||||
class NMWirelessNetwork: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMWirelessNetwork(QString ssid, QObject* parent = nullptr);
|
||||
|
||||
void addAccessPoint(NMAccessPoint* ap);
|
||||
void addConnection(NMConnectionSettings* conn);
|
||||
void addActiveConnection(NMActiveConnection* active);
|
||||
void forget();
|
||||
|
||||
[[nodiscard]] QString ssid() const { return this->mSsid; };
|
||||
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; };
|
||||
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
|
||||
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; };
|
||||
[[nodiscard]] bool known() const { return this->bKnown; };
|
||||
[[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; };
|
||||
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; };
|
||||
[[nodiscard]] NMConnectionSettings* referenceConnection() const { return this->mReferenceConn; };
|
||||
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); };
|
||||
[[nodiscard]] QList<NMConnectionSettings*> connections() const {
|
||||
return this->mConnections.values();
|
||||
}
|
||||
[[nodiscard]] QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; };
|
||||
[[nodiscard]] QBindable<bool> bindableVisible() { return &this->bVisible; };
|
||||
[[nodiscard]] bool visible() const { return this->bVisible; };
|
||||
|
||||
signals:
|
||||
void disappeared();
|
||||
void visibilityChanged(bool visible);
|
||||
void signalStrengthChanged(quint8 signal);
|
||||
void stateChanged(NMConnectionState::Enum state);
|
||||
void knownChanged(bool known);
|
||||
void securityChanged(WifiSecurityType::Enum security);
|
||||
void reasonChanged(NMConnectionStateReason::Enum reason);
|
||||
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
|
||||
void activeApPathChanged(QString path);
|
||||
|
||||
private:
|
||||
void updateReferenceAp();
|
||||
void updateReferenceConnection();
|
||||
|
||||
QString mSsid;
|
||||
QHash<QString, NMAccessPoint*> mAccessPoints;
|
||||
QHash<QString, NMConnectionSettings*> mConnections;
|
||||
NMAccessPoint* mReferenceAp = nullptr;
|
||||
NMConnectionSettings* mReferenceConn = nullptr;
|
||||
NMActiveConnection* mActiveConnection = nullptr;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bVisible, &NMWirelessNetwork::visibilityChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bKnown, &NMWirelessNetwork::knownChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionStateReason::Enum, bReason, &NMWirelessNetwork::reasonChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionState::Enum, bState, &NMWirelessNetwork::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
|
||||
// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wireless interface
|
||||
// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), frontend WifiNetwork(s).
|
||||
class NMWirelessDevice: public NMDevice {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMWirelessDevice(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool isValid() const override;
|
||||
[[nodiscard]] NMWirelessCapabilities::Enum capabilities() { return this->bCapabilities; };
|
||||
[[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; };
|
||||
[[nodiscard]] NM80211Mode::Enum mode() { return this->bMode; };
|
||||
[[nodiscard]] QBindable<bool> bindableScanning() { return &this->bScanning; };
|
||||
|
||||
signals:
|
||||
void accessPointLoaded(NMAccessPoint* ap);
|
||||
void accessPointRemoved(NMAccessPoint* ap);
|
||||
void networkAdded(WifiNetwork* net);
|
||||
void networkRemoved(WifiNetwork* net);
|
||||
void lastScanChanged(QDateTime lastScan);
|
||||
void scanningChanged(bool scanning);
|
||||
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
|
||||
void activeAccessPointChanged(const QDBusObjectPath& path);
|
||||
void modeChanged(NM80211Mode::Enum mode);
|
||||
|
||||
private slots:
|
||||
void onAccessPointAdded(const QDBusObjectPath& path);
|
||||
void onAccessPointRemoved(const QDBusObjectPath& path);
|
||||
void onAccessPointLoaded(NMAccessPoint* ap);
|
||||
void onConnectionLoaded(NMConnectionSettings* conn);
|
||||
void onActiveConnectionLoaded(NMActiveConnection* active);
|
||||
void onScanTimeout();
|
||||
void onScanningChanged(bool scanning);
|
||||
|
||||
private:
|
||||
void registerAccessPoint(const QString& path);
|
||||
void registerFrontendNetwork(NMWirelessNetwork* net);
|
||||
void removeFrontendNetwork(NMWirelessNetwork* net);
|
||||
void removeNetwork();
|
||||
bool checkVisibility(WifiNetwork* net);
|
||||
void registerAccessPoints();
|
||||
void initWireless();
|
||||
NMWirelessNetwork* registerNetwork(const QString& ssid);
|
||||
|
||||
QHash<QString, NMAccessPoint*> mAccessPoints;
|
||||
QHash<QString, NMWirelessNetwork*> mNetworks;
|
||||
QHash<QString, WifiNetwork*> mFrontendNetworks;
|
||||
|
||||
QDateTime mLastScanRequest;
|
||||
QTimer mScanTimer;
|
||||
qint32 mScanIntervalMs = 10001;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, bool, bScanning, &NMWirelessDevice::scanningChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDateTime, bLastScan, &NMWirelessDevice::lastScanChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, NMWirelessCapabilities::Enum, bCapabilities, &NMWirelessDevice::capabilitiesChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDBusObjectPath, bActiveAccessPoint, &NMWirelessDevice::activeAccessPointChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, NM80211Mode::Enum, bMode, &NMWirelessDevice::modeChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, wirelessProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pLastScan, bLastScan, wirelessProperties, "LastScan");
|
||||
QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pCapabilities, bCapabilities, wirelessProperties, "WirelessCapabilities");
|
||||
QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pActiveAccessPoint, bActiveAccessPoint, wirelessProperties, "ActiveAccessPoint");
|
||||
QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pMode, bMode, wirelessProperties, "Mode");
|
||||
// clang-format on
|
||||
|
||||
DBusNMWirelessProxy* wirelessProxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -0,0 +1,155 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Networking
|
||||
|
||||
FloatingWindow {
|
||||
color: contentItem.palette.window
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
Label {
|
||||
text: "WiFi"
|
||||
font.bold: true
|
||||
font.pointSize: 12
|
||||
}
|
||||
CheckBox {
|
||||
text: "Software"
|
||||
checked: Networking.wifiEnabled
|
||||
onClicked: Networking.wifiEnabled = !Networking.wifiEnabled
|
||||
}
|
||||
CheckBox {
|
||||
enabled: false
|
||||
text: "Hardware"
|
||||
checked: Networking.wifiHardwareEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
clip: true
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: Networking.devices
|
||||
|
||||
delegate: WrapperRectangle {
|
||||
width: parent.width
|
||||
color: "transparent"
|
||||
border.color: palette.button
|
||||
border.width: 1
|
||||
margin: 5
|
||||
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Label { text: modelData.name; font.bold: true }
|
||||
Label { text: modelData.address }
|
||||
Label { text: `(Type: ${DeviceType.toString(modelData.type)})` }
|
||||
}
|
||||
RowLayout {
|
||||
Label {
|
||||
text: DeviceConnectionState.toString(modelData.state)
|
||||
color: modelData.connected ? palette.link : palette.placeholderText
|
||||
}
|
||||
Label {
|
||||
visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.state == DeviceConnectionState.Connecting || modelData.state == DeviceConnectionState.Disconnecting)
|
||||
text: `(${NMDeviceState.toString(modelData.nmState)})`
|
||||
}
|
||||
Button {
|
||||
visible: modelData.state == DeviceConnectionState.Connected
|
||||
text: "Disconnect"
|
||||
onClicked: modelData.disconnect()
|
||||
}
|
||||
CheckBox {
|
||||
text: "Autoconnect"
|
||||
checked: modelData.autoconnect
|
||||
onClicked: modelData.autoconnect = !modelData.autoconnect
|
||||
}
|
||||
Label {
|
||||
text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}`
|
||||
visible: modelData.type == DeviceType.Wifi
|
||||
}
|
||||
CheckBox {
|
||||
text: "Scanner"
|
||||
checked: modelData.scannerEnabled
|
||||
onClicked: modelData.scannerEnabled = !modelData.scannerEnabled
|
||||
visible: modelData.type === DeviceType.Wifi
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
Layout.fillWidth: true
|
||||
model: {
|
||||
if (modelData.type !== DeviceType.Wifi) return []
|
||||
return [...modelData.networks.values].sort((a, b) => {
|
||||
if (a.connected !== b.connected) {
|
||||
return b.connected - a.connected
|
||||
}
|
||||
return b.signalStrength - a.signalStrength
|
||||
})
|
||||
}
|
||||
|
||||
WrapperRectangle {
|
||||
Layout.fillWidth: true
|
||||
color: modelData.connected ? palette.highlight : palette.button
|
||||
border.color: palette.mid
|
||||
border.width: 1
|
||||
margin: 5
|
||||
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
Label { text: modelData.name; font.bold: true }
|
||||
Label {
|
||||
text: modelData.known ? "Known" : ""
|
||||
color: palette.placeholderText
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Label {
|
||||
text: `Security: ${WifiSecurityType.toString(modelData.security)}`
|
||||
color: palette.placeholderText
|
||||
}
|
||||
Label {
|
||||
text: `| Signal strength: ${Math.round(modelData.signalStrength*100)}%`
|
||||
color: palette.placeholderText
|
||||
}
|
||||
}
|
||||
Label {
|
||||
visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.nmReason != NMConnectionStateReason.Unknown && modelData.nmReason != NMConnectionStateReason.None)
|
||||
text: `Connection change reason: ${NMConnectionStateReason.toString(modelData.nmReason)}`
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Button {
|
||||
text: "Connect"
|
||||
onClicked: modelData.connect()
|
||||
visible: !modelData.connected
|
||||
}
|
||||
Button {
|
||||
text: "Disconnect"
|
||||
onClicked: modelData.disconnect()
|
||||
visible: modelData.connected
|
||||
}
|
||||
Button {
|
||||
text: "Forget"
|
||||
onClicked: modelData.forget()
|
||||
visible: modelData.known
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 205 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user