mirror of
https://github.com/quickshell-mirror/quickshell.git
synced 2026-10-29 03:05:05 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59e9c47b0e | ||
|
|
ffe95f06ac | ||
|
|
8db8ca1fec | ||
|
|
0baa81aa03 | ||
|
|
01834e9c8f | ||
|
|
2ba2ae48b4 | ||
|
|
055e384668 | ||
|
|
11a71d233a | ||
|
|
e162429b6f | ||
|
|
783c953987 | ||
|
|
b850b8a1a9 | ||
|
|
d60498adc0 | ||
|
|
9a54119893 | ||
|
|
18075d1218 | ||
|
|
fb08eced44 | ||
|
|
d4c92973b5 | ||
|
|
7f7ab6bc8a | ||
|
|
7208f68bb7 | ||
|
|
f0d0216b3d | ||
|
|
7c5a6c4bd4 | ||
|
|
5bf6a412b0 | ||
|
|
13fe9b0d98 | ||
|
|
ad5fd9116e | ||
|
|
49d4f46cf1 | ||
|
|
9b98d10178 | ||
|
|
854088c48c | ||
|
|
b4e71cb2c0 | ||
|
|
ceac3c6cfa | ||
|
|
aaff22f4b0 | ||
|
|
50cdf98868 | ||
|
|
4b751ccb0d | ||
|
|
20c691cdf1 | ||
|
|
92b336c80c | ||
|
|
d612227740 | ||
|
|
b83c39c8af | ||
|
|
ee1100eb98 | ||
|
|
9bf752ac33 | ||
|
|
313f4e47f6 | ||
|
|
6ef86dd5aa | ||
|
|
308f1e249b | ||
|
|
08058326f0 | ||
|
|
6a244c3c56 | ||
|
|
d745184823 | ||
|
|
77c04a9447 | ||
|
|
eb6eaf59c7 | ||
|
|
7511545ee2 | ||
|
|
0cb62920a7 | ||
|
|
3520c85d77 | ||
|
|
3cf65af49f | ||
|
|
a51dcd0a01 | ||
|
|
97b2688ad6 | ||
|
|
0a859d51f2 | ||
|
|
1bd5b083cb | ||
|
|
365bf16b1e | ||
|
|
6705e2da77 | ||
|
|
9e8eecf2b8 | ||
|
|
1b2519d9f3 | ||
|
|
1123d5ab4f | ||
|
|
4b77936c80 | ||
|
|
e32b909354 | ||
|
|
178c04b59c | ||
|
|
706d6de7b0 | ||
|
|
9a9c605250 | ||
|
|
bd62179277 | ||
|
|
cf1a2aeb2d | ||
|
|
15a8409765 | ||
|
|
6bcd3d9bbf | ||
|
|
c030300191 | ||
|
|
5721955686 | ||
|
|
a849a88893 | ||
|
|
cdde4c63f4 | ||
|
|
cddb4f061b | ||
|
|
6e17efab83 |
No files matched your search
@@ -20,6 +20,7 @@ Checks: >
|
|||||||
-cppcoreguidelines-avoid-do-while,
|
-cppcoreguidelines-avoid-do-while,
|
||||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||||
-cppcoreguidelines-pro-type-vararg,
|
-cppcoreguidelines-pro-type-vararg,
|
||||||
|
-cppcoreguidelines-pro-type-union-access,
|
||||||
-cppcoreguidelines-use-enum-class,
|
-cppcoreguidelines-use-enum-class,
|
||||||
google-global-names-in-headers,
|
google-global-names-in-headers,
|
||||||
google-readability-casting,
|
google-readability-casting,
|
||||||
|
|||||||
@@ -1,82 +1,17 @@
|
|||||||
name: Crash Report
|
name: Crash Report (v1)
|
||||||
description: Quickshell has crashed
|
description: Quickshell has crashed (old)
|
||||||
labels: ["bug", "crash"]
|
labels: ["unactionable"]
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: markdown
|
||||||
id: crashinfo
|
|
||||||
attributes:
|
attributes:
|
||||||
label: General crash information
|
value: |
|
||||||
description: |
|
Thank you for taking the time to click the report button.
|
||||||
Paste the contents of the `info.txt` file in your crash folder here.
|
At this point most of the worst issues in 0.2.1 and before have been fixed and we are
|
||||||
value: "<details> <summary>General information</summary>
|
preparing for a new release. Please do not submit this report.
|
||||||
|
- type: checkboxes
|
||||||
|
id: donotcheck
|
||||||
```
|
attributes:
|
||||||
|
label: Read the text above. Do not submit the report.
|
||||||
<Paste the contents of the file here inside of the triple backticks>
|
options:
|
||||||
|
- label: Yes I want this report to be deleted.
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
</details>"
|
|
||||||
validations:
|
|
||||||
required: true
|
required: true
|
||||||
- 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: dump
|
|
||||||
attributes:
|
|
||||||
label: Minidump
|
|
||||||
description: |
|
|
||||||
Attach `minidump.dmp.log` here. If it is too big to upload, compress it.
|
|
||||||
|
|
||||||
You may skip this step if quickshell crashed while processing a password
|
|
||||||
or other sensitive information. If you skipped it write why instead.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: logs
|
|
||||||
attributes:
|
|
||||||
label: Log file
|
|
||||||
description: |
|
|
||||||
Attach `log.qslog.log` here. If it is too big to upload, compress it.
|
|
||||||
|
|
||||||
You can preview the log if you'd like using `quickshell read-log <path-to-log>`.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: config
|
|
||||||
attributes:
|
|
||||||
label: Configuration
|
|
||||||
description: |
|
|
||||||
Attach your configuration here, preferrably in full (not just one file).
|
|
||||||
Compress it into a zip, tar, etc.
|
|
||||||
|
|
||||||
This will help us reproduce the crash ourselves.
|
|
||||||
- type: textarea
|
|
||||||
id: bt
|
|
||||||
attributes:
|
|
||||||
label: Backtrace
|
|
||||||
description: |
|
|
||||||
If you have gdb installed and use systemd, or otherwise know how to get a backtrace,
|
|
||||||
we would appreciate one. (You may have gdb installed without knowing it)
|
|
||||||
|
|
||||||
1. Run `coredumpctl debug <pid>` where `pid` is the number shown after "Crashed process ID"
|
|
||||||
in the crash reporter.
|
|
||||||
2. Once it loads, type `bt -full` (then enter)
|
|
||||||
3. Copy the output and attach it as a file or in a spoiler.
|
|
||||||
- type: textarea
|
|
||||||
id: exe
|
|
||||||
attributes:
|
|
||||||
label: Executable
|
|
||||||
description: |
|
|
||||||
If the crash folder contains a executable.txt file, upload it here. If not you can ignore this field.
|
|
||||||
If it is too big to upload, compress it.
|
|
||||||
|
|
||||||
Note: executable.txt is the quickshell binary. It has a .txt extension due to github's limitations on
|
|
||||||
filetypes.
|
|
||||||
@@ -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: upload
|
||||||
|
id: report
|
||||||
|
attributes:
|
||||||
|
label: Report file
|
||||||
|
description: Attach `report.txt` here.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: upload
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Log file
|
||||||
|
description: |
|
||||||
|
Attach `log.qslog.log` here. If it is too big to upload, compress it.
|
||||||
|
|
||||||
|
You can preview the log if you'd like using `qs log <path-to-log> -r '*=true'`.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: config
|
||||||
|
attributes:
|
||||||
|
label: Configuration
|
||||||
|
description: |
|
||||||
|
Attach or link your configuration here, preferrably in full (not just one file).
|
||||||
|
Compress it into a zip, tar, etc.
|
||||||
|
|
||||||
|
This will help us reproduce the crash ourselves.
|
||||||
|
- 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.
|
||||||
@@ -55,10 +55,11 @@ jobs:
|
|||||||
libpipewire \
|
libpipewire \
|
||||||
cli11 \
|
cli11 \
|
||||||
polkit \
|
polkit \
|
||||||
jemalloc
|
jemalloc \
|
||||||
|
libunwind \
|
||||||
|
git # for cpptrace clone
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
# breakpad is annoying to build in ci due to makepkg not running as root
|
|
||||||
run: |
|
run: |
|
||||||
cmake -GNinja -B build -DCRASH_REPORTER=OFF
|
cmake -GNinja -B build -DVENDOR_CPPTRACE=ON
|
||||||
cmake --build build
|
cmake --build build
|
||||||
@@ -15,15 +15,7 @@ Please make this descriptive enough to identify your specific package, for examp
|
|||||||
- `Nixpkgs`
|
- `Nixpkgs`
|
||||||
- `Fedora COPR (errornointernet/quickshell)`
|
- `Fedora COPR (errornointernet/quickshell)`
|
||||||
|
|
||||||
`-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES/NO`
|
If you are forking quickshell, please change `CRASHREPORT_URL` to your own issue tracker.
|
||||||
|
|
||||||
If we can retrieve binaries and debug information for the package without actually running your
|
|
||||||
distribution (e.g. from an website), and you would like to strip the binary, please set this to `YES`.
|
|
||||||
|
|
||||||
If we cannot retrieve debug information, please set this to `NO` and
|
|
||||||
**ensure you aren't distributing stripped (non debuggable) binaries**.
|
|
||||||
|
|
||||||
In both cases you should build with `-DCMAKE_BUILD_TYPE=RelWithDebInfo` (then split or keep the debuginfo).
|
|
||||||
|
|
||||||
### QML Module dir
|
### QML Module dir
|
||||||
Currently all QML modules are statically linked to quickshell, but this is where
|
Currently all QML modules are statically linked to quickshell, but this is where
|
||||||
@@ -41,6 +33,7 @@ Quickshell has a set of base dependencies you will always need, names vary by di
|
|||||||
- `cmake`
|
- `cmake`
|
||||||
- `qt6base`
|
- `qt6base`
|
||||||
- `qt6declarative`
|
- `qt6declarative`
|
||||||
|
- `libdrm`
|
||||||
- `qtshadertools` (build-time)
|
- `qtshadertools` (build-time)
|
||||||
- `spirv-tools` (build-time)
|
- `spirv-tools` (build-time)
|
||||||
- `pkg-config` (build-time)
|
- `pkg-config` (build-time)
|
||||||
@@ -64,14 +57,24 @@ At least Qt 6.6 is required.
|
|||||||
|
|
||||||
All features are enabled by default and some have their own dependencies.
|
All features are enabled by default and some have their own dependencies.
|
||||||
|
|
||||||
### Crash Reporter
|
### Crash Handler
|
||||||
The crash reporter catches crashes, restarts quickshell when it crashes,
|
The crash reporter catches crashes, restarts Quickshell when it crashes,
|
||||||
and collects useful crash information in one place. Leaving this enabled will
|
and collects useful crash information in one place. Leaving this enabled will
|
||||||
enable us to fix bugs far more easily.
|
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.
|
||||||
|
|
||||||
|
When using FetchContent, `libunwind` is required, and `libdwarf` can be provided by the
|
||||||
|
package manager or fetched with FetchContent.
|
||||||
|
|
||||||
|
*Please ensure binaries have usable symbols.* We do not necessarily need full debuginfo, but
|
||||||
|
leaving symbols in the binary is extremely helpful. You can check if symbols are useful
|
||||||
|
by sending a SIGSEGV to the process and ensuring symbols for the quickshell binary are present
|
||||||
|
in the trace.
|
||||||
|
|
||||||
### Jemalloc
|
### Jemalloc
|
||||||
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
||||||
@@ -144,7 +147,6 @@ Enables streaming video from monitors and toplevel windows through various proto
|
|||||||
To disable: `-DSCREENCOPY=OFF`
|
To disable: `-DSCREENCOPY=OFF`
|
||||||
|
|
||||||
Dependencies:
|
Dependencies:
|
||||||
- `libdrm`
|
|
||||||
- `libgbm`
|
- `libgbm`
|
||||||
- `vulkan-headers` (build-time)
|
- `vulkan-headers` (build-time)
|
||||||
|
|
||||||
@@ -240,7 +242,7 @@ Only `ninja` builds are tested, but makefiles may work.
|
|||||||
|
|
||||||
#### Configuring the build
|
#### Configuring the build
|
||||||
```sh
|
```sh
|
||||||
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo [additional disable flags from above here]
|
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release [additional disable flags from above here]
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that features you do not supply dependencies for MUST be disabled with their associated flags
|
Note that features you do not supply dependencies for MUST be disabled with their associated flags
|
||||||
|
|||||||
+7
-5
@@ -1,5 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
|
project(quickshell VERSION "0.3.0" LANGUAGES CXX C)
|
||||||
|
|
||||||
set(UNRELEASED_FEATURES)
|
set(UNRELEASED_FEATURES)
|
||||||
|
|
||||||
@@ -9,6 +9,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
|
|
||||||
set(QS_BUILD_OPTIONS "")
|
set(QS_BUILD_OPTIONS "")
|
||||||
|
|
||||||
|
# should be changed for forks
|
||||||
|
set(CRASHREPORT_URL "https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml" CACHE STRING "Bugreport URL")
|
||||||
|
|
||||||
function(boption VAR NAME DEFAULT)
|
function(boption VAR NAME DEFAULT)
|
||||||
cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
|
cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
|
||||||
|
|
||||||
@@ -40,19 +43,17 @@ string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}")
|
|||||||
|
|
||||||
message(STATUS "Quickshell configuration")
|
message(STATUS "Quickshell configuration")
|
||||||
message(STATUS " Distributor: ${DISTRIBUTOR}")
|
message(STATUS " Distributor: ${DISTRIBUTOR}")
|
||||||
boption(DISTRIBUTOR_DEBUGINFO_AVAILABLE "Distributor provided debuginfo" NO)
|
|
||||||
boption(NO_PCH "Disable precompild headers (dev)" OFF)
|
boption(NO_PCH "Disable precompild headers (dev)" OFF)
|
||||||
boption(BUILD_TESTING "Build tests (dev)" OFF)
|
boption(BUILD_TESTING "Build tests (dev)" OFF)
|
||||||
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
|
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
|
||||||
boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN})
|
boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN})
|
||||||
|
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||||
boption(CRASH_REPORTER "Crash Handling" OFF)
|
|
||||||
boption(USE_JEMALLOC "Use jemalloc" OFF)
|
boption(USE_JEMALLOC "Use jemalloc" OFF)
|
||||||
else()
|
else()
|
||||||
boption(CRASH_REPORTER "Crash Handling" ON)
|
|
||||||
boption(USE_JEMALLOC "Use jemalloc" ON)
|
boption(USE_JEMALLOC "Use jemalloc" ON)
|
||||||
endif()
|
endif()
|
||||||
|
boption(CRASH_HANDLER "Crash Handling" ON)
|
||||||
boption(SOCKETS "Unix Sockets" ON)
|
boption(SOCKETS "Unix Sockets" ON)
|
||||||
boption(WAYLAND "Wayland" ON)
|
boption(WAYLAND "Wayland" ON)
|
||||||
boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND)
|
boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND)
|
||||||
@@ -86,8 +87,9 @@ include(cmake/util.cmake)
|
|||||||
|
|
||||||
add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension)
|
add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension)
|
||||||
|
|
||||||
# pipewire defines this, breaking PCH
|
# pipewire defines these, breaking PCH
|
||||||
add_compile_definitions(_REENTRANT)
|
add_compile_definitions(_REENTRANT)
|
||||||
|
add_compile_options(-fno-strict-overflow)
|
||||||
|
|
||||||
if (FRAME_POINTERS)
|
if (FRAME_POINTERS)
|
||||||
add_compile_options(-fno-omit-frame-pointer)
|
add_compile_options(-fno-omit-frame-pointer)
|
||||||
|
|||||||
+26
-221
@@ -1,235 +1,40 @@
|
|||||||
# Contributing / Development
|
# Contributing
|
||||||
Instructions for development setup and upstreaming patches.
|
|
||||||
|
|
||||||
If you just want to build or package quickshell see [BUILD.md](BUILD.md).
|
Thank you for taking the time to contribute.
|
||||||
|
To ensure nobody's time is wasted, please follow the rules below.
|
||||||
|
|
||||||
## Development
|
## Acceptable Code Contributions
|
||||||
|
|
||||||
Install the dependencies listed in [BUILD.md](BUILD.md).
|
- All changes submitted MUST be **fully understood by the submitter**. If you do not know why or how
|
||||||
You probably want all of them even if you don't use all of them
|
your change works, do not submit it to be merged. You must be able to explain your reasoning
|
||||||
to ensure tests work correctly and avoid passing a bunch of configure
|
for every change.
|
||||||
flags when you need to wipe the build directory.
|
|
||||||
|
|
||||||
Quickshell also uses `just` for common development command aliases.
|
- Changes MUST be submitted by a human who will be responsible for them. Changes submitted without
|
||||||
|
a human in the loop such as automated tooling and AI Agents are **strictly disallowed**. Accounts
|
||||||
|
responsible for such contribution attempts **will be banned**.
|
||||||
|
|
||||||
The dependencies are also available as a nix shell or nix flake which we recommend
|
- Changes MUST respect Quickshell's license and the license of any source works. Changes including
|
||||||
using with nix-direnv.
|
code from any other works must disclose the source of the code, explain why it was used, and
|
||||||
|
ensure the license is compatible.
|
||||||
|
|
||||||
Common aliases:
|
- Changes must follow the guidelines outlined in [HACKING.md](HACKING.md) for style and substance.
|
||||||
- `just configure [<debug|release> [extra cmake args]]` (note that you must specify debug/release to specify extra args)
|
|
||||||
- `just build` - runs the build, configuring if not configured already.
|
|
||||||
- `just run [args]` - runs quickshell with the given arguments
|
|
||||||
- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
|
|
||||||
|
|
||||||
### Formatting
|
- Changes must stand on their own as a unit. Do not make multiple unrelated changes in one PR.
|
||||||
All contributions should be formatted similarly to what already exists.
|
Changes depending on prior merges should be marked as a draft.
|
||||||
Group related functionality together.
|
|
||||||
|
|
||||||
Run the formatter using `just fmt`.
|
## Acceptable Non-code Contributions
|
||||||
If the results look stupid, fix the clang-format file if possible,
|
|
||||||
or disable clang-format in the affected area
|
|
||||||
using `// clang-format off` and `// clang-format on`.
|
|
||||||
|
|
||||||
#### Style preferences not caught by clang-format
|
- Bug and crash reports. You must follow the instructions in the issue templates and provide the
|
||||||
These are flexible. You can ignore them if it looks or works better to
|
information requested.
|
||||||
for one reason or another.
|
|
||||||
|
|
||||||
Use `auto` if the type of a variable can be deduced automatically, instead of
|
- Feature requests can be made via Issues. Please check to ensure nobody else has requested the same feature.
|
||||||
redeclaring the returned value's type. Additionally, auto should be used when a
|
|
||||||
constructor takes arguments.
|
|
||||||
|
|
||||||
```cpp
|
- Do not make insubstantial or pointless changes.
|
||||||
auto x = <expr>; // ok
|
|
||||||
auto x = QString::number(3); // ok
|
|
||||||
QString x; // ok
|
|
||||||
QString x = "foo"; // ok
|
|
||||||
auto x = QString("foo"); // ok
|
|
||||||
|
|
||||||
auto x = QString(); // avoid
|
- Changes to project rules / policy / governance will not be entertained, except from significant
|
||||||
QString x(); // avoid
|
long-term contributors. These changes should not be addressed through contribution channels.
|
||||||
QString x("foo"); // avoid
|
|
||||||
```
|
|
||||||
|
|
||||||
Put newlines around logical units of code, and after closing braces. If the
|
## Merge timelines
|
||||||
most reasonable logical unit of code takes only a single line, it should be
|
|
||||||
merged into the next single line logical unit if applicable.
|
|
||||||
```cpp
|
|
||||||
// multiple units
|
|
||||||
auto x = <expr>; // unit 1
|
|
||||||
auto y = <expr>; // unit 2
|
|
||||||
|
|
||||||
auto x = <expr>; // unit 1
|
We handle work for the most part on a push basis. If your PR has been ignored for a while
|
||||||
emit this->y(); // unit 2
|
and is still relevant please bump it.
|
||||||
|
|
||||||
auto x1 = <expr>; // unit 1
|
|
||||||
auto x2 = <expr>; // unit 1
|
|
||||||
auto x3 = <expr>; // unit 1
|
|
||||||
|
|
||||||
auto y1 = <expr>; // unit 2
|
|
||||||
auto y2 = <expr>; // unit 2
|
|
||||||
auto y3 = <expr>; // unit 2
|
|
||||||
|
|
||||||
// one unit
|
|
||||||
auto x = <expr>;
|
|
||||||
if (x...) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// if more than one variable needs to be used then add a newline
|
|
||||||
auto x = <expr>;
|
|
||||||
auto y = <expr>;
|
|
||||||
|
|
||||||
if (x && y) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Class formatting:
|
|
||||||
```cpp
|
|
||||||
//! Doc comment summary
|
|
||||||
/// Doc comment body
|
|
||||||
class Foo: public QObject {
|
|
||||||
// The Q_OBJECT macro comes first. Macros are ; terminated.
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_CLASSINFO(...);
|
|
||||||
// Properties must stay on a single line or the doc generator won't be able to pick them up
|
|
||||||
Q_PROPERTY(...);
|
|
||||||
/// Doc comment
|
|
||||||
Q_PROPERTY(...);
|
|
||||||
/// Doc comment
|
|
||||||
Q_PROPERTY(...);
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Classes should have explicit constructors if they aren't intended to
|
|
||||||
// implicitly cast. The constructor can be inline in the header if it has no body.
|
|
||||||
explicit Foo(QObject* parent = nullptr): QObject(parent) {}
|
|
||||||
|
|
||||||
// Instance functions if applicable.
|
|
||||||
static Foo* instance();
|
|
||||||
|
|
||||||
// Member functions unrelated to properties come next
|
|
||||||
void function();
|
|
||||||
void function();
|
|
||||||
void function();
|
|
||||||
|
|
||||||
// Then Q_INVOKABLEs
|
|
||||||
Q_INVOKABLE function();
|
|
||||||
/// Doc comment
|
|
||||||
Q_INVOKABLE function();
|
|
||||||
/// Doc comment
|
|
||||||
Q_INVOKABLE function();
|
|
||||||
|
|
||||||
// Then property related functions, in the order (bindable, getter, setter).
|
|
||||||
// Related functions may be included here as well. Function bodies may be inline
|
|
||||||
// if they are a single expression. There should be a newline between each
|
|
||||||
// property's methods.
|
|
||||||
[[nodiscard]] QBindable<T> bindableFoo() { return &this->bFoo; }
|
|
||||||
[[nodiscard]] T foo() const { return this->foo; }
|
|
||||||
void setFoo();
|
|
||||||
|
|
||||||
[[nodiscard]] T bar() const { return this->foo; }
|
|
||||||
void setBar();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
// Signals that are not property change related go first.
|
|
||||||
// Property change signals go in property definition order.
|
|
||||||
void asd();
|
|
||||||
void asd2();
|
|
||||||
void fooChanged();
|
|
||||||
void barChanged();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
// generally Q_INVOKABLEs are preferred to public slots.
|
|
||||||
void slot();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
// ...
|
|
||||||
|
|
||||||
private:
|
|
||||||
// statics, then functions, then fields
|
|
||||||
static const foo BAR;
|
|
||||||
static void foo();
|
|
||||||
|
|
||||||
void foo();
|
|
||||||
void bar();
|
|
||||||
|
|
||||||
// property related members are prefixed with `m`.
|
|
||||||
QString mFoo;
|
|
||||||
QString bar;
|
|
||||||
|
|
||||||
// Bindables go last and should be prefixed with `b`.
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Linter
|
|
||||||
All contributions should pass the linter.
|
|
||||||
|
|
||||||
Note that running the linter requires disabling precompiled
|
|
||||||
headers and including the test codepaths:
|
|
||||||
```sh
|
|
||||||
$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
|
|
||||||
$ just lint-changed
|
|
||||||
```
|
|
||||||
|
|
||||||
If the linter is complaining about something that you think it should not,
|
|
||||||
please disable the lint in your MR and explain your reasoning if it isn't obvious.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
If you feel like the feature you are working on is very complex or likely to break,
|
|
||||||
please write some tests. We will ask you to directly if you send in an MR for an
|
|
||||||
overly complex or breakable feature.
|
|
||||||
|
|
||||||
At least all tests that passed before your changes should still be passing
|
|
||||||
by the time your contribution is ready.
|
|
||||||
|
|
||||||
You can run the tests using `just test` but you must enable them first
|
|
||||||
using `-DBUILD_TESTING=ON`.
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
Most of quickshell's documentation is automatically generated from the source code.
|
|
||||||
You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
|
|
||||||
cannot handle random line breaks and will usually require you to disable clang-format if the
|
|
||||||
lines are too long.
|
|
||||||
|
|
||||||
Before submitting an MR, if adding new features please make sure the documentation is generated
|
|
||||||
reasonably using the `quickshell-docs` repo. We recommend checking it out at `/docs` in this repo.
|
|
||||||
|
|
||||||
Doc comments take the form `///` or `///!` (summary) and work with markdown.
|
|
||||||
You can reference other types using the `@@[Module.][Type.][member]` shorthand
|
|
||||||
where all parts are optional. If module or type are not specified they will
|
|
||||||
be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
|
|
||||||
Look at existing code for how it works.
|
|
||||||
|
|
||||||
Quickshell modules additionally have a `module.md` file which contains a summary, description,
|
|
||||||
and list of headers to scan for documentation.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
### Commits
|
|
||||||
Please structure your commit messages as `scope[!]: commit` where
|
|
||||||
the scope is something like `core` or `service/mpris`. (pick what has been
|
|
||||||
used historically or what makes sense if new). Add `!` for changes that break
|
|
||||||
existing APIs or functionality.
|
|
||||||
|
|
||||||
Commit descriptions should contain a summary of the changes if they are not
|
|
||||||
sufficiently addressed in the commit message.
|
|
||||||
|
|
||||||
Please squash/rebase additions or edits to previous changes and follow the
|
|
||||||
commit style to keep the history easily searchable at a glance.
|
|
||||||
Depending on the change, it is often reasonable to squash it into just
|
|
||||||
a single commit. (If you do not follow this we will squash your changes
|
|
||||||
for you.)
|
|
||||||
|
|
||||||
### Sending patches
|
|
||||||
You may contribute by submitting a pull request on github, asking for
|
|
||||||
an account on our git server, or emailing patches / git bundles
|
|
||||||
directly to `[email protected]`.
|
|
||||||
|
|
||||||
### Getting help
|
|
||||||
If you're getting stuck, you can come talk to us in the
|
|
||||||
[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
|
|
||||||
for help on implementation, conventions, etc.
|
|
||||||
Feel free to ask for advice early in your implementation if you are
|
|
||||||
unsure.
|
|
||||||
+226
@@ -0,0 +1,226 @@
|
|||||||
|
## Development
|
||||||
|
|
||||||
|
Install the dependencies listed in [BUILD.md](BUILD.md).
|
||||||
|
You probably want all of them even if you don't use all of them
|
||||||
|
to ensure tests work correctly and avoid passing a bunch of configure
|
||||||
|
flags when you need to wipe the build directory.
|
||||||
|
|
||||||
|
The dependencies are also available as a nix shell or nix flake which we recommend
|
||||||
|
using with nix-direnv.
|
||||||
|
|
||||||
|
Quickshell uses `just` for common development command aliases.
|
||||||
|
|
||||||
|
Common aliases:
|
||||||
|
- `just configure [<debug|release> [extra cmake args]]` (note that you must specify debug/release to specify extra args)
|
||||||
|
- `just build` - runs the build, configuring if not configured already.
|
||||||
|
- `just run [args]` - runs quickshell with the given arguments
|
||||||
|
- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
All contributions should be formatted similarly to what already exists.
|
||||||
|
Group related functionality together.
|
||||||
|
|
||||||
|
Run the formatter using `just fmt`.
|
||||||
|
If the results look stupid, fix the clang-format file if possible,
|
||||||
|
or disable clang-format in the affected area
|
||||||
|
using `// clang-format off` and `// clang-format on`.
|
||||||
|
|
||||||
|
#### Style preferences not caught by clang-format
|
||||||
|
These are flexible. You can ignore them if it looks or works better to
|
||||||
|
for one reason or another.
|
||||||
|
|
||||||
|
Use `auto` if the type of a variable can be deduced automatically, instead of
|
||||||
|
redeclaring the returned value's type. Additionally, auto should be used when a
|
||||||
|
constructor takes arguments.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto x = <expr>; // ok
|
||||||
|
auto x = QString::number(3); // ok
|
||||||
|
QString x; // ok
|
||||||
|
QString x = "foo"; // ok
|
||||||
|
auto x = QString("foo"); // ok
|
||||||
|
|
||||||
|
auto x = QString(); // avoid
|
||||||
|
QString x(); // avoid
|
||||||
|
QString x("foo"); // avoid
|
||||||
|
```
|
||||||
|
|
||||||
|
Put newlines around logical units of code, and after closing braces. If the
|
||||||
|
most reasonable logical unit of code takes only a single line, it should be
|
||||||
|
merged into the next single line logical unit if applicable.
|
||||||
|
```cpp
|
||||||
|
// multiple units
|
||||||
|
auto x = <expr>; // unit 1
|
||||||
|
auto y = <expr>; // unit 2
|
||||||
|
|
||||||
|
auto x = <expr>; // unit 1
|
||||||
|
emit this->y(); // unit 2
|
||||||
|
|
||||||
|
auto x1 = <expr>; // unit 1
|
||||||
|
auto x2 = <expr>; // unit 1
|
||||||
|
auto x3 = <expr>; // unit 1
|
||||||
|
|
||||||
|
auto y1 = <expr>; // unit 2
|
||||||
|
auto y2 = <expr>; // unit 2
|
||||||
|
auto y3 = <expr>; // unit 2
|
||||||
|
|
||||||
|
// one unit
|
||||||
|
auto x = <expr>;
|
||||||
|
if (x...) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// if more than one variable needs to be used then add a newline
|
||||||
|
auto x = <expr>;
|
||||||
|
auto y = <expr>;
|
||||||
|
|
||||||
|
if (x && y) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Class formatting:
|
||||||
|
```cpp
|
||||||
|
//! Doc comment summary
|
||||||
|
/// Doc comment body
|
||||||
|
class Foo: public QObject {
|
||||||
|
// The Q_OBJECT macro comes first. Macros are ; terminated.
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_CLASSINFO(...);
|
||||||
|
// Properties must stay on a single line or the doc generator won't be able to pick them up
|
||||||
|
Q_PROPERTY(...);
|
||||||
|
/// Doc comment
|
||||||
|
Q_PROPERTY(...);
|
||||||
|
/// Doc comment
|
||||||
|
Q_PROPERTY(...);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Classes should have explicit constructors if they aren't intended to
|
||||||
|
// implicitly cast. The constructor can be inline in the header if it has no body.
|
||||||
|
explicit Foo(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
|
||||||
|
// Instance functions if applicable.
|
||||||
|
static Foo* instance();
|
||||||
|
|
||||||
|
// Member functions unrelated to properties come next
|
||||||
|
void function();
|
||||||
|
void function();
|
||||||
|
void function();
|
||||||
|
|
||||||
|
// Then Q_INVOKABLEs
|
||||||
|
Q_INVOKABLE function();
|
||||||
|
/// Doc comment
|
||||||
|
Q_INVOKABLE function();
|
||||||
|
/// Doc comment
|
||||||
|
Q_INVOKABLE function();
|
||||||
|
|
||||||
|
// Then property related functions, in the order (bindable, getter, setter).
|
||||||
|
// Related functions may be included here as well. Function bodies may be inline
|
||||||
|
// if they are a single expression. There should be a newline between each
|
||||||
|
// property's methods.
|
||||||
|
[[nodiscard]] QBindable<T> bindableFoo() { return &this->bFoo; }
|
||||||
|
[[nodiscard]] T foo() const { return this->foo; }
|
||||||
|
void setFoo();
|
||||||
|
|
||||||
|
[[nodiscard]] T bar() const { return this->foo; }
|
||||||
|
void setBar();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// Signals that are not property change related go first.
|
||||||
|
// Property change signals go in property definition order.
|
||||||
|
void asd();
|
||||||
|
void asd2();
|
||||||
|
void fooChanged();
|
||||||
|
void barChanged();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
// generally Q_INVOKABLEs are preferred to public slots.
|
||||||
|
void slot();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// ...
|
||||||
|
|
||||||
|
private:
|
||||||
|
// statics, then functions, then fields
|
||||||
|
static const foo BAR;
|
||||||
|
static void foo();
|
||||||
|
|
||||||
|
void foo();
|
||||||
|
void bar();
|
||||||
|
|
||||||
|
// property related members are prefixed with `m`.
|
||||||
|
QString mFoo;
|
||||||
|
QString bar;
|
||||||
|
|
||||||
|
// Bindables go last and should be prefixed with `b`.
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Use lowercase .h suffixed Qt headers, e.g. `<qwindow.h>` over `<QWindow>`.
|
||||||
|
|
||||||
|
### Linter
|
||||||
|
All contributions should pass the linter.
|
||||||
|
|
||||||
|
Note that running the linter requires disabling precompiled
|
||||||
|
headers and including the test codepaths:
|
||||||
|
```sh
|
||||||
|
$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
|
||||||
|
$ just lint-changed
|
||||||
|
```
|
||||||
|
|
||||||
|
If the linter is complaining about something that you think it should not,
|
||||||
|
please disable the lint in your MR and explain your reasoning if it isn't obvious.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
If you feel like the feature you are working on is very complex or likely to break,
|
||||||
|
please write some tests. We will ask you to directly if you send in an MR for an
|
||||||
|
overly complex or breakable feature.
|
||||||
|
|
||||||
|
At least all tests that passed before your changes should still be passing
|
||||||
|
by the time your contribution is ready.
|
||||||
|
|
||||||
|
You can run the tests using `just test` but you must enable them first
|
||||||
|
using `-DBUILD_TESTING=ON`.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
Most of quickshell's documentation is automatically generated from the source code.
|
||||||
|
You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
|
||||||
|
cannot handle random line breaks and will usually require you to disable clang-format if the
|
||||||
|
lines are too long.
|
||||||
|
|
||||||
|
Make sure new files containing doc comments are added to a `module.md` file.
|
||||||
|
See existing module files for reference.
|
||||||
|
|
||||||
|
Doc comments take the form `///` or `///!` (summary) and work with markdown.
|
||||||
|
You can reference other types using the `@@[Module.][Type.][member]` shorthand
|
||||||
|
where all parts are optional. If module or type are not specified they will
|
||||||
|
be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
|
||||||
|
Look at existing code for how it works.
|
||||||
|
|
||||||
|
If you have made a user visible change since the last tagged release, describe it in
|
||||||
|
[changelog/next.md](changelog/next.md).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Please structure your commit messages as `scope: commit` where
|
||||||
|
the scope is something like `core` or `service/mpris`. (pick what has been
|
||||||
|
used historically or what makes sense if new).
|
||||||
|
|
||||||
|
Commit descriptions should contain a summary of the changes if they are not
|
||||||
|
sufficiently addressed in the commit message.
|
||||||
|
|
||||||
|
Please squash/rebase additions or edits to previous changes and follow the
|
||||||
|
commit style to keep the history easily searchable at a glance.
|
||||||
|
Depending on the change, it is often reasonable to squash it into just
|
||||||
|
a single commit. (If you do not follow this we will squash your changes
|
||||||
|
for you.)
|
||||||
|
|
||||||
|
### Getting help
|
||||||
|
If you're getting stuck, you can come talk to us in the
|
||||||
|
[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
|
||||||
|
for help on implementation, conventions, etc. There is also a bridged [discord server](https://discord.gg/UtZeT3xNyT).
|
||||||
|
Feel free to ask for advice early in your implementation if you are
|
||||||
|
unsure.
|
||||||
@@ -13,7 +13,7 @@ lint-changed:
|
|||||||
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||||
|
|
||||||
lint-staged:
|
lint-staged:
|
||||||
git diff --staged --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
git diff --staged --name-only --diff-filter=d HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||||
|
|
||||||
configure target='debug' *FLAGS='':
|
configure target='debug' *FLAGS='':
|
||||||
cmake -GNinja -B {{builddir}} \
|
cmake -GNinja -B {{builddir}} \
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ This repo is hosted at:
|
|||||||
- https://github.com/quickshell-mirror/quickshell
|
- https://github.com/quickshell-mirror/quickshell
|
||||||
|
|
||||||
# Contributing / Development
|
# Contributing / Development
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
- [HACKING.md](HACKING.md) - Development instructions and policy.
|
||||||
|
- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution policy.
|
||||||
|
- [BUILD.md](BUILD.md) - Packaging and build instructions.
|
||||||
|
|
||||||
#### License
|
#### License
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
## 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.
|
|
||||||
|
|
||||||
## 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).
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
## 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 network management support.
|
||||||
|
- Added support for grabbing focus from popup windows.
|
||||||
|
- Added support for IPC signal listeners.
|
||||||
|
- Added Quickshell version checking and version gated preprocessing.
|
||||||
|
- Added a way to detect if an icon is from the system icon theme or not.
|
||||||
|
- Added vulkan support to screencopy.
|
||||||
|
- Added generic WindowManager interface implementing ext-workspace.
|
||||||
|
- Added ext-background-effect window blur support.
|
||||||
|
- Added per-corner radius support to Region.
|
||||||
|
- Added ColorQuantizer region selection.
|
||||||
|
- Added dialog window support to FloatingWindow.
|
||||||
|
- Added lua config support to Hyprland module.
|
||||||
|
|
||||||
|
## Other Changes
|
||||||
|
|
||||||
|
- IPC operations filter available instances to the current display connection by default.
|
||||||
|
- PwNodeLinkTracker ignores sound level monitoring programs.
|
||||||
|
- Replaced breakpad with cpptrace.
|
||||||
|
- Reloads are prevented if no file content has changed.
|
||||||
|
- Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching.
|
||||||
|
- Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling.
|
||||||
|
- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
|
||||||
|
- Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID.
|
||||||
|
- Added `DropExpensiveFonts` pragma and `QS_DROP_EXPENSIVE_FONTS` environment variable which avoids loading fonts which may cause lag and excessive memory usage if many variants are used.
|
||||||
|
- Added `DefaultEnv` pragma which sets an environment variable if not already set.
|
||||||
|
- Unrecognized pragmas are no longer a hard error for future backward compatibility.
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- 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 a hyprland ipc crash when refreshing toplevels before workspaces.
|
||||||
|
- Fixed missing signals for system tray item title and description updates.
|
||||||
|
- Fixed asynchronous loaders not working after reload.
|
||||||
|
- Fixed asynchronous loaders not working before window creation.
|
||||||
|
- Fixed memory leak in IPC handlers.
|
||||||
|
- Fixed ClippingRectangle related crashes.
|
||||||
|
- Fixed crashes when monitors are unplugged.
|
||||||
|
- Fixed crashes when default pipewire devices are lost.
|
||||||
|
- Fixed ToplevelManager not clearing activeToplevel on deactivation.
|
||||||
|
- Desktop action order is now preserved.
|
||||||
|
- Fixed partial socket reads in greetd and hyprland on slow machines.
|
||||||
|
- Worked around Qt bug causing crashes when plugging and unplugging monitors.
|
||||||
|
- Fixed HyprlandFocusGrab crashing if windows were destroyed after being passed to it.
|
||||||
|
- Fixed ScreencopyView pixelation when scaled.
|
||||||
|
- Fixed JsonAdapter crashing and providing bad data on read when using JsonObject.
|
||||||
|
- Fixed JsonAdapter sending unnecessary property changes for primitive values.
|
||||||
|
- Fixed JsonAdapter serialization for lists.
|
||||||
|
- Fixed pipewire crashes after hotplugging devices and changing default outputs.
|
||||||
|
- Fixed launches failing for `--daemonize` on some systems.
|
||||||
|
- Fixed screencopy crashing when used across GPUs.
|
||||||
|
- Fixed pipewire volumes not working for some pw-pulse clients.
|
||||||
|
- Fixed nulls in Toplevel.screens after unplugging a monitor.
|
||||||
|
- Fixed some DbusMenu updates being dropped from apps.
|
||||||
|
|
||||||
|
## Packaging Changes
|
||||||
|
|
||||||
|
- `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.
|
||||||
|
- `DISTRIBUTOR_DEBUGINFO_AVAILABLE` was removed as it is no longer important without breakpad.
|
||||||
|
- `libdrm` is now unconditionally required as a direct dependency.
|
||||||
+17
-6
@@ -10,13 +10,16 @@
|
|||||||
ninja,
|
ninja,
|
||||||
spirv-tools,
|
spirv-tools,
|
||||||
qt6,
|
qt6,
|
||||||
breakpad,
|
cpptrace ? null,
|
||||||
|
libunwind,
|
||||||
|
libdwarf,
|
||||||
jemalloc,
|
jemalloc,
|
||||||
cli11,
|
cli11,
|
||||||
wayland,
|
wayland,
|
||||||
wayland-protocols,
|
wayland-protocols,
|
||||||
wayland-scanner,
|
wayland-scanner,
|
||||||
xorg,
|
xorg,
|
||||||
|
libxcb ? xorg.libxcb,
|
||||||
libdrm,
|
libdrm,
|
||||||
libgbm ? null,
|
libgbm ? null,
|
||||||
vulkan-headers,
|
vulkan-headers,
|
||||||
@@ -49,9 +52,11 @@
|
|||||||
withPolkit ? true,
|
withPolkit ? true,
|
||||||
withNetworkManager ? true,
|
withNetworkManager ? true,
|
||||||
}: let
|
}: let
|
||||||
|
withCrashHandler = withCrashReporter && cpptrace != null && lib.strings.compareVersions cpptrace.version "0.7.2" >= 0;
|
||||||
|
|
||||||
unwrapped = stdenv.mkDerivation {
|
unwrapped = stdenv.mkDerivation {
|
||||||
pname = "quickshell${lib.optionalString debug "-debug"}";
|
pname = "quickshell${lib.optionalString debug "-debug"}";
|
||||||
version = "0.2.1";
|
version = "0.3.0";
|
||||||
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
|
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
|
||||||
|
|
||||||
dontWrapQtApps = true; # see wrappers
|
dontWrapQtApps = true; # see wrappers
|
||||||
@@ -71,15 +76,21 @@
|
|||||||
buildInputs = [
|
buildInputs = [
|
||||||
qt6.qtbase
|
qt6.qtbase
|
||||||
qt6.qtdeclarative
|
qt6.qtdeclarative
|
||||||
|
libdrm
|
||||||
cli11
|
cli11
|
||||||
]
|
]
|
||||||
++ lib.optional withQtSvg qt6.qtsvg
|
++ lib.optional withQtSvg qt6.qtsvg
|
||||||
++ lib.optional withCrashReporter breakpad
|
++ lib.optional withCrashHandler (cpptrace.overrideAttrs (prev: {
|
||||||
|
cmakeFlags = prev.cmakeFlags ++ [
|
||||||
|
"-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
|
||||||
|
];
|
||||||
|
buildInputs = prev.buildInputs ++ [ libunwind ];
|
||||||
|
}))
|
||||||
++ lib.optional withJemalloc jemalloc
|
++ lib.optional withJemalloc jemalloc
|
||||||
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
||||||
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
||||||
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
|
++ lib.optionals (withWayland && libgbm != null) [ libgbm vulkan-headers ]
|
||||||
++ lib.optional withX11 xorg.libxcb
|
++ lib.optional withX11 libxcb
|
||||||
++ lib.optional withPam pam
|
++ lib.optional withPam pam
|
||||||
++ lib.optional withPipewire pipewire
|
++ lib.optional withPipewire pipewire
|
||||||
++ lib.optionals withPolkit [ polkit glib ];
|
++ lib.optionals withPolkit [ polkit glib ];
|
||||||
@@ -91,7 +102,7 @@
|
|||||||
(lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
|
(lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
|
||||||
(lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
|
(lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
|
||||||
(lib.cmakeFeature "GIT_REVISION" gitRev)
|
(lib.cmakeFeature "GIT_REVISION" gitRev)
|
||||||
(lib.cmakeBool "CRASH_REPORTER" withCrashReporter)
|
(lib.cmakeBool "CRASH_HANDLER" withCrashHandler)
|
||||||
(lib.cmakeBool "USE_JEMALLOC" withJemalloc)
|
(lib.cmakeBool "USE_JEMALLOC" withJemalloc)
|
||||||
(lib.cmakeBool "WAYLAND" withWayland)
|
(lib.cmakeBool "WAYLAND" withWayland)
|
||||||
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
|
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
(define-module (quickshell)
|
|
||||||
#:use-module ((guix licenses) #:prefix license:)
|
|
||||||
#:use-module (gnu packages cpp)
|
|
||||||
#:use-module (gnu packages freedesktop)
|
|
||||||
#:use-module (gnu packages gcc)
|
|
||||||
#:use-module (gnu packages gl)
|
|
||||||
#:use-module (gnu packages jemalloc)
|
|
||||||
#:use-module (gnu packages linux)
|
|
||||||
#:use-module (gnu packages ninja)
|
|
||||||
#:use-module (gnu packages pkg-config)
|
|
||||||
#:use-module (gnu packages qt)
|
|
||||||
#:use-module (gnu packages vulkan)
|
|
||||||
#:use-module (gnu packages xdisorg)
|
|
||||||
#:use-module (gnu packages xorg)
|
|
||||||
#:use-module (guix build-system cmake)
|
|
||||||
#:use-module (guix download)
|
|
||||||
#:use-module (guix gexp)
|
|
||||||
#:use-module (guix git-download)
|
|
||||||
#:use-module (guix packages)
|
|
||||||
#:use-module (guix packages)
|
|
||||||
#:use-module (guix utils))
|
|
||||||
|
|
||||||
(define-public quickshell-git
|
|
||||||
(package
|
|
||||||
(name "quickshell")
|
|
||||||
(version "git")
|
|
||||||
(source (local-file "." "quickshell-checkout"
|
|
||||||
#:recursive? #t
|
|
||||||
#:select? (or (git-predicate (current-source-directory))
|
|
||||||
(const #t))))
|
|
||||||
(build-system cmake-build-system)
|
|
||||||
(propagated-inputs (list qtbase qtdeclarative qtsvg))
|
|
||||||
(native-inputs (list ninja
|
|
||||||
gcc-14
|
|
||||||
pkg-config
|
|
||||||
qtshadertools
|
|
||||||
spirv-tools
|
|
||||||
wayland-protocols
|
|
||||||
cli11))
|
|
||||||
(inputs (list jemalloc
|
|
||||||
libdrm
|
|
||||||
libxcb
|
|
||||||
libxkbcommon
|
|
||||||
linux-pam
|
|
||||||
polkit
|
|
||||||
mesa
|
|
||||||
pipewire
|
|
||||||
qtbase
|
|
||||||
qtdeclarative
|
|
||||||
qtwayland
|
|
||||||
vulkan-headers
|
|
||||||
wayland))
|
|
||||||
(arguments
|
|
||||||
(list #:tests? #f
|
|
||||||
#:configure-flags
|
|
||||||
#~(list "-GNinja"
|
|
||||||
"-DDISTRIBUTOR=\"In-tree Guix channel\""
|
|
||||||
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
|
|
||||||
;; Breakpad is not currently packaged for Guix.
|
|
||||||
"-DCRASH_REPORTER=OFF")
|
|
||||||
#:phases
|
|
||||||
#~(modify-phases %standard-phases
|
|
||||||
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
|
|
||||||
(replace 'install (lambda _ (invoke "cmake" "--install" ".")))
|
|
||||||
(add-after 'install 'wrap-program
|
|
||||||
(lambda* (#:key inputs #:allow-other-keys)
|
|
||||||
(wrap-program (string-append #$output "/bin/quickshell")
|
|
||||||
`("QML_IMPORT_PATH" ":"
|
|
||||||
= (,(getenv "QML_IMPORT_PATH")))))))))
|
|
||||||
(home-page "https://quickshell.outfoxxed.me")
|
|
||||||
(synopsis "QtQuick-based desktop shell toolkit")
|
|
||||||
(description
|
|
||||||
"Quickshell is a flexible QtQuick-based toolkit for creating and
|
|
||||||
customizing toolbars, notification centers, and other desktop
|
|
||||||
environment tools in a live programming environment.")
|
|
||||||
(license license:lgpl3)))
|
|
||||||
|
|
||||||
quickshell-git
|
|
||||||
+2
-1
@@ -11,8 +11,9 @@ add_subdirectory(window)
|
|||||||
add_subdirectory(io)
|
add_subdirectory(io)
|
||||||
add_subdirectory(widgets)
|
add_subdirectory(widgets)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
|
add_subdirectory(windowmanager)
|
||||||
|
|
||||||
if (CRASH_REPORTER)
|
if (CRASH_HANDLER)
|
||||||
add_subdirectory(crash)
|
add_subdirectory(crash)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,10 @@ if (NOT DEFINED GIT_REVISION)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (CRASH_REPORTER)
|
if (CRASH_HANDLER)
|
||||||
set(CRASH_REPORTER_DEF 1)
|
set(CRASH_HANDLER_DEF 1)
|
||||||
else()
|
else()
|
||||||
set(CRASH_REPORTER_DEF 0)
|
set(CRASH_HANDLER_DEF 0)
|
||||||
endif()
|
|
||||||
|
|
||||||
if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
|
|
||||||
set(DEBUGINFO_AVAILABLE 1)
|
|
||||||
else()
|
|
||||||
set(DEBUGINFO_AVAILABLE 0)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
|
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
|
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
|
||||||
#define GIT_REVISION "@GIT_REVISION@"
|
#define GIT_REVISION "@GIT_REVISION@"
|
||||||
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
||||||
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
|
#define CRASH_HANDLER @CRASH_HANDLER_DEF@
|
||||||
#define CRASH_REPORTER @CRASH_REPORTER_DEF@
|
|
||||||
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
|
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
|
||||||
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
|
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
|
||||||
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
|
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
|
||||||
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
|
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
|
||||||
|
#define CRASHREPORT_URL "@CRASHREPORT_URL@"
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pkg_check_modules(libdrm REQUIRED IMPORTED_TARGET libdrm)
|
||||||
qt_add_library(quickshell-core STATIC
|
qt_add_library(quickshell-core STATIC
|
||||||
plugin.cpp
|
plugin.cpp
|
||||||
shell.cpp
|
shell.cpp
|
||||||
@@ -40,6 +41,8 @@ qt_add_library(quickshell-core STATIC
|
|||||||
scriptmodel.cpp
|
scriptmodel.cpp
|
||||||
colorquantizer.cpp
|
colorquantizer.cpp
|
||||||
toolsupport.cpp
|
toolsupport.cpp
|
||||||
|
streamreader.cpp
|
||||||
|
debuginfo.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(quickshell-core
|
qt_add_qml_module(quickshell-core
|
||||||
@@ -52,7 +55,7 @@ qt_add_qml_module(quickshell-core
|
|||||||
|
|
||||||
install_qml_module(quickshell-core)
|
install_qml_module(quickshell-core)
|
||||||
|
|
||||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build)
|
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build PkgConfig::libdrm)
|
||||||
|
|
||||||
qs_module_pch(quickshell-core SET large)
|
qs_module_pch(quickshell-core SET large)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <qnumeric.h>
|
#include <qnumeric.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
|
#include <qrect.h>
|
||||||
#include <qrgb.h>
|
#include <qrgb.h>
|
||||||
#include <qthreadpool.h>
|
#include <qthreadpool.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
@@ -24,9 +25,15 @@ namespace {
|
|||||||
QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
|
ColorQuantizerOperation::ColorQuantizerOperation(
|
||||||
|
QUrl* source,
|
||||||
|
qreal depth,
|
||||||
|
QRect imageRect,
|
||||||
|
qreal rescaleSize
|
||||||
|
)
|
||||||
: source(source)
|
: source(source)
|
||||||
, maxDepth(depth)
|
, maxDepth(depth)
|
||||||
|
, imageRect(imageRect)
|
||||||
, rescaleSize(rescaleSize) {
|
, rescaleSize(rescaleSize) {
|
||||||
this->setAutoDelete(false);
|
this->setAutoDelete(false);
|
||||||
}
|
}
|
||||||
@@ -37,6 +44,11 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCa
|
|||||||
this->colors.clear();
|
this->colors.clear();
|
||||||
|
|
||||||
auto image = QImage(this->source->toLocalFile());
|
auto image = QImage(this->source->toLocalFile());
|
||||||
|
|
||||||
|
if (this->imageRect.isValid()) {
|
||||||
|
image = image.copy(this->imageRect);
|
||||||
|
}
|
||||||
|
|
||||||
if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize)
|
if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize)
|
||||||
&& this->rescaleSize > 0)
|
&& this->rescaleSize > 0)
|
||||||
{
|
{
|
||||||
@@ -198,16 +210,27 @@ void ColorQuantizer::setDepth(qreal depth) {
|
|||||||
this->mDepth = depth;
|
this->mDepth = depth;
|
||||||
emit this->depthChanged();
|
emit this->depthChanged();
|
||||||
|
|
||||||
if (this->componentCompleted) this->quantizeAsync();
|
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ColorQuantizer::setImageRect(QRect imageRect) {
|
||||||
|
if (this->mImageRect != imageRect) {
|
||||||
|
this->mImageRect = imageRect;
|
||||||
|
emit this->imageRectChanged();
|
||||||
|
|
||||||
|
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorQuantizer::resetImageRect() { this->setImageRect(QRect()); }
|
||||||
|
|
||||||
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
||||||
if (this->mRescaleSize != rescaleSize) {
|
if (this->mRescaleSize != rescaleSize) {
|
||||||
this->mRescaleSize = rescaleSize;
|
this->mRescaleSize = rescaleSize;
|
||||||
emit this->rescaleSizeChanged();
|
emit this->rescaleSizeChanged();
|
||||||
|
|
||||||
if (this->componentCompleted) this->quantizeAsync();
|
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,8 +244,13 @@ void ColorQuantizer::quantizeAsync() {
|
|||||||
if (this->liveOperation) this->cancelAsync();
|
if (this->liveOperation) this->cancelAsync();
|
||||||
|
|
||||||
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
||||||
this->liveOperation =
|
|
||||||
new ColorQuantizerOperation(&this->mSource, this->mDepth, this->mRescaleSize);
|
this->liveOperation = new ColorQuantizerOperation(
|
||||||
|
&this->mSource,
|
||||||
|
this->mDepth,
|
||||||
|
this->mImageRect,
|
||||||
|
this->mRescaleSize
|
||||||
|
);
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->liveOperation,
|
this->liveOperation,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <qproperty.h>
|
#include <qproperty.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
#include <qqmlparserstatus.h>
|
#include <qqmlparserstatus.h>
|
||||||
|
#include <qrect.h>
|
||||||
#include <qrunnable.h>
|
#include <qrunnable.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
@@ -16,7 +17,7 @@ class ColorQuantizerOperation
|
|||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
|
explicit ColorQuantizerOperation(QUrl* source, qreal depth, QRect imageRect, qreal rescaleSize);
|
||||||
|
|
||||||
void run() override;
|
void run() override;
|
||||||
void tryCancel();
|
void tryCancel();
|
||||||
@@ -44,6 +45,7 @@ private:
|
|||||||
QList<QColor> colors;
|
QList<QColor> colors;
|
||||||
QUrl* source;
|
QUrl* source;
|
||||||
qreal maxDepth;
|
qreal maxDepth;
|
||||||
|
QRect imageRect;
|
||||||
qreal rescaleSize;
|
qreal rescaleSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -78,9 +80,16 @@ class ColorQuantizer
|
|||||||
/// binary split of the color space
|
/// binary split of the color space
|
||||||
Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
|
Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
/// Rectangle that the source image is cropped to.
|
||||||
|
///
|
||||||
|
/// Can be set to `undefined` to reset.
|
||||||
|
Q_PROPERTY(QRect imageRect READ imageRect WRITE setImageRect RESET resetImageRect NOTIFY imageRectChanged);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
|
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
|
||||||
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
|
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
|
||||||
/// > reccommended to rescale, otherwise the quantization process will take much longer.
|
/// > recommended to rescale, otherwise the quantization process will take much longer.
|
||||||
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
|
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -97,6 +106,10 @@ public:
|
|||||||
[[nodiscard]] qreal depth() const { return this->mDepth; }
|
[[nodiscard]] qreal depth() const { return this->mDepth; }
|
||||||
void setDepth(qreal depth);
|
void setDepth(qreal depth);
|
||||||
|
|
||||||
|
[[nodiscard]] QRect imageRect() const { return this->mImageRect; }
|
||||||
|
void setImageRect(QRect imageRect);
|
||||||
|
void resetImageRect();
|
||||||
|
|
||||||
[[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; }
|
[[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; }
|
||||||
void setRescaleSize(int rescaleSize);
|
void setRescaleSize(int rescaleSize);
|
||||||
|
|
||||||
@@ -104,6 +117,7 @@ signals:
|
|||||||
void colorsChanged();
|
void colorsChanged();
|
||||||
void sourceChanged();
|
void sourceChanged();
|
||||||
void depthChanged();
|
void depthChanged();
|
||||||
|
void imageRectChanged();
|
||||||
void rescaleSizeChanged();
|
void rescaleSizeChanged();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
@@ -117,6 +131,7 @@ private:
|
|||||||
ColorQuantizerOperation* liveOperation = nullptr;
|
ColorQuantizerOperation* liveOperation = nullptr;
|
||||||
QUrl mSource;
|
QUrl mSource;
|
||||||
qreal mDepth = 0;
|
qreal mDepth = 0;
|
||||||
|
QRect mImageRect;
|
||||||
qreal mRescaleSize = 0;
|
qreal mRescaleSize = 0;
|
||||||
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(
|
Q_OBJECT_BINDABLE_PROPERTY(
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
#include "debuginfo.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <qconfig.h>
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qdebug.h>
|
||||||
|
#include <qfile.h>
|
||||||
|
#include <qfloat16.h>
|
||||||
|
#include <qhashfunctions.h>
|
||||||
|
#include <qscopeguard.h>
|
||||||
|
#include <qtversion.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <xf86drm.h>
|
||||||
|
|
||||||
|
#include "build.hpp"
|
||||||
|
|
||||||
|
extern char** environ; // NOLINT
|
||||||
|
|
||||||
|
namespace qs::debuginfo {
|
||||||
|
|
||||||
|
QString qsVersion() {
|
||||||
|
return QS_VERSION " (revision " GIT_REVISION ", distributed by " DISTRIBUTOR ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString qtVersion() { return qVersion() % QStringLiteral(" (built against " QT_VERSION_STR ")"); }
|
||||||
|
|
||||||
|
QString gpuInfo() {
|
||||||
|
auto deviceCount = drmGetDevices2(0, nullptr, 0);
|
||||||
|
if (deviceCount < 0) return "Failed to get DRM device count: " % QString::number(deviceCount);
|
||||||
|
auto* devices = new drmDevicePtr[deviceCount];
|
||||||
|
auto devicesArrayGuard = qScopeGuard([&] { delete[] devices; });
|
||||||
|
auto r = drmGetDevices2(0, devices, deviceCount);
|
||||||
|
if (deviceCount < 0) return "Failed to get DRM devices: " % QString::number(r);
|
||||||
|
auto devicesGuard = qScopeGuard([&] {
|
||||||
|
for (auto i = 0; i != deviceCount; ++i) drmFreeDevice(&devices[i]); // NOLINT
|
||||||
|
});
|
||||||
|
|
||||||
|
QString info;
|
||||||
|
auto stream = QTextStream(&info);
|
||||||
|
|
||||||
|
for (auto i = 0; i != deviceCount; ++i) {
|
||||||
|
auto* device = devices[i]; // NOLINT
|
||||||
|
|
||||||
|
int deviceNodeType = -1;
|
||||||
|
if (device->available_nodes & (1 << DRM_NODE_RENDER)) deviceNodeType = DRM_NODE_RENDER;
|
||||||
|
else if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) deviceNodeType = DRM_NODE_PRIMARY;
|
||||||
|
|
||||||
|
if (deviceNodeType == -1) continue;
|
||||||
|
|
||||||
|
auto* deviceNode = device->nodes[DRM_NODE_RENDER]; // NOLINT
|
||||||
|
|
||||||
|
auto driver = [&]() -> QString {
|
||||||
|
auto fd = open(deviceNode, O_RDWR | O_CLOEXEC);
|
||||||
|
if (fd == -1) return "<failed to open device node>";
|
||||||
|
auto fdGuard = qScopeGuard([&] { close(fd); });
|
||||||
|
auto* ver = drmGetVersion(fd);
|
||||||
|
if (!ver) return "<drmGetVersion failed>";
|
||||||
|
auto verGuard = qScopeGuard([&] { drmFreeVersion(ver); });
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
return QString(ver->name)
|
||||||
|
% ' ' % QString::number(ver->version_major)
|
||||||
|
% '.' % QString::number(ver->version_minor)
|
||||||
|
% '.' % QString::number(ver->version_patchlevel)
|
||||||
|
% " (" % ver->desc % ')';
|
||||||
|
// clang-format on
|
||||||
|
}();
|
||||||
|
|
||||||
|
QString product = "unknown";
|
||||||
|
QString address = "unknown";
|
||||||
|
|
||||||
|
auto hex = [](int num, int pad) { return QString::number(num, 16).rightJustified(pad, '0'); };
|
||||||
|
|
||||||
|
switch (device->bustype) {
|
||||||
|
case DRM_BUS_PCI: {
|
||||||
|
auto* b = device->businfo.pci;
|
||||||
|
auto* d = device->deviceinfo.pci;
|
||||||
|
address = "PCI " % hex(b->bus, 2) % ':' % hex(b->dev, 2) % '.' % hex(b->func, 1);
|
||||||
|
product = hex(d->vendor_id, 4) % ':' % hex(d->device_id, 4);
|
||||||
|
} break;
|
||||||
|
case DRM_BUS_USB: {
|
||||||
|
auto* b = device->businfo.usb;
|
||||||
|
auto* d = device->deviceinfo.usb;
|
||||||
|
address = "USB " % QString::number(b->bus) % ':' % QString::number(b->dev);
|
||||||
|
product = hex(d->vendor, 4) % ':' % hex(d->product, 4);
|
||||||
|
} break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream << "GPU " << deviceNode << "\n Driver: " << driver << "\n Model: " << product
|
||||||
|
<< "\n Address: " << address << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString systemInfo() {
|
||||||
|
QString info;
|
||||||
|
auto stream = QTextStream(&info);
|
||||||
|
|
||||||
|
stream << gpuInfo() << '\n';
|
||||||
|
|
||||||
|
stream << "/etc/os-release:";
|
||||||
|
auto osReleaseFile = QFile("/etc/os-release");
|
||||||
|
if (osReleaseFile.open(QFile::ReadOnly)) {
|
||||||
|
stream << '\n' << osReleaseFile.readAll() << '\n';
|
||||||
|
osReleaseFile.close();
|
||||||
|
} else {
|
||||||
|
stream << "FAILED TO OPEN\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
stream << "/etc/lsb-release:";
|
||||||
|
auto lsbReleaseFile = QFile("/etc/lsb-release");
|
||||||
|
if (lsbReleaseFile.open(QFile::ReadOnly)) {
|
||||||
|
stream << '\n' << lsbReleaseFile.readAll();
|
||||||
|
lsbReleaseFile.close();
|
||||||
|
} else {
|
||||||
|
stream << "FAILED TO OPEN\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString envInfo() {
|
||||||
|
QString info;
|
||||||
|
auto stream = QTextStream(&info);
|
||||||
|
|
||||||
|
for (auto** envp = environ; *envp != nullptr; ++envp) { // NOLINT
|
||||||
|
auto prefixes = std::array<std::string_view, 6> {
|
||||||
|
"QS_",
|
||||||
|
"QT_",
|
||||||
|
"QML_",
|
||||||
|
"QML2_",
|
||||||
|
"QSG_",
|
||||||
|
"XDG_CURRENT_DESKTOP=",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& prefix: prefixes) {
|
||||||
|
if (strncmp(prefix.data(), *envp, prefix.length()) == 0) goto print;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
|
||||||
|
print:
|
||||||
|
stream << *envp << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString combinedInfo() {
|
||||||
|
QString info;
|
||||||
|
auto stream = QTextStream(&info);
|
||||||
|
|
||||||
|
stream << "===== Version Information =====\n";
|
||||||
|
stream << "Quickshell: " << qsVersion() << '\n';
|
||||||
|
stream << "Qt: " << qtVersion() << '\n';
|
||||||
|
|
||||||
|
stream << "\n===== Build Information =====\n";
|
||||||
|
stream << "Build Type: " << BUILD_TYPE << '\n';
|
||||||
|
stream << "Compiler: " << COMPILER << '\n';
|
||||||
|
stream << "Compile Flags: " << COMPILE_FLAGS << '\n';
|
||||||
|
stream << "Configuration:\n" << BUILD_CONFIGURATION << '\n';
|
||||||
|
|
||||||
|
stream << "\n===== System Information =====\n";
|
||||||
|
stream << systemInfo();
|
||||||
|
|
||||||
|
stream << "\n===== Environment (trimmed) =====\n";
|
||||||
|
stream << envInfo();
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::debuginfo
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
|
||||||
|
namespace qs::debuginfo {
|
||||||
|
|
||||||
|
QString qsVersion();
|
||||||
|
QString qtVersion();
|
||||||
|
QString gpuInfo();
|
||||||
|
QString systemInfo();
|
||||||
|
QString envInfo();
|
||||||
|
QString combinedInfo();
|
||||||
|
|
||||||
|
} // namespace qs::debuginfo
|
||||||
@@ -107,7 +107,10 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
|||||||
auto groupName = QString();
|
auto groupName = QString();
|
||||||
auto entries = QHash<QString, QPair<Locale, QString>>();
|
auto entries = QHash<QString, QPair<Locale, QString>>();
|
||||||
|
|
||||||
auto finishCategory = [&data, &groupName, &entries]() {
|
auto actionOrder = QStringList();
|
||||||
|
auto pendingActions = QHash<QString, DesktopActionData>();
|
||||||
|
|
||||||
|
auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() {
|
||||||
if (groupName == "Desktop Entry") {
|
if (groupName == "Desktop Entry") {
|
||||||
if (entries.value("Type").second != "Application") return;
|
if (entries.value("Type").second != "Application") return;
|
||||||
|
|
||||||
@@ -129,9 +132,10 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
|||||||
else if (key == "Terminal") data.terminal = value == "true";
|
else if (key == "Terminal") data.terminal = value == "true";
|
||||||
else if (key == "Categories") data.categories = value.split(u';', Qt::SkipEmptyParts);
|
else if (key == "Categories") data.categories = value.split(u';', Qt::SkipEmptyParts);
|
||||||
else if (key == "Keywords") data.keywords = value.split(u';', Qt::SkipEmptyParts);
|
else if (key == "Keywords") data.keywords = value.split(u';', Qt::SkipEmptyParts);
|
||||||
|
else if (key == "Actions") actionOrder = value.split(u';', Qt::SkipEmptyParts);
|
||||||
}
|
}
|
||||||
} else if (groupName.startsWith("Desktop Action ")) {
|
} else if (groupName.startsWith("Desktop Action ")) {
|
||||||
auto actionName = groupName.sliced(16);
|
auto actionName = groupName.sliced(15);
|
||||||
DesktopActionData action;
|
DesktopActionData action;
|
||||||
action.id = actionName;
|
action.id = actionName;
|
||||||
|
|
||||||
@@ -147,7 +151,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.actions.insert(actionName, action);
|
pendingActions.insert(actionName, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.clear();
|
entries.clear();
|
||||||
@@ -193,6 +197,13 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
|||||||
}
|
}
|
||||||
|
|
||||||
finishCategory();
|
finishCategory();
|
||||||
|
|
||||||
|
for (const auto& actionId: actionOrder) {
|
||||||
|
if (pendingActions.contains(actionId)) {
|
||||||
|
data.actions.append(pendingActions.value(actionId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,17 +227,18 @@ void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) {
|
|||||||
this->updateActions(newState.actions);
|
this->updateActions(newState.actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopEntry::updateActions(const QHash<QString, DesktopActionData>& newActions) {
|
void DesktopEntry::updateActions(const QVector<DesktopActionData>& newActions) {
|
||||||
auto old = this->mActions;
|
auto old = this->mActions;
|
||||||
|
this->mActions.clear();
|
||||||
|
|
||||||
for (const auto& [key, d]: newActions.asKeyValueRange()) {
|
for (const auto& d: newActions) {
|
||||||
DesktopAction* act = nullptr;
|
DesktopAction* act = nullptr;
|
||||||
if (auto found = old.find(key); found != old.end()) {
|
auto found = std::ranges::find(old, d.id, &DesktopAction::mId);
|
||||||
act = found.value();
|
if (found != old.end()) {
|
||||||
|
act = *found;
|
||||||
old.erase(found);
|
old.erase(found);
|
||||||
} else {
|
} else {
|
||||||
act = new DesktopAction(d.id, this);
|
act = new DesktopAction(d.id, this);
|
||||||
this->mActions.insert(key, act);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::beginPropertyUpdateGroup();
|
Qt::beginPropertyUpdateGroup();
|
||||||
@@ -237,6 +249,7 @@ void DesktopEntry::updateActions(const QHash<QString, DesktopActionData>& newAct
|
|||||||
Qt::endPropertyUpdateGroup();
|
Qt::endPropertyUpdateGroup();
|
||||||
|
|
||||||
act->mEntries = d.entries;
|
act->mEntries = d.entries;
|
||||||
|
this->mActions.append(act);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto* leftover: old) {
|
for (auto* leftover: old) {
|
||||||
@@ -250,7 +263,7 @@ void DesktopEntry::execute() const {
|
|||||||
|
|
||||||
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
|
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
|
||||||
|
|
||||||
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions.values(); }
|
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions; }
|
||||||
|
|
||||||
QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
||||||
QVector<QString> arguments;
|
QVector<QString> arguments;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ struct ParsedDesktopEntryData {
|
|||||||
QVector<QString> categories;
|
QVector<QString> categories;
|
||||||
QVector<QString> keywords;
|
QVector<QString> keywords;
|
||||||
QHash<QString, QString> entries;
|
QHash<QString, QString> entries;
|
||||||
QHash<QString, DesktopActionData> actions;
|
QVector<DesktopActionData> actions;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A desktop entry. See @@DesktopEntries for details.
|
/// A desktop entry. See @@DesktopEntries for details.
|
||||||
@@ -164,10 +164,10 @@ public:
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateActions(const QHash<QString, DesktopActionData>& newActions);
|
void updateActions(const QVector<DesktopActionData>& newActions);
|
||||||
|
|
||||||
ParsedDesktopEntryData state;
|
ParsedDesktopEntryData state;
|
||||||
QHash<QString, DesktopAction*> mActions;
|
QVector<DesktopAction*> mActions;
|
||||||
|
|
||||||
friend class DesktopAction;
|
friend class DesktopAction;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -209,6 +209,8 @@ bool EngineGeneration::setExtraWatchedFiles(const QVector<QString>& files) {
|
|||||||
for (const auto& file: files) {
|
for (const auto& file: files) {
|
||||||
if (!this->scanner.scannedFiles.contains(file)) {
|
if (!this->scanner.scannedFiles.contains(file)) {
|
||||||
this->extraWatchedFiles.append(file);
|
this->extraWatchedFiles.append(file);
|
||||||
|
QByteArray data;
|
||||||
|
this->scanner.readAndHashFile(file, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,6 +231,11 @@ void EngineGeneration::onFileChanged(const QString& name) {
|
|||||||
auto fileInfo = QFileInfo(name);
|
auto fileInfo = QFileInfo(name);
|
||||||
if (fileInfo.isFile() && fileInfo.size() == 0) return;
|
if (fileInfo.isFile() && fileInfo.size() == 0) return;
|
||||||
|
|
||||||
|
if (!this->scanner.hasFileContentChanged(name)) {
|
||||||
|
qCDebug(logQmlScanner) << "Ignoring file change with unchanged content:" << name;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
emit this->filesChanged();
|
emit this->filesChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,6 +244,11 @@ void EngineGeneration::onDirectoryChanged() {
|
|||||||
// try to find any files that were just deleted from a replace operation
|
// try to find any files that were just deleted from a replace operation
|
||||||
for (auto& file: this->deletedWatchedFiles) {
|
for (auto& file: this->deletedWatchedFiles) {
|
||||||
if (QFileInfo(file).exists()) {
|
if (QFileInfo(file).exists()) {
|
||||||
|
if (!this->scanner.hasFileContentChanged(file)) {
|
||||||
|
qCDebug(logQmlScanner) << "Ignoring restored file with unchanged content:" << file;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
emit this->filesChanged();
|
emit this->filesChanged();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#include "generation.hpp"
|
#include "generation.hpp"
|
||||||
|
|
||||||
// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
|
// QMenu re-calls pixmap() every time the mouse moves so it's important to cache it.
|
||||||
class PixmapCacheIconEngine: public QIconEngine {
|
class PixmapCacheIconEngine: public QIconEngine {
|
||||||
void paint(
|
void paint(
|
||||||
QPainter* /*unused*/,
|
QPainter* /*unused*/,
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
#include <qdatastream.h>
|
#include <qdatastream.h>
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
|
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.appId << info.launchTime
|
||||||
<< info.display;
|
<< info.pid << info.display;
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
|
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.appId >> info.launchTime
|
||||||
>> info.display;
|
>> info.pid >> info.display;
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ struct InstanceInfo {
|
|||||||
QString instanceId;
|
QString instanceId;
|
||||||
QString configPath;
|
QString configPath;
|
||||||
QString shellId;
|
QString shellId;
|
||||||
|
QString appId;
|
||||||
QDateTime launchTime;
|
QDateTime launchTime;
|
||||||
pid_t pid = -1;
|
pid_t pid = -1;
|
||||||
QString display;
|
QString display;
|
||||||
@@ -35,6 +36,8 @@ namespace qs::crash {
|
|||||||
|
|
||||||
struct CrashInfo {
|
struct CrashInfo {
|
||||||
int logFd = -1;
|
int logFd = -1;
|
||||||
|
int traceFd = -1;
|
||||||
|
int infoFd = -1;
|
||||||
|
|
||||||
static CrashInfo INSTANCE; // NOLINT
|
static CrashInfo INSTANCE; // NOLINT
|
||||||
};
|
};
|
||||||
|
|||||||
+27
-5
@@ -14,6 +14,7 @@
|
|||||||
#include <qlist.h>
|
#include <qlist.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
|
#include <qmutex.h>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qobjectdefs.h>
|
#include <qobjectdefs.h>
|
||||||
@@ -31,6 +32,9 @@
|
|||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "instanceinfo.hpp"
|
#include "instanceinfo.hpp"
|
||||||
#include "logcat.hpp"
|
#include "logcat.hpp"
|
||||||
@@ -67,7 +71,7 @@ bool copyFileData(int sourceFd, int destFd, qint64 size) {
|
|||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
std::array<char, 64 * 1024> buffer = {};
|
std::array<char, 64 * 1024> buffer = {};
|
||||||
auto remaining = totalTarget;
|
auto remaining = usize;
|
||||||
|
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
auto chunk = std::min(remaining, buffer.size());
|
auto chunk = std::min(remaining, buffer.size());
|
||||||
@@ -217,6 +221,7 @@ void LogManager::messageHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (display) {
|
if (display) {
|
||||||
|
auto locker = QMutexLocker(&self->stdoutMutex);
|
||||||
LogMessage::formatMessage(
|
LogMessage::formatMessage(
|
||||||
self->stdoutStream,
|
self->stdoutStream,
|
||||||
message,
|
message,
|
||||||
@@ -252,11 +257,23 @@ void LogManager::filterCategory(QLoggingCategory* category) {
|
|||||||
filter.warn = filter.info || instance->mDefaultLevel == QtWarningMsg || defaultLevel == QtWarningMsg;
|
filter.warn = filter.info || instance->mDefaultLevel == QtWarningMsg || defaultLevel == QtWarningMsg;
|
||||||
filter.critical = filter.warn || instance->mDefaultLevel == QtCriticalMsg || defaultLevel == QtCriticalMsg;
|
filter.critical = filter.warn || instance->mDefaultLevel == QtCriticalMsg || defaultLevel == QtCriticalMsg;
|
||||||
// clang-format on
|
// clang-format on
|
||||||
} else if (instance->lastCategoryFilter) {
|
} else {
|
||||||
|
if (instance->lastCategoryFilter) {
|
||||||
instance->lastCategoryFilter(category);
|
instance->lastCategoryFilter(category);
|
||||||
filter = CategoryFilter(category);
|
filter = CategoryFilter(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hides virtual/override property warnings.
|
||||||
|
// Getting rid of this is blocked by https://qt-project.atlassian.net/browse/QTBUG-145977
|
||||||
|
// for internal Quickshell types, and may still be desired for shells that want to maintain
|
||||||
|
// compatibility with Qt versions prior to 6.11.
|
||||||
|
if (categoryName == QLatin1StringView("qt.qml.propertyCache.append")
|
||||||
|
&& !qEnvironmentVariableIsSet("QS_NO_FILTER_QT_LOGS"))
|
||||||
|
{
|
||||||
|
filter.warn = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& rule: *instance->rules) {
|
for (const auto& rule: *instance->rules) {
|
||||||
filter.applyRule(categoryName, rule);
|
filter.applyRule(categoryName, rule);
|
||||||
}
|
}
|
||||||
@@ -305,10 +322,15 @@ void LogManager::init(
|
|||||||
instance->rules->append(parser.rules());
|
instance->rules->append(parser.rules());
|
||||||
}
|
}
|
||||||
|
|
||||||
qInstallMessageHandler(&LogManager::messageHandler);
|
|
||||||
|
|
||||||
instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
|
instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
|
||||||
|
|
||||||
|
if (instance->lastCategoryFilter == &LogManager::filterCategory) {
|
||||||
|
qCFatal(logLogging) << "Quickshell's log filter has been installed twice. This is a bug.";
|
||||||
|
instance->lastCategoryFilter = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
qInstallMessageHandler(&LogManager::messageHandler);
|
||||||
|
|
||||||
qCDebug(logLogging) << "Creating offthread logger...";
|
qCDebug(logLogging) << "Creating offthread logger...";
|
||||||
auto* thread = new QThread();
|
auto* thread = new QThread();
|
||||||
instance->threadProxy.moveToThread(thread);
|
instance->threadProxy.moveToThread(thread);
|
||||||
@@ -520,7 +542,7 @@ void ThreadLogging::initFs() {
|
|||||||
Qt::QueuedConnection
|
Qt::QueuedConnection
|
||||||
);
|
);
|
||||||
|
|
||||||
qCDebug(logLogging) << "Switched threaded logger to queued eventloop connection.";
|
qCDebug(logLogging) << "Switched threaded logger to queued event loop connection.";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
|
void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <qlatin1stringview.h>
|
#include <qlatin1stringview.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
|
#include <qmutex.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
@@ -135,6 +136,7 @@ private:
|
|||||||
QHash<QLatin1StringView, CategoryFilter> allFilters;
|
QHash<QLatin1StringView, CategoryFilter> allFilters;
|
||||||
|
|
||||||
QTextStream stdoutStream;
|
QTextStream stdoutStream;
|
||||||
|
QMutex stdoutMutex;
|
||||||
LoggingThreadProxy threadProxy;
|
LoggingThreadProxy threadProxy;
|
||||||
|
|
||||||
friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel);
|
friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel);
|
||||||
|
|||||||
@@ -138,6 +138,10 @@ public:
|
|||||||
qsizetype oi = 0;
|
qsizetype oi = 0;
|
||||||
for (auto* object: newValues) {
|
for (auto* object: newValues) {
|
||||||
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
|
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
|
||||||
|
// object may already be present further down (reorder case);
|
||||||
|
// drop the old row first so the same pointer is not inserted twice.
|
||||||
|
auto old = this->mValuesList.indexOf(object, oi);
|
||||||
|
if (old != -1) this->removeAt(old);
|
||||||
this->insertObject(object, oi);
|
this->insertObject(object, oi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-10
@@ -1,6 +1,7 @@
|
|||||||
#include "paths.hpp"
|
#include "paths.hpp"
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -9,7 +10,6 @@
|
|||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qpair.h>
|
|
||||||
#include <qstandardpaths.h>
|
#include <qstandardpaths.h>
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
#include <qtversionchecks.h>
|
#include <qtversionchecks.h>
|
||||||
@@ -64,7 +64,7 @@ QDir* QsPaths::baseRunDir() {
|
|||||||
if (this->baseRunState == DirState::Unknown) {
|
if (this->baseRunState == DirState::Unknown) {
|
||||||
auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR");
|
auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR");
|
||||||
if (runtimeDir.isEmpty()) {
|
if (runtimeDir.isEmpty()) {
|
||||||
runtimeDir = QString("/run/user/$1").arg(getuid());
|
runtimeDir = QString("/run/user/%1").arg(getuid());
|
||||||
qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir;
|
qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,10 +412,11 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||||
QsPaths::collectInstances(const QString& path, const QString& display) {
|
QsPaths::collectInstances(const QString& path, const QString& display) {
|
||||||
qCDebug(logPaths) << "Collecting instances from" << path;
|
qCDebug(logPaths) << "Collecting instances from" << path;
|
||||||
auto liveInstances = QVector<InstanceLockInfo>();
|
auto liveInstances = QVector<InstanceLockInfo>();
|
||||||
|
auto mismatchedInstances = QVector<InstanceLockInfo>();
|
||||||
auto deadInstances = QVector<InstanceLockInfo>();
|
auto deadInstances = QVector<InstanceLockInfo>();
|
||||||
auto dir = QDir(path);
|
auto dir = QDir(path);
|
||||||
|
|
||||||
@@ -427,20 +428,22 @@ QsPaths::collectInstances(const QString& path, const QString& display) {
|
|||||||
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
|
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
|
||||||
<< info.pid << ") at " << path;
|
<< info.pid << ") at " << path;
|
||||||
|
|
||||||
if (!display.isEmpty() && info.instance.display != display) {
|
if (info.pid == -1) {
|
||||||
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
|
deadInstances.push_back(info);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.pid == -1) {
|
if (!display.isEmpty() && info.instance.display != display) {
|
||||||
deadInstances.push_back(info);
|
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
|
||||||
} else {
|
mismatchedInstances.push_back(info);
|
||||||
liveInstances.push_back(info);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
liveInstances.push_back(info);
|
||||||
} else {
|
} else {
|
||||||
qCDebug(logPaths) << "Skipped potential instance at" << path;
|
qCDebug(logPaths) << "Skipped potential instance at" << path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return qMakePair(liveInstances, deadInstances);
|
return {liveInstances, mismatchedInstances, deadInstances};
|
||||||
}
|
}
|
||||||
+4
-1
@@ -1,4 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
#include <qdatetime.h>
|
#include <qdatetime.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qpair.h>
|
#include <qpair.h>
|
||||||
@@ -29,7 +32,7 @@ public:
|
|||||||
static QString ipcPath(const QString& id);
|
static QString ipcPath(const QString& id);
|
||||||
static bool
|
static bool
|
||||||
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
|
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
|
||||||
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
static std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||||
collectInstances(const QString& path, const QString& display);
|
collectInstances(const QString& path, const QString& display);
|
||||||
|
|
||||||
QDir* baseRunDir();
|
QDir* baseRunDir();
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
#include <qwindow.h>
|
#include <qwindow.h>
|
||||||
|
|
||||||
#include "../window/proxywindow.hpp"
|
#include "../window/proxywindow.hpp"
|
||||||
#include "../window/windowinterface.hpp"
|
|
||||||
#include "iconprovider.hpp"
|
#include "iconprovider.hpp"
|
||||||
#include "model.hpp"
|
#include "model.hpp"
|
||||||
#include "platformmenu_p.hpp"
|
#include "platformmenu_p.hpp"
|
||||||
@@ -91,10 +90,8 @@ bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relati
|
|||||||
} else if (parentWindow == nullptr) {
|
} else if (parentWindow == nullptr) {
|
||||||
qCritical() << "Cannot display PlatformMenuEntry with null parent window.";
|
qCritical() << "Cannot display PlatformMenuEntry with null parent window.";
|
||||||
return false;
|
return false;
|
||||||
} else if (auto* proxy = qobject_cast<ProxyWindowBase*>(parentWindow)) {
|
} else if (auto* proxy = ProxyWindowBase::forObject(parentWindow)) {
|
||||||
window = proxy->backingWindow();
|
window = proxy->backingWindow();
|
||||||
} else if (auto* interface = qobject_cast<WindowInterface*>(parentWindow)) {
|
|
||||||
window = interface->proxyWindow()->backingWindow();
|
|
||||||
} else {
|
} else {
|
||||||
qCritical() << "PlatformMenuEntry.display() must be called with a window.";
|
qCritical() << "PlatformMenuEntry.display() must be called with a window.";
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -9,6 +9,18 @@ static QVector<QsEnginePlugin*> plugins; // NOLINT
|
|||||||
|
|
||||||
void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); }
|
void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); }
|
||||||
|
|
||||||
|
void QsEnginePlugin::preinitPluginsOnly() {
|
||||||
|
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
|
||||||
|
|
||||||
|
std::ranges::sort(plugins, [](QsEnginePlugin* a, QsEnginePlugin* b) {
|
||||||
|
return b->dependencies().contains(a->name());
|
||||||
|
});
|
||||||
|
|
||||||
|
for (QsEnginePlugin* plugin: plugins) {
|
||||||
|
plugin->preinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void QsEnginePlugin::initPlugins() {
|
void QsEnginePlugin::initPlugins() {
|
||||||
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
|
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
|
||||||
|
|
||||||
@@ -16,6 +28,10 @@ void QsEnginePlugin::initPlugins() {
|
|||||||
return b->dependencies().contains(a->name());
|
return b->dependencies().contains(a->name());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (QsEnginePlugin* plugin: plugins) {
|
||||||
|
plugin->preinit();
|
||||||
|
}
|
||||||
|
|
||||||
for (QsEnginePlugin* plugin: plugins) {
|
for (QsEnginePlugin* plugin: plugins) {
|
||||||
plugin->init();
|
plugin->init();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ public:
|
|||||||
virtual QString name() { return QString(); }
|
virtual QString name() { return QString(); }
|
||||||
virtual QList<QString> dependencies() { return {}; }
|
virtual QList<QString> dependencies() { return {}; }
|
||||||
virtual bool applies() { return true; }
|
virtual bool applies() { return true; }
|
||||||
|
virtual void preinit() {}
|
||||||
virtual void init() {}
|
virtual void init() {}
|
||||||
virtual void registerTypes() {}
|
virtual void registerTypes() {}
|
||||||
virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT
|
virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT
|
||||||
virtual void onReload() {}
|
virtual void onReload() {}
|
||||||
|
|
||||||
static void registerPlugin(QsEnginePlugin& plugin);
|
static void registerPlugin(QsEnginePlugin& plugin);
|
||||||
|
static void preinitPluginsOnly();
|
||||||
static void initPlugins();
|
static void initPlugins();
|
||||||
static void runConstructGeneration(EngineGeneration& generation);
|
static void runConstructGeneration(EngineGeneration& generation);
|
||||||
static void runOnReload();
|
static void runOnReload();
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include <qwindow.h>
|
#include <qwindow.h>
|
||||||
|
|
||||||
#include "../window/proxywindow.hpp"
|
#include "../window/proxywindow.hpp"
|
||||||
#include "../window/windowinterface.hpp"
|
|
||||||
#include "types.hpp"
|
#include "types.hpp"
|
||||||
|
|
||||||
bool PopupAnchorState::operator==(const PopupAnchorState& other) const {
|
bool PopupAnchorState::operator==(const PopupAnchorState& other) const {
|
||||||
@@ -40,10 +39,8 @@ void PopupAnchor::setWindowInternal(QObject* window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (window) {
|
if (window) {
|
||||||
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
|
if (auto* proxy = ProxyWindowBase::forObject(window)) {
|
||||||
this->bProxyWindow = proxy;
|
this->bProxyWindow = proxy;
|
||||||
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
|
|
||||||
this->bProxyWindow = interface->proxyWindow();
|
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Tried to set popup anchor window to" << window
|
qWarning() << "Tried to set popup anchor window to" << window
|
||||||
<< "which is not a quickshell window.";
|
<< "which is not a quickshell window.";
|
||||||
|
|||||||
+28
-1
@@ -18,14 +18,17 @@
|
|||||||
#include <qscreen.h>
|
#include <qscreen.h>
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtversion.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
#include <qvariant.h>
|
#include <qvariant.h>
|
||||||
|
#include <qversionnumber.h>
|
||||||
#include <qwindowdefs.h>
|
#include <qwindowdefs.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "../io/processcore.hpp"
|
#include "../io/processcore.hpp"
|
||||||
#include "generation.hpp"
|
#include "generation.hpp"
|
||||||
#include "iconimageprovider.hpp"
|
#include "iconimageprovider.hpp"
|
||||||
|
#include "instanceinfo.hpp"
|
||||||
#include "paths.hpp"
|
#include "paths.hpp"
|
||||||
#include "qmlscreen.hpp"
|
#include "qmlscreen.hpp"
|
||||||
#include "rootwrapper.hpp"
|
#include "rootwrapper.hpp"
|
||||||
@@ -60,7 +63,9 @@ void QuickshellSettings::setWorkingDirectory(QString workingDirectory) { // NOLI
|
|||||||
emit this->workingDirectoryChanged();
|
emit this->workingDirectoryChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuickshellSettings::watchFiles() const { return this->mWatchFiles; }
|
bool QuickshellSettings::watchFiles() const {
|
||||||
|
return this->mWatchFiles && qEnvironmentVariableIsEmpty("QS_DISABLE_FILE_WATCHER");
|
||||||
|
}
|
||||||
|
|
||||||
void QuickshellSettings::setWatchFiles(bool watchFiles) {
|
void QuickshellSettings::setWatchFiles(bool watchFiles) {
|
||||||
if (watchFiles == this->mWatchFiles) return;
|
if (watchFiles == this->mWatchFiles) return;
|
||||||
@@ -151,6 +156,22 @@ qint32 QuickshellGlobal::processId() const { // NOLINT
|
|||||||
return getpid();
|
return getpid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString QuickshellGlobal::instanceId() const { // NOLINT
|
||||||
|
return InstanceInfo::CURRENT.instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QuickshellGlobal::shellId() const { // NOLINT
|
||||||
|
return InstanceInfo::CURRENT.shellId;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QuickshellGlobal::appId() const { // NOLINT
|
||||||
|
return InstanceInfo::CURRENT.appId;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime QuickshellGlobal::launchTime() const { // NOLINT
|
||||||
|
return InstanceInfo::CURRENT.launchTime;
|
||||||
|
}
|
||||||
|
|
||||||
qsizetype QuickshellGlobal::screensCount(QQmlListProperty<QuickshellScreenInfo>* /*unused*/) {
|
qsizetype QuickshellGlobal::screensCount(QQmlListProperty<QuickshellScreenInfo>* /*unused*/) {
|
||||||
return QuickshellTracked::instance()->screens.size();
|
return QuickshellTracked::instance()->screens.size();
|
||||||
}
|
}
|
||||||
@@ -324,6 +345,12 @@ bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) {
|
|||||||
return QuickshellGlobal::hasVersion(major, minor, QStringList());
|
return QuickshellGlobal::hasVersion(major, minor, QStringList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QuickshellGlobal::hasQtVersion(int major, int minor) {
|
||||||
|
auto qtVersion = QVersionNumber::fromString(qVersion());
|
||||||
|
auto requiredVersion = QVersionNumber(major, minor);
|
||||||
|
return qtVersion >= requiredVersion;
|
||||||
|
}
|
||||||
|
|
||||||
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
|
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
|
||||||
auto* qsg = new QuickshellGlobal();
|
auto* qsg = new QuickshellGlobal();
|
||||||
auto* generation = EngineGeneration::findEngineGeneration(engine);
|
auto* generation = EngineGeneration::findEngineGeneration(engine);
|
||||||
|
|||||||
+32
-1
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
#include "../io/processcore.hpp"
|
#include "../io/processcore.hpp"
|
||||||
#include "doc.hpp"
|
#include "doc.hpp"
|
||||||
|
#include "instanceinfo.hpp"
|
||||||
#include "qmlscreen.hpp"
|
#include "qmlscreen.hpp"
|
||||||
|
|
||||||
///! Accessor for some options under the Quickshell type.
|
///! Accessor for some options under the Quickshell type.
|
||||||
@@ -83,6 +84,21 @@ class QuickshellGlobal: public QObject {
|
|||||||
// clang-format off
|
// clang-format off
|
||||||
/// Quickshell's process id.
|
/// Quickshell's process id.
|
||||||
Q_PROPERTY(qint32 processId READ processId CONSTANT);
|
Q_PROPERTY(qint32 processId READ processId CONSTANT);
|
||||||
|
/// A unique identifier for this Quickshell instance
|
||||||
|
Q_PROPERTY(QString instanceId READ instanceId CONSTANT)
|
||||||
|
/// The shell ID, used to differentiate between different shell configurations.
|
||||||
|
///
|
||||||
|
/// Defaults to a stable value derived from the config path.
|
||||||
|
/// Can be overridden with `//@ pragma ShellId <id>` in the root qml file.
|
||||||
|
Q_PROPERTY(QString shellId READ shellId CONSTANT)
|
||||||
|
/// The desktop application ID.
|
||||||
|
///
|
||||||
|
/// Defaults to `org.quickshell`.
|
||||||
|
/// Can be overridden with `//@ pragma AppId <id>` in the root qml file
|
||||||
|
/// or the `QS_APP_ID` environment variable.
|
||||||
|
Q_PROPERTY(QString appId READ appId CONSTANT)
|
||||||
|
/// The time at which this Quickshell instance was launched.
|
||||||
|
Q_PROPERTY(QDateTime launchTime READ launchTime CONSTANT)
|
||||||
/// All currently connected screens.
|
/// All currently connected screens.
|
||||||
///
|
///
|
||||||
/// This property updates as connected screens change.
|
/// This property updates as connected screens change.
|
||||||
@@ -149,6 +165,10 @@ class QuickshellGlobal: public QObject {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] qint32 processId() const;
|
[[nodiscard]] qint32 processId() const;
|
||||||
|
[[nodiscard]] QString instanceId() const;
|
||||||
|
[[nodiscard]] QString shellId() const;
|
||||||
|
[[nodiscard]] QString appId() const;
|
||||||
|
[[nodiscard]] QDateTime launchTime() const;
|
||||||
|
|
||||||
QQmlListProperty<QuickshellScreenInfo> screens();
|
QQmlListProperty<QuickshellScreenInfo> screens();
|
||||||
|
|
||||||
@@ -224,7 +244,7 @@ public:
|
|||||||
/// it is assumed that all unreleased features are present. The unreleased feature list
|
/// it is assumed that all unreleased features are present. The unreleased feature list
|
||||||
/// may be omitted.
|
/// may be omitted.
|
||||||
///
|
///
|
||||||
/// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which
|
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
|
||||||
/// > has the same function available.
|
/// > has the same function available.
|
||||||
/// >
|
/// >
|
||||||
/// > ```qml
|
/// > ```qml
|
||||||
@@ -234,6 +254,17 @@ public:
|
|||||||
/// > ```
|
/// > ```
|
||||||
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
|
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
|
||||||
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
|
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
|
||||||
|
/// Check if Qt's version is at least `major.minor`.
|
||||||
|
///
|
||||||
|
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
|
||||||
|
/// > has the same function available.
|
||||||
|
/// >
|
||||||
|
/// > ```qml
|
||||||
|
/// > //@ if hasVersion(6, 10)
|
||||||
|
/// > ...
|
||||||
|
/// > //@ endif
|
||||||
|
/// > ```
|
||||||
|
Q_INVOKABLE static bool hasQtVersion(qint32 major, qint32 minor);
|
||||||
|
|
||||||
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
|
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
|
||||||
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
|
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
/// Monitor object useful for setting the monitor for a @@QsWindow
|
/// Monitor object useful for setting the monitor for a @@QsWindow
|
||||||
/// or querying information about the monitor.
|
/// or querying information about the monitor.
|
||||||
///
|
///
|
||||||
/// > [!WARNING] If the monitor is disconnected than any stored copies of its ShellMonitor will
|
/// > [!WARNING] If the monitor is disconnected, then any stored copies of its ShellMonitor will
|
||||||
/// > be marked as dangling and all properties will return default values.
|
/// > be marked as dangling and all properties will return default values.
|
||||||
/// > Reconnecting the monitor will not reconnect it to the ShellMonitor object.
|
/// > Reconnecting the monitor will not reconnect it to the ShellMonitor object.
|
||||||
///
|
///
|
||||||
|
|||||||
+134
-1
@@ -1,4 +1,5 @@
|
|||||||
#include "region.hpp"
|
#include "region.hpp"
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
@@ -18,6 +19,11 @@ PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
|
|||||||
QObject::connect(this, &PendingRegion::yChanged, this, &PendingRegion::changed);
|
QObject::connect(this, &PendingRegion::yChanged, this, &PendingRegion::changed);
|
||||||
QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed);
|
QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed);
|
||||||
QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed);
|
QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed);
|
||||||
|
QObject::connect(this, &PendingRegion::radiusChanged, this, &PendingRegion::changed);
|
||||||
|
QObject::connect(this, &PendingRegion::topLeftRadiusChanged, this, &PendingRegion::changed);
|
||||||
|
QObject::connect(this, &PendingRegion::topRightRadiusChanged, this, &PendingRegion::changed);
|
||||||
|
QObject::connect(this, &PendingRegion::bottomLeftRadiusChanged, this, &PendingRegion::changed);
|
||||||
|
QObject::connect(this, &PendingRegion::bottomRightRadiusChanged, this, &PendingRegion::changed);
|
||||||
QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed);
|
QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +51,79 @@ void PendingRegion::onItemDestroyed() { this->mItem = nullptr; }
|
|||||||
|
|
||||||
void PendingRegion::onChildDestroyed() { this->mRegions.removeAll(this->sender()); }
|
void PendingRegion::onChildDestroyed() { this->mRegions.removeAll(this->sender()); }
|
||||||
|
|
||||||
|
qint32 PendingRegion::radius() const { return this->mRadius; }
|
||||||
|
|
||||||
|
void PendingRegion::setRadius(qint32 radius) {
|
||||||
|
if (radius == this->mRadius) return;
|
||||||
|
this->mRadius = radius;
|
||||||
|
emit this->radiusChanged();
|
||||||
|
|
||||||
|
if (!(this->mCornerOverrides & TopLeft)) emit this->topLeftRadiusChanged();
|
||||||
|
if (!(this->mCornerOverrides & TopRight)) emit this->topRightRadiusChanged();
|
||||||
|
if (!(this->mCornerOverrides & BottomLeft)) emit this->bottomLeftRadiusChanged();
|
||||||
|
if (!(this->mCornerOverrides & BottomRight)) emit this->bottomRightRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint32 PendingRegion::topLeftRadius() const {
|
||||||
|
return (this->mCornerOverrides & TopLeft) ? this->mTopLeftRadius : this->mRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::setTopLeftRadius(qint32 radius) {
|
||||||
|
this->mTopLeftRadius = radius;
|
||||||
|
this->mCornerOverrides |= TopLeft;
|
||||||
|
emit this->topLeftRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::resetTopLeftRadius() {
|
||||||
|
this->mCornerOverrides &= ~TopLeft;
|
||||||
|
emit this->topLeftRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint32 PendingRegion::topRightRadius() const {
|
||||||
|
return (this->mCornerOverrides & TopRight) ? this->mTopRightRadius : this->mRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::setTopRightRadius(qint32 radius) {
|
||||||
|
this->mTopRightRadius = radius;
|
||||||
|
this->mCornerOverrides |= TopRight;
|
||||||
|
emit this->topRightRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::resetTopRightRadius() {
|
||||||
|
this->mCornerOverrides &= ~TopRight;
|
||||||
|
emit this->topRightRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint32 PendingRegion::bottomLeftRadius() const {
|
||||||
|
return (this->mCornerOverrides & BottomLeft) ? this->mBottomLeftRadius : this->mRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::setBottomLeftRadius(qint32 radius) {
|
||||||
|
this->mBottomLeftRadius = radius;
|
||||||
|
this->mCornerOverrides |= BottomLeft;
|
||||||
|
emit this->bottomLeftRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::resetBottomLeftRadius() {
|
||||||
|
this->mCornerOverrides &= ~BottomLeft;
|
||||||
|
emit this->bottomLeftRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint32 PendingRegion::bottomRightRadius() const {
|
||||||
|
return (this->mCornerOverrides & BottomRight) ? this->mBottomRightRadius : this->mRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::setBottomRightRadius(qint32 radius) {
|
||||||
|
this->mBottomRightRadius = radius;
|
||||||
|
this->mCornerOverrides |= BottomRight;
|
||||||
|
emit this->bottomRightRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::resetBottomRightRadius() {
|
||||||
|
this->mCornerOverrides &= ~BottomRight;
|
||||||
|
emit this->bottomRightRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
QQmlListProperty<PendingRegion> PendingRegion::regions() {
|
QQmlListProperty<PendingRegion> PendingRegion::regions() {
|
||||||
return QQmlListProperty<PendingRegion>(
|
return QQmlListProperty<PendingRegion>(
|
||||||
this,
|
this,
|
||||||
@@ -90,6 +169,60 @@ QRegion PendingRegion::build() const {
|
|||||||
region = QRegion(this->mX, this->mY, this->mWidth, this->mHeight, type);
|
region = QRegion(this->mX, this->mY, this->mWidth, this->mHeight, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->mShape == RegionShape::Rect && !region.isEmpty()) {
|
||||||
|
auto tl = std::max(this->topLeftRadius(), 0);
|
||||||
|
auto tr = std::max(this->topRightRadius(), 0);
|
||||||
|
auto bl = std::max(this->bottomLeftRadius(), 0);
|
||||||
|
auto br = std::max(this->bottomRightRadius(), 0);
|
||||||
|
|
||||||
|
if (tl > 0 || tr > 0 || bl > 0 || br > 0) {
|
||||||
|
auto rect = region.boundingRect();
|
||||||
|
auto x = rect.x();
|
||||||
|
auto y = rect.y();
|
||||||
|
auto w = rect.width();
|
||||||
|
auto h = rect.height();
|
||||||
|
|
||||||
|
// Normalize so adjacent corners don't exceed their shared edge.
|
||||||
|
// Each corner is scaled by the tightest constraint of its two edges.
|
||||||
|
auto topScale = tl + tr > w ? static_cast<double>(w) / (tl + tr) : 1.0;
|
||||||
|
auto bottomScale = bl + br > w ? static_cast<double>(w) / (bl + br) : 1.0;
|
||||||
|
auto leftScale = tl + bl > h ? static_cast<double>(h) / (tl + bl) : 1.0;
|
||||||
|
auto rightScale = tr + br > h ? static_cast<double>(h) / (tr + br) : 1.0;
|
||||||
|
|
||||||
|
tl = static_cast<qint32>(tl * std::min(topScale, leftScale));
|
||||||
|
tr = static_cast<qint32>(tr * std::min(topScale, rightScale));
|
||||||
|
bl = static_cast<qint32>(bl * std::min(bottomScale, leftScale));
|
||||||
|
br = static_cast<qint32>(br * std::min(bottomScale, rightScale));
|
||||||
|
|
||||||
|
// Unlock each corner: subtract (cornerBox - quarterEllipse) from the
|
||||||
|
// full rect. Each corner only modifies pixels inside its own box,
|
||||||
|
// so no diagonal overlap is possible.
|
||||||
|
if (tl > 0) {
|
||||||
|
auto box = QRegion(x, y, tl, tl);
|
||||||
|
auto ellipse = QRegion(x, y, tl * 2, tl * 2, QRegion::Ellipse);
|
||||||
|
region -= box - (ellipse & box);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr > 0) {
|
||||||
|
auto box = QRegion(x + w - tr, y, tr, tr);
|
||||||
|
auto ellipse = QRegion(x + w - tr * 2, y, tr * 2, tr * 2, QRegion::Ellipse);
|
||||||
|
region -= box - (ellipse & box);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bl > 0) {
|
||||||
|
auto box = QRegion(x, y + h - bl, bl, bl);
|
||||||
|
auto ellipse = QRegion(x, y + h - bl * 2, bl * 2, bl * 2, QRegion::Ellipse);
|
||||||
|
region -= box - (ellipse & box);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (br > 0) {
|
||||||
|
auto box = QRegion(x + w - br, y + h - br, br, br);
|
||||||
|
auto ellipse = QRegion(x + w - br * 2, y + h - br * 2, br * 2, br * 2, QRegion::Ellipse);
|
||||||
|
region -= box - (ellipse & box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& childRegion: this->mRegions) {
|
for (const auto& childRegion: this->mRegions) {
|
||||||
region = childRegion->applyTo(region);
|
region = childRegion->applyTo(region);
|
||||||
}
|
}
|
||||||
@@ -109,7 +242,7 @@ QRegion PendingRegion::applyTo(QRegion& region) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QRegion PendingRegion::applyTo(const QRect& rect) const {
|
QRegion PendingRegion::applyTo(const QRect& rect) const {
|
||||||
// if left as the default, dont combine it with the whole rect area, leave it as is.
|
// if left as the default, don't combine it with the whole rect area, leave it as is.
|
||||||
if (this->mIntersection == Intersection::Combine) {
|
if (this->mIntersection == Intersection::Combine) {
|
||||||
return this->build();
|
return this->build();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -66,6 +66,29 @@ class PendingRegion: public QObject {
|
|||||||
Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged);
|
Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged);
|
||||||
/// Defaults to 0. Does nothing if @@item is set.
|
/// Defaults to 0. Does nothing if @@item is set.
|
||||||
Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged);
|
Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged);
|
||||||
|
// clang-format off
|
||||||
|
/// Corner radius for rounded rectangles. Only applies when @@shape is `Rect`. Defaults to 0.
|
||||||
|
///
|
||||||
|
/// Acts as the default for @@topLeftRadius, @@topRightRadius, @@bottomLeftRadius,
|
||||||
|
/// and @@bottomRightRadius.
|
||||||
|
Q_PROPERTY(qint32 radius READ radius WRITE setRadius NOTIFY radiusChanged);
|
||||||
|
/// Top-left corner radius. Only applies when @@shape is `Rect`.
|
||||||
|
///
|
||||||
|
/// Defaults to @@radius, and may be reset by assigning `undefined`.
|
||||||
|
Q_PROPERTY(qint32 topLeftRadius READ topLeftRadius WRITE setTopLeftRadius RESET resetTopLeftRadius NOTIFY topLeftRadiusChanged);
|
||||||
|
/// Top-right corner radius. Only applies when @@shape is `Rect`.
|
||||||
|
///
|
||||||
|
/// Defaults to @@radius, and may be reset by assigning `undefined`.
|
||||||
|
Q_PROPERTY(qint32 topRightRadius READ topRightRadius WRITE setTopRightRadius RESET resetTopRightRadius NOTIFY topRightRadiusChanged);
|
||||||
|
/// Bottom-left corner radius. Only applies when @@shape is `Rect`.
|
||||||
|
///
|
||||||
|
/// Defaults to @@radius, and may be reset by assigning `undefined`.
|
||||||
|
Q_PROPERTY(qint32 bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius RESET resetBottomLeftRadius NOTIFY bottomLeftRadiusChanged);
|
||||||
|
/// Bottom-right corner radius. Only applies when @@shape is `Rect`.
|
||||||
|
///
|
||||||
|
/// Defaults to @@radius, and may be reset by assigning `undefined`.
|
||||||
|
Q_PROPERTY(qint32 bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius RESET resetBottomRightRadius NOTIFY bottomRightRadiusChanged);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
/// Regions to apply on top of this region.
|
/// Regions to apply on top of this region.
|
||||||
///
|
///
|
||||||
@@ -91,6 +114,25 @@ public:
|
|||||||
|
|
||||||
void setItem(QQuickItem* item);
|
void setItem(QQuickItem* item);
|
||||||
|
|
||||||
|
[[nodiscard]] qint32 radius() const;
|
||||||
|
void setRadius(qint32 radius);
|
||||||
|
|
||||||
|
[[nodiscard]] qint32 topLeftRadius() const;
|
||||||
|
void setTopLeftRadius(qint32 radius);
|
||||||
|
void resetTopLeftRadius();
|
||||||
|
|
||||||
|
[[nodiscard]] qint32 topRightRadius() const;
|
||||||
|
void setTopRightRadius(qint32 radius);
|
||||||
|
void resetTopRightRadius();
|
||||||
|
|
||||||
|
[[nodiscard]] qint32 bottomLeftRadius() const;
|
||||||
|
void setBottomLeftRadius(qint32 radius);
|
||||||
|
void resetBottomLeftRadius();
|
||||||
|
|
||||||
|
[[nodiscard]] qint32 bottomRightRadius() const;
|
||||||
|
void setBottomRightRadius(qint32 radius);
|
||||||
|
void resetBottomRightRadius();
|
||||||
|
|
||||||
QQmlListProperty<PendingRegion> regions();
|
QQmlListProperty<PendingRegion> regions();
|
||||||
|
|
||||||
[[nodiscard]] bool empty() const;
|
[[nodiscard]] bool empty() const;
|
||||||
@@ -109,6 +151,11 @@ signals:
|
|||||||
void yChanged();
|
void yChanged();
|
||||||
void widthChanged();
|
void widthChanged();
|
||||||
void heightChanged();
|
void heightChanged();
|
||||||
|
void radiusChanged();
|
||||||
|
void topLeftRadiusChanged();
|
||||||
|
void topRightRadiusChanged();
|
||||||
|
void bottomLeftRadiusChanged();
|
||||||
|
void bottomRightRadiusChanged();
|
||||||
void childrenChanged();
|
void childrenChanged();
|
||||||
|
|
||||||
/// Triggered when the region's geometry changes.
|
/// Triggered when the region's geometry changes.
|
||||||
@@ -130,12 +177,25 @@ private:
|
|||||||
static void
|
static void
|
||||||
regionsReplace(QQmlListProperty<PendingRegion>* prop, qsizetype i, PendingRegion* region);
|
regionsReplace(QQmlListProperty<PendingRegion>* prop, qsizetype i, PendingRegion* region);
|
||||||
|
|
||||||
|
enum CornerOverride : quint8 {
|
||||||
|
TopLeft = 0b1,
|
||||||
|
TopRight = 0b10,
|
||||||
|
BottomLeft = 0b100,
|
||||||
|
BottomRight = 0b1000,
|
||||||
|
};
|
||||||
|
|
||||||
QQuickItem* mItem = nullptr;
|
QQuickItem* mItem = nullptr;
|
||||||
|
|
||||||
qint32 mX = 0;
|
qint32 mX = 0;
|
||||||
qint32 mY = 0;
|
qint32 mY = 0;
|
||||||
qint32 mWidth = 0;
|
qint32 mWidth = 0;
|
||||||
qint32 mHeight = 0;
|
qint32 mHeight = 0;
|
||||||
|
qint32 mRadius = 0;
|
||||||
|
qint32 mTopLeftRadius = 0;
|
||||||
|
qint32 mTopRightRadius = 0;
|
||||||
|
qint32 mBottomLeftRadius = 0;
|
||||||
|
qint32 mBottomRightRadius = 0;
|
||||||
|
quint8 mCornerOverrides = 0;
|
||||||
|
|
||||||
QList<PendingRegion*> mRegions;
|
QList<PendingRegion*> mRegions;
|
||||||
};
|
};
|
||||||
+1
-1
@@ -116,7 +116,7 @@ QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId
|
|||||||
auto* reloadable = qobject_cast<Reloadable*>(child);
|
auto* reloadable = qobject_cast<Reloadable*>(child);
|
||||||
if (reloadable != nullptr) {
|
if (reloadable != nullptr) {
|
||||||
if (reloadable->mReloadableId == reloadId) return reloadable;
|
if (reloadable->mReloadableId == reloadId) return reloadable;
|
||||||
// if not then don't check its children as thats a seperate reload scope.
|
// if not then don't check its children as that's a separate reload scope.
|
||||||
} else {
|
} else {
|
||||||
auto* reloadable = Reloadable::getChildByReloadId(child, reloadId);
|
auto* reloadable = Reloadable::getChildByReloadId(child, reloadId);
|
||||||
if (reloadable != nullptr) return reloadable;
|
if (reloadable != nullptr) return reloadable;
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ class Reloadable
|
|||||||
/// previous state.
|
/// previous state.
|
||||||
///
|
///
|
||||||
/// Simply keeping a stable identifier across config versions (saves) is
|
/// Simply keeping a stable identifier across config versions (saves) is
|
||||||
/// enough to help the reloader figure out which object in the old revision corrosponds to
|
/// enough to help the reloader figure out which object in the old revision corresponds to
|
||||||
/// this object in the current revision, and facilitate smoother reloading.
|
/// this object in the current revision, and facilitate smoother reloading.
|
||||||
///
|
///
|
||||||
/// Note that identifiers are scoped, and will try to do the right thing in context.
|
/// Note that identifiers are scoped, and will try to do the right thing in context.
|
||||||
|
|||||||
+44
-18
@@ -3,6 +3,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qcryptographichash.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
#include <qjsengine.h>
|
#include <qjsengine.h>
|
||||||
@@ -21,6 +22,25 @@
|
|||||||
|
|
||||||
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
||||||
|
|
||||||
|
bool QmlScanner::readAndHashFile(const QString& path, QByteArray& data) {
|
||||||
|
auto file = QFile(path);
|
||||||
|
if (!file.open(QFile::ReadOnly)) return false;
|
||||||
|
data = file.readAll();
|
||||||
|
this->fileHashes.insert(path, QCryptographicHash::hash(data, QCryptographicHash::Md5));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QmlScanner::hasFileContentChanged(const QString& path) const {
|
||||||
|
auto it = this->fileHashes.constFind(path);
|
||||||
|
if (it == this->fileHashes.constEnd()) return true;
|
||||||
|
|
||||||
|
auto file = QFile(path);
|
||||||
|
if (!file.open(QFile::ReadOnly)) return true;
|
||||||
|
|
||||||
|
auto newHash = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
|
||||||
|
return newHash != it.value();
|
||||||
|
}
|
||||||
|
|
||||||
void QmlScanner::scanDir(const QDir& dir) {
|
void QmlScanner::scanDir(const QDir& dir) {
|
||||||
if (this->scannedDirs.contains(dir)) return;
|
if (this->scannedDirs.contains(dir)) return;
|
||||||
this->scannedDirs.push_back(dir);
|
this->scannedDirs.push_back(dir);
|
||||||
@@ -69,7 +89,7 @@ void QmlScanner::scanDir(const QDir& dir) {
|
|||||||
QString qmldir;
|
QString qmldir;
|
||||||
auto stream = QTextStream(&qmldir);
|
auto stream = QTextStream(&qmldir);
|
||||||
|
|
||||||
// cant derive a module name if not in shell path
|
// can't derive a module name if not in shell path
|
||||||
if (path.startsWith(this->rootPath.path())) {
|
if (path.startsWith(this->rootPath.path())) {
|
||||||
auto end = path.sliced(this->rootPath.path().length());
|
auto end = path.sliced(this->rootPath.path().length());
|
||||||
|
|
||||||
@@ -109,13 +129,13 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||||||
|
|
||||||
qCDebug(logQmlScanner) << "Scanning qml file" << path;
|
qCDebug(logQmlScanner) << "Scanning qml file" << path;
|
||||||
|
|
||||||
auto file = QFile(path);
|
QByteArray fileData;
|
||||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
if (!this->readAndHashFile(path, fileData)) {
|
||||||
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto stream = QTextStream(&file);
|
auto stream = QTextStream(&fileData);
|
||||||
auto imports = QVector<QString>();
|
auto imports = QVector<QString>();
|
||||||
|
|
||||||
bool inHeader = true;
|
bool inHeader = true;
|
||||||
@@ -125,10 +145,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||||||
QString overrideText;
|
QString overrideText;
|
||||||
bool isOverridden = false;
|
bool isOverridden = false;
|
||||||
|
|
||||||
auto pragmaEngine = QJSEngine();
|
auto& pragmaEngine = *QmlScanner::preprocEngine();
|
||||||
pragmaEngine.globalObject().setPrototype(
|
|
||||||
pragmaEngine.newQObject(new qs::scan::env::PreprocEnv())
|
|
||||||
);
|
|
||||||
|
|
||||||
auto postError = [&, this](QString error) {
|
auto postError = [&, this](QString error) {
|
||||||
this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum});
|
this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum});
|
||||||
@@ -143,7 +160,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||||||
if (!singleton && line == "pragma Singleton") {
|
if (!singleton && line == "pragma Singleton") {
|
||||||
singleton = true;
|
singleton = true;
|
||||||
} else if (line.startsWith("import")) {
|
} else if (line.startsWith("import")) {
|
||||||
// we dont care about "import qs" as we always load the root folder
|
// we don't care about "import qs" as we always load the root folder
|
||||||
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
|
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
|
||||||
importCursor += 4;
|
importCursor += 4;
|
||||||
QString path;
|
QString path;
|
||||||
@@ -194,7 +211,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||||||
mask = false;
|
mask = false;
|
||||||
}
|
}
|
||||||
if (!sourceMasked && mask) hideMask = true;
|
if (!sourceMasked && mask) hideMask = true;
|
||||||
mask = sourceMasked || mask; // cant unmask if a nested if passes
|
mask = sourceMasked || mask; // can't unmask if a nested if passes
|
||||||
ifScopes.append(mask);
|
ifScopes.append(mask);
|
||||||
if (mask) isOverridden = true;
|
if (mask) isOverridden = true;
|
||||||
sourceMasked = mask;
|
sourceMasked = mask;
|
||||||
@@ -219,8 +236,6 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||||||
postError("unclosed preprocessor if block");
|
postError("unclosed preprocessor if block");
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
if (isOverridden) {
|
if (isOverridden) {
|
||||||
this->fileIntercepts.insert(path, overrideText);
|
this->fileIntercepts.insert(path, overrideText);
|
||||||
}
|
}
|
||||||
@@ -257,8 +272,11 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.endsWith(".js")) this->scannedFiles.push_back(cpath);
|
if (import.endsWith(".js")) {
|
||||||
else this->scanDir(cpath);
|
this->scannedFiles.push_back(cpath);
|
||||||
|
QByteArray jsData;
|
||||||
|
this->readAndHashFile(cpath, jsData);
|
||||||
|
} else this->scanDir(cpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -273,14 +291,12 @@ void QmlScanner::scanQmlRoot(const QString& path) {
|
|||||||
bool QmlScanner::scanQmlJson(const QString& path) {
|
bool QmlScanner::scanQmlJson(const QString& path) {
|
||||||
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
|
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
|
||||||
|
|
||||||
auto file = QFile(path);
|
QByteArray data;
|
||||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
if (!this->readAndHashFile(path, data)) {
|
||||||
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto data = file.readAll();
|
|
||||||
|
|
||||||
// Importing this makes CI builds fail for some reason.
|
// Importing this makes CI builds fail for some reason.
|
||||||
QJsonParseError error; // NOLINT (misc-include-cleaner)
|
QJsonParseError error; // NOLINT (misc-include-cleaner)
|
||||||
auto json = QJsonDocument::fromJson(data, &error);
|
auto json = QJsonDocument::fromJson(data, &error);
|
||||||
@@ -351,3 +367,13 @@ QPair<QString, QString> QmlScanner::jsonToQml(const QJsonValue& value, int inden
|
|||||||
return qMakePair(QStringLiteral("var"), "null");
|
return qMakePair(QStringLiteral("var"), "null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJSEngine* QmlScanner::preprocEngine() {
|
||||||
|
static auto* engine = [] {
|
||||||
|
auto* engine = new QJSEngine();
|
||||||
|
engine->globalObject().setPrototype(engine->newQObject(new qs::scan::env::PreprocEnv()));
|
||||||
|
return engine;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <qbytearray.h>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
|
#include <qjsengine.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qvector.h>
|
#include <qvector.h>
|
||||||
|
|
||||||
@@ -21,6 +23,7 @@ public:
|
|||||||
|
|
||||||
QVector<QDir> scannedDirs;
|
QVector<QDir> scannedDirs;
|
||||||
QVector<QString> scannedFiles;
|
QVector<QString> scannedFiles;
|
||||||
|
QHash<QString, QByteArray> fileHashes;
|
||||||
QHash<QString, QString> fileIntercepts;
|
QHash<QString, QString> fileIntercepts;
|
||||||
|
|
||||||
struct ScanError {
|
struct ScanError {
|
||||||
@@ -31,10 +34,15 @@ public:
|
|||||||
|
|
||||||
QVector<ScanError> scanErrors;
|
QVector<ScanError> scanErrors;
|
||||||
|
|
||||||
|
bool readAndHashFile(const QString& path, QByteArray& data);
|
||||||
|
[[nodiscard]] bool hasFileContentChanged(const QString& path) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDir rootPath;
|
QDir rootPath;
|
||||||
|
|
||||||
bool scanQmlFile(const QString& path, bool& singleton, bool& internal);
|
bool scanQmlFile(const QString& path, bool& singleton, bool& internal);
|
||||||
bool scanQmlJson(const QString& path);
|
bool scanQmlJson(const QString& path);
|
||||||
[[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0);
|
[[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0);
|
||||||
|
|
||||||
|
static QJSEngine* preprocEngine();
|
||||||
};
|
};
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
|
#include <qtversion.h>
|
||||||
|
#include <qversionnumber.h>
|
||||||
|
|
||||||
#include "build.hpp"
|
#include "build.hpp"
|
||||||
|
|
||||||
@@ -20,6 +22,12 @@ bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
|
|||||||
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
|
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PreprocEnv::hasQtVersion(int major, int minor) {
|
||||||
|
auto qtVersion = QVersionNumber::fromString(qVersion());
|
||||||
|
auto requiredVersion = QVersionNumber(major, minor);
|
||||||
|
return qtVersion >= requiredVersion;
|
||||||
|
}
|
||||||
|
|
||||||
QString PreprocEnv::env(const QString& variable) {
|
QString PreprocEnv::env(const QString& variable) {
|
||||||
return qEnvironmentVariable(variable.toStdString().c_str());
|
return qEnvironmentVariable(variable.toStdString().c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class PreprocEnv: public QObject {
|
|||||||
public:
|
public:
|
||||||
Q_INVOKABLE static bool
|
Q_INVOKABLE static bool
|
||||||
hasVersion(int major, int minor, const QStringList& features = QStringList());
|
hasVersion(int major, int minor, const QStringList& features = QStringList());
|
||||||
|
Q_INVOKABLE static bool hasQtVersion(int major, int minor);
|
||||||
|
|
||||||
Q_INVOKABLE static QString env(const QString& variable);
|
Q_INVOKABLE static QString env(const QString& variable);
|
||||||
Q_INVOKABLE static bool isEnvSet(const QString& variable);
|
Q_INVOKABLE static bool isEnvSet(const QString& variable);
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#include "streamreader.hpp"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <qbytearray.h>
|
||||||
|
#include <qiodevice.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
void StreamReader::setDevice(QIODevice* device) {
|
||||||
|
this->reset();
|
||||||
|
this->device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamReader::startTransaction() {
|
||||||
|
this->cursor = 0;
|
||||||
|
this->failed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StreamReader::fill() {
|
||||||
|
auto available = this->device->bytesAvailable();
|
||||||
|
if (available <= 0) return false;
|
||||||
|
auto oldSize = this->buffer.size();
|
||||||
|
this->buffer.resize(oldSize + available);
|
||||||
|
auto bytesRead = this->device->read(this->buffer.data() + oldSize, available); // NOLINT
|
||||||
|
|
||||||
|
if (bytesRead <= 0) {
|
||||||
|
this->buffer.resize(oldSize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->buffer.resize(oldSize + bytesRead);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray StreamReader::readBytes(qsizetype count) {
|
||||||
|
if (this->failed) return {};
|
||||||
|
|
||||||
|
auto needed = this->cursor + count;
|
||||||
|
|
||||||
|
while (this->buffer.size() < needed) {
|
||||||
|
if (!this->fill()) {
|
||||||
|
this->failed = true;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = this->buffer.mid(this->cursor, count);
|
||||||
|
this->cursor += count;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray StreamReader::readUntil(char terminator) {
|
||||||
|
if (this->failed) return {};
|
||||||
|
|
||||||
|
auto searchFrom = this->cursor;
|
||||||
|
auto idx = this->buffer.indexOf(terminator, searchFrom);
|
||||||
|
|
||||||
|
while (idx == -1) {
|
||||||
|
searchFrom = this->buffer.size();
|
||||||
|
if (!this->fill()) {
|
||||||
|
this->failed = true;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = this->buffer.indexOf(terminator, searchFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto length = idx - this->cursor + 1;
|
||||||
|
auto result = this->buffer.mid(this->cursor, length);
|
||||||
|
this->cursor += length;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamReader::readInto(char* ptr, qsizetype count) {
|
||||||
|
auto data = this->readBytes(count);
|
||||||
|
if (!data.isEmpty()) memcpy(ptr, data.data(), count);
|
||||||
|
}
|
||||||
|
|
||||||
|
qint32 StreamReader::readI32() {
|
||||||
|
qint32 value = 0;
|
||||||
|
this->readInto(reinterpret_cast<char*>(&value), sizeof(qint32));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StreamReader::commitTransaction() {
|
||||||
|
if (this->failed) {
|
||||||
|
this->cursor = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->buffer.remove(0, this->cursor);
|
||||||
|
this->cursor = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamReader::reset() {
|
||||||
|
this->buffer.clear();
|
||||||
|
this->cursor = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qbytearray.h>
|
||||||
|
#include <qiodevice.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
class StreamReader {
|
||||||
|
public:
|
||||||
|
void setDevice(QIODevice* device);
|
||||||
|
|
||||||
|
void startTransaction();
|
||||||
|
QByteArray readBytes(qsizetype count);
|
||||||
|
QByteArray readUntil(char terminator);
|
||||||
|
void readInto(char* ptr, qsizetype count);
|
||||||
|
qint32 readI32();
|
||||||
|
bool commitTransaction();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool fill();
|
||||||
|
|
||||||
|
QIODevice* device = nullptr;
|
||||||
|
QByteArray buffer;
|
||||||
|
qsizetype cursor = 0;
|
||||||
|
bool failed = false;
|
||||||
|
};
|
||||||
@@ -8,3 +8,4 @@ qs_test(transformwatcher transformwatcher.cpp)
|
|||||||
qs_test(ringbuffer ringbuf.cpp)
|
qs_test(ringbuffer ringbuf.cpp)
|
||||||
qs_test(scriptmodel scriptmodel.cpp)
|
qs_test(scriptmodel scriptmodel.cpp)
|
||||||
qs_test(stacklist stacklist.cpp)
|
qs_test(stacklist stacklist.cpp)
|
||||||
|
qs_test(objectmodel objectmodel.cpp)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#include "objectmodel.hpp"
|
||||||
|
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qtest.h>
|
||||||
|
#include <qtestcase.h>
|
||||||
|
|
||||||
|
#include "../model.hpp"
|
||||||
|
|
||||||
|
void TestObjectModel::diffUpdateInsertRemove() {
|
||||||
|
QObject a;
|
||||||
|
QObject b;
|
||||||
|
QObject c;
|
||||||
|
QObject d;
|
||||||
|
|
||||||
|
auto model = ObjectModel<QObject>(nullptr);
|
||||||
|
model.insertObject(&a);
|
||||||
|
model.insertObject(&b);
|
||||||
|
model.insertObject(&c);
|
||||||
|
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c}));
|
||||||
|
|
||||||
|
// drop b, append d — relative order of survivors unchanged
|
||||||
|
model.diffUpdate({&a, &c, &d});
|
||||||
|
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &c, &d}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestObjectModel::diffUpdateReorder() {
|
||||||
|
QObject a;
|
||||||
|
QObject b;
|
||||||
|
QObject c;
|
||||||
|
QObject d;
|
||||||
|
|
||||||
|
auto model = ObjectModel<QObject>(nullptr);
|
||||||
|
model.insertObject(&d);
|
||||||
|
model.insertObject(&a);
|
||||||
|
model.insertObject(&b);
|
||||||
|
model.insertObject(&c);
|
||||||
|
QCOMPARE(model.valueList(), (QList<QObject*> {&d, &a, &b, &c}));
|
||||||
|
|
||||||
|
// pure permutation: same elements, different order. Previously turned
|
||||||
|
// [d,a,b,c] into [a,b,c,d,a,b,c] because the old row was never removed
|
||||||
|
// before inserting at the new index.
|
||||||
|
model.diffUpdate({&a, &b, &c, &d});
|
||||||
|
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c, &d}));
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(TestObjectModel);
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
class TestObjectModel: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
static void diffUpdateInsertRemove();
|
||||||
|
static void diffUpdateReorder();
|
||||||
|
};
|
||||||
@@ -90,7 +90,7 @@ void TestScriptModel::unique_data() {
|
|||||||
QTest::addRow("move_overlapping")
|
QTest::addRow("move_overlapping")
|
||||||
<< "ABCDEFG" << "ABDEFCG" << OpList({{ModelOperation::Move, 3, 3, 2}});
|
<< "ABCDEFG" << "ABDEFCG" << OpList({{ModelOperation::Move, 3, 3, 2}});
|
||||||
|
|
||||||
// Ensure iterators arent skipping anything at the end of operations by performing
|
// Ensure iterators aren't skipping anything at the end of operations by performing
|
||||||
// multiple back to back.
|
// multiple back to back.
|
||||||
|
|
||||||
QTest::addRow("insert_state_ok") << "ABCDEFG" << "ABXXEFG"
|
QTest::addRow("insert_state_ok") << "ABCDEFG" << "ABXXEFG"
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ void QmlToolingSupport::updateToolingFs(
|
|||||||
auto fileInfo = QFileInfo(path);
|
auto fileInfo = QFileInfo(path);
|
||||||
if (!fileInfo.isFile()) continue;
|
if (!fileInfo.isFile()) continue;
|
||||||
|
|
||||||
|
if (scanner.fileIntercepts.contains(path)) continue;
|
||||||
|
|
||||||
auto spath = linkDir.filePath(name);
|
auto spath = linkDir.filePath(name);
|
||||||
auto sFileInfo = QFileInfo(spath);
|
auto sFileInfo = QFileInfo(spath);
|
||||||
|
|
||||||
@@ -205,8 +207,10 @@ void QmlToolingSupport::updateToolingFs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto spath = linkDir.filePath(name);
|
auto spath = linkDir.filePath(name);
|
||||||
|
QFile::remove(spath);
|
||||||
|
|
||||||
auto file = QFile(spath);
|
auto file = QFile(spath);
|
||||||
if (!file.open(QFile::ReadWrite | QFile::Text)) {
|
if (!file.open(QFile::ReadWrite | QFile::Text | QFile::NewOnly)) {
|
||||||
qCCritical(logTooling) << "Failed to open injected file" << spath;
|
qCCritical(logTooling) << "Failed to open injected file" << spath;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ void Variants::updateVariants() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
if (this->mInstances.contains(variant)) {
|
if (this->mInstances.contains(variant)) {
|
||||||
continue; // we dont need to recreate this one
|
continue; // we don't need to recreate this one
|
||||||
}
|
}
|
||||||
|
|
||||||
auto variantMap = QVariantMap();
|
auto variantMap = QVariantMap();
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class Variants: public Reloadable {
|
|||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
/// The component to create instances of.
|
/// The component to create instances of.
|
||||||
///
|
///
|
||||||
/// The delegate should define a `modelData` property that will be popuplated with a value
|
/// The delegate should define a `modelData` property that will be populated with a value
|
||||||
/// from the @@model.
|
/// from the @@model.
|
||||||
Q_PROPERTY(QQmlComponent* delegate MEMBER mDelegate);
|
Q_PROPERTY(QQmlComponent* delegate MEMBER mDelegate);
|
||||||
/// The list of sets of properties to create instances with.
|
/// The list of sets of properties to create instances with.
|
||||||
|
|||||||
@@ -6,12 +6,51 @@ qt_add_library(quickshell-crash STATIC
|
|||||||
|
|
||||||
qs_pch(quickshell-crash SET large)
|
qs_pch(quickshell-crash SET large)
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
if (VENDOR_CPPTRACE)
|
||||||
pkg_check_modules(breakpad REQUIRED IMPORTED_TARGET breakpad)
|
message(STATUS "Vendoring cpptrace...")
|
||||||
# only need client?? take only includes from pkg config todo
|
include(FetchContent)
|
||||||
target_link_libraries(quickshell-crash PRIVATE PkgConfig::breakpad -lbreakpad_client)
|
|
||||||
|
# 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
|
# 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)
|
target_link_libraries(quickshell PRIVATE quickshell-crash)
|
||||||
+165
-103
@@ -1,12 +1,15 @@
|
|||||||
#include "handler.hpp"
|
#include "handler.hpp"
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <csignal>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
#include <bits/types/sigset_t.h>
|
#include <cpptrace/basic.hpp>
|
||||||
#include <breakpad/client/linux/handler/exception_handler.h>
|
#include <cpptrace/forward.hpp>
|
||||||
#include <breakpad/client/linux/handler/minidump_descriptor.h>
|
#include <cpptrace/utils.hpp>
|
||||||
#include <breakpad/common/linux/linux_libc_support.h>
|
|
||||||
#include <qdatastream.h>
|
#include <qdatastream.h>
|
||||||
#include <qfile.h>
|
#include <qfile.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
@@ -19,98 +22,77 @@
|
|||||||
|
|
||||||
extern char** environ; // NOLINT
|
extern char** environ; // NOLINT
|
||||||
|
|
||||||
using namespace google_breakpad;
|
|
||||||
|
|
||||||
namespace qs::crash {
|
namespace qs::crash {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
QS_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
|
||||||
}
|
|
||||||
|
|
||||||
struct CrashHandlerPrivate {
|
void writeEnvInt(char* buf, const char* name, int value) {
|
||||||
ExceptionHandler* exceptionHandler = nullptr;
|
// NOLINTBEGIN (cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||||
int minidumpFd = -1;
|
while (*name != '\0') *buf++ = *name++;
|
||||||
int infoFd = -1;
|
*buf++ = '=';
|
||||||
|
|
||||||
static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded);
|
if (value < 0) {
|
||||||
};
|
*buf++ = '-';
|
||||||
|
value = -value;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qCInfo(logCrashHandler) << "Crash handler initialized.";
|
if (value == 0) {
|
||||||
}
|
*buf++ = '0';
|
||||||
|
*buf = '\0';
|
||||||
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.";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFile file;
|
auto* start = buf;
|
||||||
|
while (value > 0) {
|
||||||
if (!file.open(this->d->infoFd, QFile::ReadWrite)) {
|
*buf++ = static_cast<char>('0' + (value % 10));
|
||||||
qCCritical(
|
value /= 10;
|
||||||
logCrashHandler
|
|
||||||
) << "Failed to open instance info memfd, crash recovery will not work.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream ds(&file);
|
*buf = '\0';
|
||||||
ds << info;
|
std::reverse(start, buf);
|
||||||
file.flush();
|
// NOLINTEND
|
||||||
|
|
||||||
qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CrashHandler::~CrashHandler() {
|
void signalHandler(
|
||||||
delete this->d->exceptionHandler;
|
int sig,
|
||||||
delete this->d;
|
siginfo_t* /*info*/, // NOLINT (misc-include-cleaner)
|
||||||
}
|
void* /*context*/
|
||||||
|
|
||||||
bool CrashHandlerPrivate::minidumpCallback(
|
|
||||||
const MinidumpDescriptor& /*descriptor*/,
|
|
||||||
void* context,
|
|
||||||
bool /*success*/
|
|
||||||
) {
|
) {
|
||||||
// A fork that just dies to ensure the coredump is caught by the system.
|
// NOLINTBEGIN (misc-include-cleaner)
|
||||||
auto coredumpPid = fork();
|
sigset_t set;
|
||||||
|
sigfillset(&set);
|
||||||
|
sigprocmask(SIG_UNBLOCK, &set, nullptr);
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
if (coredumpPid == 0) {
|
if (CrashInfo::INSTANCE.traceFd != -1) {
|
||||||
return false;
|
auto traceBuffer = std::array<cpptrace::frame_ptr, 1024>();
|
||||||
|
auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
auto* wptr = reinterpret_cast<char*>(&frame);
|
||||||
|
auto* end = wptr + sizeof(cpptrace::safe_object_frame); // NOLINT
|
||||||
|
while (wptr != end) {
|
||||||
|
auto r = write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame));
|
||||||
|
if (r < 0 && errno == EINTR) continue;
|
||||||
|
if (r <= 0) goto fail;
|
||||||
|
wptr += r; // NOLINT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* self = static_cast<CrashHandlerPrivate*>(context);
|
fail:;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: coredump fork and crash reporter remain as zombies, fix
|
||||||
|
auto coredumpPid = fork();
|
||||||
|
if (coredumpPid == 0) {
|
||||||
|
raise(sig);
|
||||||
|
_exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
auto exe = std::array<char, 4096>();
|
auto exe = std::array<char, 4096>();
|
||||||
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
|
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
|
||||||
@@ -123,17 +105,19 @@ bool CrashHandlerPrivate::minidumpCallback(
|
|||||||
auto env = std::array<char*, 4096>();
|
auto env = std::array<char*, 4096>();
|
||||||
auto envi = 0;
|
auto envi = 0;
|
||||||
|
|
||||||
auto infoFd = dup(self->infoFd);
|
// dup to remove CLOEXEC
|
||||||
auto infoFdStr = std::array<char, 38>();
|
auto infoFdStr = std::array<char, 48>();
|
||||||
memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30);
|
writeEnvInt(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD", dup(CrashInfo::INSTANCE.infoFd));
|
||||||
if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10);
|
|
||||||
env[envi++] = infoFdStr.data();
|
env[envi++] = infoFdStr.data();
|
||||||
|
|
||||||
auto corePidStr = std::array<char, 39>();
|
auto corePidStr = std::array<char, 48>();
|
||||||
memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31);
|
writeEnvInt(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID", coredumpPid);
|
||||||
if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10);
|
|
||||||
env[envi++] = corePidStr.data();
|
env[envi++] = corePidStr.data();
|
||||||
|
|
||||||
|
auto sigStr = std::array<char, 48>();
|
||||||
|
writeEnvInt(sigStr.data(), "__QUICKSHELL_CRASH_SIGNAL", sig);
|
||||||
|
env[envi++] = sigStr.data();
|
||||||
|
|
||||||
auto populateEnv = [&]() {
|
auto populateEnv = [&]() {
|
||||||
auto senvi = 0;
|
auto senvi = 0;
|
||||||
while (envi != 4095) {
|
while (envi != 4095) {
|
||||||
@@ -145,30 +129,17 @@ bool CrashHandlerPrivate::minidumpCallback(
|
|||||||
env[envi] = nullptr;
|
env[envi] = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
sigset_t sigset;
|
|
||||||
sigemptyset(&sigset); // NOLINT (include)
|
|
||||||
sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT
|
|
||||||
|
|
||||||
auto pid = fork();
|
auto pid = fork();
|
||||||
|
|
||||||
if (pid == -1) {
|
if (pid == -1) {
|
||||||
perror("Failed to fork and launch crash reporter.\n");
|
perror("Failed to fork and launch crash reporter.\n");
|
||||||
return false;
|
_exit(-1);
|
||||||
} else if (pid == 0) {
|
} else if (pid == 0) {
|
||||||
// dup to remove CLOEXEC
|
// dup to remove CLOEXEC
|
||||||
// if already -1 will return -1
|
auto dumpFdStr = std::array<char, 48>();
|
||||||
auto dumpFd = dup(self->minidumpFd);
|
auto logFdStr = std::array<char, 48>();
|
||||||
auto logFd = dup(CrashInfo::INSTANCE.logFd);
|
writeEnvInt(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD", dup(CrashInfo::INSTANCE.traceFd));
|
||||||
|
writeEnvInt(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD", 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);
|
|
||||||
|
|
||||||
env[envi++] = dumpFdStr.data();
|
env[envi++] = dumpFdStr.data();
|
||||||
env[envi++] = logFdStr.data();
|
env[envi++] = logFdStr.data();
|
||||||
@@ -185,8 +156,99 @@ bool CrashHandlerPrivate::minidumpCallback(
|
|||||||
perror("Failed to relaunch quickshell.\n");
|
perror("Failed to relaunch quickshell.\n");
|
||||||
_exit(-1);
|
_exit(-1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false; // should make sure it hits the system coredump handler
|
void handleCppTerminate() {
|
||||||
|
if (auto ptr = std::current_exception()) {
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(ptr);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
qFatal().nospace() << "Terminate called with C++ exception ("
|
||||||
|
<< cpptrace::demangle(typeid(e).name()).data() << "): " << e.what();
|
||||||
|
} catch (...) {
|
||||||
|
qFatal() << "Terminate called with non exception object";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qFatal() << "Terminate called without active C++ exception";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void CrashHandler::init() {
|
||||||
|
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)
|
||||||
|
|
||||||
|
std::set_terminate(&handleCppTerminate);
|
||||||
|
|
||||||
|
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
|
} // namespace qs::crash
|
||||||
+2
-11
@@ -5,19 +5,10 @@
|
|||||||
#include "../core/instanceinfo.hpp"
|
#include "../core/instanceinfo.hpp"
|
||||||
namespace qs::crash {
|
namespace qs::crash {
|
||||||
|
|
||||||
struct CrashHandlerPrivate;
|
|
||||||
|
|
||||||
class CrashHandler {
|
class CrashHandler {
|
||||||
public:
|
public:
|
||||||
explicit CrashHandler();
|
static void init();
|
||||||
~CrashHandler();
|
static void setRelaunchInfo(const RelaunchInfo& info);
|
||||||
Q_DISABLE_COPY_MOVE(CrashHandler);
|
|
||||||
|
|
||||||
void init();
|
|
||||||
void setRelaunchInfo(const RelaunchInfo& info);
|
|
||||||
|
|
||||||
private:
|
|
||||||
CrashHandlerPrivate* d;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace qs::crash
|
} // namespace qs::crash
|
||||||
+16
-15
@@ -5,6 +5,7 @@
|
|||||||
#include <qapplication.h>
|
#include <qapplication.h>
|
||||||
#include <qboxlayout.h>
|
#include <qboxlayout.h>
|
||||||
#include <qconfig.h>
|
#include <qconfig.h>
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
#include <qdesktopservices.h>
|
#include <qdesktopservices.h>
|
||||||
#include <qfont.h>
|
#include <qfont.h>
|
||||||
#include <qfontinfo.h>
|
#include <qfontinfo.h>
|
||||||
@@ -12,11 +13,22 @@
|
|||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qpushbutton.h>
|
#include <qpushbutton.h>
|
||||||
|
#include <qtenvironmentvariables.h>
|
||||||
#include <qtversion.h>
|
#include <qtversion.h>
|
||||||
#include <qwidget.h>
|
#include <qwidget.h>
|
||||||
|
|
||||||
#include "build.hpp"
|
#include "build.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QString crashreportUrl() {
|
||||||
|
if (auto url = qEnvironmentVariable("QS_CRASHREPORT_URL"); !url.isEmpty()) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CRASHREPORT_URL;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
class ReportLabel: public QWidget {
|
class ReportLabel: public QWidget {
|
||||||
public:
|
public:
|
||||||
ReportLabel(const QString& label, const QString& content, QWidget* parent): QWidget(parent) {
|
ReportLabel(const QString& label, const QString& content, QWidget* parent): QWidget(parent) {
|
||||||
@@ -67,22 +79,16 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
|
|||||||
|
|
||||||
if (qtVersionMatches) {
|
if (qtVersionMatches) {
|
||||||
mainLayout->addWidget(
|
mainLayout->addWidget(
|
||||||
new QLabel("Please open a bug report for this issue via github or email.")
|
new QLabel("Please open a bug report for this issue on the issue tracker.")
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
mainLayout->addWidget(new QLabel(
|
mainLayout->addWidget(new QLabel(
|
||||||
"Please rebuild Quickshell against the current Qt version.\n"
|
"Please rebuild Quickshell against the current Qt version.\n"
|
||||||
"If this does not solve the problem, please open a bug report via github or email."
|
"If this does not solve the problem, please open a bug report on the issue tracker."
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
mainLayout->addWidget(new ReportLabel(
|
mainLayout->addWidget(new ReportLabel("Tracker:", crashreportUrl(), this));
|
||||||
"Github:",
|
|
||||||
"https://github.com/quickshell-mirror/quickshell/issues/new?template=crash.yml",
|
|
||||||
this
|
|
||||||
));
|
|
||||||
|
|
||||||
mainLayout->addWidget(new ReportLabel("Email:", "[email protected]", this));
|
|
||||||
|
|
||||||
auto* buttons = new QWidget(this);
|
auto* buttons = new QWidget(this);
|
||||||
buttons->setMinimumWidth(900);
|
buttons->setMinimumWidth(900);
|
||||||
@@ -112,10 +118,5 @@ void CrashReporterGui::openFolder() {
|
|||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(this->reportFolder));
|
QDesktopServices::openUrl(QUrl::fromLocalFile(this->reportFolder));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrashReporterGui::openReportUrl() {
|
void CrashReporterGui::openReportUrl() { QDesktopServices::openUrl(QUrl(crashreportUrl())); }
|
||||||
QDesktopServices::openUrl(
|
|
||||||
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CrashReporterGui::cancel() { QApplication::quit(); }
|
void CrashReporterGui::cancel() { QApplication::quit(); }
|
||||||
+106
-50
@@ -1,9 +1,11 @@
|
|||||||
#include "main.hpp"
|
#include "main.hpp"
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <cpptrace/basic.hpp>
|
||||||
|
#include <cpptrace/formatting.hpp>
|
||||||
#include <qapplication.h>
|
#include <qapplication.h>
|
||||||
#include <qconfig.h>
|
|
||||||
#include <qcoreapplication.h>
|
#include <qcoreapplication.h>
|
||||||
#include <qdatastream.h>
|
#include <qdatastream.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
@@ -12,15 +14,19 @@
|
|||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
#include <qtextstream.h>
|
#include <qtextstream.h>
|
||||||
#include <qtversion.h>
|
#include <qtypes.h>
|
||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../core/debuginfo.hpp"
|
||||||
#include "../core/instanceinfo.hpp"
|
#include "../core/instanceinfo.hpp"
|
||||||
#include "../core/logcat.hpp"
|
#include "../core/logcat.hpp"
|
||||||
#include "../core/logging.hpp"
|
#include "../core/logging.hpp"
|
||||||
|
#include "../core/logging_p.hpp"
|
||||||
#include "../core/paths.hpp"
|
#include "../core/paths.hpp"
|
||||||
#include "build.hpp"
|
#include "../core/plugin.hpp"
|
||||||
|
#include "../core/ringbuf.hpp"
|
||||||
#include "interface.hpp"
|
#include "interface.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -61,6 +67,76 @@ int tryDup(int fd, const QString& path) {
|
|||||||
return 0;
|
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) {
|
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||||
qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path();
|
qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path();
|
||||||
|
|
||||||
@@ -71,74 +147,49 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
|
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
|
||||||
|
auto crashSignal = qEnvironmentVariable("__QUICKSHELL_CRASH_SIGNAL").toInt();
|
||||||
auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt();
|
auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt();
|
||||||
auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt();
|
auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt();
|
||||||
|
|
||||||
qCDebug(logCrashReporter) << "Saving minidump from fd" << dumpFd;
|
qCDebug(logCrashReporter) << "Resolving stacktrace from fd" << dumpFd;
|
||||||
auto dumpDupStatus = tryDup(dumpFd, crashDir.filePath("minidump.dmp.log"));
|
auto stacktrace = resolveStacktrace(dumpFd);
|
||||||
if (dumpDupStatus != 0) {
|
|
||||||
qCCritical(logCrashReporter) << "Failed to write minidump:" << dumpDupStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logCrashReporter) << "Saving log from fd" << logFd;
|
qCDebug(logCrashReporter) << "Reading recent log lines from fd" << logFd;
|
||||||
auto logDupStatus = tryDup(logFd, crashDir.filePath("log.qslog.log"));
|
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) {
|
if (logDupStatus != 0) {
|
||||||
qCCritical(logCrashReporter) << "Failed to save log:" << logDupStatus;
|
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)) {
|
if (!extraInfoFile.open(QFile::WriteOnly)) {
|
||||||
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
|
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
|
||||||
} else {
|
} else {
|
||||||
auto stream = QTextStream(&extraInfoFile);
|
auto stream = QTextStream(&extraInfoFile);
|
||||||
stream << "===== Build Information =====\n";
|
stream << qs::debuginfo::combinedInfo();
|
||||||
stream << "Git Revision: " << GIT_REVISION << '\n';
|
|
||||||
stream << "Buildtime Qt Version: " << QT_VERSION_STR << "\n";
|
|
||||||
stream << "Build Type: " << BUILD_TYPE << '\n';
|
|
||||||
stream << "Compiler: " << COMPILER << '\n';
|
|
||||||
stream << "Complie Flags: " << COMPILE_FLAGS << "\n\n";
|
|
||||||
stream << "Build configuration:\n" << BUILD_CONFIGURATION << "\n";
|
|
||||||
|
|
||||||
stream << "\n===== Runtime Information =====\n";
|
stream << "\n===== Instance Information =====\n";
|
||||||
stream << "Runtime Qt Version: " << qVersion() << '\n';
|
stream << "Signal: " << strsignal(crashSignal) << " (" << crashSignal << ")\n"; // NOLINT
|
||||||
stream << "Crashed process ID: " << crashProc << '\n';
|
stream << "Crashed process ID: " << crashProc << '\n';
|
||||||
stream << "Run ID: " << instance.instanceId << '\n';
|
stream << "Run ID: " << instance.instanceId << '\n';
|
||||||
stream << "Shell ID: " << instance.shellId << '\n';
|
stream << "Shell ID: " << instance.shellId << '\n';
|
||||||
stream << "Config Path: " << instance.configPath << '\n';
|
stream << "Config Path: " << instance.configPath << '\n';
|
||||||
|
|
||||||
stream << "\n===== Report Integrity =====\n";
|
stream << "\n===== Stacktrace =====\n";
|
||||||
stream << "Minidump save status: " << dumpDupStatus << '\n';
|
if (stacktrace.empty()) {
|
||||||
stream << "Log save status: " << logDupStatus << '\n';
|
stream << "(no trace available)\n";
|
||||||
stream << "Binary copy status: " << copyBinStatus << '\n';
|
|
||||||
|
|
||||||
stream << "\n===== System Information =====\n\n";
|
|
||||||
stream << "/etc/os-release:";
|
|
||||||
auto osReleaseFile = QFile("/etc/os-release");
|
|
||||||
if (osReleaseFile.open(QFile::ReadOnly)) {
|
|
||||||
stream << '\n' << osReleaseFile.readAll() << '\n';
|
|
||||||
osReleaseFile.close();
|
|
||||||
} else {
|
} else {
|
||||||
stream << "FAILED TO OPEN\n";
|
auto formatter = cpptrace::formatter().header(std::string());
|
||||||
|
auto traceStr = formatter.format(stacktrace);
|
||||||
|
stream << QString::fromStdString(traceStr) << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
stream << "/etc/lsb-release:";
|
stream << "\n===== Log Tail =====\n";
|
||||||
auto lsbReleaseFile = QFile("/etc/lsb-release");
|
stream << recentLogs;
|
||||||
if (lsbReleaseFile.open(QFile::ReadOnly)) {
|
|
||||||
stream << '\n' << lsbReleaseFile.readAll();
|
|
||||||
lsbReleaseFile.close();
|
|
||||||
} else {
|
|
||||||
stream << "FAILED TO OPEN\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
extraInfoFile.close();
|
extraInfoFile.close();
|
||||||
}
|
}
|
||||||
@@ -180,12 +231,17 @@ void qsCheckCrash(int argc, char** argv) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
auto app = QApplication(argc, argv);
|
auto app = QApplication(argc, argv);
|
||||||
QApplication::setDesktopFileName("org.quickshell");
|
auto desktopId =
|
||||||
|
info.instance.appId.isEmpty() ? QStringLiteral("org.quickshell") : info.instance.appId;
|
||||||
|
QApplication::setDesktopFileName(desktopId);
|
||||||
|
|
||||||
auto crashDir = QsPaths::crashDir(info.instance.instanceId);
|
auto crashDir = QsPaths::crashDir(info.instance.instanceId);
|
||||||
|
|
||||||
qCInfo(logCrashReporter) << "Starting crash reporter...";
|
qCInfo(logCrashReporter) << "Starting crash reporter...";
|
||||||
|
|
||||||
|
// Required platform compatibility hooks
|
||||||
|
QsEnginePlugin::preinitPluginsOnly();
|
||||||
|
|
||||||
recordCrashInfo(crashDir, info.instance);
|
recordCrashInfo(crashDir, info.instance);
|
||||||
|
|
||||||
auto gui = CrashReporterGui(crashDir.path(), crashProc);
|
auto gui = CrashReporterGui(crashDir.path(), crashProc);
|
||||||
|
|||||||
@@ -301,6 +301,13 @@ DBusMenu::DBusMenu(const QString& service, const QString& path, QObject* parent)
|
|||||||
&DBusMenu::onLayoutUpdated
|
&DBusMenu::onLayoutUpdated
|
||||||
);
|
);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
this->interface,
|
||||||
|
&DBusMenuInterface::ItemsPropertiesUpdated,
|
||||||
|
this,
|
||||||
|
&DBusMenu::onItemPropertiesUpdated
|
||||||
|
);
|
||||||
|
|
||||||
this->properties.setInterface(this->interface);
|
this->properties.setInterface(this->interface);
|
||||||
this->properties.updateAllViaGetAll();
|
this->properties.updateAllViaGetAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ void SplitParser::parseBytes(QByteArray& incoming, QByteArray& buffer) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we dont miss any delimiters in the buffer if the delimiter changes
|
// make sure we don't miss any delimiters in the buffer if the delimiter changes
|
||||||
if (this->mSplitMarkerChanged) {
|
if (this->mSplitMarkerChanged) {
|
||||||
this->mSplitMarkerChanged = false;
|
this->mSplitMarkerChanged = false;
|
||||||
this->parseBytes(buffer, buffer);
|
this->parseBytes(buffer, buffer);
|
||||||
|
|||||||
+2
-2
@@ -29,9 +29,9 @@ class FileViewError: public QObject {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
enum Enum : quint8 {
|
enum Enum : quint8 {
|
||||||
/// No error occured.
|
/// No error occurred.
|
||||||
Success = 0,
|
Success = 0,
|
||||||
/// An unknown error occured. Check the logs for details.
|
/// An unknown error occurred. Check the logs for details.
|
||||||
Unknown = 1,
|
Unknown = 1,
|
||||||
/// The file to read does not exist.
|
/// The file to read does not exist.
|
||||||
FileNotFound = 2,
|
FileNotFound = 2,
|
||||||
|
|||||||
+52
-15
@@ -1,11 +1,13 @@
|
|||||||
#include "jsonadapter.hpp"
|
#include "jsonadapter.hpp"
|
||||||
|
|
||||||
|
#include <qassociativeiterable.h>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qjsonarray.h>
|
#include <qjsonarray.h>
|
||||||
#include <qjsondocument.h>
|
#include <qjsondocument.h>
|
||||||
#include <qjsonobject.h>
|
#include <qjsonobject.h>
|
||||||
#include <qjsonvalue.h>
|
#include <qjsonvalue.h>
|
||||||
#include <qjsvalue.h>
|
#include <qjsvalue.h>
|
||||||
|
#include <qmetacontainer.h>
|
||||||
#include <qmetaobject.h>
|
#include <qmetaobject.h>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
@@ -14,6 +16,7 @@
|
|||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlinfo.h>
|
#include <qqmlinfo.h>
|
||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
|
#include <qsequentialiterable.h>
|
||||||
#include <qstringview.h>
|
#include <qstringview.h>
|
||||||
#include <qvariant.h>
|
#include <qvariant.h>
|
||||||
|
|
||||||
@@ -131,13 +134,22 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas
|
|||||||
}
|
}
|
||||||
|
|
||||||
json.insert(prop.name(), array);
|
json.insert(prop.name(), array);
|
||||||
} else if (val.canConvert<QJSValue>()) {
|
|
||||||
auto variant = val.value<QJSValue>().toVariant();
|
|
||||||
auto jv = QJsonValue::fromVariant(variant);
|
|
||||||
json.insert(prop.name(), jv);
|
|
||||||
} else {
|
} else {
|
||||||
auto jv = QJsonValue::fromVariant(val);
|
if (val.canConvert<QJSValue>()) val = val.value<QJSValue>().toVariant();
|
||||||
json.insert(prop.name(), jv);
|
|
||||||
|
auto jsonVal = QJsonValue::fromVariant(val);
|
||||||
|
|
||||||
|
if (jsonVal.isNull() && !val.isNull() && val.isValid()) {
|
||||||
|
if (val.canConvert<QAssociativeIterable>()) {
|
||||||
|
val.convert(QMetaType::fromType<QVariantMap>());
|
||||||
|
} else if (val.canConvert<QSequentialIterable>()) {
|
||||||
|
val.convert(QMetaType::fromType<QVariantList>());
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonVal = QJsonValue::fromVariant(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
json.insert(prop.name(), jsonVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,14 +166,16 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
|||||||
auto jval = json.value(prop.name());
|
auto jval = json.value(prop.name());
|
||||||
|
|
||||||
if (prop.metaType() == QMetaType::fromType<QVariant>()) {
|
if (prop.metaType() == QMetaType::fromType<QVariant>()) {
|
||||||
auto variant = jval.toVariant();
|
auto newVariant = jval.toVariant();
|
||||||
auto oldValue = prop.read(this).value<QJSValue>();
|
auto oldValue = prop.read(obj);
|
||||||
|
auto oldVariant =
|
||||||
|
oldValue.canConvert<QJSValue>() ? oldValue.value<QJSValue>().toVariant() : oldValue;
|
||||||
|
|
||||||
// Calling prop.write with a new QJSValue will cause a property update
|
// Calling prop.write with a new QJSValue will cause a property update
|
||||||
// even if content is identical.
|
// even if content is identical.
|
||||||
if (jval.toVariant() != oldValue.toVariant()) {
|
if (newVariant != oldVariant) {
|
||||||
auto jsValue = qmlEngine(this)->fromVariant<QJSValue>(jval.toVariant());
|
auto jsValue = qmlEngine(this)->fromVariant<QJSValue>(newVariant);
|
||||||
prop.write(this, QVariant::fromValue(jsValue));
|
prop.write(obj, QVariant::fromValue(jsValue));
|
||||||
}
|
}
|
||||||
} else if (QMetaType::canView(prop.metaType(), QMetaType::fromType<JsonObject*>())) {
|
} else if (QMetaType::canView(prop.metaType(), QMetaType::fromType<JsonObject*>())) {
|
||||||
// FIXME: This doesn't support creating descendants of JsonObject, as QMetaType.metaObject()
|
// FIXME: This doesn't support creating descendants of JsonObject, as QMetaType.metaObject()
|
||||||
@@ -196,7 +210,7 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
|||||||
QMetaType::fromType<QQmlListProperty<JsonObject>>()
|
QMetaType::fromType<QQmlListProperty<JsonObject>>()
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
auto pval = prop.read(this);
|
auto pval = prop.read(obj);
|
||||||
|
|
||||||
if (pval.canConvert<QQmlListProperty<JsonObject>>()) {
|
if (pval.canConvert<QQmlListProperty<JsonObject>>()) {
|
||||||
auto lp = pval.value<QQmlListProperty<JsonObject>>();
|
auto lp = pval.value<QQmlListProperty<JsonObject>>();
|
||||||
@@ -247,12 +261,35 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto variant = jval.toVariant();
|
auto variant = jval.toVariant();
|
||||||
|
auto convVariant = variant;
|
||||||
|
|
||||||
if (variant.convert(prop.metaType())) {
|
if (convVariant.convert(prop.metaType())) {
|
||||||
prop.write(obj, variant);
|
prop.write(obj, convVariant);
|
||||||
|
} else {
|
||||||
|
auto pval = prop.read(obj);
|
||||||
|
if (variant.canConvert<QSequentialIterable>() && pval.canView<QSequentialIterable>()) {
|
||||||
|
auto targetv = QVariant(pval.metaType());
|
||||||
|
auto target = targetv.view<QSequentialIterable>().metaContainer();
|
||||||
|
auto valueType = target.valueMetaType();
|
||||||
|
auto i = 0;
|
||||||
|
|
||||||
|
for (QVariant item: variant.value<QSequentialIterable>()) {
|
||||||
|
if (item.convert(valueType)) {
|
||||||
|
target.addValueAtEnd(targetv.data(), item.constData());
|
||||||
|
} else {
|
||||||
|
qmlWarning(this) << "Failed to deserialize list member " << i << " of property "
|
||||||
|
<< prop.name() << ": expected " << valueType.name() << " but got "
|
||||||
|
<< item.typeName();
|
||||||
|
}
|
||||||
|
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
prop.write(obj, targetv);
|
||||||
} else {
|
} else {
|
||||||
qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected "
|
qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected "
|
||||||
<< prop.metaType().name() << " but got " << jval.toVariant().typeName();
|
<< prop.metaType().name() << " but got "
|
||||||
|
<< jval.toVariant().typeName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -96,7 +96,7 @@ class SocketServer: public Reloadable {
|
|||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
/// If the socket server is currently active. Defaults to false.
|
/// If the socket server is currently active. Defaults to false.
|
||||||
///
|
///
|
||||||
/// Setting this to false will destory all active connections and delete
|
/// Setting this to false will destroy all active connections and delete
|
||||||
/// the socket file on disk.
|
/// the socket file on disk.
|
||||||
///
|
///
|
||||||
/// If path is empty setting this property will have no effect.
|
/// If path is empty setting this property will have no effect.
|
||||||
@@ -105,7 +105,7 @@ class SocketServer: public Reloadable {
|
|||||||
///
|
///
|
||||||
/// Setting this property while the server is active will have no effect.
|
/// Setting this property while the server is active will have no effect.
|
||||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
|
||||||
/// Connection handler component. Must creeate a @@Socket.
|
/// Connection handler component. Must create a @@Socket.
|
||||||
///
|
///
|
||||||
/// The created socket should not set @@connected or @@path or the incoming
|
/// The created socket should not set @@connected or @@path or the incoming
|
||||||
/// socket connection will be dropped (they will be set by the socket server.)
|
/// socket connection will be dropped (they will be set by the socket server.)
|
||||||
|
|||||||
+4
-1
@@ -3,6 +3,7 @@
|
|||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include <qbuffer.h>
|
#include <qbuffer.h>
|
||||||
|
#include <qcoreapplication.h>
|
||||||
#include <qlocalserver.h>
|
#include <qlocalserver.h>
|
||||||
#include <qlocalsocket.h>
|
#include <qlocalsocket.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
@@ -127,7 +128,9 @@ int IpcClient::connect(const QString& id, const std::function<void(IpcClient& cl
|
|||||||
|
|
||||||
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
|
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
|
||||||
qInfo() << "Exiting due to IPC request.";
|
qInfo() << "Exiting due to IPC request.";
|
||||||
EngineGeneration::currentGeneration()->quit();
|
auto* generation = EngineGeneration::currentGeneration();
|
||||||
|
if (generation) generation->quit();
|
||||||
|
else QCoreApplication::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace qs::ipc
|
} // namespace qs::ipc
|
||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
template <typename... Types>
|
template <typename... Types>
|
||||||
constexpr void assertSerializable() {
|
constexpr void assertSerializable() {
|
||||||
// monostate being zero ensures transactional reads wont break
|
// monostate being zero ensures transactional reads won't break
|
||||||
static_assert(
|
static_assert(
|
||||||
std::is_same_v<std::variant_alternative_t<0, std::variant<Types...>>, std::monostate>,
|
std::is_same_v<std::variant_alternative_t<0, std::variant<Types...>>, std::monostate>,
|
||||||
"Serialization of variants without std::monostate at index 0 is disallowed."
|
"Serialization of variants without std::monostate at index 0 is disallowed."
|
||||||
|
|||||||
+43
-27
@@ -12,7 +12,6 @@
|
|||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
#include <qguiapplication.h>
|
|
||||||
#include <qjsonarray.h>
|
#include <qjsonarray.h>
|
||||||
#include <qjsondocument.h>
|
#include <qjsondocument.h>
|
||||||
#include <qjsonobject.h>
|
#include <qjsonobject.h>
|
||||||
@@ -25,12 +24,12 @@
|
|||||||
#include <qtversion.h>
|
#include <qtversion.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../core/debuginfo.hpp"
|
||||||
#include "../core/instanceinfo.hpp"
|
#include "../core/instanceinfo.hpp"
|
||||||
#include "../core/logging.hpp"
|
#include "../core/logging.hpp"
|
||||||
#include "../core/paths.hpp"
|
#include "../core/paths.hpp"
|
||||||
#include "../io/ipccomm.hpp"
|
#include "../io/ipccomm.hpp"
|
||||||
#include "../ipc/ipc.hpp"
|
#include "../ipc/ipc.hpp"
|
||||||
#include "build.hpp"
|
|
||||||
#include "launch_p.hpp"
|
#include "launch_p.hpp"
|
||||||
|
|
||||||
namespace qs::launch {
|
namespace qs::launch {
|
||||||
@@ -120,7 +119,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qCCritical(logBare) << "Could not open maifest at path" << *cmd.config.manifest;
|
qCCritical(logBare) << "Could not open manifest at path" << *cmd.config.manifest;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -179,13 +178,17 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
|||||||
}
|
}
|
||||||
} else if (!cmd.instance.id->isEmpty()) {
|
} else if (!cmd.instance.id->isEmpty()) {
|
||||||
path = basePath->filePath("by-pid");
|
path = basePath->filePath("by-pid");
|
||||||
auto [liveInstances, deadInstances] =
|
auto [liveInstances, mismatchedInstances, deadInstances] =
|
||||||
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
||||||
|
|
||||||
liveInstances.removeIf([&](const InstanceLockInfo& info) {
|
liveInstances.removeIf([&](const InstanceLockInfo& info) {
|
||||||
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mismatchedInstances.removeIf([&](const InstanceLockInfo& info) {
|
||||||
|
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||||
|
});
|
||||||
|
|
||||||
deadInstances.removeIf([&](const InstanceLockInfo& info) {
|
deadInstances.removeIf([&](const InstanceLockInfo& info) {
|
||||||
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||||
});
|
});
|
||||||
@@ -193,6 +196,18 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
|||||||
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
|
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
|
||||||
|
|
||||||
if (instances.isEmpty()) {
|
if (instances.isEmpty()) {
|
||||||
|
if (!mismatchedInstances.isEmpty()) {
|
||||||
|
qCInfo(logBare) << "No running instances on the current display" << getDisplayConnection()
|
||||||
|
<< "start with" << *cmd.instance.id;
|
||||||
|
|
||||||
|
qCInfo(logBare) << "Some instances on other displays match:";
|
||||||
|
|
||||||
|
for (auto& instance: mismatchedInstances) {
|
||||||
|
qCInfo(logBare).noquote().nospace()
|
||||||
|
<< " - " << instance.instance.instanceId << " (" << instance.instance.display << ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (deadFallback) {
|
if (deadFallback) {
|
||||||
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
|
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
|
||||||
} else {
|
} else {
|
||||||
@@ -213,7 +228,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
|||||||
|
|
||||||
for (auto& instance: instances) {
|
for (auto& instance: instances) {
|
||||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId
|
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId
|
||||||
<< (instance.pid == -1 ? " (dead)" : "");
|
<< (instance.pid == -1 ? "(dead)" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
@@ -230,7 +245,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
|||||||
|
|
||||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||||
|
|
||||||
auto [liveInstances, deadInstances] =
|
auto [liveInstances, mismatchedInstances, deadInstances] =
|
||||||
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
||||||
|
|
||||||
auto instances = liveInstances;
|
auto instances = liveInstances;
|
||||||
@@ -251,6 +266,16 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
|||||||
for (auto& instance: deadInstances) {
|
for (auto& instance: deadInstances) {
|
||||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
|
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
|
||||||
}
|
}
|
||||||
|
} else if (!mismatchedInstances.isEmpty()) {
|
||||||
|
qCInfo(logBare) << "No running instances for" << configFilePath
|
||||||
|
<< " present on the current display" << getDisplayConnection();
|
||||||
|
|
||||||
|
qCInfo(logBare) << "Some instances on other displays match:";
|
||||||
|
|
||||||
|
for (auto& instance: mismatchedInstances) {
|
||||||
|
qCInfo(logBare).noquote().nospace()
|
||||||
|
<< " - " << instance.instance.instanceId << " (" << instance.instance.display << ')';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qCInfo(logBare) << "No running instances for" << configFilePath;
|
qCInfo(logBare) << "No running instances for" << configFilePath;
|
||||||
}
|
}
|
||||||
@@ -314,7 +339,7 @@ int listInstances(CommandState& cmd) {
|
|||||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(
|
auto [liveInstances, mismatchedInstances, deadInstances] = QsPaths::collectInstances(
|
||||||
path,
|
path,
|
||||||
cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection()
|
cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection()
|
||||||
);
|
);
|
||||||
@@ -519,20 +544,10 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.misc.printVersion) {
|
if (state.misc.printVersion) {
|
||||||
qCInfo(logBare).noquote().nospace() << "quickshell " << QS_VERSION << ", revision "
|
if (state.log.verbosity == 0) {
|
||||||
<< GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
qCInfo(logBare).noquote() << "Quickshell" << qs::debuginfo::qsVersion();
|
||||||
|
} else {
|
||||||
if (state.log.verbosity > 1) {
|
qCInfo(logBare).noquote() << qs::debuginfo::combinedInfo();
|
||||||
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
|
||||||
qCInfo(logBare).noquote() << "Runtime Qt Version:" << qVersion();
|
|
||||||
qCInfo(logBare).noquote() << "Compiler:" << COMPILER;
|
|
||||||
qCInfo(logBare).noquote() << "Compile Flags:" << COMPILE_FLAGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.log.verbosity > 0) {
|
|
||||||
qCInfo(logBare).noquote() << "\nBuild Type:" << BUILD_TYPE;
|
|
||||||
qCInfo(logBare).noquote() << "Build configuration:";
|
|
||||||
qCInfo(logBare).noquote().nospace() << BUILD_CONFIGURATION;
|
|
||||||
}
|
}
|
||||||
} else if (*state.subcommand.log) {
|
} else if (*state.subcommand.log) {
|
||||||
return readLogFile(state);
|
return readLogFile(state);
|
||||||
@@ -557,16 +572,17 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString getDisplayConnection() {
|
QString getDisplayConnection() {
|
||||||
auto platform = qEnvironmentVariable("QT_QPA_PLATFORM");
|
// Doesn't actually have to match what qt picks, but will 99% of the time.
|
||||||
|
// Running quickshell under wayland in x11 mode doesn't really make any sense.
|
||||||
auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY");
|
auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY");
|
||||||
auto xDisplay = qEnvironmentVariable("DISPLAY");
|
auto xDisplay = qEnvironmentVariable("DISPLAY");
|
||||||
|
|
||||||
if (platform == "wayland" || (platform.isEmpty() && !wlDisplay.isEmpty())) {
|
if (!wlDisplay.isEmpty()) {
|
||||||
return "wayland," + wlDisplay;
|
return "wayland/" + wlDisplay;
|
||||||
} else if (platform == "xcb" || (platform.isEmpty() && !xDisplay.isEmpty())) {
|
} else if (!xDisplay.isEmpty()) {
|
||||||
return "x11," + xDisplay;
|
return "x11/" + xDisplay;
|
||||||
} else {
|
} else {
|
||||||
return "unk," + QGuiApplication::platformName();
|
return "unk";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+80
-20
@@ -27,7 +27,7 @@
|
|||||||
#include "build.hpp"
|
#include "build.hpp"
|
||||||
#include "launch_p.hpp"
|
#include "launch_p.hpp"
|
||||||
|
|
||||||
#if CRASH_REPORTER
|
#if CRASH_HANDLER
|
||||||
#include "../crash/handler.hpp"
|
#include "../crash/handler.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -76,6 +76,9 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
|||||||
bool useSystemStyle = false;
|
bool useSystemStyle = false;
|
||||||
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
|
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
|
||||||
QHash<QString, QString> envOverrides;
|
QHash<QString, QString> envOverrides;
|
||||||
|
QHash<QString, QString> defaultEnv;
|
||||||
|
QString appId = qEnvironmentVariable("QS_APP_ID");
|
||||||
|
bool dropExpensiveFonts = false;
|
||||||
QString dataDir;
|
QString dataDir;
|
||||||
QString stateDir;
|
QString stateDir;
|
||||||
QString cacheDir;
|
QString cacheDir;
|
||||||
@@ -87,23 +90,31 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
|||||||
if (line.startsWith("//@ pragma ")) {
|
if (line.startsWith("//@ pragma ")) {
|
||||||
auto pragma = line.sliced(11).trimmed();
|
auto pragma = line.sliced(11).trimmed();
|
||||||
|
|
||||||
if (pragma == "UseQApplication") pragmas.useQApplication = true;
|
auto isEnv = pragma.startsWith("Env ");
|
||||||
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
|
auto isDefaultEnv = pragma.startsWith("DefaultEnv ");
|
||||||
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
|
|
||||||
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
|
if (isEnv || isDefaultEnv) {
|
||||||
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
|
auto content = pragma.sliced(isDefaultEnv ? 11 : 4);
|
||||||
else if (pragma.startsWith("Env ")) {
|
auto splitIdx = content.indexOf('=');
|
||||||
auto envPragma = pragma.sliced(4);
|
|
||||||
auto splitIdx = envPragma.indexOf('=');
|
|
||||||
|
|
||||||
if (splitIdx == -1) {
|
if (splitIdx == -1) {
|
||||||
qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'";
|
qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto var = envPragma.sliced(0, splitIdx).trimmed();
|
auto var = content.sliced(0, splitIdx).trimmed();
|
||||||
auto val = envPragma.sliced(splitIdx + 1).trimmed();
|
auto val = content.sliced(splitIdx + 1).trimmed();
|
||||||
pragmas.envOverrides.insert(var, val);
|
|
||||||
|
if (isDefaultEnv) pragmas.defaultEnv.insert(var, val);
|
||||||
|
else pragmas.envOverrides.insert(var, val);
|
||||||
|
} else if (pragma == "UseQApplication") pragmas.useQApplication = true;
|
||||||
|
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
|
||||||
|
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
|
||||||
|
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
|
||||||
|
else if (pragma == "DropExpensiveFonts") pragmas.dropExpensiveFonts = true;
|
||||||
|
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
|
||||||
|
else if (pragma.startsWith("AppId ")) {
|
||||||
|
pragmas.appId = pragma.sliced(6).trimmed();
|
||||||
} else if (pragma.startsWith("ShellId ")) {
|
} else if (pragma.startsWith("ShellId ")) {
|
||||||
shellId = pragma.sliced(8).trimmed();
|
shellId = pragma.sliced(8).trimmed();
|
||||||
} else if (pragma.startsWith("DataDir ")) {
|
} else if (pragma.startsWith("DataDir ")) {
|
||||||
@@ -113,8 +124,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
|||||||
} else if (pragma.startsWith("CacheDir ")) {
|
} else if (pragma.startsWith("CacheDir ")) {
|
||||||
pragmas.cacheDir = pragma.sliced(9).trimmed();
|
pragmas.cacheDir = pragma.sliced(9).trimmed();
|
||||||
} else {
|
} else {
|
||||||
qCritical() << "Unrecognized pragma" << pragma;
|
qWarning() << "Unrecognized pragma" << pragma;
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
} else if (line.startsWith("import")) break;
|
} else if (line.startsWith("import")) break;
|
||||||
}
|
}
|
||||||
@@ -128,22 +138,26 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
|||||||
qInfo() << "Shell ID:" << shellId << "Path ID" << pathId;
|
qInfo() << "Shell ID:" << shellId << "Path ID" << pathId;
|
||||||
|
|
||||||
auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch();
|
auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch();
|
||||||
|
auto appId = pragmas.appId.isEmpty() ? QStringLiteral("org.quickshell") : pragmas.appId;
|
||||||
|
|
||||||
InstanceInfo::CURRENT = InstanceInfo {
|
InstanceInfo::CURRENT = InstanceInfo {
|
||||||
.instanceId = base36Encode(getpid()) + base36Encode(launchTime),
|
.instanceId = base36Encode(getpid()) + base36Encode(launchTime),
|
||||||
.configPath = args.configPath,
|
.configPath = args.configPath,
|
||||||
.shellId = shellId,
|
.shellId = shellId,
|
||||||
|
.appId = appId,
|
||||||
.launchTime = qs::Common::LAUNCH_TIME,
|
.launchTime = qs::Common::LAUNCH_TIME,
|
||||||
.pid = getpid(),
|
.pid = getpid(),
|
||||||
.display = getDisplayConnection(),
|
.display = getDisplayConnection(),
|
||||||
};
|
};
|
||||||
|
|
||||||
#if CRASH_REPORTER
|
#if CRASH_HANDLER
|
||||||
auto crashHandler = crash::CrashHandler();
|
if (qEnvironmentVariableIsSet("QS_DISABLE_CRASH_HANDLER")) {
|
||||||
crashHandler.init();
|
qInfo() << "Crash handling disabled.";
|
||||||
|
} else {
|
||||||
|
crash::CrashHandler::init();
|
||||||
|
|
||||||
{
|
|
||||||
auto* log = LogManager::instance();
|
auto* log = LogManager::instance();
|
||||||
crashHandler.setRelaunchInfo({
|
crash::CrashHandler::setRelaunchInfo({
|
||||||
.instance = InstanceInfo::CURRENT,
|
.instance = InstanceInfo::CURRENT,
|
||||||
.noColor = !log->colorLogs,
|
.noColor = !log->colorLogs,
|
||||||
.timestamp = log->timestampLogs,
|
.timestamp = log->timestampLogs,
|
||||||
@@ -166,10 +180,56 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
|||||||
qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion");
|
qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto [var, val]: pragmas.defaultEnv.asKeyValueRange()) {
|
||||||
|
if (!qEnvironmentVariableIsSet(var.toUtf8())) qputenv(var.toUtf8(), val.toUtf8());
|
||||||
|
}
|
||||||
|
|
||||||
for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) {
|
for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) {
|
||||||
qputenv(var.toUtf8(), val.toUtf8());
|
qputenv(var.toUtf8(), val.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pragmas.dropExpensiveFonts |= qEnvironmentVariableIntValue("QS_DROP_EXPENSIVE_FONTS") == 1;
|
||||||
|
|
||||||
|
if (pragmas.dropExpensiveFonts) {
|
||||||
|
if (auto* runDir = QsPaths::instance()->instanceRunDir()) {
|
||||||
|
auto baseConfigPath = qEnvironmentVariable("FONTCONFIG_FILE");
|
||||||
|
if (baseConfigPath.isEmpty()) baseConfigPath = "/etc/fonts/fonts.conf";
|
||||||
|
|
||||||
|
auto filterPath = runDir->filePath("fonts-override.conf");
|
||||||
|
auto filterFile = QFile(filterPath);
|
||||||
|
if (filterFile.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) {
|
||||||
|
auto filterTemplate = QStringLiteral(R"(<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
|
||||||
|
<fontconfig>
|
||||||
|
<include ignore_missing="no">%1</include>
|
||||||
|
<selectfont>
|
||||||
|
<rejectfont>
|
||||||
|
<pattern>
|
||||||
|
<patelt name="fontwrapper">
|
||||||
|
<string>woff</string>
|
||||||
|
</patelt>
|
||||||
|
</pattern>
|
||||||
|
<pattern>
|
||||||
|
<patelt name="fontwrapper">
|
||||||
|
<string>woff2</string>
|
||||||
|
</patelt>
|
||||||
|
</pattern>
|
||||||
|
</rejectfont>
|
||||||
|
</selectfont>
|
||||||
|
</fontconfig>
|
||||||
|
)");
|
||||||
|
|
||||||
|
QTextStream(&filterFile) << filterTemplate.arg(baseConfigPath);
|
||||||
|
filterFile.close();
|
||||||
|
qputenv("FONTCONFIG_FILE", filterPath.toUtf8());
|
||||||
|
} else {
|
||||||
|
qCritical() << "Could not write fontconfig filter to" << filterPath;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCritical() << "Could not create fontconfig filter: instance run directory unavailable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The qml engine currently refuses to cache non file (qsintercept) paths.
|
// The qml engine currently refuses to cache non file (qsintercept) paths.
|
||||||
|
|
||||||
// if (auto* cacheDir = QsPaths::instance()->cacheDir()) {
|
// if (auto* cacheDir = QsPaths::instance()->cacheDir()) {
|
||||||
@@ -230,7 +290,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
|||||||
app = new QGuiApplication(qArgC, argv);
|
app = new QGuiApplication(qArgC, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
QGuiApplication::setDesktopFileName("org.quickshell");
|
QGuiApplication::setDesktopFileName(appId);
|
||||||
|
|
||||||
if (args.debugPort != -1) {
|
if (args.debugPort != -1) {
|
||||||
QQmlDebuggingEnabler::enableDebugging(true);
|
QQmlDebuggingEnabler::enableDebugging(true);
|
||||||
|
|||||||
+21
-13
@@ -16,7 +16,7 @@
|
|||||||
#include "build.hpp"
|
#include "build.hpp"
|
||||||
#include "launch_p.hpp"
|
#include "launch_p.hpp"
|
||||||
|
|
||||||
#if CRASH_REPORTER
|
#if CRASH_HANDLER
|
||||||
#include "../crash/main.hpp"
|
#include "../crash/main.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ namespace qs::launch {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
|
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
|
||||||
#if CRASH_REPORTER
|
#if CRASH_HANDLER
|
||||||
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
|
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
|
||||||
|
|
||||||
if (!lastInfoFdStr.isEmpty()) {
|
if (!lastInfoFdStr.isEmpty()) {
|
||||||
@@ -84,27 +84,35 @@ void exitDaemon(int code) {
|
|||||||
|
|
||||||
close(DAEMON_PIPE);
|
close(DAEMON_PIPE);
|
||||||
|
|
||||||
close(STDIN_FILENO);
|
auto fd = open("/dev/null", O_RDWR);
|
||||||
close(STDOUT_FILENO);
|
if (fd == -1) {
|
||||||
close(STDERR_FILENO);
|
qCritical().nospace() << "Failed to open /dev/null for daemon stdio" << errno << ": "
|
||||||
|
<< qt_error_string();
|
||||||
if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT
|
return;
|
||||||
qFatal() << "Failed to open /dev/null on stdin";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT
|
if (dup2(fd, STDIN_FILENO) != STDIN_FILENO) { // NOLINT
|
||||||
qFatal() << "Failed to open /dev/null on stdout";
|
qCritical().nospace() << "Failed to set daemon stdin to /dev/null" << errno << ": "
|
||||||
|
<< qt_error_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT
|
if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) { // NOLINT
|
||||||
qFatal() << "Failed to open /dev/null on stderr";
|
qCritical().nospace() << "Failed to set daemon stdout to /dev/null" << errno << ": "
|
||||||
|
<< qt_error_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) { // NOLINT
|
||||||
|
qCritical().nospace() << "Failed to set daemon stderr to /dev/null" << errno << ": "
|
||||||
|
<< qt_error_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
QCoreApplication::setApplicationName("quickshell");
|
QCoreApplication::setApplicationName("quickshell");
|
||||||
|
|
||||||
#if CRASH_REPORTER
|
#if CRASH_HANDLER
|
||||||
qsCheckCrash(argc, argv);
|
qsCheckCrash(argc, argv);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ add_subdirectory(nm)
|
|||||||
qt_add_library(quickshell-network STATIC
|
qt_add_library(quickshell-network STATIC
|
||||||
network.cpp
|
network.cpp
|
||||||
device.cpp
|
device.cpp
|
||||||
|
wired.cpp
|
||||||
wifi.cpp
|
wifi.cpp
|
||||||
|
enums.cpp
|
||||||
|
qml.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(quickshell-network PRIVATE
|
target_include_directories(quickshell-network PRIVATE
|
||||||
|
|||||||
+13
-43
@@ -8,6 +8,8 @@
|
|||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "../core/logcat.hpp"
|
#include "../core/logcat.hpp"
|
||||||
|
#include "enums.hpp"
|
||||||
|
#include "network.hpp"
|
||||||
|
|
||||||
namespace qs::network {
|
namespace qs::network {
|
||||||
|
|
||||||
@@ -15,49 +17,9 @@ namespace {
|
|||||||
QS_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg);
|
||||||
} // namespace
|
} // 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) {
|
NetworkDevice::NetworkDevice(DeviceType::Enum type, QObject* parent): QObject(parent), mType(type) {
|
||||||
this->bindableConnected().setBinding([this]() {
|
this->bindableConnected().setBinding([this]() {
|
||||||
return this->bState == DeviceConnectionState::Connected;
|
return this->bState == ConnectionState::Connected;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,12 +28,17 @@ void NetworkDevice::setAutoconnect(bool autoconnect) {
|
|||||||
emit this->requestSetAutoconnect(autoconnect);
|
emit this->requestSetAutoconnect(autoconnect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetworkDevice::setNmManaged(bool managed) {
|
||||||
|
if (this->bNmManaged == managed) return;
|
||||||
|
emit this->requestSetNmManaged(managed);
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkDevice::disconnect() {
|
void NetworkDevice::disconnect() {
|
||||||
if (this->bState == DeviceConnectionState::Disconnected) {
|
if (this->bState == ConnectionState::Disconnected) {
|
||||||
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected";
|
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->bState == DeviceConnectionState::Disconnecting) {
|
if (this->bState == ConnectionState::Disconnecting) {
|
||||||
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting";
|
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,4 +46,7 @@ void NetworkDevice::disconnect() {
|
|||||||
this->requestDisconnect();
|
this->requestDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetworkDevice::networkAdded(Network* net) { this->mNetworks.insertObject(net); }
|
||||||
|
void NetworkDevice::networkRemoved(Network* net) { this->mNetworks.removeObject(net); }
|
||||||
|
|
||||||
} // namespace qs::network
|
} // namespace qs::network
|
||||||
+45
-79
@@ -6,88 +6,44 @@
|
|||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
#include "../core/doc.hpp"
|
||||||
|
#include "../core/model.hpp"
|
||||||
|
#include "enums.hpp"
|
||||||
|
#include "network.hpp"
|
||||||
|
|
||||||
namespace qs::network {
|
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.
|
///! A network device.
|
||||||
/// When @@type is `Wifi`, the device is a @@WifiDevice, which can be used to scan for and connect to access points.
|
/// The @@type property may be used to determine if this device is a @@WifiDevice or @@WiredDevice.
|
||||||
class NetworkDevice: public QObject {
|
class NetworkDevice: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
QML_UNCREATABLE("Devices can only be acquired through Network");
|
QML_UNCREATABLE("Devices can only be acquired through Network");
|
||||||
// clang-format off
|
// clang-format off
|
||||||
/// The device type.
|
/// The device type.
|
||||||
|
///
|
||||||
|
/// When the device type is `Wifi`, the device object is a @@WifiDevice.
|
||||||
|
/// When the device type is `Wired`, the device object is a @@WiredDevice.
|
||||||
|
/// connection and scanning.
|
||||||
Q_PROPERTY(DeviceType::Enum type READ type CONSTANT);
|
Q_PROPERTY(DeviceType::Enum type READ type CONSTANT);
|
||||||
/// The name of the device's control interface.
|
/// The name of the device's control interface.
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName);
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName);
|
||||||
|
/// A list of available or connected networks for this device.
|
||||||
|
///
|
||||||
|
/// When the device type is 'Wifi', this model will only contain @@WifiNetwork.
|
||||||
|
QSDOC_TYPE_OVERRIDE(ObjectModel<Network>*);
|
||||||
|
Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT);
|
||||||
/// The hardware address of the device in the XX:XX:XX:XX:XX:XX format.
|
/// 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);
|
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
|
||||||
/// True if the device is connected.
|
/// True if the device is connected.
|
||||||
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
|
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
|
||||||
/// Connection state of the device.
|
/// Connection state of the device.
|
||||||
Q_PROPERTY(qs::network::DeviceConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
|
Q_PROPERTY(qs::network::ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
|
||||||
/// A more specific device state when the backend is NetworkManager.
|
/// True if the device is managed by NetworkManager.
|
||||||
Q_PROPERTY(qs::network::NMDeviceState::Enum nmState READ default NOTIFY nmStateChanged BINDABLE bindableNmState);
|
///
|
||||||
/// True if the device is allowed to autoconnect.
|
/// > [!WARNING] Only valid for the NetworkManager backend.
|
||||||
|
Q_PROPERTY(bool nmManaged READ nmManaged WRITE setNmManaged NOTIFY nmManagedChanged)
|
||||||
|
/// True if the device is allowed to autoconnect to a network.
|
||||||
Q_PROPERTY(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged);
|
Q_PROPERTY(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
@@ -97,35 +53,45 @@ public:
|
|||||||
/// Disconnects the device and prevents it from automatically activating further connections.
|
/// Disconnects the device and prevents it from automatically activating further connections.
|
||||||
Q_INVOKABLE void disconnect();
|
Q_INVOKABLE void disconnect();
|
||||||
|
|
||||||
[[nodiscard]] DeviceType::Enum type() const { return this->mType; };
|
virtual void networkAdded(Network* net);
|
||||||
QBindable<QString> bindableName() { return &this->bName; };
|
virtual void networkRemoved(Network* net);
|
||||||
[[nodiscard]] QString name() const { return this->bName; };
|
|
||||||
QBindable<QString> bindableAddress() { return &this->bAddress; };
|
[[nodiscard]] ObjectModel<Network>* networks() { return &this->mNetworks; }
|
||||||
QBindable<bool> bindableConnected() { return &this->bConnected; };
|
[[nodiscard]] DeviceType::Enum type() const { return this->mType; }
|
||||||
QBindable<DeviceConnectionState::Enum> bindableState() { return &this->bState; };
|
QBindable<QString> bindableName() { return &this->bName; }
|
||||||
QBindable<NMDeviceState::Enum> bindableNmState() { return &this->bNmState; };
|
[[nodiscard]] QString name() const { return this->bName; }
|
||||||
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
|
QBindable<QString> bindableAddress() { return &this->bAddress; }
|
||||||
QBindable<bool> bindableAutoconnect() { return &this->bAutoconnect; };
|
QBindable<bool> bindableConnected() { return &this->bConnected; }
|
||||||
|
QBindable<ConnectionState::Enum> bindableState() { return &this->bState; }
|
||||||
|
QBindable<bool> bindableNmManaged() { return &this->bNmManaged; }
|
||||||
|
[[nodiscard]] bool nmManaged() { return this->bNmManaged; }
|
||||||
|
void setNmManaged(bool managed);
|
||||||
|
QBindable<bool> bindableAutoconnect() { return &this->bAutoconnect; }
|
||||||
|
[[nodiscard]] bool autoconnect() { return this->bAutoconnect; }
|
||||||
void setAutoconnect(bool autoconnect);
|
void setAutoconnect(bool autoconnect);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void requestDisconnect();
|
QSDOC_HIDE void requestDisconnect();
|
||||||
void requestSetAutoconnect(bool autoconnect);
|
QSDOC_HIDE void requestSetAutoconnect(bool autoconnect);
|
||||||
|
QSDOC_HIDE void requestSetNmManaged(bool managed);
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
void addressChanged();
|
void addressChanged();
|
||||||
void connectedChanged();
|
void connectedChanged();
|
||||||
void stateChanged();
|
void stateChanged();
|
||||||
void nmStateChanged();
|
void nmManagedChanged();
|
||||||
void autoconnectChanged();
|
void autoconnectChanged();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ObjectModel<Network> mNetworks {this};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DeviceType::Enum mType;
|
DeviceType::Enum mType;
|
||||||
// clang-format off
|
// clang-format off
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, DeviceConnectionState::Enum, bState, &NetworkDevice::stateChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, ConnectionState::Enum, bState, &NetworkDevice::stateChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NMDeviceState::Enum, bNmState, &NetworkDevice::nmStateChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bNmManaged, &NetworkDevice::nmManagedChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bAutoconnect, &NetworkDevice::autoconnectChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bAutoconnect, &NetworkDevice::autoconnectChanged);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#include "enums.hpp"
|
||||||
|
|
||||||
|
#include <qstring.h>
|
||||||
|
|
||||||
|
namespace qs::network {
|
||||||
|
|
||||||
|
QString NetworkConnectivity::toString(NetworkConnectivity::Enum conn) {
|
||||||
|
switch (conn) {
|
||||||
|
case Unknown: return QStringLiteral("Unknown");
|
||||||
|
case None: return QStringLiteral("Not connected to a network");
|
||||||
|
case Portal: return QStringLiteral("Connection intercepted by a captive portal");
|
||||||
|
case Limited: return QStringLiteral("Partial internet connectivity");
|
||||||
|
case Full: return QStringLiteral("Full internet connectivity");
|
||||||
|
default: return QStringLiteral("Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetworkBackendType::toString(NetworkBackendType::Enum type) {
|
||||||
|
switch (type) {
|
||||||
|
case NetworkBackendType::None: return "None";
|
||||||
|
case NetworkBackendType::NetworkManager: return "NetworkManager";
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ConnectionState::toString(ConnectionState::Enum state) {
|
||||||
|
switch (state) {
|
||||||
|
case Unknown: return QStringLiteral("Unknown");
|
||||||
|
case Connecting: return QStringLiteral("Connecting");
|
||||||
|
case Connected: return QStringLiteral("Connected");
|
||||||
|
case Disconnecting: return QStringLiteral("Disconnecting");
|
||||||
|
case Disconnected: return QStringLiteral("Disconnected");
|
||||||
|
default: return QStringLiteral("Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ConnectionFailReason::toString(ConnectionFailReason::Enum reason) {
|
||||||
|
switch (reason) {
|
||||||
|
case Unknown: return QStringLiteral("Unknown");
|
||||||
|
case NoSecrets: return QStringLiteral("Secrets were required but not provided");
|
||||||
|
case WifiClientDisconnected: return QStringLiteral("Wi-Fi supplicant disconnected");
|
||||||
|
case WifiClientFailed: return QStringLiteral("Wi-Fi supplicant failed");
|
||||||
|
case WifiAuthTimeout: return QStringLiteral("Wi-Fi connection took too long to authenticate");
|
||||||
|
case WifiNetworkLost: return QStringLiteral("Wi-Fi network could not be found");
|
||||||
|
default: return QStringLiteral("Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DeviceType::toString(DeviceType::Enum type) {
|
||||||
|
switch (type) {
|
||||||
|
case None: return QStringLiteral("None");
|
||||||
|
case Wifi: return QStringLiteral("Wifi");
|
||||||
|
case Wired: return QStringLiteral("Wired");
|
||||||
|
default: return QStringLiteral("Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WifiSecurityType::toString(WifiSecurityType::Enum type) {
|
||||||
|
switch (type) {
|
||||||
|
case Unknown: return QStringLiteral("Unknown");
|
||||||
|
case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit");
|
||||||
|
case Sae: return QStringLiteral("WPA3");
|
||||||
|
case Wpa2Eap: return QStringLiteral("WPA2 Enterprise");
|
||||||
|
case Wpa2Psk: return QStringLiteral("WPA2");
|
||||||
|
case WpaEap: return QStringLiteral("WPA Enterprise");
|
||||||
|
case WpaPsk: return QStringLiteral("WPA");
|
||||||
|
case StaticWep: return QStringLiteral("WEP");
|
||||||
|
case DynamicWep: return QStringLiteral("Dynamic WEP");
|
||||||
|
case Leap: return QStringLiteral("LEAP");
|
||||||
|
case Owe: return QStringLiteral("OWE");
|
||||||
|
case Open: return QStringLiteral("Open");
|
||||||
|
default: return QStringLiteral("Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case Unknown: return QStringLiteral("Unknown");
|
||||||
|
case AdHoc: return QStringLiteral("Ad-Hoc");
|
||||||
|
case Station: return QStringLiteral("Station");
|
||||||
|
case AccessPoint: return QStringLiteral("Access Point");
|
||||||
|
case Mesh: return QStringLiteral("Mesh");
|
||||||
|
default: return QStringLiteral("Unknown");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::network
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
namespace qs::network {
|
||||||
|
|
||||||
|
///! The degree to which the host can reach the internet.
|
||||||
|
class NetworkConnectivity: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
/// Network connectivity is unknown. This means the connectivity checks are disabled or have not run yet.
|
||||||
|
Unknown = 0,
|
||||||
|
/// The host is not connected to any network.
|
||||||
|
None = 1,
|
||||||
|
/// The internet connection is hijacked by a captive portal gateway.
|
||||||
|
/// This indicates the shell should open a sandboxed web browser window for the purpose of authenticating to a gateway.
|
||||||
|
Portal = 2,
|
||||||
|
/// The host is connected to a network but does not appear to be able to reach the full internet.
|
||||||
|
Limited = 3,
|
||||||
|
/// The host is connected to a network and appears to be able to reach the full internet.
|
||||||
|
Full = 4,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
Q_INVOKABLE static QString toString(NetworkConnectivity::Enum conn);
|
||||||
|
};
|
||||||
|
|
||||||
|
///! The backend supplying the Network service.
|
||||||
|
class NetworkBackendType: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
None = 0,
|
||||||
|
NetworkManager = 1,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
Q_INVOKABLE static QString toString(NetworkBackendType::Enum type);
|
||||||
|
};
|
||||||
|
|
||||||
|
///! The connection state of a device or network.
|
||||||
|
class ConnectionState: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
Unknown = 0,
|
||||||
|
Connecting = 1,
|
||||||
|
Connected = 2,
|
||||||
|
Disconnecting = 3,
|
||||||
|
Disconnected = 4,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
Q_INVOKABLE static QString toString(ConnectionState::Enum state);
|
||||||
|
};
|
||||||
|
|
||||||
|
///! The reason a connection failed.
|
||||||
|
class ConnectionFailReason: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
/// The connection failed for an unknown reason.
|
||||||
|
Unknown = 0,
|
||||||
|
/// Secrets were required, but not provided.
|
||||||
|
NoSecrets = 1,
|
||||||
|
/// The Wi-Fi supplicant disconnected.
|
||||||
|
WifiClientDisconnected = 2,
|
||||||
|
/// The Wi-Fi supplicant failed.
|
||||||
|
WifiClientFailed = 3,
|
||||||
|
/// The Wi-Fi connection took too long to authenticate.
|
||||||
|
WifiAuthTimeout = 4,
|
||||||
|
/// The Wi-Fi network could not be found.
|
||||||
|
WifiNetworkLost = 5,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
Q_INVOKABLE static QString toString(ConnectionFailReason::Enum reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
///! Type of a @@NetworkDevice.
|
||||||
|
class DeviceType: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
None = 0,
|
||||||
|
Wifi = 1,
|
||||||
|
Wired = 2,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
Q_INVOKABLE static QString toString(DeviceType::Enum type);
|
||||||
|
};
|
||||||
|
|
||||||
|
///! The security type of a @@WifiNetwork.
|
||||||
|
class WifiSecurityType: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
Wpa3SuiteB192 = 0,
|
||||||
|
Sae = 1,
|
||||||
|
Wpa2Eap = 2,
|
||||||
|
Wpa2Psk = 3,
|
||||||
|
WpaEap = 4,
|
||||||
|
WpaPsk = 5,
|
||||||
|
StaticWep = 6,
|
||||||
|
DynamicWep = 7,
|
||||||
|
Leap = 8,
|
||||||
|
Owe = 9,
|
||||||
|
Open = 10,
|
||||||
|
Unknown = 11,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
Q_INVOKABLE static QString toString(WifiSecurityType::Enum type);
|
||||||
|
};
|
||||||
|
|
||||||
|
///! The 802.11 mode of a @@WifiDevice.
|
||||||
|
class WifiDeviceMode: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
/// The device is part of an Ad-Hoc network without a central access point.
|
||||||
|
AdHoc = 0,
|
||||||
|
/// The device is a station that can connect to networks.
|
||||||
|
Station = 1,
|
||||||
|
/// The device is a local hotspot/access point.
|
||||||
|
AccessPoint = 2,
|
||||||
|
/// The device is an 802.11s mesh point.
|
||||||
|
Mesh = 3,
|
||||||
|
/// The device mode is unknown.
|
||||||
|
Unknown = 4,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::network
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
name = "Quickshell.Networking"
|
name = "Quickshell.Networking"
|
||||||
description = "Network API"
|
description = "Network API"
|
||||||
headers = [
|
headers = [
|
||||||
|
"qml.hpp",
|
||||||
"network.hpp",
|
"network.hpp",
|
||||||
"device.hpp",
|
"device.hpp",
|
||||||
"wifi.hpp",
|
"wifi.hpp",
|
||||||
|
"wired.hpp",
|
||||||
|
"enums.hpp",
|
||||||
|
"nm/settings.hpp",
|
||||||
]
|
]
|
||||||
-----
|
-----
|
||||||
This module exposes Network management APIs provided by a supported network backend.
|
This module exposes Network management APIs provided by a supported network backend.
|
||||||
|
|||||||
+48
-43
@@ -1,15 +1,15 @@
|
|||||||
#include "network.hpp"
|
#include "network.hpp"
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <qdebug.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qstring.h>
|
#include <qstring.h>
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "../core/logcat.hpp"
|
#include "../core/logcat.hpp"
|
||||||
#include "device.hpp"
|
#include "enums.hpp"
|
||||||
#include "nm/backend.hpp"
|
#include "nm/settings.hpp"
|
||||||
|
|
||||||
namespace qs::network {
|
namespace qs::network {
|
||||||
|
|
||||||
@@ -17,49 +17,54 @@ namespace {
|
|||||||
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
QString NetworkState::toString(NetworkState::Enum state) {
|
Network::Network(QString name, NetworkDevice* device, QObject* parent)
|
||||||
switch (state) {
|
: QObject(parent)
|
||||||
case NetworkState::Connecting: return QStringLiteral("Connecting");
|
, bName(std::move(name))
|
||||||
case NetworkState::Connected: return QStringLiteral("Connected");
|
, mDevice(device) {
|
||||||
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] {
|
this->bStateChanging.setBinding([this] {
|
||||||
auto state = this->bState.value();
|
auto state = this->bState.value();
|
||||||
return state == NetworkState::Connecting || state == NetworkState::Disconnecting;
|
return state == ConnectionState::Connecting || state == ConnectionState::Disconnecting;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void Network::connect() {
|
||||||
|
if (this->bConnected) {
|
||||||
|
qCCritical(logNetwork) << this << "is already connected.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->requestConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Network::connectWithSettings(NMSettings* settings) {
|
||||||
|
if (this->bConnected) {
|
||||||
|
qCCritical(logNetwork) << this << "is already connected.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->bNmSettings.value().indexOf(settings) == -1) return;
|
||||||
|
this->requestConnectWithSettings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Network::disconnect() {
|
||||||
|
if (!this->bConnected) {
|
||||||
|
qCCritical(logNetwork) << this << "is not currently connected";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->requestDisconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Network::forget() { this->requestForget(); }
|
||||||
|
|
||||||
|
void Network::settingsAdded(NMSettings* settings) {
|
||||||
|
auto list = this->bNmSettings.value();
|
||||||
|
if (list.contains(settings)) return;
|
||||||
|
list.append(settings);
|
||||||
|
this->bNmSettings = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Network::settingsRemoved(NMSettings* settings) {
|
||||||
|
auto list = this->bNmSettings.value();
|
||||||
|
list.removeOne(settings);
|
||||||
|
this->bNmSettings = list;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace qs::network
|
} // namespace qs::network
|
||||||
+66
-101
@@ -6,137 +6,102 @@
|
|||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../core/model.hpp"
|
#include "../core/doc.hpp"
|
||||||
#include "device.hpp"
|
#include "enums.hpp"
|
||||||
|
#include "nm/settings.hpp"
|
||||||
|
|
||||||
|
namespace qs::network {
|
||||||
|
class NetworkDevice;
|
||||||
|
}
|
||||||
|
|
||||||
namespace qs::network {
|
namespace qs::network {
|
||||||
|
|
||||||
///! The connection state of a Network.
|
|
||||||
class NetworkState: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_SINGLETON;
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum Enum : quint8 {
|
|
||||||
Unknown = 0,
|
|
||||||
Connecting = 1,
|
|
||||||
Connected = 2,
|
|
||||||
Disconnecting = 3,
|
|
||||||
Disconnected = 4,
|
|
||||||
};
|
|
||||||
Q_ENUM(Enum);
|
|
||||||
Q_INVOKABLE static QString toString(NetworkState::Enum state);
|
|
||||||
};
|
|
||||||
|
|
||||||
///! The backend supplying the Network service.
|
|
||||||
class NetworkBackendType: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_SINGLETON;
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum Enum : quint8 {
|
|
||||||
None = 0,
|
|
||||||
NetworkManager = 1,
|
|
||||||
};
|
|
||||||
Q_ENUM(Enum);
|
|
||||||
};
|
|
||||||
|
|
||||||
class NetworkBackend: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
[[nodiscard]] virtual bool isAvailable() const = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
|
|
||||||
};
|
|
||||||
|
|
||||||
///! The Network service.
|
|
||||||
/// An interface to a network backend (currently only NetworkManager),
|
|
||||||
/// which can be used to view, configure, and connect to various networks.
|
|
||||||
class Networking: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_SINGLETON;
|
|
||||||
QML_ELEMENT;
|
|
||||||
// clang-format off
|
|
||||||
/// A list of all network devices.
|
|
||||||
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::network::NetworkDevice>*);
|
|
||||||
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
|
|
||||||
/// The backend being used to power the Network service.
|
|
||||||
Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT);
|
|
||||||
/// Switch for the rfkill software block of all wireless devices.
|
|
||||||
Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged);
|
|
||||||
/// State of the rfkill hardware block of all wireless devices.
|
|
||||||
Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled);
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Networking(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
[[nodiscard]] ObjectModel<NetworkDevice>* devices() { return &this->mDevices; };
|
|
||||||
[[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; };
|
|
||||||
QBindable<bool> bindableWifiEnabled() { return &this->bWifiEnabled; };
|
|
||||||
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; };
|
|
||||||
void setWifiEnabled(bool enabled);
|
|
||||||
QBindable<bool> bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; };
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void requestSetWifiEnabled(bool enabled);
|
|
||||||
void wifiEnabledChanged();
|
|
||||||
void wifiHardwareEnabledChanged();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void deviceAdded(NetworkDevice* dev);
|
|
||||||
void deviceRemoved(NetworkDevice* dev);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ObjectModel<NetworkDevice> mDevices {this};
|
|
||||||
NetworkBackend* mBackend = nullptr;
|
|
||||||
NetworkBackendType::Enum mBackendType = NetworkBackendType::None;
|
|
||||||
// clang-format off
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged);
|
|
||||||
// clang-format on
|
|
||||||
};
|
|
||||||
|
|
||||||
///! A network.
|
///! A network.
|
||||||
|
/// A network. Networks derived from a @@WifiDevice are @@WifiNetwork instances.
|
||||||
class Network: public QObject {
|
class Network: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
QML_UNCREATABLE("BaseNetwork can only be aqcuired through network devices");
|
QML_UNCREATABLE("Network can only be aqcuired through networking devices");
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
/// The name of the network.
|
/// The name of the network.
|
||||||
Q_PROPERTY(QString name READ name CONSTANT);
|
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
|
||||||
|
/// The device this network belongs to.
|
||||||
|
Q_PROPERTY(NetworkDevice* device READ device CONSTANT);
|
||||||
|
/// A list of NetworkManager connection settings profiles for this network.
|
||||||
|
///
|
||||||
|
/// > [!WARNING] Only valid for the NetworkManager backend.
|
||||||
|
Q_PROPERTY(QList<NMSettings*> nmSettings READ default NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
|
||||||
/// True if the network is connected.
|
/// True if the network is connected.
|
||||||
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
|
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
|
||||||
|
/// True if the wifi network has known connection settings saved.
|
||||||
|
Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown);
|
||||||
/// The connectivity state of the network.
|
/// The connectivity state of the network.
|
||||||
Q_PROPERTY(NetworkState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
|
Q_PROPERTY(ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
|
||||||
/// If the network is currently connecting or disconnecting. Shorthand for checking @@state.
|
/// If the network is currently connecting or disconnecting. Shorthand for checking @@state.
|
||||||
Q_PROPERTY(bool stateChanging READ default NOTIFY stateChangingChanged BINDABLE bindableStateChanging);
|
Q_PROPERTY(bool stateChanging READ default NOTIFY stateChangingChanged BINDABLE bindableStateChanging);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Network(QString name, QObject* parent = nullptr);
|
explicit Network(QString name, NetworkDevice* device, QObject* parent = nullptr);
|
||||||
|
/// Attempt to connect to the network.
|
||||||
|
///
|
||||||
|
/// > [!NOTE] If the network is a @@WifiNetwork and requires secrets, a @@connectionFailed(s)
|
||||||
|
/// > signal will be emitted with `NoSecrets`.
|
||||||
|
/// > @@WifiNetwork.connectWithPsk() can be used to provide secrets.
|
||||||
|
Q_INVOKABLE void connect();
|
||||||
|
/// Attempt to connect to the network with a specific @@nmSettings entry.
|
||||||
|
///
|
||||||
|
/// > [!WARNING] Only valid for the NetworkManager backend.
|
||||||
|
Q_INVOKABLE void connectWithSettings(NMSettings* settings);
|
||||||
|
/// Disconnect from the network.
|
||||||
|
Q_INVOKABLE void disconnect();
|
||||||
|
/// Forget all connection settings for this network.
|
||||||
|
Q_INVOKABLE void forget();
|
||||||
|
|
||||||
[[nodiscard]] QString name() const { return this->mName; };
|
void settingsAdded(NMSettings* settings);
|
||||||
|
void settingsRemoved(NMSettings* settings);
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
[[nodiscard]] QString name() const { return this->bName; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
|
||||||
|
[[nodiscard]] NetworkDevice* device() const { return this->mDevice; }
|
||||||
|
[[nodiscard]] const QList<NMSettings*>& nmSettings() const { return this->bNmSettings; }
|
||||||
|
QBindable<QList<NMSettings*>> bindableNmSettings() const { return &this->bNmSettings; }
|
||||||
QBindable<bool> bindableConnected() { return &this->bConnected; }
|
QBindable<bool> bindableConnected() { return &this->bConnected; }
|
||||||
QBindable<NetworkState::Enum> bindableState() { return &this->bState; }
|
QBindable<bool> bindableKnown() { return &this->bKnown; }
|
||||||
|
[[nodiscard]] ConnectionState::Enum state() const { return this->bState; }
|
||||||
|
QBindable<ConnectionState::Enum> bindableState() { return &this->bState; }
|
||||||
QBindable<bool> bindableStateChanging() { return &this->bStateChanging; }
|
QBindable<bool> bindableStateChanging() { return &this->bStateChanging; }
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
/// Signals that a connection to the network has failed because of the given @@ConnectionFailReason.
|
||||||
|
void connectionFailed(ConnectionFailReason::Enum reason);
|
||||||
|
|
||||||
|
void nameChanged();
|
||||||
void connectedChanged();
|
void connectedChanged();
|
||||||
|
void knownChanged();
|
||||||
void stateChanged();
|
void stateChanged();
|
||||||
void stateChangingChanged();
|
void stateChangingChanged();
|
||||||
|
void nmSettingsChanged();
|
||||||
|
QSDOC_HIDE void requestConnect();
|
||||||
|
QSDOC_HIDE void requestConnectWithSettings(NMSettings* settings);
|
||||||
|
QSDOC_HIDE void requestDisconnect();
|
||||||
|
QSDOC_HIDE void requestForget();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QString mName;
|
// clang-format off
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(Network, QString, bName, &Network::nameChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Network, NetworkState::Enum, bState, &Network::stateChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bKnown, &Network::knownChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(Network, ConnectionState::Enum, bState, &Network::stateChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(Network, QList<NMSettings*>, bNmSettings, &Network::nmSettingsChanged);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
private:
|
||||||
|
NetworkDevice* mDevice;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace qs::network
|
} // namespace qs::network
|
||||||
@@ -29,6 +29,16 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES
|
|||||||
dbus_nm_wireless
|
dbus_nm_wireless
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_source_files_properties(org.freedesktop.NetworkManager.Device.Wired.xml PROPERTIES
|
||||||
|
CLASSNAME DBusNMWiredProxy
|
||||||
|
NO_NAMESPACE TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||||
|
org.freedesktop.NetworkManager.Device.Wired.xml
|
||||||
|
dbus_nm_wired
|
||||||
|
)
|
||||||
|
|
||||||
set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES
|
set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES
|
||||||
CLASSNAME DBusNMAccessPointProxy
|
CLASSNAME DBusNMAccessPointProxy
|
||||||
NO_NAMESPACE TRUE
|
NO_NAMESPACE TRUE
|
||||||
@@ -63,10 +73,14 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES
|
|||||||
qt_add_library(quickshell-network-nm STATIC
|
qt_add_library(quickshell-network-nm STATIC
|
||||||
backend.cpp
|
backend.cpp
|
||||||
device.cpp
|
device.cpp
|
||||||
connection.cpp
|
active_connection.cpp
|
||||||
|
settings.cpp
|
||||||
accesspoint.cpp
|
accesspoint.cpp
|
||||||
wireless.cpp
|
wireless.cpp
|
||||||
|
wired.cpp
|
||||||
utils.cpp
|
utils.cpp
|
||||||
|
dbus_types.cpp
|
||||||
|
network.cpp
|
||||||
enums.hpp
|
enums.hpp
|
||||||
${NM_DBUS_INTERFACES}
|
${NM_DBUS_INTERFACES}
|
||||||
)
|
)
|
||||||
@@ -77,3 +91,4 @@ target_include_directories(quickshell-network-nm PUBLIC
|
|||||||
|
|
||||||
target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus)
|
target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus)
|
||||||
qs_add_link_dependencies(quickshell-network-nm quickshell-dbus)
|
qs_add_link_dependencies(quickshell-network-nm quickshell-dbus)
|
||||||
|
qs_add_link_dependencies(quickshell-network-nm quickshell-network)
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../../dbus/properties.hpp"
|
#include "../../dbus/properties.hpp"
|
||||||
#include "../wifi.hpp"
|
#include "../enums.hpp"
|
||||||
#include "dbus_nm_accesspoint.h"
|
#include "dbus_nm_accesspoint.h"
|
||||||
#include "enums.hpp"
|
#include "enums.hpp"
|
||||||
|
|
||||||
@@ -48,14 +48,14 @@ public:
|
|||||||
[[nodiscard]] bool isValid() const;
|
[[nodiscard]] bool isValid() const;
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
[[nodiscard]] QString address() const;
|
[[nodiscard]] QString address() const;
|
||||||
[[nodiscard]] QByteArray ssid() const { return this->bSsid; };
|
[[nodiscard]] QByteArray ssid() const { return this->bSsid; }
|
||||||
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; };
|
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
|
||||||
[[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; };
|
[[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; }
|
||||||
[[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; };
|
[[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; }
|
||||||
[[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; };
|
[[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; }
|
||||||
[[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; };
|
[[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; }
|
||||||
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; };
|
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; }
|
||||||
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
|
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void loaded();
|
void loaded();
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#include "active_connection.hpp"
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qdbusconnection.h>
|
||||||
|
#include <qdbuspendingcall.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qloggingcategory.h>
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qstring.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
#include "../../core/logcat.hpp"
|
||||||
|
#include "../../dbus/properties.hpp"
|
||||||
|
#include "dbus_nm_active_connection.h"
|
||||||
|
#include "enums.hpp"
|
||||||
|
|
||||||
|
namespace qs::network {
|
||||||
|
using namespace qs::dbus;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QObject(parent) {
|
||||||
|
this->proxy = new DBusNMActiveConnectionProxy(
|
||||||
|
"org.freedesktop.NetworkManager",
|
||||||
|
path,
|
||||||
|
QDBusConnection::systemBus(),
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this->proxy->isValid()) {
|
||||||
|
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
QObject::connect(&this->activeConnectionProperties, &DBusPropertyGroup::getAllFinished, this, &NMActiveConnection::loaded, Qt::SingleShotConnection);
|
||||||
|
QObject::connect(this->proxy, &DBusNMActiveConnectionProxy::StateChanged, this, &NMActiveConnection::onStateChanged);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
this->activeConnectionProperties.setInterface(this->proxy);
|
||||||
|
this->activeConnectionProperties.updateAllViaGetAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) {
|
||||||
|
auto enumReason = static_cast<NMConnectionStateReason::Enum>(reason);
|
||||||
|
if (this->bStateReason == enumReason) return;
|
||||||
|
this->bStateReason = enumReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NMActiveConnection::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||||
|
QString NMActiveConnection::address() const {
|
||||||
|
return this->proxy ? this->proxy->service() : QString();
|
||||||
|
}
|
||||||
|
QString NMActiveConnection::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||||
|
|
||||||
|
} // namespace qs::network
|
||||||
|
|
||||||
|
namespace qs::dbus {
|
||||||
|
|
||||||
|
DBusResult<qs::network::NMConnectionState::Enum>
|
||||||
|
DBusDataTransform<qs::network::NMConnectionState::Enum>::fromWire(quint32 wire) {
|
||||||
|
return DBusResult(static_cast<qs::network::NMConnectionState::Enum>(wire));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::dbus
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <qbytearray.h>
|
||||||
#include <qdbusextratypes.h>
|
#include <qdbusextratypes.h>
|
||||||
#include <qdbuspendingcall.h>
|
#include <qdbuspendingcall.h>
|
||||||
#include <qdbuspendingreply.h>
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qproperty.h>
|
#include <qproperty.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qqmlintegration.h>
|
||||||
|
#include <qstringlist.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../../dbus/properties.hpp"
|
#include "../../dbus/properties.hpp"
|
||||||
#include "../wifi.hpp"
|
|
||||||
#include "dbus_nm_active_connection.h"
|
#include "dbus_nm_active_connection.h"
|
||||||
#include "dbus_nm_connection_settings.h"
|
|
||||||
#include "dbus_types.hpp"
|
|
||||||
#include "enums.hpp"
|
#include "enums.hpp"
|
||||||
|
|
||||||
namespace qs::dbus {
|
namespace qs::dbus {
|
||||||
@@ -28,40 +26,6 @@ struct DBusDataTransform<qs::network::NMConnectionState::Enum> {
|
|||||||
|
|
||||||
namespace qs::network {
|
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.
|
// Proxy of a /org/freedesktop/NetworkManager/ActiveConnection/* object.
|
||||||
class NMActiveConnection: public QObject {
|
class NMActiveConnection: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
@@ -72,31 +36,27 @@ public:
|
|||||||
[[nodiscard]] bool isValid() const;
|
[[nodiscard]] bool isValid() const;
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
[[nodiscard]] QString address() const;
|
[[nodiscard]] QString address() const;
|
||||||
[[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; };
|
[[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; }
|
||||||
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; };
|
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
|
||||||
[[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->mStateReason; };
|
[[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->bStateReason; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void loaded();
|
void loaded();
|
||||||
void connectionChanged(QDBusObjectPath path);
|
void connectionChanged(QDBusObjectPath path);
|
||||||
void stateChanged(NMConnectionState::Enum state);
|
void stateChanged(NMConnectionState::Enum state);
|
||||||
void stateReasonChanged(NMConnectionStateReason::Enum reason);
|
void stateReasonChanged(NMConnectionStateReason::Enum reason);
|
||||||
void uuidChanged(const QString& uuid);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onStateChanged(quint32 state, quint32 reason);
|
void onStateChanged(quint32 state, quint32 reason);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NMConnectionStateReason::Enum mStateReason = NMConnectionStateReason::Unknown;
|
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QDBusObjectPath, bConnection, &NMActiveConnection::connectionChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QDBusObjectPath, bConnection, &NMActiveConnection::connectionChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QString, bUuid, &NMActiveConnection::uuidChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionState::Enum, bState, &NMActiveConnection::stateChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionState::Enum, bState, &NMActiveConnection::stateChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionStateReason::Enum, bStateReason, &NMActiveConnection::stateReasonChanged);
|
||||||
|
|
||||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMActiveConnection, activeConnectionProperties);
|
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMActiveConnection, activeConnectionProperties);
|
||||||
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pConnection, bConnection, activeConnectionProperties, "Connection");
|
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");
|
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pState, bState, activeConnectionProperties, "State");
|
||||||
// clang-format on
|
// clang-format on
|
||||||
DBusNMActiveConnectionProxy* proxy = nullptr;
|
DBusNMActiveConnectionProxy* proxy = nullptr;
|
||||||
+47
-73
@@ -1,5 +1,6 @@
|
|||||||
#include "backend.hpp"
|
#include "backend.hpp"
|
||||||
|
|
||||||
|
#include <qbytearray.h>
|
||||||
#include <qdbusconnection.h>
|
#include <qdbusconnection.h>
|
||||||
#include <qdbusextratypes.h>
|
#include <qdbusextratypes.h>
|
||||||
#include <qdbusmetatype.h>
|
#include <qdbusmetatype.h>
|
||||||
@@ -14,14 +15,13 @@
|
|||||||
|
|
||||||
#include "../../core/logcat.hpp"
|
#include "../../core/logcat.hpp"
|
||||||
#include "../../dbus/properties.hpp"
|
#include "../../dbus/properties.hpp"
|
||||||
#include "../device.hpp"
|
#include "../qml.hpp"
|
||||||
#include "../network.hpp"
|
|
||||||
#include "../wifi.hpp"
|
|
||||||
#include "dbus_nm_backend.h"
|
#include "dbus_nm_backend.h"
|
||||||
#include "dbus_nm_device.h"
|
#include "dbus_nm_device.h"
|
||||||
#include "dbus_types.hpp"
|
#include "dbus_types.hpp"
|
||||||
#include "device.hpp"
|
#include "device.hpp"
|
||||||
#include "enums.hpp"
|
#include "enums.hpp"
|
||||||
|
#include "wired.hpp"
|
||||||
#include "wireless.hpp"
|
#include "wireless.hpp"
|
||||||
|
|
||||||
namespace qs::network {
|
namespace qs::network {
|
||||||
@@ -31,7 +31,8 @@ QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWa
|
|||||||
}
|
}
|
||||||
|
|
||||||
NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
|
NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
|
||||||
qDBusRegisterMetaType<ConnectionSettingsMap>();
|
qCDebug(logNetworkManager) << "Connecting to NetworkManager";
|
||||||
|
qDBusRegisterMetaType<NMSettingsMap>();
|
||||||
|
|
||||||
auto bus = QDBusConnection::systemBus();
|
auto bus = QDBusConnection::systemBus();
|
||||||
if (!bus.isConnected()) {
|
if (!bus.isConnected()) {
|
||||||
@@ -69,6 +70,23 @@ void NetworkManager::init() {
|
|||||||
this->registerDevices();
|
this->registerDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetworkManager::checkConnectivity() {
|
||||||
|
auto pending = this->proxy->CheckConnectivity();
|
||||||
|
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||||
|
|
||||||
|
auto responseCallback = [](QDBusPendingCallWatcher* call) {
|
||||||
|
const QDBusPendingReply<quint32> reply = *call;
|
||||||
|
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCInfo(logNetworkManager) << "Failed to check connectivity: " << reply.error().message();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete call;
|
||||||
|
};
|
||||||
|
|
||||||
|
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkManager::registerDevices() {
|
void NetworkManager::registerDevices() {
|
||||||
auto pending = this->proxy->GetAllDevices();
|
auto pending = this->proxy->GetAllDevices();
|
||||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||||
@@ -113,27 +131,27 @@ void NetworkManager::registerDevice(const QString& path) {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break;
|
case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break;
|
||||||
|
case NMDeviceType::Ethernet: dev = new NMWiredDevice(path); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev) {
|
if (dev) {
|
||||||
|
qCDebug(logNetworkManager) << "Device added:" << path;
|
||||||
if (!dev->isValid()) {
|
if (!dev->isValid()) {
|
||||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
||||||
delete dev;
|
delete dev;
|
||||||
} else {
|
} else {
|
||||||
this->mDevices[path] = dev;
|
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
|
// clang-format off
|
||||||
QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
|
QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
|
||||||
QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection);
|
QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection);
|
||||||
QObject::connect(dev, &NMDevice::managedChanged, this, onManagedChanged);
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
QObject::connect(dev, &NMDevice::loaded, this, [this, dev]() {
|
||||||
if (dev->managed()) this->registerFrontendDevice(type, dev);
|
emit this->deviceAdded(dev->frontend());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
qCDebug(logNetworkManager) << "Ignoring registration of unsupported device:" << path;
|
||||||
}
|
}
|
||||||
temp->deleteLater();
|
temp->deleteLater();
|
||||||
}
|
}
|
||||||
@@ -142,66 +160,6 @@ void NetworkManager::registerDevice(const QString& path) {
|
|||||||
qs::dbus::asyncReadProperty<uint>(*temp, "DeviceType", callback);
|
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) {
|
void NetworkManager::onDevicePathAdded(const QDBusObjectPath& path) {
|
||||||
this->registerDevice(path.path());
|
this->registerDevice(path.path());
|
||||||
}
|
}
|
||||||
@@ -215,7 +173,8 @@ void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
|
|||||||
auto* dev = iter.value();
|
auto* dev = iter.value();
|
||||||
this->mDevices.erase(iter);
|
this->mDevices.erase(iter);
|
||||||
if (dev) {
|
if (dev) {
|
||||||
this->removeFrontendDevice(dev);
|
qCDebug(logNetworkManager) << "Device removed:" << path.path();
|
||||||
|
emit this->deviceRemoved(dev->frontend());
|
||||||
delete dev;
|
delete dev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,7 +199,7 @@ void NetworkManager::activateConnection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NetworkManager::addAndActivateConnection(
|
void NetworkManager::addAndActivateConnection(
|
||||||
const ConnectionSettingsMap& settings,
|
const NMSettingsMap& settings,
|
||||||
const QDBusObjectPath& devPath,
|
const QDBusObjectPath& devPath,
|
||||||
const QDBusObjectPath& specificObjectPath
|
const QDBusObjectPath& specificObjectPath
|
||||||
) {
|
) {
|
||||||
@@ -259,6 +218,12 @@ void NetworkManager::addAndActivateConnection(
|
|||||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetworkManager::setConnectivityCheckEnabled(bool enabled) {
|
||||||
|
if (enabled == this->bConnectivityCheckEnabled) return;
|
||||||
|
this->bConnectivityCheckEnabled = enabled;
|
||||||
|
this->pConnectivityCheckEnabled.write();
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkManager::setWifiEnabled(bool enabled) {
|
void NetworkManager::setWifiEnabled(bool enabled) {
|
||||||
if (enabled == this->bWifiEnabled) return;
|
if (enabled == this->bWifiEnabled) return;
|
||||||
this->bWifiEnabled = enabled;
|
this->bWifiEnabled = enabled;
|
||||||
@@ -268,3 +233,12 @@ void NetworkManager::setWifiEnabled(bool enabled) {
|
|||||||
bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); };
|
bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); };
|
||||||
|
|
||||||
} // namespace qs::network
|
} // namespace qs::network
|
||||||
|
|
||||||
|
namespace qs::dbus {
|
||||||
|
|
||||||
|
DBusResult<qs::network::NMConnectivityState::Enum>
|
||||||
|
DBusDataTransform<qs::network::NMConnectivityState::Enum>::fromWire(quint32 wire) {
|
||||||
|
return DBusResult(static_cast<qs::network::NMConnectivityState::Enum>(wire));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::dbus
|
||||||
@@ -8,9 +8,22 @@
|
|||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../../dbus/properties.hpp"
|
#include "../../dbus/properties.hpp"
|
||||||
#include "../network.hpp"
|
#include "../qml.hpp"
|
||||||
#include "dbus_nm_backend.h"
|
#include "dbus_nm_backend.h"
|
||||||
|
#include "dbus_types.hpp"
|
||||||
#include "device.hpp"
|
#include "device.hpp"
|
||||||
|
#include "enums.hpp"
|
||||||
|
|
||||||
|
namespace qs::dbus {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct DBusDataTransform<qs::network::NMConnectivityState::Enum> {
|
||||||
|
using Wire = quint32;
|
||||||
|
using Data = qs::network::NMConnectivityState::Enum;
|
||||||
|
static DBusResult<Data> fromWire(Wire wire);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::dbus
|
||||||
|
|
||||||
namespace qs::network {
|
namespace qs::network {
|
||||||
|
|
||||||
@@ -21,24 +34,34 @@ public:
|
|||||||
explicit NetworkManager(QObject* parent = nullptr);
|
explicit NetworkManager(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] bool isAvailable() const override;
|
[[nodiscard]] bool isAvailable() const override;
|
||||||
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; };
|
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }
|
||||||
[[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; };
|
[[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; }
|
||||||
|
[[nodiscard]] bool connectivityCheckAvailable() const {
|
||||||
|
return this->bConnectivityCheckAvailable;
|
||||||
|
};
|
||||||
|
[[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; }
|
||||||
|
[[nodiscard]] NMConnectivityState::Enum connectivity() const { return this->bConnectivity; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void deviceAdded(NetworkDevice* device);
|
void deviceAdded(NetworkDevice* device);
|
||||||
void deviceRemoved(NetworkDevice* device);
|
void deviceRemoved(NetworkDevice* device);
|
||||||
void wifiEnabledChanged(bool enabled);
|
void wifiEnabledChanged(bool enabled);
|
||||||
void wifiHardwareEnabledChanged(bool enabled);
|
void wifiHardwareEnabledChanged(bool enabled);
|
||||||
|
void connectivityStateChanged(NMConnectivityState::Enum state);
|
||||||
|
void connectivityCheckAvailableChanged(bool available);
|
||||||
|
void connectivityCheckEnabledChanged(bool enabled);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setWifiEnabled(bool enabled);
|
void setWifiEnabled(bool enabled);
|
||||||
|
void setConnectivityCheckEnabled(bool enabled);
|
||||||
|
void checkConnectivity();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onDevicePathAdded(const QDBusObjectPath& path);
|
void onDevicePathAdded(const QDBusObjectPath& path);
|
||||||
void onDevicePathRemoved(const QDBusObjectPath& path);
|
void onDevicePathRemoved(const QDBusObjectPath& path);
|
||||||
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
|
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
|
||||||
void addAndActivateConnection(
|
void addAndActivateConnection(
|
||||||
const ConnectionSettingsMap& settings,
|
const NMSettingsMap& settings,
|
||||||
const QDBusObjectPath& devPath,
|
const QDBusObjectPath& devPath,
|
||||||
const QDBusObjectPath& specificObjectPath
|
const QDBusObjectPath& specificObjectPath
|
||||||
);
|
);
|
||||||
@@ -47,19 +70,22 @@ private:
|
|||||||
void init();
|
void init();
|
||||||
void registerDevices();
|
void registerDevices();
|
||||||
void registerDevice(const QString& path);
|
void registerDevice(const QString& path);
|
||||||
void registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev);
|
|
||||||
void removeFrontendDevice(NMDevice* dev);
|
|
||||||
|
|
||||||
QHash<QString, NMDevice*> mDevices;
|
QHash<QString, NMDevice*> mDevices;
|
||||||
QHash<QString, NetworkDevice*> mFrontendDevices;
|
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiHardwareEnabled, &NetworkManager::wifiHardwareEnabledChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiHardwareEnabled, &NetworkManager::wifiHardwareEnabledChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, NMConnectivityState::Enum, bConnectivity, &NetworkManager::connectivityStateChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bConnectivityCheckAvailable, &NetworkManager::connectivityCheckAvailableChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bConnectivityCheckEnabled, &NetworkManager::connectivityCheckEnabledChanged);
|
||||||
|
|
||||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties);
|
QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties);
|
||||||
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiEnabled, bWifiEnabled, dbusProperties, "WirelessEnabled");
|
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiEnabled, bWifiEnabled, dbusProperties, "WirelessEnabled");
|
||||||
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiHardwareEnabled, bWifiHardwareEnabled, dbusProperties, "WirelessHardwareEnabled");
|
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiHardwareEnabled, bWifiHardwareEnabled, dbusProperties, "WirelessHardwareEnabled");
|
||||||
|
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivity, bConnectivity, dbusProperties, "Connectivity");
|
||||||
|
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckAvailable, bConnectivityCheckAvailable, dbusProperties, "ConnectivityCheckAvailable");
|
||||||
|
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckEnabled, bConnectivityCheckEnabled, dbusProperties, "ConnectivityCheckEnabled");
|
||||||
// clang-format on
|
// clang-format on
|
||||||
DBusNetworkManagerProxy* proxy = nullptr;
|
DBusNetworkManagerProxy* proxy = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
#include "connection.hpp"
|
|
||||||
|
|
||||||
#include <qdbusconnection.h>
|
|
||||||
#include <qdbusmetatype.h>
|
|
||||||
#include <qdbuspendingcall.h>
|
|
||||||
#include <qdbuspendingreply.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qstring.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
#include "../../core/logcat.hpp"
|
|
||||||
#include "../../dbus/properties.hpp"
|
|
||||||
#include "../wifi.hpp"
|
|
||||||
#include "dbus_nm_active_connection.h"
|
|
||||||
#include "dbus_nm_connection_settings.h"
|
|
||||||
#include "dbus_types.hpp"
|
|
||||||
#include "enums.hpp"
|
|
||||||
#include "utils.hpp"
|
|
||||||
|
|
||||||
namespace qs::network {
|
|
||||||
using namespace qs::dbus;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
NMConnectionSettings::NMConnectionSettings(const QString& path, QObject* parent): QObject(parent) {
|
|
||||||
qDBusRegisterMetaType<ConnectionSettingsMap>();
|
|
||||||
|
|
||||||
this->proxy = new DBusNMConnectionSettingsProxy(
|
|
||||||
"org.freedesktop.NetworkManager",
|
|
||||||
path,
|
|
||||||
QDBusConnection::systemBus(),
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!this->proxy->isValid()) {
|
|
||||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
this->proxy,
|
|
||||||
&DBusNMConnectionSettingsProxy::Updated,
|
|
||||||
this,
|
|
||||||
&NMConnectionSettings::updateSettings
|
|
||||||
);
|
|
||||||
this->bSecurity.setBinding([this]() { return securityFromConnectionSettings(this->bSettings); });
|
|
||||||
|
|
||||||
this->connectionSettingsProperties.setInterface(this->proxy);
|
|
||||||
this->connectionSettingsProperties.updateAllViaGetAll();
|
|
||||||
|
|
||||||
this->updateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NMConnectionSettings::updateSettings() {
|
|
||||||
auto pending = this->proxy->GetSettings();
|
|
||||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
|
||||||
|
|
||||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
|
||||||
const QDBusPendingReply<ConnectionSettingsMap> reply = *call;
|
|
||||||
|
|
||||||
if (reply.isError()) {
|
|
||||||
qCWarning(logNetworkManager)
|
|
||||||
<< "Failed to get" << this->path() << "settings:" << reply.error().message();
|
|
||||||
} else {
|
|
||||||
this->bSettings = reply.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->mLoaded) {
|
|
||||||
emit this->loaded();
|
|
||||||
this->mLoaded = true;
|
|
||||||
}
|
|
||||||
delete call;
|
|
||||||
};
|
|
||||||
|
|
||||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NMConnectionSettings::forget() {
|
|
||||||
auto pending = this->proxy->Delete();
|
|
||||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
|
||||||
|
|
||||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
|
||||||
const QDBusPendingReply<> reply = *call;
|
|
||||||
|
|
||||||
if (reply.isError()) {
|
|
||||||
qCWarning(logNetworkManager)
|
|
||||||
<< "Failed to forget" << this->path() << ":" << reply.error().message();
|
|
||||||
}
|
|
||||||
delete call;
|
|
||||||
};
|
|
||||||
|
|
||||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NMConnectionSettings::isValid() const { return this->proxy && this->proxy->isValid(); }
|
|
||||||
QString NMConnectionSettings::address() const {
|
|
||||||
return this->proxy ? this->proxy->service() : QString();
|
|
||||||
}
|
|
||||||
QString NMConnectionSettings::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
|
||||||
|
|
||||||
NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QObject(parent) {
|
|
||||||
this->proxy = new DBusNMActiveConnectionProxy(
|
|
||||||
"org.freedesktop.NetworkManager",
|
|
||||||
path,
|
|
||||||
QDBusConnection::systemBus(),
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!this->proxy->isValid()) {
|
|
||||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
QObject::connect(&this->activeConnectionProperties, &DBusPropertyGroup::getAllFinished, this, &NMActiveConnection::loaded, Qt::SingleShotConnection);
|
|
||||||
QObject::connect(this->proxy, &DBusNMActiveConnectionProxy::StateChanged, this, &NMActiveConnection::onStateChanged);
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
this->activeConnectionProperties.setInterface(this->proxy);
|
|
||||||
this->activeConnectionProperties.updateAllViaGetAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) {
|
|
||||||
auto enumReason = static_cast<NMConnectionStateReason::Enum>(reason);
|
|
||||||
if (this->mStateReason == enumReason) return;
|
|
||||||
this->mStateReason = enumReason;
|
|
||||||
emit this->stateReasonChanged(enumReason);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NMActiveConnection::isValid() const { return this->proxy && this->proxy->isValid(); }
|
|
||||||
QString NMActiveConnection::address() const {
|
|
||||||
return this->proxy ? this->proxy->service() : QString();
|
|
||||||
}
|
|
||||||
QString NMActiveConnection::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
|
||||||
|
|
||||||
} // namespace qs::network
|
|
||||||
|
|
||||||
namespace qs::dbus {
|
|
||||||
|
|
||||||
DBusResult<qs::network::NMConnectionState::Enum>
|
|
||||||
DBusDataTransform<qs::network::NMConnectionState::Enum>::fromWire(quint32 wire) {
|
|
||||||
return DBusResult(static_cast<qs::network::NMConnectionState::Enum>(wire));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::dbus
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#include "dbus_types.hpp"
|
||||||
|
|
||||||
|
#include <qbytearray.h>
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qdbusargument.h>
|
||||||
|
#include <qmap.h>
|
||||||
|
#include <qmetatype.h>
|
||||||
|
#include <qstring.h>
|
||||||
|
|
||||||
|
namespace qs::network {
|
||||||
|
|
||||||
|
const QDBusArgument& operator>>(const QDBusArgument& argument, NMSettingsMap& map) {
|
||||||
|
argument.beginMap();
|
||||||
|
while (!argument.atEnd()) {
|
||||||
|
argument.beginMapEntry();
|
||||||
|
QString groupName;
|
||||||
|
argument >> groupName;
|
||||||
|
|
||||||
|
QVariantMap group;
|
||||||
|
argument >> group;
|
||||||
|
|
||||||
|
map.insert(groupName, group);
|
||||||
|
argument.endMapEntry();
|
||||||
|
}
|
||||||
|
argument.endMap();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QDBusArgument& operator<<(QDBusArgument& argument, const NMSettingsMap& map) {
|
||||||
|
argument.beginMap(qMetaTypeId<QString>(), qMetaTypeId<QVariantMap>());
|
||||||
|
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
|
||||||
|
argument.beginMapEntry();
|
||||||
|
argument << it.key();
|
||||||
|
argument << it.value();
|
||||||
|
argument.endMapEntry();
|
||||||
|
}
|
||||||
|
argument.endMap();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QDBusArgument& operator>>(const QDBusArgument& argument, NMIPv6Address& addr) {
|
||||||
|
argument.beginStructure();
|
||||||
|
argument >> addr.address >> addr.prefix >> addr.gateway;
|
||||||
|
argument.endStructure();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QDBusArgument& operator<<(QDBusArgument& argument, const NMIPv6Address& addr) {
|
||||||
|
argument.beginStructure();
|
||||||
|
argument << addr.address << addr.prefix << addr.gateway;
|
||||||
|
argument.endStructure();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QDBusArgument& operator>>(const QDBusArgument& argument, NMIPv6Route& route) {
|
||||||
|
argument.beginStructure();
|
||||||
|
argument >> route.destination >> route.prefix >> route.nexthop >> route.metric;
|
||||||
|
argument.endStructure();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QDBusArgument& operator<<(QDBusArgument& argument, const NMIPv6Route& route) {
|
||||||
|
argument.beginStructure();
|
||||||
|
argument << route.destination << route.prefix << route.nexthop << route.metric;
|
||||||
|
argument.endStructure();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::network
|
||||||
@@ -1,9 +1,40 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qdbusextratypes.h>
|
#include <qbytearray.h>
|
||||||
|
#include <qdbusargument.h>
|
||||||
#include <qmap.h>
|
#include <qmap.h>
|
||||||
#include <qstring.h>
|
#include <qstring.h>
|
||||||
|
#include <qtypes.h>
|
||||||
#include <qvariant.h>
|
#include <qvariant.h>
|
||||||
|
|
||||||
using ConnectionSettingsMap = QMap<QString, QVariantMap>;
|
namespace qs::network {
|
||||||
Q_DECLARE_METATYPE(ConnectionSettingsMap);
|
|
||||||
|
using NMSettingsMap = QMap<QString, QVariantMap>;
|
||||||
|
|
||||||
|
const QDBusArgument& operator>>(const QDBusArgument& argument, NMSettingsMap& map);
|
||||||
|
const QDBusArgument& operator<<(QDBusArgument& argument, const NMSettingsMap& map);
|
||||||
|
|
||||||
|
struct NMIPv6Address {
|
||||||
|
QByteArray address;
|
||||||
|
quint32 prefix = 0;
|
||||||
|
QByteArray gateway;
|
||||||
|
};
|
||||||
|
|
||||||
|
const QDBusArgument& operator>>(const QDBusArgument& argument, qs::network::NMIPv6Address& addr);
|
||||||
|
const QDBusArgument& operator<<(QDBusArgument& argument, const qs::network::NMIPv6Address& addr);
|
||||||
|
|
||||||
|
struct NMIPv6Route {
|
||||||
|
QByteArray destination;
|
||||||
|
quint32 prefix = 0;
|
||||||
|
QByteArray nexthop;
|
||||||
|
quint32 metric = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const QDBusArgument& operator>>(const QDBusArgument& argument, qs::network::NMIPv6Route& route);
|
||||||
|
const QDBusArgument& operator<<(QDBusArgument& argument, const qs::network::NMIPv6Route& route);
|
||||||
|
|
||||||
|
} // namespace qs::network
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(qs::network::NMSettingsMap);
|
||||||
|
Q_DECLARE_METATYPE(qs::network::NMIPv6Address);
|
||||||
|
Q_DECLARE_METATYPE(qs::network::NMIPv6Route);
|
||||||
+86
-18
@@ -15,8 +15,13 @@
|
|||||||
#include "../../core/logcat.hpp"
|
#include "../../core/logcat.hpp"
|
||||||
#include "../../dbus/properties.hpp"
|
#include "../../dbus/properties.hpp"
|
||||||
#include "../device.hpp"
|
#include "../device.hpp"
|
||||||
#include "connection.hpp"
|
#include "../enums.hpp"
|
||||||
|
#include "active_connection.hpp"
|
||||||
#include "dbus_nm_device.h"
|
#include "dbus_nm_device.h"
|
||||||
|
#include "dbus_types.hpp"
|
||||||
|
#include "enums.hpp"
|
||||||
|
#include "network.hpp"
|
||||||
|
#include "settings.hpp"
|
||||||
|
|
||||||
namespace qs::network {
|
namespace qs::network {
|
||||||
using namespace qs::dbus;
|
using namespace qs::dbus;
|
||||||
@@ -39,19 +44,68 @@ NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
QObject::connect(this, &NMDevice::availableConnectionPathsChanged, this, &NMDevice::onAvailableConnectionPathsChanged);
|
QObject::connect(this, &NMDevice::availableSettingsPathsChanged, this, &NMDevice::onAvailableSettingsPathsChanged);
|
||||||
QObject::connect(this, &NMDevice::activeConnectionPathChanged, this, &NMDevice::onActiveConnectionPathChanged);
|
QObject::connect(this, &NMDevice::activeConnectionPathChanged, this, &NMDevice::onActiveConnectionPathChanged);
|
||||||
|
QObject::connect(this->deviceProxy, &DBusNMDeviceProxy::StateChanged, this, &NMDevice::onStateChanged);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
this->deviceProperties.setInterface(this->deviceProxy);
|
this->deviceProperties.setInterface(this->deviceProxy);
|
||||||
this->deviceProperties.updateAllViaGetAll();
|
this->deviceProperties.updateAllViaGetAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NMDevice::bindFrontend(NetworkDevice* frontend) {
|
||||||
|
auto translateState = [this]() {
|
||||||
|
switch (this->state()) {
|
||||||
|
case 0 ... 20: return ConnectionState::Unknown;
|
||||||
|
case 30: return ConnectionState::Disconnected;
|
||||||
|
case 40 ... 90: return ConnectionState::Connecting;
|
||||||
|
case 100: return ConnectionState::Connected;
|
||||||
|
case 110 ... 120: return ConnectionState::Disconnecting;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// clang-format off
|
||||||
|
frontend->bindableName().setBinding([this]() { return this->interface(); });
|
||||||
|
frontend->bindableAddress().setBinding([this]() { return this->hwAddress(); });
|
||||||
|
frontend->bindableState().setBinding(translateState);
|
||||||
|
frontend->bindableAutoconnect().setBinding([this]() { return this->autoconnect(); });
|
||||||
|
frontend->bindableNmManaged().setBinding([this]() { return this->managed(); });
|
||||||
|
QObject::connect(frontend, &NetworkDevice::requestDisconnect, this, &NMDevice::disconnect);
|
||||||
|
QObject::connect(frontend, &NetworkDevice::requestSetAutoconnect, this, &NMDevice::setAutoconnect);
|
||||||
|
QObject::connect(frontend, &NetworkDevice::requestSetNmManaged, this, &NMDevice::setManaged);
|
||||||
|
QObject::connect(this, &NMDevice::networkAdded, frontend, &NetworkDevice::networkAdded);
|
||||||
|
QObject::connect(this, &NMDevice::networkRemoved, frontend, &NetworkDevice::networkRemoved);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 reason) {
|
||||||
|
auto enumReason = static_cast<NMDeviceStateReason::Enum>(reason);
|
||||||
|
auto enumNewState = static_cast<NMDeviceState::Enum>(newState);
|
||||||
|
if (enumNewState == NMDeviceState::Failed) this->bLastFailReason = enumReason;
|
||||||
|
if (this->bStateReason == enumReason) return;
|
||||||
|
this->bStateReason = enumReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMDevice::bindNetwork(NMNetwork* net) {
|
||||||
|
net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); });
|
||||||
|
QObject::connect(net, &NMNetwork::requestDisconnect, this, &NMDevice::disconnect);
|
||||||
|
QObject::connect(net, &NMNetwork::requestActivateConnection, this, [this](const QString& settingsPath){
|
||||||
|
emit this->activateConnection(QDBusObjectPath(settingsPath), QDBusObjectPath(this->path()));
|
||||||
|
});
|
||||||
|
QObject::connect(net, &NMNetwork::requestAddAndActivateConnection, this, [this](const NMSettingsMap& settingsMap, const QString& specificObject){
|
||||||
|
emit this->addAndActivateConnection(settingsMap, QDBusObjectPath(this->path()), QDBusObjectPath(specificObject));
|
||||||
|
});
|
||||||
|
QObject::connect(net, &NMNetwork::visibilityChanged, this, [this, net](bool visible) {
|
||||||
|
if (visible) emit this->networkAdded(net->frontend());
|
||||||
|
else emit this->networkRemoved(net->frontend());
|
||||||
|
});
|
||||||
|
if (net->visible()) emit this->networkAdded(net->frontend());
|
||||||
|
}
|
||||||
|
|
||||||
void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
|
void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
|
||||||
const QString stringPath = path.path();
|
const QString stringPath = path.path();
|
||||||
|
|
||||||
// Remove old active connection
|
// Remove old active connection
|
||||||
if (this->mActiveConnection) {
|
if (this->mActiveConnection) {
|
||||||
|
qCDebug(logNetworkManager) << "Active connection removed:" << this->mActiveConnection->path();
|
||||||
QObject::disconnect(this->mActiveConnection, nullptr, this, nullptr);
|
QObject::disconnect(this->mActiveConnection, nullptr, this, nullptr);
|
||||||
delete this->mActiveConnection;
|
delete this->mActiveConnection;
|
||||||
this->mActiveConnection = nullptr;
|
this->mActiveConnection = nullptr;
|
||||||
@@ -64,6 +118,7 @@ void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
|
|||||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << stringPath;
|
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << stringPath;
|
||||||
delete active;
|
delete active;
|
||||||
} else {
|
} else {
|
||||||
|
qCDebug(logNetworkManager) << "Active connection added:" << stringPath;
|
||||||
this->mActiveConnection = active;
|
this->mActiveConnection = active;
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
active,
|
active,
|
||||||
@@ -76,42 +131,44 @@ void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NMDevice::onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths) {
|
void NMDevice::onAvailableSettingsPathsChanged(const QList<QDBusObjectPath>& paths) {
|
||||||
QSet<QString> newPathSet;
|
QSet<QString> newPathSet;
|
||||||
for (const QDBusObjectPath& path: paths) {
|
for (const QDBusObjectPath& path: paths) {
|
||||||
newPathSet.insert(path.path());
|
newPathSet.insert(path.path());
|
||||||
}
|
}
|
||||||
const auto existingPaths = this->mConnections.keys();
|
const auto existingPaths = this->mSettings.keys();
|
||||||
const QSet<QString> existingPathSet(existingPaths.begin(), existingPaths.end());
|
const QSet<QString> existingPathSet(existingPaths.begin(), existingPaths.end());
|
||||||
|
|
||||||
const auto addedConnections = newPathSet - existingPathSet;
|
const auto addedSettings = newPathSet - existingPathSet;
|
||||||
const auto removedConnections = existingPathSet - newPathSet;
|
const auto removedSettings = existingPathSet - newPathSet;
|
||||||
|
|
||||||
for (const QString& path: addedConnections) {
|
for (const QString& path: addedSettings) {
|
||||||
this->registerConnection(path);
|
this->registerSettings(path);
|
||||||
}
|
}
|
||||||
for (const QString& path: removedConnections) {
|
for (const QString& path: removedSettings) {
|
||||||
auto* connection = this->mConnections.take(path);
|
auto* connection = this->mSettings.take(path);
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
qCDebug(logNetworkManager) << "Sent removal signal for" << path << "which is not registered.";
|
qCDebug(logNetworkManager) << "Sent removal signal for" << path << "which is not registered.";
|
||||||
} else {
|
} else {
|
||||||
|
qCDebug(logNetworkManager) << "Connection settings removed:" << path;
|
||||||
delete connection;
|
delete connection;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void NMDevice::registerConnection(const QString& path) {
|
void NMDevice::registerSettings(const QString& path) {
|
||||||
auto* connection = new NMConnectionSettings(path, this);
|
auto* settings = new NMSettings(path, this);
|
||||||
if (!connection->isValid()) {
|
if (!settings->isValid()) {
|
||||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
||||||
delete connection;
|
delete settings;
|
||||||
} else {
|
} else {
|
||||||
this->mConnections.insert(path, connection);
|
qCDebug(logNetworkManager) << "Connection settings added:" << path;
|
||||||
|
this->mSettings.insert(path, settings);
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
connection,
|
settings,
|
||||||
&NMConnectionSettings::loaded,
|
&NMSettings::loaded,
|
||||||
this,
|
this,
|
||||||
[this, connection]() { emit this->connectionLoaded(connection); },
|
[this, settings]() { emit this->settingsLoaded(settings); },
|
||||||
Qt::SingleShotConnection
|
Qt::SingleShotConnection
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -125,6 +182,12 @@ void NMDevice::setAutoconnect(bool autoconnect) {
|
|||||||
this->pAutoconnect.write();
|
this->pAutoconnect.write();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NMDevice::setManaged(bool managed) {
|
||||||
|
if (managed == this->bManaged) return;
|
||||||
|
this->bManaged = managed;
|
||||||
|
this->pManaged.write();
|
||||||
|
}
|
||||||
|
|
||||||
bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); }
|
bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); }
|
||||||
QString NMDevice::address() const {
|
QString NMDevice::address() const {
|
||||||
return this->deviceProxy ? this->deviceProxy->service() : QString();
|
return this->deviceProxy ? this->deviceProxy->service() : QString();
|
||||||
@@ -140,4 +203,9 @@ DBusDataTransform<qs::network::NMDeviceState::Enum>::fromWire(quint32 wire) {
|
|||||||
return DBusResult(static_cast<qs::network::NMDeviceState::Enum>(wire));
|
return DBusResult(static_cast<qs::network::NMDeviceState::Enum>(wire));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DBusResult<qs::network::NMDeviceInterfaceFlags::Enum>
|
||||||
|
DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum>::fromWire(quint32 wire) {
|
||||||
|
return DBusResult(static_cast<qs::network::NMDeviceInterfaceFlags::Enum>(wire));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace qs::dbus
|
} // namespace qs::dbus
|
||||||
+49
-16
@@ -8,8 +8,12 @@
|
|||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../../dbus/properties.hpp"
|
#include "../../dbus/properties.hpp"
|
||||||
#include "connection.hpp"
|
#include "../device.hpp"
|
||||||
|
#include "active_connection.hpp"
|
||||||
#include "dbus_nm_device.h"
|
#include "dbus_nm_device.h"
|
||||||
|
#include "enums.hpp"
|
||||||
|
#include "network.hpp"
|
||||||
|
#include "settings.hpp"
|
||||||
|
|
||||||
namespace qs::dbus {
|
namespace qs::dbus {
|
||||||
|
|
||||||
@@ -20,6 +24,13 @@ struct DBusDataTransform<qs::network::NMDeviceState::Enum> {
|
|||||||
static DBusResult<Data> fromWire(Wire wire);
|
static DBusResult<Data> fromWire(Wire wire);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum> {
|
||||||
|
using Wire = quint32;
|
||||||
|
using Data = qs::network::NMDeviceInterfaceFlags::Enum;
|
||||||
|
static DBusResult<Data> fromWire(Wire wire);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace qs::dbus
|
} // namespace qs::dbus
|
||||||
|
|
||||||
namespace qs::network {
|
namespace qs::network {
|
||||||
@@ -36,43 +47,61 @@ public:
|
|||||||
[[nodiscard]] virtual bool isValid() const;
|
[[nodiscard]] virtual bool isValid() const;
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
[[nodiscard]] QString address() const;
|
[[nodiscard]] QString address() const;
|
||||||
[[nodiscard]] QString interface() const { return this->bInterface; };
|
[[nodiscard]] QString interface() const { return this->bInterface; }
|
||||||
[[nodiscard]] QString hwAddress() const { return this->bHwAddress; };
|
[[nodiscard]] QString hwAddress() const { return this->bHwAddress; }
|
||||||
[[nodiscard]] bool managed() const { return this->bManaged; };
|
[[nodiscard]] bool managed() const { return this->bManaged; }
|
||||||
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; };
|
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; }
|
||||||
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
|
[[nodiscard]] NMDeviceStateReason::Enum stateReason() const { return this->bStateReason; }
|
||||||
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; };
|
[[nodiscard]] NMDeviceStateReason::Enum lastFailReason() const { return this->bLastFailReason; }
|
||||||
|
[[nodiscard]] NMDeviceInterfaceFlags::Enum interfaceFlags() const {
|
||||||
|
return this->bInterfaceFlags;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; }
|
||||||
|
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; }
|
||||||
|
[[nodiscard]] virtual NetworkDevice* frontend() = 0;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void loaded();
|
||||||
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
|
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
|
||||||
void addAndActivateConnection(
|
void addAndActivateConnection(
|
||||||
const ConnectionSettingsMap& settings,
|
const NMSettingsMap& settings,
|
||||||
const QDBusObjectPath& devPath,
|
const QDBusObjectPath& devPath,
|
||||||
const QDBusObjectPath& apPath
|
const QDBusObjectPath& specificObjectPath
|
||||||
);
|
);
|
||||||
void connectionLoaded(NMConnectionSettings* connection);
|
void networkAdded(Network* net);
|
||||||
void connectionRemoved(NMConnectionSettings* connection);
|
void networkRemoved(Network* net);
|
||||||
void availableConnectionPathsChanged(QList<QDBusObjectPath> paths);
|
void settingsLoaded(NMSettings* settings);
|
||||||
|
void settingsRemoved(NMSettings* settings);
|
||||||
|
void availableSettingsPathsChanged(QList<QDBusObjectPath> paths);
|
||||||
void activeConnectionPathChanged(const QDBusObjectPath& connection);
|
void activeConnectionPathChanged(const QDBusObjectPath& connection);
|
||||||
void activeConnectionLoaded(NMActiveConnection* active);
|
void activeConnectionLoaded(NMActiveConnection* active);
|
||||||
void interfaceChanged(const QString& interface);
|
void interfaceChanged(const QString& interface);
|
||||||
void hwAddressChanged(const QString& hwAddress);
|
void hwAddressChanged(const QString& hwAddress);
|
||||||
void managedChanged(bool managed);
|
void managedChanged(bool managed);
|
||||||
void stateChanged(NMDeviceState::Enum state);
|
void stateChanged(NMDeviceState::Enum state);
|
||||||
|
void stateReasonChanged(NMDeviceStateReason::Enum reason);
|
||||||
|
void lastFailReasonChanged(NMDeviceStateReason::Enum reason);
|
||||||
void autoconnectChanged(bool autoconnect);
|
void autoconnectChanged(bool autoconnect);
|
||||||
|
void interfaceFlagsChanged(NMDeviceInterfaceFlags::Enum flags);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void disconnect();
|
void disconnect();
|
||||||
void setAutoconnect(bool autoconnect);
|
void setAutoconnect(bool autoconnect);
|
||||||
|
void setManaged(bool managed);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void bindFrontend(NetworkDevice* frontend);
|
||||||
|
void bindNetwork(NMNetwork* net);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths);
|
void onStateChanged(quint32 newState, quint32 oldState, quint32 reason);
|
||||||
|
void onAvailableSettingsPathsChanged(const QList<QDBusObjectPath>& paths);
|
||||||
void onActiveConnectionPathChanged(const QDBusObjectPath& path);
|
void onActiveConnectionPathChanged(const QDBusObjectPath& path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void registerConnection(const QString& path);
|
void registerSettings(const QString& path);
|
||||||
|
|
||||||
QHash<QString, NMConnectionSettings*> mConnections;
|
QHash<QString, NMSettings*> mSettings;
|
||||||
NMActiveConnection* mActiveConnection = nullptr;
|
NMActiveConnection* mActiveConnection = nullptr;
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -80,9 +109,12 @@ private:
|
|||||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bHwAddress, &NMDevice::hwAddressChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bHwAddress, &NMDevice::hwAddressChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceStateReason::Enum, bStateReason, &NMDevice::stateReasonChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceStateReason::Enum, bLastFailReason, &NMDevice::lastFailReasonChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableConnectionPathsChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableSettingsPathsChanged);
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged);
|
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceInterfaceFlags::Enum, bInterfaceFlags, &NMDevice::interfaceFlagsChanged);
|
||||||
|
|
||||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties);
|
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties);
|
||||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface");
|
QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface");
|
||||||
@@ -92,6 +124,7 @@ private:
|
|||||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect");
|
QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect");
|
||||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections");
|
QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections");
|
||||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection");
|
QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection");
|
||||||
|
QS_DBUS_PROPERTY_BINDING(NMDevice, pInterfaceFlags, bInterfaceFlags, deviceProperties, "InterfaceFlags");
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
DBusNMDeviceProxy* deviceProxy = nullptr;
|
DBusNMDeviceProxy* deviceProxy = nullptr;
|
||||||
|
|||||||
@@ -7,6 +7,20 @@
|
|||||||
|
|
||||||
namespace qs::network {
|
namespace qs::network {
|
||||||
|
|
||||||
|
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMConnectivityState
|
||||||
|
class NMConnectivityState: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
Unknown = 0,
|
||||||
|
None = 1,
|
||||||
|
Portal = 2,
|
||||||
|
Limited = 3,
|
||||||
|
Full = 4,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Indicates the type of hardware represented by a device object.
|
// Indicates the type of hardware represented by a device object.
|
||||||
class NMDeviceType: public QObject {
|
class NMDeviceType: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
@@ -52,6 +66,140 @@ public:
|
|||||||
Q_ENUM(Enum);
|
Q_ENUM(Enum);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState.
|
||||||
|
class NMDeviceState: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
Unknown = 0,
|
||||||
|
Unmanaged = 10,
|
||||||
|
Unavailable = 20,
|
||||||
|
Disconnected = 30,
|
||||||
|
Prepare = 40,
|
||||||
|
Config = 50,
|
||||||
|
NeedAuth = 60,
|
||||||
|
IPConfig = 70,
|
||||||
|
IPCheck = 80,
|
||||||
|
Secondaries = 90,
|
||||||
|
Activated = 100,
|
||||||
|
Deactivating = 110,
|
||||||
|
Failed = 120,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Device state change reason codes.
|
||||||
|
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceStateReason.
|
||||||
|
class NMDeviceStateReason: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
None = 0,
|
||||||
|
Unknown = 1,
|
||||||
|
NowManaged = 2,
|
||||||
|
NowUnmanaged = 3,
|
||||||
|
ConfigFailed = 4,
|
||||||
|
IpConfigUnavailable = 5,
|
||||||
|
IpConfigExpired = 6,
|
||||||
|
NoSecrets = 7,
|
||||||
|
SupplicantDisconnect = 8,
|
||||||
|
SupplicantConfigFailed = 9,
|
||||||
|
SupplicantFailed = 10,
|
||||||
|
SupplicantTimeout = 11,
|
||||||
|
PppStartFailed = 12,
|
||||||
|
PppDisconnect = 13,
|
||||||
|
PppFailed = 14,
|
||||||
|
DhcpStartFailed = 15,
|
||||||
|
DhcpError = 16,
|
||||||
|
DhcpFailed = 17,
|
||||||
|
SharedStartFailed = 18,
|
||||||
|
SharedFailed = 19,
|
||||||
|
AutoIpStartFailed = 20,
|
||||||
|
AutoIpError = 21,
|
||||||
|
AutoIpFailed = 22,
|
||||||
|
ModemBusy = 23,
|
||||||
|
ModemNoDialTone = 24,
|
||||||
|
ModemNoCarrier = 25,
|
||||||
|
ModemDialTimeout = 26,
|
||||||
|
ModemDialFailed = 27,
|
||||||
|
ModemInitFailed = 28,
|
||||||
|
GsmApnFailed = 29,
|
||||||
|
GsmRegistrationNotSearching = 30,
|
||||||
|
GsmRegistrationDenied = 31,
|
||||||
|
GsmRegistrationTimeout = 32,
|
||||||
|
GsmRegistrationFailed = 33,
|
||||||
|
GsmPinCheckFailed = 34,
|
||||||
|
FirmwareMissing = 35,
|
||||||
|
Removed = 36,
|
||||||
|
Sleeping = 37,
|
||||||
|
ConnectionRemoved = 38,
|
||||||
|
UserRequested = 39,
|
||||||
|
Carrier = 40,
|
||||||
|
ConnectionAssumed = 41,
|
||||||
|
SupplicantAvailable = 42,
|
||||||
|
ModemNotFound = 43,
|
||||||
|
BtFailed = 44,
|
||||||
|
GsmSimNotInserted = 45,
|
||||||
|
GsmSimPinRequired = 46,
|
||||||
|
GsmSimPukRequired = 47,
|
||||||
|
GsmSimWrong = 48,
|
||||||
|
InfinibandMode = 49,
|
||||||
|
DependencyFailed = 50,
|
||||||
|
Br2684Failed = 51,
|
||||||
|
ModemManagerUnavailable = 52,
|
||||||
|
SsidNotFound = 53,
|
||||||
|
SecondaryConnectionFailed = 54,
|
||||||
|
DcbFcoeFailed = 55,
|
||||||
|
TeamdControlFailed = 56,
|
||||||
|
ModemFailed = 57,
|
||||||
|
ModemAvailable = 58,
|
||||||
|
SimPinIncorrect = 59,
|
||||||
|
NewActivation = 60,
|
||||||
|
ParentChanged = 61,
|
||||||
|
ParentManagedChanged = 62,
|
||||||
|
OvsdbFailed = 63,
|
||||||
|
IpAddressDuplicate = 64,
|
||||||
|
IpMethodUnsupported = 65,
|
||||||
|
SriovConfigurationFailed = 66,
|
||||||
|
PeerNotFound = 67,
|
||||||
|
DeviceHandlerFailed = 68,
|
||||||
|
UnmanagedByDefault = 69,
|
||||||
|
UnmanagedExternalDown = 70,
|
||||||
|
UnmanagedLinkNotInit = 71,
|
||||||
|
UnmanagedQuitting = 72,
|
||||||
|
UnmanagedManagerDisabled = 73,
|
||||||
|
UnmanagedUserConf = 74,
|
||||||
|
UnmanagedUserExplicit = 75,
|
||||||
|
UnmanagedUserSettings = 76,
|
||||||
|
UnmanagedUserUdev = 77,
|
||||||
|
NetworkingOff = 78,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Flags for a network interface.
|
||||||
|
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceInterfaceFlags.
|
||||||
|
class NMDeviceInterfaceFlags: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint32 {
|
||||||
|
None = 0x0,
|
||||||
|
Up = 0x1,
|
||||||
|
LowerUp = 0x2,
|
||||||
|
Promisc = 0x4,
|
||||||
|
Carrier = 0x10000,
|
||||||
|
LldpClientEnabled = 0x20000,
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
};
|
||||||
|
|
||||||
// 802.11 specific device encryption and authentication capabilities.
|
// 802.11 specific device encryption and authentication capabilities.
|
||||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities.
|
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities.
|
||||||
class NMWirelessCapabilities: public QObject {
|
class NMWirelessCapabilities: public QObject {
|
||||||
@@ -153,4 +301,31 @@ public:
|
|||||||
Q_ENUM(Enum);
|
Q_ENUM(Enum);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionStateReason.
|
||||||
|
class NMConnectionStateReason: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_SINGLETON;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Enum : quint8 {
|
||||||
|
Unknown = 0,
|
||||||
|
None = 1,
|
||||||
|
UserDisconnected = 2,
|
||||||
|
DeviceDisconnected = 3,
|
||||||
|
ServiceStopped = 4,
|
||||||
|
IpConfigInvalid = 5,
|
||||||
|
ConnectTimeout = 6,
|
||||||
|
ServiceStartTimeout = 7,
|
||||||
|
ServiceStartFailed = 8,
|
||||||
|
NoSecrets = 9,
|
||||||
|
LoginFailed = 10,
|
||||||
|
ConnectionRemoved = 11,
|
||||||
|
DependencyFailed = 12,
|
||||||
|
DeviceRealizeFailed = 13,
|
||||||
|
DeviceRemoved = 14
|
||||||
|
};
|
||||||
|
Q_ENUM(Enum);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace qs::network
|
} // namespace qs::network
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
#include "network.hpp"
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <qdbusconnection.h>
|
||||||
|
#include <qdbusextratypes.h>
|
||||||
|
#include <qdbuspendingcall.h>
|
||||||
|
#include <qdbuspendingreply.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qloggingcategory.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qpointer.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
#include "../../core/logcat.hpp"
|
||||||
|
#include "../enums.hpp"
|
||||||
|
#include "../network.hpp"
|
||||||
|
#include "../wifi.hpp"
|
||||||
|
#include "accesspoint.hpp"
|
||||||
|
#include "active_connection.hpp"
|
||||||
|
#include "enums.hpp"
|
||||||
|
#include "settings.hpp"
|
||||||
|
#include "utils.hpp"
|
||||||
|
|
||||||
|
namespace qs::network {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
NMNetwork::NMNetwork(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, bKnown(false)
|
||||||
|
, bReason(NMConnectionStateReason::None)
|
||||||
|
, bState(NMConnectionState::Deactivated) {}
|
||||||
|
|
||||||
|
void NMNetwork::updateReferenceSettings() {
|
||||||
|
// If the network has no connections, the reference is nullptr.
|
||||||
|
if (this->mSettings.isEmpty()) {
|
||||||
|
this->bReferenceSettings = nullptr;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the network has an active connection, use its settings as the reference.
|
||||||
|
if (this->mActiveConnection) {
|
||||||
|
auto* settings = this->mSettings.value(this->mActiveConnection->connection().path());
|
||||||
|
if (settings && settings != this->bReferenceSettings) {
|
||||||
|
this->bReferenceSettings = settings;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, choose the settings responsible for the last successful connection.
|
||||||
|
NMSettings* selectedSettings = nullptr;
|
||||||
|
quint64 selectedTimestamp = 0;
|
||||||
|
for (auto* settings: this->mSettings.values()) {
|
||||||
|
const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong();
|
||||||
|
if (!selectedSettings || timestamp > selectedTimestamp) {
|
||||||
|
selectedSettings = settings;
|
||||||
|
selectedTimestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->bReferenceSettings != selectedSettings) {
|
||||||
|
this->bReferenceSettings = selectedSettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMNetwork::addSettings(NMSettings* settings) {
|
||||||
|
if (this->mSettings.contains(settings->path())) return;
|
||||||
|
this->mSettings.insert(settings->path(), settings);
|
||||||
|
|
||||||
|
auto onDestroyed = [this, settings]() {
|
||||||
|
if (this->mSettings.take(settings->path())) {
|
||||||
|
emit this->settingsRemoved(settings);
|
||||||
|
this->updateReferenceSettings();
|
||||||
|
if (this->mSettings.isEmpty()) this->bKnown = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
|
||||||
|
this->bKnown = true;
|
||||||
|
this->updateReferenceSettings();
|
||||||
|
emit this->settingsAdded(settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
void NMNetwork::addActiveConnection(NMActiveConnection* active) {
|
||||||
|
if (this->mActiveConnection) return;
|
||||||
|
this->mActiveConnection = active;
|
||||||
|
|
||||||
|
this->bState.setBinding([active]() { return active->state(); });
|
||||||
|
this->bReason.setBinding([active]() { return active->stateReason(); });
|
||||||
|
auto onDestroyed = [this, active]() {
|
||||||
|
if (this->mActiveConnection && this->mActiveConnection == active) {
|
||||||
|
this->mActiveConnection = nullptr;
|
||||||
|
this->updateReferenceSettings();
|
||||||
|
this->bState = NMConnectionState::Deactivated;
|
||||||
|
this->bReason = NMConnectionStateReason::None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
|
||||||
|
this->updateReferenceSettings();
|
||||||
|
};
|
||||||
|
|
||||||
|
void NMNetwork::forget() {
|
||||||
|
if (this->mSettings.isEmpty()) return;
|
||||||
|
for (auto* conn: this->mSettings.values()) {
|
||||||
|
conn->forget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMNetwork::bindFrontend(Network* frontend) {
|
||||||
|
auto translateState = [this]() { return this->state() == NMConnectionState::Activated; };
|
||||||
|
frontend->bindableConnected().setBinding(translateState);
|
||||||
|
frontend->bindableKnown().setBinding([this]() { return this->known(); });
|
||||||
|
frontend->bindableState().setBinding([this]() {
|
||||||
|
return static_cast<ConnectionState::Enum>(this->state());
|
||||||
|
});
|
||||||
|
frontend->bindableStateChanging().setBinding([this]() {
|
||||||
|
auto s = static_cast<ConnectionState::Enum>(this->state());
|
||||||
|
return s == ConnectionState::Connecting || s == ConnectionState::Disconnecting;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(this, &NMNetwork::reasonChanged, this, [this, frontend]() {
|
||||||
|
if (this->reason() == NMConnectionStateReason::DeviceDisconnected) {
|
||||||
|
auto deviceReason = this->deviceFailReason();
|
||||||
|
if (deviceReason == NMDeviceStateReason::NoSecrets)
|
||||||
|
emit frontend->connectionFailed(ConnectionFailReason::NoSecrets);
|
||||||
|
if (deviceReason == NMDeviceStateReason::SupplicantDisconnect)
|
||||||
|
emit frontend->connectionFailed(ConnectionFailReason::WifiClientDisconnected);
|
||||||
|
if (deviceReason == NMDeviceStateReason::SupplicantFailed)
|
||||||
|
emit frontend->connectionFailed(ConnectionFailReason::WifiClientFailed);
|
||||||
|
if (deviceReason == NMDeviceStateReason::SupplicantTimeout)
|
||||||
|
emit frontend->connectionFailed(ConnectionFailReason::WifiAuthTimeout);
|
||||||
|
if (deviceReason == NMDeviceStateReason::SsidNotFound)
|
||||||
|
emit frontend->connectionFailed(ConnectionFailReason::WifiNetworkLost);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
frontend,
|
||||||
|
&Network::requestConnectWithSettings,
|
||||||
|
this,
|
||||||
|
[this](NMSettings* settings) {
|
||||||
|
if (settings) {
|
||||||
|
emit this->requestActivateConnection(settings->path());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qCInfo(
|
||||||
|
logNetworkManager
|
||||||
|
) << "Failed to connectWithSettings: The provided settings no longer exist.";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
QObject::connect(frontend, &Network::requestForget, this, &NMNetwork::forget);
|
||||||
|
QObject::connect(frontend, &Network::requestDisconnect, this, &NMNetwork::requestDisconnect);
|
||||||
|
QObject::connect(this, &NMNetwork::settingsAdded, frontend, &Network::settingsAdded);
|
||||||
|
QObject::connect(this, &NMNetwork::settingsRemoved, frontend, &Network::settingsRemoved);
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
NMGenericNetwork::NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent)
|
||||||
|
: NMNetwork(parent)
|
||||||
|
, mFrontend(new Network(std::move(name), device, this)) {
|
||||||
|
// Regiter and bind the frontend Network.
|
||||||
|
this->bindFrontend();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMGenericNetwork::bindFrontend() {
|
||||||
|
auto* frontend = this->mFrontend;
|
||||||
|
this->NMNetwork::bindFrontend(frontend);
|
||||||
|
QObject::connect(frontend, &Network::requestConnect, this, [this]() {
|
||||||
|
if (auto* settingsRef = this->referenceSettings()) {
|
||||||
|
emit this->requestActivateConnection(settingsRef->path());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
NMWirelessNetwork::NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent)
|
||||||
|
: NMNetwork(parent)
|
||||||
|
, mSsid(ssid)
|
||||||
|
, bSecurity(WifiSecurityType::Unknown) {
|
||||||
|
|
||||||
|
auto updateSecurity = [this]() {
|
||||||
|
if (NMSettings* settings = this->bReferenceSettings) {
|
||||||
|
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
|
||||||
|
} else if (NMAccessPoint* ap = this->bReferenceAp) {
|
||||||
|
this->bSecurity.setBinding([ap]() { return ap->security(); });
|
||||||
|
} else {
|
||||||
|
this->bSecurity = WifiSecurityType::Unknown;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto checkDisappeared = [this]() {
|
||||||
|
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
|
||||||
|
};
|
||||||
|
|
||||||
|
QObject::connect(this, &NMWirelessNetwork::referenceSettingsChanged, this, updateSecurity);
|
||||||
|
QObject::connect(this, &NMWirelessNetwork::referenceApChanged, this, updateSecurity);
|
||||||
|
QObject::connect(this, &NMWirelessNetwork::settingsRemoved, this, checkDisappeared);
|
||||||
|
QObject::connect(this, &NMWirelessNetwork::apRemoved, this, checkDisappeared);
|
||||||
|
|
||||||
|
// Register and bind the frontend WifiNetwork.
|
||||||
|
this->mFrontend = new WifiNetwork(ssid, device, this);
|
||||||
|
this->bindFrontend();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMWirelessNetwork::updateReferenceAp() {
|
||||||
|
// If the network has no APs, the reference is a nullptr.
|
||||||
|
if (this->mAccessPoints.isEmpty()) {
|
||||||
|
this->bReferenceAp = nullptr;
|
||||||
|
this->bSignalStrength = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, choose the AP with the strongest signal.
|
||||||
|
NMAccessPoint* selectedAp = nullptr;
|
||||||
|
for (auto* ap: this->mAccessPoints.values()) {
|
||||||
|
// Always prefer the active AP.
|
||||||
|
if (ap->path() == this->bActiveApPath) {
|
||||||
|
selectedAp = ap;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) {
|
||||||
|
selectedAp = ap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->bReferenceAp != selectedAp) {
|
||||||
|
this->bReferenceAp = selectedAp;
|
||||||
|
this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
|
||||||
|
if (this->mAccessPoints.contains(ap->path())) return;
|
||||||
|
this->mAccessPoints.insert(ap->path(), ap);
|
||||||
|
auto onDestroyed = [this, ap]() {
|
||||||
|
if (this->mAccessPoints.take(ap->path())) {
|
||||||
|
emit this->apRemoved(ap);
|
||||||
|
this->updateReferenceAp();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// clang-format off
|
||||||
|
QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp);
|
||||||
|
QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed);
|
||||||
|
// clang-format on
|
||||||
|
this->updateReferenceAp();
|
||||||
|
};
|
||||||
|
|
||||||
|
void NMWirelessNetwork::bindFrontend() {
|
||||||
|
auto* frontend = this->mFrontend;
|
||||||
|
this->NMNetwork::bindFrontend(frontend);
|
||||||
|
|
||||||
|
auto translateSignal = [this]() { return this->signalStrength() / 100.0; };
|
||||||
|
frontend->bindableSignalStrength().setBinding(translateSignal);
|
||||||
|
frontend->bindableSecurity().setBinding([this]() { return this->security(); });
|
||||||
|
|
||||||
|
QObject::connect(frontend, &WifiNetwork::requestConnect, this, [this]() {
|
||||||
|
if (auto* settingsRef = this->referenceSettings()) {
|
||||||
|
emit this->requestActivateConnection(settingsRef->path());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto* apRef = this->referenceAp()) {
|
||||||
|
emit this->requestAddAndActivateConnection(NMSettingsMap(), apRef->path());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(frontend, &WifiNetwork::requestConnectWithPsk, this, [this](const QString& psk) {
|
||||||
|
NMSettingsMap settings;
|
||||||
|
settings["802-11-wireless-security"]["psk"] = psk;
|
||||||
|
if (const QPointer<NMSettings> ref = this->referenceSettings()) {
|
||||||
|
auto* call = ref->updateSettings(settings);
|
||||||
|
QObject::connect(
|
||||||
|
call,
|
||||||
|
&QDBusPendingCallWatcher::finished,
|
||||||
|
this,
|
||||||
|
[this, ref](QDBusPendingCallWatcher* call) {
|
||||||
|
const QDBusPendingReply<> reply = *call;
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCInfo(logNetworkManager) << "Failed to write PSK: " << reply.error().message();
|
||||||
|
} else {
|
||||||
|
if (!ref) {
|
||||||
|
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The settings disappeared.";
|
||||||
|
} else {
|
||||||
|
emit this->requestActivateConnection(ref->path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete call;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto* apRef = this->referenceAp()) {
|
||||||
|
emit this->requestAddAndActivateConnection(settings, apRef->path());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The network disappeared.";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::network
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qobject.h>
|
||||||
|
|
||||||
|
#include "../enums.hpp"
|
||||||
|
#include "../network.hpp"
|
||||||
|
#include "../wifi.hpp"
|
||||||
|
#include "accesspoint.hpp"
|
||||||
|
#include "active_connection.hpp"
|
||||||
|
#include "enums.hpp"
|
||||||
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
namespace qs::network {
|
||||||
|
|
||||||
|
// NMNetwork aggregates NMActiveConnections and NMSettings of the same network.
|
||||||
|
class NMNetwork: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NMNetwork(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
void addSettings(NMSettings* settings);
|
||||||
|
void addActiveConnection(NMActiveConnection* active);
|
||||||
|
void forget();
|
||||||
|
void connect(const QString& devPath);
|
||||||
|
void connectWithSettings(const QString& devPath, NMSettings* settings);
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
|
||||||
|
[[nodiscard]] bool known() const { return this->bKnown; }
|
||||||
|
[[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }
|
||||||
|
QBindable<NMDeviceStateReason::Enum> bindableDeviceFailReason() { return &this->bDeviceFailReason; }
|
||||||
|
[[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; }
|
||||||
|
[[nodiscard]] QList<NMSettings*> settings() const { return this->mSettings.values(); }
|
||||||
|
[[nodiscard]] NMSettings* referenceSettings() const { return this->bReferenceSettings; }
|
||||||
|
[[nodiscard]] virtual Network* frontend() = 0;
|
||||||
|
QBindable<bool> bindableVisible() { return &this->bVisible; }
|
||||||
|
[[nodiscard]] bool visible() const { return this->bVisible; }
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void requestDisconnect();
|
||||||
|
void requestActivateConnection(const QString& settingsPath);
|
||||||
|
void
|
||||||
|
requestAddAndActivateConnection(const NMSettingsMap& settingsMap, const QString& specificObject);
|
||||||
|
|
||||||
|
void settingsAdded(NMSettings* settings);
|
||||||
|
void settingsRemoved(NMSettings* settings);
|
||||||
|
void stateChanged(NMConnectionState::Enum state);
|
||||||
|
void knownChanged(bool known);
|
||||||
|
void reasonChanged(NMConnectionStateReason::Enum reason);
|
||||||
|
void deviceFailReasonChanged(NMDeviceStateReason::Enum reason);
|
||||||
|
void referenceSettingsChanged(NMSettings* settings);
|
||||||
|
void visibilityChanged(bool visible);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void bindFrontend(Network* frontend);
|
||||||
|
QHash<QString, NMSettings*> mSettings;
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(
|
||||||
|
NMNetwork,
|
||||||
|
NMSettings*,
|
||||||
|
bReferenceSettings,
|
||||||
|
&NMNetwork::referenceSettingsChanged
|
||||||
|
);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateReferenceSettings();
|
||||||
|
|
||||||
|
NMActiveConnection* mActiveConnection = nullptr;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bVisible, &NMNetwork::visibilityChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bKnown, &NMNetwork::knownChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionStateReason::Enum, bReason, &NMNetwork::reasonChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionState::Enum, bState, &NMNetwork::stateChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMNetwork::deviceFailReasonChanged);
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
|
// NMGenericNetwork extends NMNetwork to bind and handle the lifetime of a frontend Network.
|
||||||
|
// This is useful for devices with one network that don't need to extend the base Network class.
|
||||||
|
class NMGenericNetwork: public NMNetwork {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent = nullptr);
|
||||||
|
[[nodiscard]] Network* frontend() override { return this->mFrontend; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void bindFrontend();
|
||||||
|
Network* mFrontend;
|
||||||
|
};
|
||||||
|
|
||||||
|
// NMWirelessNetwork extends NMNetwork to also aggregate NMAccessPoints of the same network and scanning functionality.
|
||||||
|
class NMWirelessNetwork: public NMNetwork {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent = nullptr);
|
||||||
|
|
||||||
|
void addAccessPoint(NMAccessPoint* ap);
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
[[nodiscard]] QString ssid() const { return this->mSsid; }
|
||||||
|
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
|
||||||
|
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
|
||||||
|
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->bReferenceAp; }
|
||||||
|
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); }
|
||||||
|
QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; }
|
||||||
|
[[nodiscard]] WifiNetwork* frontend() override { return this->mFrontend; };
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void disappeared();
|
||||||
|
void signalStrengthChanged(quint8 signal);
|
||||||
|
void securityChanged(WifiSecurityType::Enum security);
|
||||||
|
void activeApPathChanged(QString path);
|
||||||
|
void referenceApChanged(NMAccessPoint* ap);
|
||||||
|
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
|
||||||
|
void apRemoved(NMAccessPoint* ap);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateReferenceAp();
|
||||||
|
void bindFrontend();
|
||||||
|
|
||||||
|
WifiNetwork* mFrontend;
|
||||||
|
QString mSsid;
|
||||||
|
QHash<QString, NMAccessPoint*> mAccessPoints;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMAccessPoint*, bReferenceAp, &NMWirelessNetwork::referenceApChanged);
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::network
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<node>
|
||||||
|
<interface name="org.freedesktop.NetworkManager.Device.Wired">
|
||||||
|
</interface>
|
||||||
|
</node>
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
<node>
|
<node>
|
||||||
<interface name="org.freedesktop.NetworkManager.Device">
|
<interface name="org.freedesktop.NetworkManager.Device">
|
||||||
<method name="Disconnect"/>
|
<method name="Disconnect"/>
|
||||||
|
<signal name="StateChanged">
|
||||||
|
<arg name="new_state" type="u"/>
|
||||||
|
<arg name="old_state" type="u"/>
|
||||||
|
<arg name="reason" type="u"/>
|
||||||
|
</signal>
|
||||||
</interface>
|
</interface>
|
||||||
</node>
|
</node>
|
||||||
@@ -2,8 +2,18 @@
|
|||||||
<interface name="org.freedesktop.NetworkManager.Settings.Connection">
|
<interface name="org.freedesktop.NetworkManager.Settings.Connection">
|
||||||
<method name="GetSettings">
|
<method name="GetSettings">
|
||||||
<arg name="settings" type="a{sa{sv}}" direction="out"/>
|
<arg name="settings" type="a{sa{sv}}" direction="out"/>
|
||||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ConnectionSettingsMap"/>
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="qs::network::NMSettingsMap"/>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="GetSecrets">
|
||||||
|
<arg name="setting_name" type="s" direction="in"/>
|
||||||
|
<arg name="secrets" type="a{sa{sv}}" direction="out"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="qs::network::NMSettingsMap"/>
|
||||||
|
</method>
|
||||||
|
<method name="Update">
|
||||||
|
<arg name="properties" type="a{sa{sv}}" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="qs::network::NMSettingsMap"/>
|
||||||
|
</method>
|
||||||
|
<method name="ClearSecrets"/>
|
||||||
<method name="Delete"/>
|
<method name="Delete"/>
|
||||||
<signal name="Updated"/>
|
<signal name="Updated"/>
|
||||||
<signal name="Removed"/>
|
<signal name="Removed"/>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<node>
|
<node>
|
||||||
<interface name="org.freedesktop.NetworkManager">
|
<interface name="org.freedesktop.NetworkManager">
|
||||||
|
<method name="CheckConnectivity">
|
||||||
|
<arg direction="out" type="u" name="connectivity"/>
|
||||||
|
</method>
|
||||||
<method name="GetAllDevices">
|
<method name="GetAllDevices">
|
||||||
<arg direction="out" type="ao" name="devices"/>
|
<arg direction="out" type="ao" name="devices"/>
|
||||||
</method>
|
</method>
|
||||||
@@ -11,7 +14,7 @@
|
|||||||
</method>
|
</method>
|
||||||
<method name="AddAndActivateConnection">
|
<method name="AddAndActivateConnection">
|
||||||
<arg direction="in" type="a{sa{sv}}" name="connection"/>
|
<arg direction="in" type="a{sa{sv}}" name="connection"/>
|
||||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="ConnectionSettingsMap"/>
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="qs::network::NMSettingsMap"/>
|
||||||
<arg direction="in" type="o" name="device"/>
|
<arg direction="in" type="o" name="device"/>
|
||||||
<arg direction="in" type="o" name="specific_object"/>
|
<arg direction="in" type="o" name="specific_object"/>
|
||||||
<arg direction="out" type="o" name="path"/>
|
<arg direction="out" type="o" name="path"/>
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qdbusconnection.h>
|
||||||
|
#include <qdbusmetatype.h>
|
||||||
|
#include <qdbuspendingcall.h>
|
||||||
|
#include <qdbuspendingreply.h>
|
||||||
|
#include <qdebug.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qloggingcategory.h>
|
||||||
|
#include <qmetatype.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qstring.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
#include "../../core/logcat.hpp"
|
||||||
|
#include "../../dbus/properties.hpp"
|
||||||
|
#include "dbus_nm_connection_settings.h"
|
||||||
|
#include "dbus_types.hpp"
|
||||||
|
#include "utils.hpp"
|
||||||
|
|
||||||
|
namespace qs::network {
|
||||||
|
using namespace qs::dbus;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network", QtWarningMsg);
|
||||||
|
QS_LOGGING_CATEGORY(logNMSettings, "quickshell.network.nm_settings", QtWarningMsg);
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
NMSettings::NMSettings(const QString& path, QObject* parent): QObject(parent) {
|
||||||
|
qDBusRegisterMetaType<QList<QVariantMap>>();
|
||||||
|
qDBusRegisterMetaType<QList<quint32>>();
|
||||||
|
qDBusRegisterMetaType<QList<QList<quint32>>>();
|
||||||
|
qDBusRegisterMetaType<QList<QByteArray>>();
|
||||||
|
qDBusRegisterMetaType<NMIPv6Address>();
|
||||||
|
qDBusRegisterMetaType<QList<NMIPv6Address>>();
|
||||||
|
qDBusRegisterMetaType<NMIPv6Route>();
|
||||||
|
qDBusRegisterMetaType<QList<NMIPv6Route>>();
|
||||||
|
qDBusRegisterMetaType<QMap<QString, QString>>();
|
||||||
|
|
||||||
|
this->proxy = new DBusNMConnectionSettingsProxy(
|
||||||
|
"org.freedesktop.NetworkManager",
|
||||||
|
path,
|
||||||
|
QDBusConnection::systemBus(),
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this->proxy->isValid()) {
|
||||||
|
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection settings at"
|
||||||
|
<< path;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
this->proxy,
|
||||||
|
&DBusNMConnectionSettingsProxy::Updated,
|
||||||
|
this,
|
||||||
|
&NMSettings::getSettings
|
||||||
|
);
|
||||||
|
|
||||||
|
this->bId.setBinding([this]() { return this->bSettings.value()["connection"]["id"].toString(); });
|
||||||
|
this->bUuid.setBinding([this]() {
|
||||||
|
return this->bSettings.value()["connection"]["uuid"].toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
this->settingsProperties.setInterface(this->proxy);
|
||||||
|
this->settingsProperties.updateAllViaGetAll();
|
||||||
|
|
||||||
|
this->getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMSettings::getSettings() {
|
||||||
|
auto pending = this->proxy->GetSettings();
|
||||||
|
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||||
|
|
||||||
|
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||||
|
const QDBusPendingReply<NMSettingsMap> reply = *call;
|
||||||
|
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCWarning(logNetworkManager)
|
||||||
|
<< "Failed to get settings for" << this->path() << ":" << reply.error().message();
|
||||||
|
} else {
|
||||||
|
auto settings = reply.value();
|
||||||
|
manualSettingDemarshall(settings);
|
||||||
|
this->bSettings = settings;
|
||||||
|
qCDebug(logNetworkManager) << "Settings map updated for" << this->path();
|
||||||
|
|
||||||
|
if (!this->mLoaded) {
|
||||||
|
emit this->loaded();
|
||||||
|
this->mLoaded = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
delete call;
|
||||||
|
};
|
||||||
|
|
||||||
|
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusPendingCallWatcher* NMSettings::updateSettings(
|
||||||
|
const NMSettingsMap& settingsToChange,
|
||||||
|
const NMSettingsMap& settingsToRemove
|
||||||
|
) {
|
||||||
|
auto settings = removeSettingsInMap(this->bSettings, settingsToRemove);
|
||||||
|
settings = mergeSettingsMaps(settings, settingsToChange);
|
||||||
|
auto pending = this->proxy->Update(settings);
|
||||||
|
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||||
|
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMSettings::clearSecrets() {
|
||||||
|
auto pending = this->proxy->ClearSecrets();
|
||||||
|
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||||
|
|
||||||
|
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||||
|
const QDBusPendingReply<> reply = *call;
|
||||||
|
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCWarning(logNetworkManager)
|
||||||
|
<< "Failed to clear secrets for" << this->path() << ":" << reply.error().message();
|
||||||
|
} else {
|
||||||
|
qCDebug(logNetworkManager) << "Cleared secrets for" << this->path();
|
||||||
|
}
|
||||||
|
delete call;
|
||||||
|
};
|
||||||
|
|
||||||
|
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMSettings::forget() {
|
||||||
|
auto pending = this->proxy->Delete();
|
||||||
|
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||||
|
|
||||||
|
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||||
|
const QDBusPendingReply<> reply = *call;
|
||||||
|
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCWarning(logNetworkManager)
|
||||||
|
<< "Failed to forget" << this->path() << ":" << reply.error().message();
|
||||||
|
} else {
|
||||||
|
qCDebug(logNetworkManager) << "Successfully deletion of" << this->path();
|
||||||
|
}
|
||||||
|
delete call;
|
||||||
|
};
|
||||||
|
|
||||||
|
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap NMSettings::read() {
|
||||||
|
QVariantMap result;
|
||||||
|
const auto& settings = this->bSettings.value();
|
||||||
|
for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
|
||||||
|
QVariantMap group;
|
||||||
|
for (auto jt = it.value().constBegin(); jt != it.value().constEnd(); ++jt) {
|
||||||
|
group.insert(jt.key(), settingTypeToQml(jt.value()));
|
||||||
|
}
|
||||||
|
result.insert(it.key(), group);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMSettings::write(const QVariantMap& settings) {
|
||||||
|
NMSettingsMap changedSettings;
|
||||||
|
NMSettingsMap removedSettings;
|
||||||
|
QStringList failedSettings;
|
||||||
|
|
||||||
|
for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
|
||||||
|
if (!it.value().canConvert<QVariantMap>()) continue;
|
||||||
|
|
||||||
|
auto group = it.value().toMap();
|
||||||
|
QVariantMap toChange;
|
||||||
|
QVariantMap toRemove;
|
||||||
|
for (auto jt = group.constBegin(); jt != group.constEnd(); ++jt) {
|
||||||
|
if (jt.value().isNull()) {
|
||||||
|
toRemove.insert(jt.key(), QVariant());
|
||||||
|
} else {
|
||||||
|
auto converted = settingTypeFromQml(it.key(), jt.key(), jt.value());
|
||||||
|
if (!converted.isValid()) failedSettings.append(it.key() + "." + jt.key());
|
||||||
|
else toChange.insert(jt.key(), converted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!toChange.isEmpty()) changedSettings.insert(it.key(), toChange);
|
||||||
|
if (!toRemove.isEmpty()) removedSettings.insert(it.key(), toRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failedSettings.isEmpty()) {
|
||||||
|
qCWarning(logNMSettings) << "A write to" << this
|
||||||
|
<< "has received bad types for the following settings:"
|
||||||
|
<< failedSettings.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* call = this->updateSettings(changedSettings, removedSettings);
|
||||||
|
|
||||||
|
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||||
|
const QDBusPendingReply<> reply = *call;
|
||||||
|
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCWarning(logNetworkManager)
|
||||||
|
<< "Failed to update settings for" << this->path() << ":" << reply.error().message();
|
||||||
|
} else {
|
||||||
|
qCDebug(logNMSettings) << "Successful write to" << this;
|
||||||
|
}
|
||||||
|
delete call;
|
||||||
|
};
|
||||||
|
|
||||||
|
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NMSettings::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||||
|
QString NMSettings::address() const { return this->proxy ? this->proxy->service() : QString(); }
|
||||||
|
QString NMSettings::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||||
|
|
||||||
|
} // namespace qs::network
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, const qs::network::NMSettings* settings) {
|
||||||
|
auto saver = QDebugStateSaver(debug);
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
|
debug.nospace() << "NMSettings(" << static_cast<const void*>(settings)
|
||||||
|
<< ", uuid=" << settings->uuid() << ")";
|
||||||
|
} else {
|
||||||
|
debug << "WifiNetwork(nullptr)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qbytearray.h>
|
||||||
|
#include <qdbusextratypes.h>
|
||||||
|
#include <qdbuspendingcall.h>
|
||||||
|
#include <qdbuspendingreply.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qproperty.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qstringlist.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
#include "../../dbus/properties.hpp"
|
||||||
|
#include "dbus_nm_connection_settings.h"
|
||||||
|
#include "dbus_types.hpp"
|
||||||
|
|
||||||
|
namespace qs::network {
|
||||||
|
|
||||||
|
// Proxy of a /org/freedesktop/NetworkManager/Settings/Connection/* object.
|
||||||
|
///! A NetworkManager connection settings profile.
|
||||||
|
class NMSettings: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_UNCREATABLE("");
|
||||||
|
|
||||||
|
/// The human-readable unique identifier for the connection.
|
||||||
|
Q_PROPERTY(QString id READ default NOTIFY idChanged BINDABLE bindableId);
|
||||||
|
/// A universally unique identifier for the connection.
|
||||||
|
Q_PROPERTY(QString uuid READ uuid NOTIFY uuidChanged BINDABLE bindableUuid);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NMSettings(const QString& path, QObject* parent = nullptr);
|
||||||
|
|
||||||
|
/// Clear all of the secrets belonging to the settings.
|
||||||
|
Q_INVOKABLE void clearSecrets();
|
||||||
|
/// Delete the settings.
|
||||||
|
Q_INVOKABLE void forget();
|
||||||
|
/// Update the connection with new settings and save the connection to disk.
|
||||||
|
/// Only changed fields need to be included.
|
||||||
|
/// Writing a setting to `null` will remove the setting or reset it to its default.
|
||||||
|
///
|
||||||
|
/// > [!NOTE] Secrets may be part of the update request,
|
||||||
|
/// > and will be either stored in persistent storage or sent to a Secret Agent for storage,
|
||||||
|
/// > depending on the flags associated with each secret.
|
||||||
|
Q_INVOKABLE void write(const QVariantMap& settings);
|
||||||
|
/// Get the settings map describing this network configuration.
|
||||||
|
///
|
||||||
|
/// > [!NOTE] This will never include any secrets required for connection to the network, as those are often protected.
|
||||||
|
Q_INVOKABLE QVariantMap read();
|
||||||
|
|
||||||
|
[[nodiscard]] bool isValid() const;
|
||||||
|
[[nodiscard]] QString path() const;
|
||||||
|
[[nodiscard]] QString address() const;
|
||||||
|
[[nodiscard]] NMSettingsMap map() { return this->bSettings; }
|
||||||
|
QDBusPendingCallWatcher*
|
||||||
|
updateSettings(const NMSettingsMap& settingsToChange, const NMSettingsMap& settingsToRemove = {});
|
||||||
|
QBindable<QString> bindableId() { return &this->bId; }
|
||||||
|
[[nodiscard]] QString uuid() const { return this->bUuid; }
|
||||||
|
QBindable<QString> bindableUuid() { return &this->bUuid; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void loaded();
|
||||||
|
void settingsChanged(NMSettingsMap settings);
|
||||||
|
void idChanged(QString id);
|
||||||
|
void uuidChanged(QString uuid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool mLoaded = false;
|
||||||
|
|
||||||
|
void getSettings();
|
||||||
|
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMSettings, NMSettingsMap, bSettings, &NMSettings::settingsChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMSettings, QString, bId, &NMSettings::idChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(NMSettings, QString, bUuid, &NMSettings::uuidChanged);
|
||||||
|
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMSettings, settingsProperties);
|
||||||
|
DBusNMConnectionSettingsProxy* proxy = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::network
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, const qs::network::NMSettings* settings);
|
||||||
Loaded 100 of 199 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user